Microsoft is not just focused on its own Windows operating system, and the company is taking steps to improve the OneDrive experience for macOS users. Rolling out from now until late November is a new Native Sync Engine for OneDrive for macOS. Microsoft says that this will help improve sync speed, reliability, and resource efficiency. The update should also make it easier to access files in Finder. In a notice posted to the Microsoft 365 Message Center, Microsoft says: “We're introducing a new Native Sync Engine for OneDrive on macOS. The redesigned engine integrates more closely with macOS to improve… [Continue Reading]
Anna Hoffman is a Principal Group Product Manager for Azure Data on Microsoft's SQL Engineering team, where she works across SQL Server, Azure SQL, and SQL database in Fabric. She is also the host of Data Exposed, Microsoft's video series covering the data platform. Anna holds an engineering degree from Georgia Tech along with a master's degree in data science, and started her career as a data and applied scientist before moving into product management. She continues to blog regularly for the Azure SQL Devs' Corner. You can find her on GitHub and on X at @AnalyticAnna.
In this update-packed episode James and Frank dive into developer tooling: the VS Code Mobile Canvas that embeds emulators into your editor, MAUI DevFlow’s rewrite enabling plain .NET/native (and desktop AppKit) support, and the growing MCP/agent ecosystem that ties editors, the Copilot app and CLI together. They share practical takeaways—Mobile Canvas works across frameworks, DevFlow supports non‑MAUI apps, agent instruction files matter, and you can automate Microsoft Store publishing—giving cross‑platform builders clear next steps.
Opposition to AI data centers has become one of America’s fastest-moving bipartisan political issues, driven by concerns about electricity, water, noise, property values, jobs, and a deeper mistrust of Big Tech. NLW provides an end-to-end primer on what data centers actually do, why public sentiment has turned so sharply, where the loudest claims fall apart, and how transparency, community control, and direct local benefits could offer a path forward.
Ray has emerged as a leading open source distributed compute framework for scaling enterprise AI workloads. After its donation to the PyTorch Foundation in 2025, adoption accelerated across the industry, and Red Hat OpenShift AI includes prebuilt, fully supported Ray cluster runtime images.
Training Hub is a Python package providing a standardized set of fine-tuning algorithms for large language models (LLMs). It supports supervised fine-tuning (SFT), offline supervised fine-tuning (OSFT), Low-Rank adaptation (LoRA), and group relative policy optimization (GRPO). Starting with Red Hat OpenShift AI 3.5, Training Hub is preinstalled in a Ray CUDA runtime image, meaning you can run any of these algorithms on a Ray cluster without pip installs, without dependency management, and in air-gapped environments.
This article walks you through running a LoRA fine-tuning job on Ray using Training Hub and the CodeFlare software development kit (SDK). You'll submit a training job fine-tuning Qwen2.5-1.5B-Instruct for SQL generation and evaluate the fine-tuned model, all from a Jupyter notebook.
You can find complete step-by-step notebooks for all 4 algorithms in the Red Hat AI examples repository. Clone the repo and follow along.
Why Ray for fine-tuning?
You might already be using the Kubeflow Training backend for SFT, OSFT, and LoRA fine-tuning on Red Hat OpenShift AI. Ray provides a second backend with its own strengths:
Elastic scaling. Ray clusters scale dynamically. If your job needs 4 GPUs, Ray provisions 4 workers; when it finishes, those resources are released.
Unified runtime. Data preprocessing, training, and serving can all run on the same Ray cluster, simplifying multi-stage pipelines.
verl integration. OpenShift AI includes verl, an open source library for distributed reinforcement learning. It coordinates the rollout phase (where the model generates trial answers) and the training phase across your Ray cluster automatically.
Both backends consume the same algorithms (SFT, OSFT, LoRA, and GRPO) from Training Hub, Red Hat's unified fine-tuning interface, and produce the same fine-tuned models. Choose based on your infrastructure:
Kubeflow Trainer
Ray
Submit from
Kubeflow SDK
CodeFlare SDK
Cluster management
Automatic (TrainJob)
Explicit (RayCluster + RayJob)
Scaling
Static
Elastic
Best for
Teams already using Kubeflow
Teams already using Ray, or multi-stage pipelines
GRPO backend
ART
verl (Ray-native)
The fine-tuned models are interchangeable. A LoRA adapter trained on Ray works identically to one trained on Kubeflow Trainer. If you already have Ray clusters for other workloads, fine-tuning fits in naturally.
What is available
Red Hat OpenShift AI 3.5 ships 4 fine-tuning algorithms on Ray:
Algorithm
Description
Use case
SFT
Supervised fine-tuning
Teach domain knowledge from labeled examples
OSFT
Offline supervised fine-tuning
Learn new knowledge while preserving existing capabilities
LoRA_SFT
Low-Rank adaptation
Parameter-efficient fine-tuning with a smaller memory footprint
LoRA_GRPO
Group relative policy optimization
Reinforcement learning from verifiable rewards (reasoning, tool calling)
Each algorithm has a complete working example in the Red Hat AI examples repository, including data preparation, cluster configuration, training, and evaluation.
Prerequisites
Before starting, ensure your environment meets the following infrastructure and developer requirements.
Cluster administrator requirements
You need access to a Red Hat OpenShift cluster with the following:
Red Hat OpenShift AI (version 3.5 or later) with the dashboard, workbenches, and Ray components enabled.
An OpenShift storage provisioner supporting dynamic provisioning of persistent volume claims (PVCs) with ReadWriteMany (RWX) access mode.
A worker node with an NVIDIA GPU (A100 or H100 recommended). For multi-GPU distributed training, you need a node with multiple GPUs or multiple GPU nodes.
Note on OpenShift storage
RWX access mode is required because both the workbench and the Ray cluster pods need to mount the PVC simultaneously. The workbench writes the model and data. The Ray cluster reads them for training and writes checkpoints, and the workbench then reads those checkpoints for evaluation.
Developer requirements
Access to an OpenShift AI data science project.
Permission to launch workbenches and create PVCs within your namespace.
Set up your environment
Prepare your Red Hat OpenShift AI environment by launching a workbench and configuring shared persistent storage.
Create a workbench
Access the Red Hat OpenShift AI dashboard from the top navigation bar of the OpenShift web console. Navigate to Data Science Projects and create a new project (Figure 1).
Figure 1: Specify a name like fine-tuning to create a dedicated Data Science Project namespace for your training assets.
Select Create a workbench, then choose a standard data science notebook image (such as Standard Data Science or any image including the CodeFlare SDK). The workbench is your control plane. You use it to submit and monitor Ray jobs, not to run the training itself (Figure 2).
Figure 2: Choose a notebook image with the CodeFlare SDK to act as your cluster control plane.
Note
The actual training runs on the Ray cluster, not the workbench. The workbench only needs the CodeFlare SDK to submit jobs and download results. The Ray CUDA runtime image already has Training Hub preinstalled.
Create shared persistent storage with RWX access mode (50 GiB recommended) for the model, dataset, and training checkpoints (Figure 3).
Create storage form with fine-tuning name, ReadWriteMany access mode selected, 50 GiB storage size, and finetune mount path.
Once OpenShift provisions your workbench, select Open to launch JupyterLab.
Clone the example notebook
From the workbench, clone the examples repository:
Navigate to examples/fine-tuning/lora_ray/ and open the LoRA notebook. (For other algorithms, navigate to the corresponding sft_ray/, osft_ray/, or grpo_ray/ directory.)
Prepare the model and data
The notebook begins by configuring paths and hyperparameters:
It then downloads the base model to the shared PVC:
from huggingface_hub import snapshot_download
snapshot_download(
repo_id=MODEL_ID,
local_dir=f"/opt/app-root/src/{PVC_PATH}/{MODEL_ID}",
token=HF_TOKEN or True,
resume_download=True,
local_dir_use_symlinks=False,
)
Note
In air-gapped environments, preload the model and dataset onto the PVC using an alternative method (for example, copying from an internal registry). Training Hub doesn't require network access at training time.
We'll use the public b-mc2/sql-create-context dataset, which pairs natural language questions with valid SQL queries. The notebook formats these pairs into conversational prompts and saves them as JSONL files to your shared storage.
Configure the Ray cluster
The CodeFlare SDK provides a Python API for creating Ray clusters on Red Hat OpenShift AI. The notebook uses ManagedClusterConfig to define a single-GPU cluster. LoRA is parameter-efficient and runs well on 1 GPU:
ManagedClusterConfig provisions a single-node cluster mounting your shared storage. Because the Ray CUDA container preinstalls the Training Hub runtime, all 4 fine-tuning algorithms run without manual dependency installation.
Build the entrypoint and submit the job
The training entrypoint calls training_hub.lora_sft() directly with the parameters configured earlier:
The SDK creates a RayJob custom resource with an embedded RayCluster spec. KubeRay spins up the cluster, runs the entrypoint on the head node, and tears everything down when the job finishes.
Monitor training progress
You can check job status directly from the notebook:
job.status()
The status might show as PENDING while the RayCluster is spinning up and pulling the image. LoRA training is fast. With default parameters on a single L40S (48 GB), expect approximately 5–15 minutes depending on dataset size. You should see the loss decrease steadily over the course of training.
Evaluate the fine-tuned model
After training completes, the job saves the LoRA adapter to the shared PVC. The notebook loads the adapter, merges it with the base model, and tests SQL generation with a set of example prompts. To compare model responses before and after fine-tuning, you can first test the unmodified base model on the same prompts. Add the following cell to the notebook before section 8 ("Test the Trained Model"):
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
base_model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
dtype=torch.float16,
device_map="auto",
)
base_model.eval()
base_tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
def generate_sql_base(question: str, schema: str, max_tokens: int = 64) -> str:
messages = [
{
"role": "user",
"content": f"Given the following database schema:\n\n{schema}\n\n"
f"Write a SQL query to answer this question: {question}",
}
]
prompt = base_tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = base_tokenizer(prompt, return_tensors="pt").to(base_model.device)
outputs = base_model.generate(
**inputs,
max_new_tokens=max_tokens,
temperature=0.1,
do_sample=True,
pad_token_id=base_tokenizer.eos_token_id,
)
response = base_tokenizer.decode(
outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True
)
return response.strip()
test_examples = [
{
"schema": "CREATE TABLE employees (id INT, name VARCHAR, department VARCHAR, salary DECIMAL, hire_date DATE)",
"question": "What is the average salary of employees in the engineering department?",
},
{
"schema": "CREATE TABLE orders (order_id INT, customer_id INT, product_name VARCHAR, quantity INT, order_date DATE)",
"question": "How many orders were placed in the last 30 days?",
},
{
"schema": "CREATE TABLE students (student_id INT, name VARCHAR, grade INT, subject VARCHAR, score DECIMAL)",
"question": "Find the top 5 students with the highest average score across all subjects.",
},
]
for i, example in enumerate(test_examples, 1):
print(f"\nExample {i}:")
print(f"Question: {example['question']}")
sql = generate_sql_base(example["question"], example["schema"])
print(f"Generated SQL: {sql}")
print("-" * 60)
Run this cell and check the output. Then continue with section 8 of the notebook, which loads the LoRA adapter, merges it with the base model, and runs the same test prompts:
base_model = AutoModelForCausalLM.from_pretrained(
MODEL_PATH,
dtype=torch.float16,
device_map="auto",
)
model = PeftModel.from_pretrained(base_model, CHECKPOINTS_PATH)
model = model.merge_and_unload()
model.eval()
Comparing the results
The fine-tuned model outputs clean, bare SQL across all 3 test prompts:
Test prompt
Fine-tuned output
Average salary
SELECT AVG(salary) FROM employees WHERE department = "Engineering"
Orders in last 30 days
SELECT COUNT(*) FROM orders WHERE order_date >= CURDATE() - INTERVAL 30 DAY
Top 5 students
SELECT student_id, name, AVG(score) AS avg_score FROM students GROUP BY student_id ORDER BY avg_score DESC LIMIT 5
If you are feeding model outputs directly into a database API, conversational preamble breaks your application. Fine-tuning teaches the model to return raw, executable SQL, saving you from writing fragile parsing logic. The difference is clearest when comparing raw outputs for the same prompt. The base model wraps the correct SQL in explanation and Markdown formatting:
To find the average salary of employees in the engineering department, you can
use the following SQL query:
SELECT AVG(salary) AS average_salary
FROM employees
WHERE department = 'engineering';
This query selects the average (AVG) salary from the employees table where the
department...
The fine-tuned model outputs the query directly:
SELECT AVG(salary) FROM employees WHERE department = "Engineering"
The base model already knows SQL. It produces correct queries. What LoRA fine-tuning taught was the format: output the query directly, without preamble or explanation. This is exactly the pattern in the sql-create-context training data, where each example pairs a question with a bare SQL query.
Clean up
The RayCluster is automatically torn down when the job finishes. The RayJob custom resource is cleaned up after ttl_seconds_after_finished (600 seconds by default). Use job.delete() for immediate cleanup if needed:
job.delete()
The 4 examples at a glance
Because all 4 algorithms share identical cluster and storage setups, switching from LoRA to GRPO or SFT requires changing only your dataset and function calls—not your underlying pipeline. Each of the 4 Ray examples in the Red Hat AI examples repository (sft_ray, osft_ray, lora_ray, and grpo_ray) follows the same core workflow:
Example
Algorithm
Dataset
Evaluation
sft_ray/
SFT
Table reasoning
Extract-and-compare table results
osft_ray/
OSFT
Domain knowledge
JSON output + knowledge retention tests
lora_ray/
LoRA
SQL generation
SQL correctness check
grpo_ray/
GRPO
Tool calling
Tool call validation
All 4 use the same cluster setup and PVC configuration. The only differences are the Training Hub algorithm flag and the evaluation criteria.