Skip to content

Releases

v1.17.1

Choose a tag to compare

@albertvillanova albertvillanova released this 30 Oct 14:15
44809ba

What's Changed

  • Keep a parenthesis inside an LFM2.5 tool-call argument from closing the call by @albertvillanova in #7942

Full Changelog: v1.17.0...v1.17.1

v1.17.0

Choose a tag to compare

@albertvillanova albertvillanova released this 29 Oct 15:51
e257382
TRL v1 17 meets Le Chonk

Features

πŸ”₯ Mistral Large 4: fine-tune a 1T-parameter model on one node

TRL now supports mistralai/Mistral-Large-4-1T-A52B: 1T parameters, 52B active. The new LoRA example fine-tunes it with SFTTrainer on a single 8Γ—B300 node, on top of its FP8 weights: the experts are split 8 ways with token dispatch, everything else is sharded across the 8 GPUs at load time by transformers' DistributedConfig, and LoRA trains attention and the dense and shared-expert MLPs.

Measured: 32.7k tokens per step in ~8 s, 142 GiB per GPU, with packing and assistant-only loss on reasoning conversations.

mistral-large4-trl_v7_hq.mp4
training_args = SFTConfig(
    model_init_kwargs={
        "dtype": "auto",  # keep the FP8 weights
        "distributed_config": DistributedConfig(fsdp_size=8, ep_size=8),
    },
    packing=True,
    assistant_only_loss=True,
    ...
)
trainer = SFTTrainer(model="mistralai/Mistral-Large-4-1T-A52B", args=training_args, peft_config=LoraConfig(...), ...)

assistant_only_loss=True works out of the box: TRL ships a training chat template that matches the released checkpoint's, including its reasoning_effort levels (low, medium, high). Needs transformers>=5.22.0, peft>=0.21.0 and accelerate>=1.15.0.

🎯 Unbiased importance sampling under top-k, top-p and min-p

vLLM normalizes its log-probabilities over the tokens left after top_k, top_p and min_p filtering, while the trainer normalized over the whole vocabulary. Every sampled token's log-ratio was off by the log of the probability mass the filter kept, so the vLLM importance sampling ratio was biased towards 0 (around 1e-4 instead of 1 with top_p=0.9). The trainers now normalize over the candidate set each token was sampled from, following Keep Sampling Mask from DeepSeek-V3.2.

  • AsyncGRPO: renormalize log-probs over the top-k/top-p candidate set by @AmineDiro in #7627
  • GRPO: renormalize log-probs over the top-k/top-p candidate set (vLLM colocate mode, vLLM >= 0.28; server mode will follow) by @albertvillanova in #7866
  • Require top_k with min_p in AsyncGRPO so the importance sampling ratio stays unbiased by @albertvillanova in #7937
  • Track the support maximum in the chunked log-prob kernel, so a far-below-max candidate set no longer gives a log-probability of 0 and non-finite gradients, by @kashif in #7983

πŸ’₯ What this changes for you. vLLM bounds the candidate set it returns with top_k, so with vLLM and the importance sampling correction on (the default), GRPOConfig now raises when top_p < 1.0 or min_p > 0.0 is set without top_k > 0. Set top_k (e.g. top_k=512), or vllm_importance_sampling_correction=False.

βš–οΈ Score centering is the default AsyncGRPO objective

AsyncGRPO's old log-probs are the vLLM sampler's, so its ratio mixes staleness with the numerical mismatch between the server and the trainer. With an FP8 sampler and a bf16 trainer, the clipped objective collapses: reward goes from 0.60 to 0.34 (FP8 weights) or 0.10 (FP8 weights + KV cache) by step 300, against 0.71 with a bf16 sampler.

Score centering with masked importance sampling is now the default (score_centering=True, importance_sampling_min=0.5, importance_sampling_max=5.0). With it, the FP8 sampler trains exactly like the bf16 one (0.72). It costs 15–20% of generation throughput.

πŸ’₯ What this changes for you. AsyncGRPO runs now train with a different objective by default, and tokens whose ratio to the sampler falls outside [0.5, 5.0] are dropped. Set score_centering=False to keep the clipped objective.

by @AmineDiro in #7760

