prime-rl training run — the RL trainer (and the distillation algorithms that run through it) and the SFT trainer. For multi-node and cluster layouts, see Scaling. For the loss math and algorithm knobs, see Algorithms.
AI agents working in this repo: the equivalent runbooks are atskills/training/— top-level routing inskills/training/SKILL.md, launch details inskills/training/start-run/SKILL.md, and check-in / restart procedures inskills/training/monitor-run/SKILL.md.
Table of Contents
Entrypoints
RL Trainer
Launch
The minimal RL run trains an SFT-warmedQwen3-0.6B on the reverse-text task — the env is bundled with the verifiers submodule, so nothing else needs to be installed:
Useful Knobs
A condensed view of the knobs you’ll most often tune. For trainer-side parallelism, sampling, optimizer, and loss knobs see Scaling and Algorithms. Data and algorithm:
Monitoring:
Run management:
Algorithms
The RL entrypoint supports several training algorithms, switched via[orchestrator.algo]’s type (see Algorithms for the full reference, model references, and per-algorithm customization):
A new algorithm is a named class in code, not a config — see Algorithms § Authoring an Algorithm.
Frozen models are declared inline on the algorithm, named where the model is used —
[orchestrator.algo.teacher] for opd (the frozen model scored against), [orchestrator.algo.sampling.source] for sft (the model it samples from) — each with name + base_url. opsd declares no frozen model: it self-distills against the live policy. The rl entrypoint only manages policy inference — start frozen-model servers yourself and point base_url at them:
uv run sft entrypoint is the more traditional SFT path — pure dataset-based, no orchestrator. Use the sft algorithm only when you want a frozen model to generate the supervision on the fly.
Important Metrics
Pulled from the console logs and mirrored to W&B. Progress (orchestrator):reward/{all,env}/mean— main signal. Should trend upward over hundreds of steps.seq_len/{all,env}/meanandis_truncated/{all,env}/mean— rollout length and truncation rate.num_turns/{all,env}/mean— for multi-turn envs.empty_rollouts/{all,env},errored_rollouts/{all,env}— non-zero is fine in small numbers; sustained > 5% is a smell.eval/{env}/{avg@k,pass@k}— eval scores when[orchestrator.eval]is set.
mismatch_kl/{all,env}/{mean,std,max}— KL between trainer’s current policy and the (older) inference policy that generated the rollouts. A sustained, growing mean is the early-warning sign for off-policy collapse.entropy/{all,env}/mean— too low means mode-collapse; too high means the model isn’t committing.is_masked/mean— fraction of tokens masked by the IPO trust region.optim/grad_norm— spikes precede divergence; check the loss config or lower the LR.
SFT Trainer
uv run sft runs supervised fine-tuning from a HF dataset. It shares model loaders, FSDP setup, checkpointing, and the chat-template plumbing with the RL trainer, so a typical workflow is SFT → RL → SFT → … without any reformatting.
Dataset Format
Two accepted layouts:- Prompt-completion: a HF dataset with
promptandcompletioncolumns (TRL format). The trainer masks out the prompt and computes loss only over the completion. - Messages: a HF dataset with a single
messagescolumn containing a list of chat turns. The trainer interprets the whole conversation as one sample, applies role-based loss masking, and trains over all assistant turns.
messages takes precedence.
Tool definitions and renderer controls. For tool-use SFT, add a tools column (OpenAI function-calling format) or tool_defs (verifiers rollout format). Each row’s value can be either a list of dicts or a JSON-encoded string of a list — both are accepted, and tool_defs rows are auto-converted to OAI shape before being passed into the renderer.
Renderer-backed SFT reads template controls from the typed [renderer] config in the SFT TOML. For example:
renderers (for example a new field on the relevant *RendererConfig) and consume it in the renderer implementation.
Renderer-backed tokenization. SFT tokenization is renderer-only. The renderers package owns message-to-token conversion and loss attribution end-to-end, so position-dependent chat templates (for example templates that strip past <think> blocks across user turns) do not corrupt the loss mask. [renderer] defaults to name = "auto"; set a typed renderer config only when you need model-specific template controls. Hand-coded renderers ship for Qwen3, Qwen3.5, GLM-5, GLM-4.5, Kimi K2/K2.5, MiniMax M2, DeepSeek V3, Nemotron 3, GPT-OSS, and VLM families such as Qwen3-VL/Qwen3.5.
VLM training requires a custom PrimeRL implementation. Training a model with [model.vlm] set (SFT or RL) requires model.impl = "custom" and only works for models with a registered PrimeRL VLM class (currently Qwen3.5 dense and MoE).
See Algorithms § Multi-Turn Trajectories for the full picture.
Launch
The minimal SFT run trainsQwen3-0.6B on the reverse-text SFT dataset:
sft entrypoint manages this for you — see Scaling § SFT and Torchrun for non-default layouts; multi-node SFT goes through SLURM).
Online Evals
uv run sft can evaluate the model on rollout-based envs as it trains, reusing the RL orchestrator’s eval machinery. Configure an [eval] block — the same shape as [orchestrator.eval]: multiple [[eval.source]] envs with per-source interval / num_examples / group_size / sampling overrides — plus an [inference] block for the vLLM server:
evals process next to the trainer. NCCL is the default weight transport. The trainer broadcasts weights at startup (fail-fast) and at every step an eval env is due, Every broadcast runs the same four-stage handshake in broadcasts/step_{n}: the trainer offers the version (.sender_ready) and blocks, the evals process acknowledges (.receiver_ready), then the trainer transfers (.started) and commits (.finished). It runs the due envs sequentially per broadcast, so every epoch measures exactly one policy version. Set [weight_broadcast] type = "filesystem" to reload weights from disk instead. LoRA and externally managed inference use filesystem broadcast automatically. The base model is evaluated before the first step (disable with eval.skip_first_step), and the final broadcast always fires every env. In-flight eval episodes are cancelled by default when the next checkpoint is ready, so stale evals do not delay a weight update. Set eval.cancel_on_new_checkpoint = false to drain every triggered epoch instead. The trainer can idle while it waits for slow evals. They are sized by the same adaptive concurrency controller as the orchestrator; bound it with [eval.concurrency] (min_inflight / max_inflight; set them equal for fixed concurrency).
Multi-Node Trainer and Inference Pool
On amulti_node deployment, one SLURM job reserves deployment.num_train_nodes + deployment.num_infer_nodes nodes. The first num_infer_nodes run the inference pool, router, env servers, and evals process. The remaining nodes run the trainer. The inference pool runs one vLLM engine per DP rank behind one router, with gpus_per_node / inference.vllm.tensor_parallel_size engines per node:
max_steps, evals never sees a final broadcast, so the job remains active until walltime. Trainer and evals log to one shared W&B run. The trainer creates it, and evals finalizes it.
SFT-Specific Knobs
Important Metrics
Pulled from the console log and mirrored to W&B. Progress and loss:loss/mean,loss/perplexity— main signal. Should decrease through the run.val/loss,val/perplexity— validation metrics when[val]is set, logged everyval.intervalsteps.eval/{env}/...— online eval metrics when[eval]is set, logged at each evaluated checkpoint step.progress/epoch,progress/num_samples,progress/num_tokens— dataset progress.progress/<subset>/ratio_{samples,tokens}— when training on multiple HF subsets/splits, the realized mixing ratio.
optim/grad_norm— spikes precede divergence.optim/lr— LR schedule.- For MoE:
max_vio/mean(load-balancing violation),routing_confidence/mean— both are logged when non-zero.
Checkpointing
Checkpointing is split across processes because the orchestrator and trainer can be on different machines and on different steps at any given time. Inference is stateless.Enabling Checkpoints
Checkpointing is off by default to save disk. Enable it with--ckpt:
Resuming a Run
Re-run the same launch command and pass--resume (latest checkpoint) or --resume.step <N>. Resuming reuses the run directory, so the run needs a name you can point back at — launch with --run.name (or pass the first run’s auto-generated name). Make sure --max-steps is at least the target final step, not the remaining delta:
Exporting Checkpoints
Trainer checkpoints are DCP-sharded; export them to HF-format safetensors withtools/convert_dcp_to_bf16.py. The script reads the model config from the run’s resolved config and writes sharded safetensors plus config/tokenizer assets to <ckpt_dir>/weights (or a second positional arg). It exports full fine-tunes only — LoRA checkpoints are rejected.
uv run inference --vllm.model <dir> or any HF consumer. Quantize it to blockwise FP8 (DeepSeek/GLM format, loads natively in vLLM) with tools/convert_bf16_to_fp8.py <dir>, or go straight from the checkpoint with tools/convert_dcp_to_fp8.py <ckpt_dir> (each rank quantizes its gathered slice, writes only <ckpt_dir>/weights-FP8 — no intermediate bf16 export); dequantize an fp8-only release (e.g. GLM-5-FP8) for training with tools/convert_fp8_to_bf16.py <dir>.
Observability
Config Files
Each launch writes its command, input TOML, and resolved JSON files to<run_dir>/configs/attempt_<n>/. Resumed runs keep the earlier configs.
command.txt uses shell-safe quoting.
configs/latest points to the current attempt.
Log Files
The launcher tees every process’s stdout/stderr into<run_dir>/logs/attempt_<n>/ — every launch (fresh or resumed) gets its own numbered attempt directory, and logs/latest symlinks to the current one. The full layout (single-node runs skip the node_*.log and router.log files — there the router logs into inference.log):
orchestrator.log.vf_level. For multi-rank trainer debugging, drop into logs/latest/trainer/torchrun/<rdzv>/attempt_0/<rank>/{stdout,stderr}.log — verbose and per-rank.
Live tailing from a single point (works on the head node for multi-node runs over a shared filesystem):
Dashboard
uv run dashboard [output_dir ...] (default outputs/) serves a local web dashboard at http://localhost:7788 with five views per run: metrics (the W&B overview sections, read from metrics.jsonl), the resolved configs, a rollout trace viewer with a per-token advantage/logprob view, merged component logs, and markdown reports from <run>/reports/. It only reads the run dirs, so it is safe to point at a live run; pass several output directories to track parallel experiments. A taken port automatically bumps to the next free one, so several dashboards coexist on one node.
A coding agent on the same machine can drive the open dashboard: POST /api/view with an on-disk address ({"run", "tab", "step", "kind", "subset", "episode", "highlight": [...]}) navigates every connected tab there and paints quote-anchored highlights in the trace viewer. Reports cite traces with [^id] markers whose JSON definitions carry the same address plus a verbatim quote; the dashboard re-checks each quote against the trace files and marks the citation verified or broken, so answers stay grounded in what is actually on disk. The dashboard skill documents the full contract.
Weights & Biases
W&B is off by default (the file monitor, which writesmetrics.jsonl and the per-step trace files to the run directory, is on by default):
monitors.wandb.offline = true.
prime-rl deliberately logs a large number of metrics for maximum observability: every rollout metric is emitted per subset (all/effective), per statistic (mean/max/min/p10/p90), and per environment alongside a cross-env aggregate, so a multi-env run can emit thousands of series. To keep that navigable, every training run (RL and SFT) gets an auto-created overview saved view curating the handful of metrics that matter into train, eval, stability, and performance sections (with per-env breakdowns). The view is created once per project and adapts to the run’s environments; if a later run uses a different set of environments, a new versioned view (overview-v2, …) is created instead of overwriting the first.
Platform Monitoring
Register a run on the Prime Intellect platform (Prime Lab) and stream training metrics and episodes to the platform dashboard. Bare flag uses defaults:PRIME_API_KEY (set via prime login or env var) and an allowlisted team. Currently internal-only.
Rules of Thumb
- Start small. Run
examples/basic/reverse-text/rl.tomlend-to-end on 2 GPUs before scaling. If the smoke run finishes cleanly, your install is good. - Batch size ≥ 64. Smaller batches give noisy gradient estimates and the trainer’s overhead-per-step dominates throughput. 64 is the practical floor; 128–512 is the range for quick ablations; production RL often runs at 1024+.
- Group size ≥ 8. Bigger groups (
orchestrator.group_size) make it more likely that a task produces a mix of high- and low-reward rollouts, which is what gives the trainer a usable signal — if all rollouts in a group succeed or all fail, the within-group advantage collapses to zero and the trainer learns nothing from that task. Bigger groups also tighten advantage normalization. 8 is the floor; 16–32 is common. - Runs never share a directory. Every launch writes to its own run directory
<output_dir>/<run_name>, auto-named<envs>--<model>--<short-id>by default. Name runs you want to find again or resume with--run.name <name>; re-using a name blocks unless you resume or pass--clean. - Use
--dry-runbefore SLURM. Validators (e.g. CP needs flash-attention) fail fast in dry-run and slow in queue.