GPUs for work, not for people.
A post-training service you host yourself. RL and SFT on your Kubernetes cluster, behind the Tinker API.
OpenRL Authors
github.com/openrl-dev/openrlPart 1
Part 3
Part 4
Part 5
Part 6
Part 2
It runs RL and SFT post-training on your Kubernetes cluster, behind the Tinker API.
The bottleneck has shifted from model architecture to running the RL loop.
The hard part now is running the loop: GPUs, scheduling, weight sync, and job lifecycles.
Sample from the current policy, grade the samples, update the weights, hand them to the sampler. Repeat for a few hundred steps.
Write plain Python with the Tinker SDK. The environment, reward, and loss function stay in your code.
The control plane queues API requests and schedules workers. The data plane runs training and sampling jobs.
LoRA jobs on the same base model share a running vLLM and trainer process. Full fine-tuning jobs time-slice the GPU and offload inactive state to host memory.
Reproducing LoRA Without Regret on GSM8K RL: full fine-tuning against LoRA rank 32 and rank 1, two seeds each, six runs training at once on one OpenRL cluster.
Mean reward, last 10 of 40 steps
Seed-to-seed variance is 0.013 to 0.016; LoRA rank 1 matches full fine-tuning within noise.
All six runs trained concurrently. Four LoRA runs shared one trainer and one sampler process, while two full fine-tuning runs time-sliced a single GPU allocation.
Qwen3-8B on H100, 40 steps of 64 episodes. Line: mean of two seeds, 5-step rolling mean. Band: the spread between the seeds. Data: docs/experiments/lora-without-regret on branch experiment/lora-without-regret. Paper: thinkingmachines.ai/blog/lora
Running three jobs on two shared H100s instead of six dedicated H100s yields identical learning curves at 2.2x the trainer duty cycle.
Time-sliced, three jobs on two H100s
On six dedicated GPUs: 15.6% duty cycle and 2.10 GPU-hours. The same two GPUs running the jobs one after another: 54 minutes.
Context switches take 0.5 to 1.6 s (median), and phase queue waits take 1 to 15 s. All three jobs matched their dedicated-GPU learning curves.
Jobs: Text-to-SQL RL on Qwen3-1.7B, math RL on Qwen2.5-7B, dialogue SFT on Gemma 4 E2B. Figure from llm-d.ai/blog/increase-researcher-velocity-rl-llm-d-time-slicing
Create a cluster with shared storage, install the NVIDIA and DRA drivers, apply the manifest.
Create a GKE cluster on 1.35 or newer, with Filestore as the shared filesystem.
$ gcloud container clusters create ml \ --location us-central1 --release-channel regular \ --addons GcpFilestoreCsiDriver
Add a GPU pool that opts in to the scheduler. Install the NVIDIA driver (GKE’s installer DaemonSet) and the NVIDIA DRA driver.
$ gcloud container node-pools create gpus --cluster ml \ --accelerator type=nvidia-l4,count=2,gpu-driver-version=disabled \ --node-labels openrl.io/enabled=true,\ gke-no-default-nvidia-gpu-device-plugin=true $ kubectl apply -f nvidia-driver-installer/cos/daemonset-preloaded-latest.yaml $ helm install nvidia-dra-driver-gpu nvidia/nvidia-dra-driver-gpu
Install OpenRL. The standard manifest enables both LoRA and full fine-tuning.
$ kubectl apply --server-side -f https://github.com/openrl-dev/openrl/releases/latest/download/openrl-fft.yaml
Point the Tinker SDK at the cluster URL. The script from Part 3 runs unchanged, with OpenRL settings passed in user_metadata.
import tinker # your cluster, not Tinker's cloud svc = tinker.ServiceClient( base_url="http://openrl.my-cluster:8000", user_metadata={"openrl.fine_tuning_type": "full"}) # the SDK has one client type; the setting above makes it full fine-tuning trainer = await svc.create_lora_training_client_async( base_model="google/gemma-4-e2b", rank=16) # then sample, score, forward_backward, optim_step
openrl-doctor --base-url … --train checks the deployment end to end. The full walk-through is docs/getting-started.md.
What runs in OpenRL today, and what we are building next.
nowRuns our internal workloads and the Harvey legal benchmark (LAB). Single-manifest install; per-job configuration.
nextDay-0 and day-2 operating guides. A versioned API with an upgrade path.
nowAutomodel trainers on up to 8 GPUs: FSDP2 and context parallelism. Sampler replicas.
nextMulti-GPU full fine-tuning and time-slicing, tensor-parallel samplers, Megatron.
nowNVIDIA L4 and H100 through Kubernetes DRA.
nextA TPU proof of concept, then an MVP.
nowDense Qwen and Gemma to 10B on one GPU; Gemma 4 31B with multi-GPU LoRA trainers.
nextFull fine-tuning at 30B. MoE.
nowModel status API, capabilities and settings schema over HTTP, openrl-doctor, an agent skill.
nextA CLI agents can consume, a dashboard, per-step metrics, cancelling a job.
nowHarvey LAB (agentic, sandboxed), Text-to-SQL, math RL, SFT notebooks, autoresearch.
nextFinancial domain recipes, paper reproductions, custom sampler images, and the checkpoints API.
The full plan, by focus area and initiative, is in ROADMAP.md. To change it, open an issue with the roadmap label.
We have applied to donate OpenRL to the CNCF, for a vendor-neutral home.
The roadmap and the issues are public. File one, or pick one up.
docs/getting-started.md · examples/harvey_labs · examples/text-to-sql · openrl-skill.tar.gz on every release
GPUs for work, not for people. Standard RL loops on shared Kubernetes accelerators.
OpenRL Authors
github.com/openrl-dev/openrlAll three come from the same setup: one job, one stack, one block of GPUs.
The trainer diffs current weights against the previous step and writes only the changed elements. The sampler patches weights in place in vLLM without restarting or reloading 16 GB.
Gemma 4 E4B, per step
Qwen3-8B-Base sync volume drops from 16.4 GB to 1.48 GB, and step time drops from 189 s to 36 s.
A delta is a standard safetensors file for vLLM's checkpoint weight patch loader, so one format serves Gemma 4 and Qwen3 with no model-specific code.
GRPO on GSM8K, 8 groups of 8 rollouts, 100 steps, one H100 trainer and one H100 sampler over Zonal Filestore NFS. Medians per step, mean of 3 seeds. Figure from openrl.dev/blog/delta-weight-sync.
3.3% of weights change on step 1, then fewer than 1% on later steps, while GSM8K accuracy climbs from 39% to 67%.
Gemma 4 E4B, lr 3e-6
Each delta file records which layers changed and by how much, giving per-step layer mutation metrics at no extra cost.
Qwen3-8B-Base and Qwen3-4B-Base show the same shape at a higher learning rate.
Nobody releases idle GPUs. When re-queuing takes two weeks, hoarding is the rational choice.
Research and infrastructure ship as one container, so one person maintains both.
RL-as-a-service used to be built for an entire org. With coding agents, a single researcher generates enough jobs to need one.
The training loop stays in your script. OpenRL turns API calls into sampling and training units and places them across a shared GPU pool.
OpenRL provides the API server and the work-unit scheduler, built on standard open-source training and serving runtimes.
Extras
The researcher's code stays on top, OpenRL runs underneath, and the Tinker API is the contract between them.
Each job is a researcher’s own loop. They start together, and OpenRL places their workers on whatever GPUs the pool has.
Full fine-tuning needs the whole device, so co-located workers take turns. A timeslicer grants the turn; llm-d's snapshot agent parks the state in between.
Qwen3-8B on GSM8K math RL. The share of parameters that change in a step falls from 12% at step 1 to under 3% by step 25, while reward climbs. So the trainer ships only the changed parameters, and the sampler patches them in place.
Delta weight sync, Qwen3-8B
A full checkpoint is 16 GB and took 35 s to load on the sampler. Deltas travel over shared NFS on GKE, so trainer and sampler need no RDMA link and can be placed independently.
The trainer already holds the previous weights in host memory for time-slicing; that is what the delta is computed against.
Figure from the note “Delta Weights in RL” (Jul 2026). Per-step density for the same model is in docs/experiments/lora-without-regret/delta_density.csv on branch experiment/lora-without-regret.
LoRA jobs on one base model share a trainer and a sampler. Full fine-tuning jobs take turns on a GPU through a time-slicer built with llm-d.
The batch and the model set what a unit needs. Dynamic Resource Allocation carves an accelerator shape to fit, so the pool packs tighter.
After the first few steps, under 1% of Gemma 4 E4B's weights change per step. Send only those: 0.38 GB instead of 16 GB, and an RL step drops from 191 s to 32 s.
A job waits for its next unit, not for eight GPUs. A new job starts as soon as a unit fits, and a finished job frees the pool at once. Nothing is held idle.
LoRA and full fine-tuning mixed. Every job finished. The whole batch took eight minutes.
Harvey's Legal Agent Benchmark: documents in a sandbox, tools to read them, deliverables graded by a judge. Recipe in the repo, 32k-token turns in a 192k context.
Every capability we care about is already in it, at low probability.
RL raises the probability of what you reward.