πŸš€ Faster AsyncGRPO rollouts and packing

  • Adaptive in-flight cap from the vLLM load: the rollout worker follows the server's KV cache usage instead of admitting a fixed max_inflight_tasks, so requests stop queueing and being preempted on the server. No new config: everything is read from the server. By @AmineDiro in #7610
  • Filter zero-advantage groups (DAPO's dynamic sampling): groups whose rollouts all got the same reward, or none of which could be scored, no longer cost a forward and backward. By @AmineDiro in #7609
  • Pack the sampling mask ragged and pad it on the device: TreeRow.pack goes from 250 s to 31 s on rank 0 over a 15-step run, and from 1040 ms to 66 ms on a 28k-token multi-turn row. By @AmineDiro in #7798
  • Keep filling a micro-batch when another row has free room by @clementtisseau in #7793
  • Use FlashAttention-4 on Blackwell GPUs in async GRPO and async distillation by @lewtun in #7792

🌳 Tree attention for linear-attention hybrids

Tree packing (v1.16) now supports the Gated DeltaNet hybrids: Qwen3.5, Qwen3.6, Qwen3-Next and OLMo-Hybrid. Since v1.16 the trainer refused them at init; before it, sequence packing accepted them but was silently wrong on them. The causal convolution and the delta-rule recurrence now follow the tree instead of the row, through FLA's chunked kernels.

by @AmineDiro in #7686

🎚️ Precise chunked log-probabilities

The chunked log-probability and KL/JSD heads get the two precision fixes from A Fix for Softmax and Cross-Entropy: confident targets keep their small gradients, and matching teachers produce zero gradients. Entropy-preserving reinforcement learning (Appendix B.4) reports the same high-confidence policy-gradient issue.

  • Fix chunked loss precision by @kashif in #7882
  • Fix the fused log-prob/entropy backward on strided inputs (shifted views from SFT/DPO) by @winglian in #7890

πŸ”€ X-Token cross-tokenizer distillation in GOLD

GOLDTrainer gets off-policy cross-tokenizer distillation based on X-Token: set lmbda=0.0 and xtoken_loss_type="p_kl" or "h_kl" with a precomputed projection matrix.

  • Add X-Token cross-tokenizer KD to GOLD trainer by @kashif in #6126
  • Support byte-fallback tokenizers (Gemma) in GOLD byte-offset alignment by @kashif in #8010

πŸŸ₯ Training on AMD GPUs

TRL's trainers now run on AMD Instinct GPUs through PyTorch's ROCm build, with a guide and an AMD CI job that runs the test suite on every PR.

🧩 More models

  • Beam: tiny model, SFT/DPO/GRPO/RLOO tests, GRPO tool calling, and a LoRA SFT example on one 8Γ—B300 node. Needs transformers>=5.20.1. By @qgallouedec in #7629
  • Mistral Medium 3.5: training chat template for assistant-only loss by @qgallouedec in #7940
  • LFM2.5-24B-A2B (and LFM2.5-2.6B's current template) for tool calling by @qgallouedec in #7753
  • LFM2.5-1.2B-Instruct and LFM2.5-VL-450M for tool calling by @albertvillanova in #7909, #7910 and #7911
  • TRL's own Qwen3 training template for response parsing, so a tokenizer saved by assistant-only SFT works in GRPO, by @edbeeching in #8007

Other

Removals and requirement bumps

  • Remove the experimental TPO trainer by @qgallouedec in #7807
  • **Require top_k with top_p < 1.0 or `min...
Read more

v1.16.0

Choose a tag to compare

@qgallouedec qgallouedec released this 15 Oct 18:00
1b5c75e

Features

πŸ› οΈ TRL now trains on AWS Trainium

Neon Warehouse_ AWS and Trainium Support

The fused LM head from v1.15 is what made Neuron tractable; the remaining gaps are closed here. There is a guide to go with it.

Neuron recompiles once per input shape, so an unpadded run stalls about a minute on every new sequence length. pad_to_multiple_of now defaults to "auto": 128 on Neuron, None elsewhere, so nothing changes on GPU. Trainium2, Qwen3-0.6B SFT, 20 steps: 130 s with the default against 260 s without.

🌳 Tree attention for AsyncGRPO

AsyncGRPO now packs every micro-batch row as a prefix tree instead of a flat sequence. Multi-turn and multi-generation rollouts repeat a lot of prefix: on a shared prompt, those tokens used to be forwarded once per row. Tree packing forwards them once.

rollout 0:  [ prompt ] [ completion A ]              prompt ──┬── completion A ── later turn
rollout 1:  [ prompt ] [ completion B ]        =>             └── completion B
rollout 2:  [ prompt ] [ completion A ] [ … ]

Chains without a fork collapse into segments, and each segment's queries attend to the K/V gathered along its root-to-segment path in a single FA3 varlen call. No mask tensor: every layer except attention runs once per unique token, and only K/V is duplicated along shared prefixes.

Qwen3-1.7B, 32K tokens per rollout, forward + backward:

layout packing ratio sequence packing tree packing peak GiB
shared prompt 1.00 8677 ms 8737 ms (0.99Γ—) 35.2 β†’ 35.2
shared prompt 2.29 8266 ms 4341 ms (1.90Γ—) 35.2 β†’ 18.0
shared prompt 3.37 8179 ms 2755 ms (2.97Γ—) 35.2 β†’ 13.6
nested fork 1.60 8557 ms 6217 ms (1.38Γ—) 35.2 β†’ 23.8
nested fork 3.56 8172 ms 2635 ms (3.10Γ—) 35.2 β†’ 13.5

There is no packing option to choose: with nothing shared a tree row is exactly the old sequence packing, at a ~1% cost.

by @AmineDiro in #7328

Muse Spark Open

Support for meta-models/Muse-Spark-Open-816B-A42B: the tiny-model script, coverage in the VLM tests (SFT, DPO, KTO, chat-template utils, fused LM head), and a LoRA SFT example on 8 nodes of 8Γ—H100 with DistributedConfig(fsdp_size=64, ep_size=8), token dispatch and packing. Measured at 14.2 s/step, 131k tokens/step, 40.2 GB per GPU.

Needs transformers>=5.20.0; the adapter save additionally needs transformers#49500 with peft 0.21.2.

by @qgallouedec in #7536

AMD GPUs in the async trainers

Tool-call arguments typed by the tool's JSON schema

parse_response now receives the tools, so tool-call arguments come back typed from the tool's own schema instead of whatever the raw parse produced.

by @qgallouedec in #7650

Other

Removals and requirement bumps

Fixes

  • Fix silently wrong torch.compile results in the Triton loss kernels on torch < 2.11. torch_compile=True trained with loss=0.0 and grad_norm=nan at every step. Inductor passes scalar kernel args as constexpr before torch 2.11, the .to(tl.float32) casts raised on those, and Inductor then assumed every input was mutated and miscompiled. By @albertvillanova in #7688
  • Fix FSDP2 vLLM colocate weight sync for tied-embedding models by @Abdennacer-Badaoui in #7647
  • Prevent VLM policies from generating the image placeholder token in GRPO, RLOO and Distillation by @albertvillanova in #7695
  • Fix Online DPO VLM training when the prompt exceeds max_length (#7703) and for processors with 5D pixel values (#7704), both by @albertvillanova
  • Fix activation offloading on torch < 2.4 by @albertvillanova in #7748
  • Fix torch.compile(fullgraph=True) of selective_log_softmax_and_entropy on torch 2.6 by @albertvillanova in #7719
  • Keep the Triton loss kernels in fp32 under torch.compile by @qgallouedec in #7600
  • Read is_triton_available once in the fused LM head kernels so Dynamo does not warn by @qgallouedec in #7731
  • Fix Distillation for models split across devices by @albertvillanova in #7573
  • Restore the gradient checkpointing kwargs after generation by @qgallouedec in #7668
  • Drop the FlashAttention warnings for padding-free and packing in SFT by @qgallouedec in #7620
  • Restore the distributed env vars that vLLM colocate tests leave behind by @Abdennacer-Badaoui in #7723

Clearer errors

A sweep making failure modes explicit instead of crashing deep in the stack, all by @albertvillanova:

  • Raise a clear error when the fused LM head is used without Triton (#7639)
  • Raise a clear error for prefix tuning on peft versions that cannot support it (#7646)
  • Raise a clear error when the Distillation student and teacher heads are on different devices (#7605)
  • Reject a text-only teacher in GOLD VLM distillation instead of crashing (#7581)
  • Name the missing vLLM server setting when the server cannot serve TRL (#7611)
  • Raise early for use_kernels with transformers 5.2.0 and 5.3.0 (#7577), and if kernels is installed with transformers 5.3.0 in the tiny NemotronH scripts (#7575)

Documentation

CI

Real objects, not mocks. The bulk of this cycle, applying the repo's testing rule across the suite, all by @albertvillanova: run the GRPO entropy mask tests on a real trainer instead of a Mock (#7566), the teacher logprob tests on a real trainer instead of a MagicMock (#7579), build real GOLD trainers in the init (#7582), input preparation (#7614), on-policy generation (#7613, #7615), output decoding (#7633), ULD helper (#7634) and no-backend tokenizer (#7635) tests, check GOLD dataset preparation through the trainer (#7632), and build real async trainers in the rollout state checkpoint tests (#7583)

Coverage, all by @albertvillanova: test SFT (#7552) and DPO/KTO/Reward/GRPO/RLOO (#7601) on a model ...

Read more

v1.15.0

Choose a tag to compare

@qgallouedec qgallouedec released this 08 Oct 19:26
dae7b6a
Neon-Gripped Industrial Valve

Features

⚑ Fused LM head: up to 6.9Γ— longer sequences on the same GPU

SFT, DPO, KTO, GRPO, RLOO and Distillation now score tokens with a fused LM head: a Triton kernel projects the hidden states through the LM head in tiles and reduces to per-token log-probs and entropy directly, so the [batch, seq, vocab] logits tensor is never built. It is on by default, there is nothing to turn on.

Max trainable sequence length (gemma-3-1b, 262k vocabulary, 79 GiB of GPU memory, bf16, batch size 1, gradient checkpointing, sdpa):

Trainer v1.14.2 v1.15.0
DPO 10,240 59,392 5.80Γ—
KTO (batch size 2) 9,216 63,488 6.89Γ—
GRPO (scoring only) 28,672 114,688 4.00Γ—
RLOO (scoring only) 23,552 100,352 4.26Γ—
SFT (loss_type="nll") 20,480 107,520 5.25Γ—
SFT (default chunked_nll) 107,520 107,520 1.00Γ—

At 8,192 tokens, peak memory drops 52% to 82% and steps are 2.3% to 10.9% faster:

Trainer Peak GiB v1.14.2 β†’ v1.15.0 Tokens/s
DPO 48.97 β†’ 12.00 (-75.5%) 1.035Γ—
KTO 64.91 β†’ 11.98 (-81.5%) 1.045Γ—
GRPO 24.15 β†’ 10.30 (-57.4%) 1.023Γ—
RLOO 29.28 β†’ 14.02 (-52.1%) 1.029Γ—
SFT (nll) 30.33 β†’ 8.87 (-70.7%) 1.029Γ—

The gain is largest for small models with large vocabularies. SFT's previous default (chunked_nll) already avoided full logits, so it is unchanged. Scoring needs Triton on a GPU (Linux with CUDA, ROCm or XPU).

πŸ’₯ What this changes for you. The full-logits scoring path, the use_liger_kernel chunked path and _forward_redirection are gone from GRPO, RLOO, DPO and KTO:

  • A PEFT adapter on lm_head now raises. Use modules_to_save=["lm_head"] instead.
  • use_liger_kernel=True is deprecated in these four trainers and will be removed in v2.0.0. Liger's layer kernels still apply, but fused_linear_cross_entropy is forced to False (it would replace the fused head) and setting it explicitly raises. Use model_init_kwargs={"use_kernels": True}.
  • In DPO and KTO, compute_metrics and compute_loss(..., return_outputs=True) still receive the full logits, from an extra forward pass taken only when they are used. Both warn that this is deprecated and will be removed in v2.0.0.

Nothing else the trainers logged is lost: the kernel gained log_sum_sq_probs (WPO weighting), mean_logits (logits/chosen, logits/rejected) and is_top1 (DPO's mean_token_accuracy) as opt-in outputs, each checked against the full logits in tests.

Benchmark details

1Γ— B300 with the PyTorch allocator capped at 79 GiB (about an H100 80GB's usable memory), google/gemma-3-1b-pt built from its config with random bf16 weights, lr 1e-6, AdamW, synthetic token ids at exact length. Max length: one fresh process per attempt, 2 training steps, doubling from 8k then bisection to 1024 tokens, arms interleaved round-robin in the same job. Throughput: 12 steps, first 2 discarded, median of 3 A/B/A/B repeats in the same job. Noise floor (A vs A) is 0.1% to 0.8%, below every gap reported. GRPO/RLOO replace generation with fixed random completions, so only scoring is measured.

Not measured: H100 hardware itself, flash-attention (sdpa only), real generation, multi-GPU.

Selective activation checkpointing in SFT

With gradient checkpointing on, the attention output is now saved during the forward instead of being recomputed in the backward, recovering most of the checkpointing slowdown at long context for one extra hidden-state-sized tensor per layer. Same eager SAC approach torchtitan uses under FSDP2, no torch.compile needed.

SFTConfig(
    gradient_checkpointing=True,
    gradient_checkpointing_kwargs={"selective": True},
)

PEFT + DeepSpeed ZeRO-3 is rejected: that combination needs reentrant checkpointing, which SAC cannot use.

by @kashif in #7194

Assistant-only loss on vision datasets in SFT

assistant_only_loss=True now works on vision datasets. SFTTrainer used to refuse it because processors did not return usable assistant masks once image placeholders were expanded; transformers#48793 fixed that, and it ships in transformers 5.18.

SFTConfig(assistant_only_loss=True)  # now fine with an image dataset

The vision collator tokenizes conversational examples through the processor's apply_chat_template and requests the assistant masks to mask non-assistant tokens in the labels. Older transformers still raises, now with the required version in the message.

by @albertvillanova in #7377

Conversations in the completions table

Prompts and completions are logged as raw conversation lists instead of batch_decode'd flat strings, so the table renders turn by turn. Much more readable for multi-turn and tool-calling setups, and for VLMs.

Completions table rendered turn by turn

The rendered system prompt (including tool definitions injected by the chat template) is no longer visible, since the original messages are shown rather than the fully-templated string.

by @qgallouedec in #5309

AsyncGRPO and AsyncDistillation

  • AsyncGRPO: train OpenEnv harnesses from validated token captures by @adithya-s-k in #6947
  • Add PEFT support to AsyncDistillationTrainer by @kashif in #7476
  • Fix duplicate completions in AsyncGRPO groups with data-parallel vLLM by @lewtun in #7549

Other

Breaking changes and removals

  • Drop Python 3.10 support by @qgallouedec in #7497
  • Refuse nn.DataParallel (and drop the multi-GPU slow test job) by @qgallouedec in #7407
  • Remove the experimental MiniLLM trainer by @qgallouedec in #7500
  • use_liger_kernel is deprecated in DPOTrainer, KTOTrainer, GRPOTrainer and RLOOTrainer and will be removed in v2.0.0. These trainers now use the fused LM head, so only Liger's layer kernels apply; use model_init_kwargs={"use_kernels": True} instead. Docs updated by @qgallouedec in #7535
  • Drop vLLM 0.20.0 / 0.20.1 / 0.20.2 support by @qgallouedec in #7435, #7499 and #7568
  • Remove the unreachable prepare_multimodal_messages_vllm by @behroozazarkhalili in #6946

Fixes

  • Fix chat template drift: recognize the current LFM2 (#7494) and LFM2.5 (#7493) chat templates, the other Gemma 4 revisions (#7495), parse the current LFM2 tool calls (#7522), and read templates shipped as named variants by their default variant (#7530), all by @albertvillanova
  • **Fix SFTTrainer not appending EOS when `data...
Read more

v1.14.2

Choose a tag to compare

@qgallouedec qgallouedec released this 06 Oct 20:30

Patch release fixing two cases of silently wrong training and three crashes.

Worth calling out: #7505 affects GRPO, RLOO and Distillation on the default generation path (use_vllm=False). Models that end a turn with an id only their generation config declares (Gemma 3/4, Phi-3.5, ...) did not stop at the end of the turn, and everything generated after it was trained on, up to max_completion_length. The only visible sign was completions/clipped_ratio close to 1. With vLLM, generation stopped correctly but the metrics were still wrong, and mask_truncated_completions=True dropped every finished completion. Runs on models whose tokenizer eos is their only end-of-turn id are unchanged.

What's Changed

  • Keep the DFT loss finite on a batch with no trainable tokens by @albertvillanova in #7471
  • Fix precompute_ref_log_probs under FSDP with no reference model by @qgallouedec in #7507
  • Fix: Start the completion where the tokenized prompt and prompt+completion diverge by @qgallouedec in #7463
  • End completions on every eos id the model declares in GRPO, RLOO and Distillation by @albertvillanova in #7505
  • Guard the requests and urllib3 imports in the vLLM client by @qgallouedec in #7545

Full Changelog: v1.14.1...v1.14.2

v1.14.1

Choose a tag to compare

@AmineDiro AmineDiro released this 29 Sep 13:41

What's Changed

Full Changelog: v1.14.0...v1.14.1

v1.14.0

Choose a tag to compare

@albertvillanova albertvillanova released this 25 Sep 06:40
c6a6a16

Features

trl.losses is gone: DPO, KTO and GRPO stream their own log-probs

Warning

from trl.losses import FusedLinearDPOLoss (or FusedLinearKTOLoss, FusedLinearGRPOLoss, FusedLinearJSDLoss) no longer works. The module introduced in v1.13 has been removed.

v1.13 vendored Liger's chunked_loss into trl.losses as a holding action, not a destination (#7063). The problem it was holding: those classes reimplement each trainer's loss math, so TRL carried two copies of every formula, and copies drift. That drift is where the bugs were. GRPO's grad_norm differed from the default path, KTO ignored the reference model under PEFT, KTO class weights and DPO label_smoothing were silently dropped. Worst of all, under plain DDP the fused path ran the loss on the unwrapped model, so DistributedDataParallel's reducer was never armed and gradients were never all-reduced: every rank silently kept its own.

The fix is to stop reimplementing. All DPO, KTO and GRPO need from the fused path is per-token log-probs of the selected tokens, and TRL already had _ChunkedLogProbFunction for exactly that: it streams the vocabulary with an online logsumexp and recomputes in the backward pass. Each trainer now runs backbone -> _ChunkedLogProbFunction -> its own existing loss code, unchanged. One implementation of each loss, and the memory win is kept, because full logits are still never materialized.

Thirteen restrictions existed only because the loss had been rewritten in a form that could not express those options. Most are gone: DPO with use_liger_kernel=True now accepts mixed loss types, f-divergences and precomputed reference log-probs, and GRPO gains entropy and off-policy masking. Still refused: use_weighting, compute_metrics, a PEFT adapter on lm_head, prompt-learning PEFT, and the MoE auxiliary loss.

Single H100, Qwen3-0.6B, bs=4, seq 512:

config median step peak
v1.13 fused, inline ref 0.2243 s 7.05 GB
v1.14 chunked, inline ref 0.2457 s (+9.5%) 7.05 GB
v1.14 chunked, precompute_ref_log_probs=True 0.1971 s (-12.1%) 4.83 GB (-31%)

The third row is the point: that configuration did not exist before, because the fused path rejected precomputed reference log-probs outright.

use_liger_kernel=True still works and still enables Liger's model kernels through transformers. In DPO, GRPO and KTO it now selects TRL's chunked log-probability path instead of Liger's fused loss.

  • Stream KTO log probabilities by @kashif in #7075
  • Stream GRPO log probabilities by @kashif in #7077
  • Move DPO to chunked log probabilities by @kashif in #7243
  • Drop the fused Liger JSD path from the experimental distillation trainers by @kashif in #7301
  • Skip frozen chunked log probability gradients by @kashif in #7076
  • Redirect the KTO liger loss through the distributed wrapper under DDP by @qgallouedec in #7247
  • Document what use_liger_kernel actually does in each trainer by @qgallouedec in #7361

A fused Triton logprob + entropy kernel now ships in TRL

The loss head is the only hot path TRL owns: transformers already kernelizes the model internals, and nothing on the Hub covers what happens after the decoder. On one H100 (bf16, V=151936, H=4096, 8192 tokens), selective_log_softmax plus entropy_from_logits cost 12.8 ms and 2.32 GiB over [8, 1024, 151936]. A fused Triton kernel does the same in 0.89 ms, roughly 14x, and lands closer to the fp32 reference than the bf16 path it replaces. GRPO runs this two to four times per step.

It now lives in-tree at trl.kernels and is on by default on CUDA, ROCm and XPU, with the PyTorch implementation as fallback. It was briefly loaded from the Hub; that was reversed because this kernel is pure Triton and compiles at runtime, so the Hub bought nothing while costing a version gate unrelated to whether the kernel works, a download that fails offline, trust_remote_code=True for anyone with kernels installed, a silent except Exception: pass wrapped around all of it, and 13 of 13 tests skipped in CI.

Other

Breaking

Six experimental trainers removed

GRPOWithReplayBufferTrainer, BCOTrainer, PRMTrainer, XPOTrainer, NashMDTrainer and the GSPO-token trainer are gone from trl.experimental.

What changed is not the guarantee, which was always that trl.experimental has none, but the fact that the decision is now written down. Removals used to be argued one pull request at a time, with every thread relitigating the policy before it got to the feature. #7182 settled what the judgment weighs, and #7254 put it in the docs: usage, external issues and pull requests that come from people actually running the feature, whether a stable trainer already covers it, maintenance cost, downstream consumers, owner, and age. Deliberately inputs to a judgment rather than a test: no threshold, no notice cycle, and the numbers stay in the removal pull request where you can check them. A paper index entry outlives the code it described.

The numbers are what make the case. BCOTrainer was 1,600 lines carrying its own reference-model handling, DeepSpeed preparation, tokenization and eval loop, for 11 genuine trainings in two months; it was also the only reason TRL depended on scikit-learn and joblib at all, and both are now gone from pyproject.toml. GRPOWithReplayBufferTrainer had hand-copied _generate_and_score_completions from GRPO and drifted to 385 of 616 lines different, for 8 trainings.

Also breaking, and covered above: trl.losses was removed along the way.

Fixes

  • Gradients were never all-reduced for KTO under plain DDP. compute_loss redirected through the distributed wrapper only for ZeRO-3 and FSDP, so on plain DDP the loss ran on the unwrapped model, DistributedDataParallel.forward() never ran, its reducer was never armed, and every rank silently kept its own gradients. By @qgallouedec in #7247
  • Wrapped packing duplicated data when the input table was sliced. _pack_wrapped sized the output from the sliced view but read from the raw child buffer, so it re-read from the start of the buffer instead of from the slice, and datasets.map(batched=True) passes sliced tables. By @qgallouedec in #6670
  • A reward function logging on only some ranks hung or crashed the run. GRPO and RLOO looped over the keys each rank held and called one collective per key, so ranks called different collectives: log_metric on rank 0 alone hung on NCCL, and log_extra crashed inside gather_object with a 1EB allocation. By @behroozazarkhalili in #7382
  • Special token ids now reach the config the Trainer actually reads, so it no longer realigns them at train time and logs a change the user did not make (part of #7093): eos in the trainers (#7127), written to the text config for composite models (#7315), the reward model pad token (#7318), and the requested pad token applied to vision datasets (#7316, #7352)
  • Do not quantize the teacher by default in the distillation and GOLD entry points by @behroozazarkhalili in #6769
  • Prepare the dataset once per node when the datasets cache is node-local by @qgallouedec in #7241
  • Read the MoE auxiliary loss coefficient from the model config by @qgallouedec in #7248
  • Await async reward functions written as callable classes by @albertvillanova in #7213
  • Fix two stream races in OffloadActivations by @kashif in #7195
  • Only dedupe offloaded activation storages in streams mode by @kashif in #7193
  • Pass the FSDP2 ignored_params kwarg only...
Read more

v1.13.0

Choose a tag to compare

@qgallouedec qgallouedec released this 10 Sep 00:50
3d9261f

Features

Training beyond 1M tokens

image

A new long-context guide plus a runnable example that trains a book-length sequence per step on a single 8Γ—H100 node.

Measured: Qwen3-8B at 1,048,576 tokens, 380 s/step, 56.2 GB per GPU (bf16, per_device_train_batch_size=1, loss_type="chunked_nll"). Runs end to end and saves. The guide documents the levers in the order you hit them (chunked_nll, gradient-checkpointing offload, YaRN RoPE) and the constraints (full attention only, no packing). Needs transformers >= 5.16 for checkpointing offload.

Chunked-CE lm_head projection now runs on tensor cores

The inner loop of the default loss_type="chunked_nll" was doing h.float() @ w.float().t(). Both operands are already bf16, so the upcast bought no information: it moved the GEMM off the tensor cores onto the fp32 SIMT path and materialized an fp32 copy of the whole lm_head weight (2.03 GB for a 248k vocabulary), rebuilt for every chunk and again on every gradient-checkpoint recompute. In an 8Γ—H100 profile of trl sft on Qwen3.6-35B-A3B those two fp32 SIMT GEMMs were 21.6% of all GPU kernel time.

One chunk, 256 tokens Γ— vocab 248,320 Γ— hidden 2048, 1Γ—H100, bf16, fwd+bwd:

fwd+bwd peak mem
before 23.37 ms 5.99 GB
after 3.86 ms (6.0Γ—) 3.03 GB

End to end, 16,384 tokens per step, tokens/s/GPU:

model mode before after
gemma-3-270m (vocab 262k) full FT, 1Γ—H100 24036 31609 1.32Γ—
Qwen3-0.6B (vocab 152k) full FT, 1Γ—H100 21276 26641 1.25Γ—
Qwen3-8B full FT, 2Γ—H100 FSDP2 3554 6009 1.69Γ—
Qwen3-8B LoRA r16, 2Γ—H100 FSDP2 4531 7125 1.57Γ—
Qwen3-30B-A3B (MoE) LoRA attn, 2Γ—H100 FSDP2 4251 5101 1.20Γ—

Same fix applied to the distillation trainers (which paid it twice per chunk, student and teacher). Under accelerate mixed precision the numerics are bit-identical (autocast was already casting the operands back down); without autocast over the projection the GEMM moves to bf16 tensor cores, which is exactly what loss_type="nll" already does.

by @qgallouedec in #6863

Fused linear losses vendored into trl.losses

TRL's only import from liger-kernel was liger_kernel.chunked_loss (the fused linear DPO / KTO / GRPO / JSD losses). That module is pure PyTorch, and most of the GRPO and DPO variants in it were written for TRL. Upstream reviews had stalled, so the code comes home as trl.losses: FusedLinearDPOLoss, FusedLinearKTOLoss, FusedLinearGRPOLoss, FusedLinearJSDLoss (copied from Liger-Kernel v0.8.2, BSD-2 notice kept, bitwise identical to the installed Liger on random inputs).

No new config flag: use_liger_kernel=True keeps selecting the fused loss exactly as before, and still needs liger-kernel installed because transformers' Trainer patches the model kernels with it.

Contributor skills at .agents/skills

Agent skills for contributing to TRL now ship in the repo at .agents/skills.

by @qgallouedec in #6901

Docs: stable vs experimental taxonomy, and how to change the objective

QLoRA on the quantization_config argument

The QLoRA test suites move onto the quantization_config trainer argument added in v1.8, and gain coverage on the VLM paths.

Dependency floors raised

Other

Breaking

⚠️ PPO is removed

PPOTrainer, PPOConfig, and modeling_value_head.py (PreTrainedModelWrapper, AutoModelForCausalLMWithValueHead, AutoModelForSeq2SeqLMWithValueHead) are gone, along with their tests, doc page, and examples.

Bit of a moment for TRL: PPOTrainer predates every PR in this repo. It landed in dfb6a580 on 2020-03-28, the commit that first added the library, back when the package was called lm_ppo. It was the oldest thing in TRL and the last piece of the original codebase.

Why now: unmaintained (no feature work in over a year, every PPO commit since #4482 a drive-by fix), the only trainer never aligned on the input format (it still took tokenized input_ids), near-zero recorded usage, and a magnet for automated bug hunters filing real reports against code nobody runs. Nobody loses anything: from trl import PPOTrainer already stopped working in v1.10, so anyone actually running PPO is pinned to an older TRL and those installs keep resolving exactly as they do today.

create_reference_model stays (BCO, A2PO and Online DPO use it).

by @qgallouedec in #7020

Fixes

  • Fix missing entropy gradient in _ChunkedLogProbFunction backward β€” backward accepted grad_entropy but never used it, so any loss backpropagating through the entropy output silently got zero gradient from that term, with no error or warning. Only AsyncGRPOTrainer read entropy (under no_grad, for logging), so no user-facing regression today, but this is exported non-experimental code and an entropy-bonus loss term would have hit it silently. By @verma8076 in #6625
  • vLLM weight sync now fails instead of hanging by @AmineDiro in #6913
  • Fail fast on incompatible vLLM for default Async GRPO weight transfer by @DimensionSTP in #6830
  • Fix VLM support in AsyncGRPO by @AmineDiro in #6839
  • Fix variable reference in BCO docs by @albertvillanova in #7046
  • Fix the IW-OPD self-distillation loss assertion in tests by @albertvillanova in #7123
  • Fix typos in help strings and er...
Read more

v1.12.0

Choose a tag to compare

@qgallouedec qgallouedec released this 26 Aug 20:05
59c4a8e

⚠️ v1.12.0 is an accidental duplicate of v1.11.0

During the v1.11.0 release, VERSION was bumped to 1.12.0 instead of 1.12.0.dev0. Our publish workflow fires on any push to main that touches VERSION and only skips the upload when the version contains "dev", so trl 1.12.0 was auto-published to PyPI at 19:46 on 2026-08-26, 90 seconds after 1.11.0, with identical code.

trl==1.12.0 on PyPI is bit-identical to trl==1.11.0. There are no features, fixes, or behavior changes in this version.

The version number is burned (PyPI does not allow yanking + re-uploading the same version), so we skip 1.12 entirely for real development. The next feature release is v1.13.0.

Full Changelog: v1.11.0...v1.12.0 (empty)

v1.11.0

Choose a tag to compare

@qgallouedec qgallouedec released this 26 Aug 20:02
d0a2cd5

Features

Use vLLM's own server instead of TRL's

trl vllm-serve used to run a custom FastAPI app around vLLM's LLM, with its own data-parallel fan-out and a worker extension for weight sync. vLLM's own server covers all of that. The custom server is gone: trl/scripts/vllm_serve.py drops from 1218 to ~130 lines and now translates its flags into vllm serve, prints the equivalent command, forwards any extra argument, and sets the three settings TRL needs (--weight-transfer-config, --logprobs-mode processed_logprobs, --max-logprobs -1). fastapi, uvicorn and pydantic leave the vllm extra.

Weight sync now uses vLLM's NCCL weight-transfer engine β€” tensors are announced once and streamed in a single packed broadcast, instead of one HTTP request + broadcast per tensor. Distillation teacher logprobs come from prompt_logprobs.

Benchmark (GRPO server-mode on H100, Qwen2.5-1.5B / Qwen2.5-VL-3B, same seed, same data):

Setup before after speedup
Text, TP=1 1.08, 0.91 s/step 0.63, 0.62 s/step 1.59Γ—
Text, TP=2 0.96, 0.96 s/step 0.62, 0.60 s/step 1.57Γ—
VLM, TP=1 2.14, 2.13 s/step 1.48, 1.48 s/step 1.44Γ—

Bit-identical completions vs the old server (16/16 over 4 prompts Γ— n=4, logprobs identical to 0.0 over 499 tokens). trl vllm-serve still accepts the same flags β€” it just forwards to vllm serve now.

by @qgallouedec in #6765

New experimental AsyncDistillationTrainer (+ multi-teacher MOPD)

Async on-policy distillation, architected like AsyncGRPOTrainer: a background rollout worker generates the student's own completions and scores them against a teacher served over HTTP, so generation and training overlap instead of alternating. The teacher is never loaded locally β€” just a vLLM server URL.

Also supports MOPD (multi-teacher on-policy distillation): pass more than one entry in teacher_server_urls and route each sample to a teacher via a teacher_id column (e.g. a math teacher and a code teacher, each served independently).

from trl.experimental.async_distillation import AsyncDistillationConfig, AsyncDistillationTrainer

trainer = AsyncDistillationTrainer(
    model="Qwen/Qwen3-4B",
    args=AsyncDistillationConfig(
        teacher_server_urls=["http://teacher-math:8000", "http://teacher-code:8000"],
        teacher_top_k=16,
    ),
    train_dataset=dataset,  # rows carry a `teacher_id` column for MOPD
)
trainer.train()

by @kashif in #6705

New model & template support

Examples reorganized into per-example folders

examples/ was split by format (scripts/ vs notebooks/), which scattered related files (e.g. sft_nemotron_3 existed as both a script and a notebook, in different folders; OpenEnv notebooks lived apart from OpenEnv scripts). Everything is now one folder per story β€” the name says the method and the task (grpo_wordle, sft_gpt_oss, ppo_tldr), and the folder holds every file the example needs (scripts, notebooks, prompts, chat templates, eval code). Thin single-trainer demo scripts are removed (they live in trl/scripts/ and each trainer doc page has a runnable snippet). Old examples/scripts/… and examples/notebooks/… GitHub / Colab links will 404 β€” worth flagging in downstream posts.

by @qgallouedec in #6820

AsyncGRPO: metrics rework, dtype control, and cleanup

Rewritten TRL skill (Python-API first) + drop trl skills install

The TRL skill is now written around the Python API β€” trainer selection, dataset format, load-bearing config fields, GRPO reward-function contract β€” with the CLI as a footnote. trl skills install is dropped and the skill source moves out of the package.

DistillationTrainer supports tool calling

Continuing the graduation cycle from v1.10.

by @qgallouedec in #6723

Suggest migrating to DistillationTrainer when the GKD config is fully covered

GKDTrainer now emits a hint when the config is fully covered by the stable DistillationTrainer β€” no behavior change, just a nudge for users still on the older API.

by @qgallouedec in #6847

Other

vLLM version sweep

Fixes

  • Fix GRPO/RLOO training with multiple images for VLMs without grid metadata by @qgallouedec in #6570
  • Fix GRPO/RLOO with dynamic-tiling VLMs (InternVL) by @qgallouedec in #6619
  • Handle None token logprobs from vLLM in GRPO importance sampling by @behroozazarkhalili in #6693
  • [GRPO] Match the Liger dapo / cispo / vespo normalizer to the non-Liger path β€” completes the fix from v1.10 (#6024) on the Liger path. By @kashif in #5890
  • [SFT] Treat past_key_values and attentions as optional on the model output by @Hakureirm in #6686
  • Fix AsyncGRPOTrainer crashing on environment-owned datasets by @albertvillanova in #6824
  • Fix SDPO scale_rewards CLI parsing and warn on ignored feedback by @sergiopaniego in #6341
  • Fix opencode.py proxy trace / log paths after OpenEnv moved them to functions by @sergiopaniego in #6672

Documentation and Examples

  • Rework AsyncGRPO docs alongside the metrics rework (@AmineDiro, #6715)
  • Reorganize examples into self-contained per-example folders (see above)
  • Suggest migrating to DistillationTrainer when GKD config is covered (see above)

CI

Big CI-hygiene cycle by @albertvillanova. Highlights:

Read more