Repository navigation
[rl] Support context parallelism in the RL trainer - #5424
Merged
Merged
Conversation
wwwjn
marked this pull request as ready for review
October 15, 2026 19:03
wwwjn
requested review from
fegin,
felipemello1,
pzhan9,
tianyu-l and
wconstab
as code owners
October 15, 2026 19:03
tianyu-l
reviewed
Oct 15, 2026
wwwjn
requested review from
IvanKobzarev,
SherlockNoMad,
aditvenk and
sanketpurandare
as code owners
October 17, 2026 00:37
tianyu-l
reviewed
Oct 17, 2026
| max_num_documents: int | None = None, | ||
| max_context_length: int | None = None, | ||
| global_routing_token_counts: torch.Tensor | None = None, | ||
| loss_kwargs: dict[str, Any] | None = None, |
Contributor
There was a problem hiding this comment.
any reason we don't
- remove the function
to_loss_kwargsentirely - put loss kwargs in
as_input_dict
Another option is to override as_input_dict method of
Contributor
Author
There was a problem hiding this comment.
Sorry I didn't realize these are better places.
I think option2 sounds more natural to me because current loss_kwargs are only used for RL.
Then we'd have to override as_input_dict() in decoder in following format:
def as_input_dict(self) -> dict[str, Any]:
# Explicit base call: zero-argument super() fails in slots=True dataclasses.
return {
**TokenizedTrainingMicrobatch.as_input_dict(self),
"loss_kwargs": {
"generator_logprobs": self.generator_logprobs,
"temperature": self.temperature,
"loss_mask": self.loss_mask,
"advantages": self.advantages,
},
}
We'd have to use nest dict because otherwise preprocess_inputs will need to know each key / hardcode each key and shard them across CP. What does sounds like?
tianyu-l
approved these changes
Oct 19, 2026
Under CP, preprocess_inputs shards the labels along the packed token dimension, but the per-token loss arguments the RL losses consume (advantages, generator logprobs, loss mask, temperature) reached the loss unsharded, so the RL trainer rejected CP > 1. BaseModel.preprocess_inputs now also prepares the loss arguments and returns (inputs, labels, model_kwargs, loss_kwargs). Each model gives the loss-argument tensors the labels layout, so the existing _cp_shard / shard_tensors and SPMD annotation path shards them exactly like the labels, including under a CP load-balancing permutation. The PP path passes the sharded arguments on as loss_kwarg_mbs. Flux has no token labels and rejects loss arguments. The RL trainer config no longer rejects context_parallel_degree > 1. Adds the 8-GPU integration test rl_grpo_cp2_fsdp2_gen_tp2 (Qwen3-0.6B, trainer FSDP=2 x Ulysses varlen CP=2, two generators at TP=2) and a CPU test that runs a decoder's preprocess_inputs on a CP=2 mesh.
Review feedback: drop the separate loss-argument channel. The RL TrainingMicrobatch now overrides as_input_dict and nests its per-token tensors under "loss_kwargs", and TrainingMicrobatch.loss_kwargs() / to_loss_kwargs() are removed. to_input_dict moves the nested tensors to the device with the rest of the inputs. preprocess_inputs pops input_dict["loss_kwargs"] instead of taking a loss_kwargs parameter, shards those tensors like the labels, and still returns them as the fourth element. The nested key keeps the models free of RL-specific names: they only need to know "loss_kwargs".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Context parallelism (CP) in the RL trainer.
Under CP,
preprocess_inputsshards the labels along the packed token dimension, but the per-token loss arguments that the RL losses consume (advantages, generator logprobs, loss mask, temperature) reached the loss unsharded. CP > 1 therefore could not train.shard_per_token_tensors(intorchtitan/distributed/context_parallel.py) keeps a CP rank's contiguous token shard of each per-token tensor. This is the same layoutshard_tensorsproduces without a load-balancing permutation.TrainingEngineshards the loss arguments with it when CP is enabled. It raises if a CP load balancer is configured, because the permutation would reorder the labels but not the loss arguments. Ulysses CP, the CP mode for varlen attention, already requirescontext_parallel_load_balancer=None.Why the losses need no change
The GRPO and DAPO losses are masked per-token sums divided by a global loss-token count. Each CP rank sums over its own tokens. FSDP shards on
(dp_shard, cp)with gradient division disabled, so summing gradients over CP reproduces the full-sequence gradient. Loss metrics are summed over the existingdp x cploss mesh.Using it
A recipe sets
context_parallel_degreeon the trainer and converts the trainer's attention to a CP kernel. Generators keep plain attention. The new CI config shows the pattern:Tests
New CPU tests (
tests/unit_tests/cpu/distributed/test_context_parallel_per_token.py, 7 tests), all passing. They check:Existing CPU suites: distributed, RL trainer, losses, batcher and trainer loop pass (189). Two FSDP mixed-precision tests fail with and without this change: the local torch build predates
MixedPrecisionPolicy(param_dtype_override_fn=...).New 8-GPU integration test
rl_grpo_cp2_fsdp2_gen_tp2: Qwen3-0.6B, trainer FSDP=2 x Ulysses varlen CP=2, two generators at TP=2. The config builds with Ulysses varlen attention in all 28 trainer layers and plain varlen attention in the generators.GPU numerics, CP=2 vs no CP. One RL GRPO forward/backward plus optimizer step on Qwen3-0.6B (random init), on 4 GB300 GPUs with a 2026-10-12 nightly. Both layouts use the same rank-local initial weights and two fixed 4,096-token microbatches: packed documents, padding, and per-token advantages, logprobs and masks. Deterministic mode is on.
The no-CP repeat is bitwise identical. The CP differences are bf16 reduction-order noise; per-token loss arguments misaligned with the labels would move the loss by O(1). Peak active memory per GPU: 4.47 GiB without CP, 3.92 GiB with CP.
GPU end to end. The integration config's CP path on one 4-GPU host (trainer FSDP=1 x Ulysses varlen CP=2, one generator at TP=2), and the same with FSDP=2 and no CP. Both run all 5 steps and exit cleanly.
The two runs sample different rollouts, so their rows are not expected to match. At this 1,024-token context, CP=2 trains about half as many tokens per second as FSDP=2 on the same two GPUs. Both runs set
VLLM_ALLREDUCE_USE_FLASHINFER=0for the generator, because FlashInfer's all-reduce JIT build failed in that environment.Relation to #3980
#3980 (pipeline parallelism in the RL trainer) adds a guard that rejects CP until per-token loss arguments are sharded. This PR does that sharding on the non-PP path. Whichever lands second should drop that guard for the non-PP case. CP combined with PP still needs the sharded loss arguments packed into the PP targets, which is out of scope here.