Repository navigation
[pipeline] Make loss-count ownership explicit - #5327
sanketpurandare wants to merge 1 commit into
Conversation
bcdff3d to
4342f1f
Compare
4342f1f to
07f4759
Compare
| global_token_counts = ( | ||
| torch.cat( | ||
| ( | ||
| local_loss_token_counts.reshape(-1), | ||
| local_routing_token_counts.reshape(-1), | ||
| ) | ||
| ) | ||
| if owns_loss | ||
| else local_routing_token_counts.reshape(-1) |
There was a problem hiding this comment.
I think we should do either of
- only handle loss related logic when
owns_loss=True(need to be careful to not hang in collective due to non-spmd behavior) - or continue to let every rank handle loss -- I used to see some codebase sending final loss to all PP ranks.
We shouldn't use the name global_token_counts but fills it implicitly with local values.
There was a problem hiding this comment.
Loss counts are now absent on non-loss stages, rather than local values under a global name. Loss-owner DP/CP peers make the same reduction decision, while routing denominators still reduce on every MoE stage.
| if parallelism_context.dp_enabled: | ||
| global_token_counts = dist_utils.dist_sum_tensor( | ||
| global_token_counts, parallelism_context.get_mesh("dp") | ||
| ) |
There was a problem hiding this comment.
I guess the purpose of this PR is to save this all-reduce on non last-stage PP ranks, thus improving throughput? Not clear how much it'd bring from this tiny collective.
There was a problem hiding this comment.
This skips loss-only payload and validation reduction work on non-owners, not the routing collective. I removed the implied throughput claim; I haven't measured a TPS improvement attributable to this tiny collective alone.
07f4759 to
412f0a5
Compare
Prepared with AI assistance. Loss normalization belongs to stages that evaluate the objective. Require denominators on those owners, and discard unused denominators on other ranks before device conversion or optimizer-step mutation. Preserve existing RL, TorchFT and direct engine callers instead of requiring an unrelated API change. Routing denominators remain globally reduced on every MoE stage. Keep token-seen and auxiliary metric collectives on all ranks; reduce and log loss/count metrics only on owners. Non-owners retain the existing explicit loss sentinel. No throughput improvement is claimed for the smaller payload. Test plan: - Existing engine ownership regression failed before the boundary fix. - 91 RL/core trainer tests passed, including valid legacy non-owner input, missing-owner errors before mutation, and unchanged routing/metric behavior. - Owning non-type lint hooks passed; three unchanged main type findings remain. - Final full-stack docs and real pipeline/graph verification are tracked separately and are not claimed by this owning test result.
412f0a5 to
bdc6158
Compare
|
@claude review |
|
Claude finished @tianyu-l's task in 2m 19s —— View job Verdict: minor changes requested. The design follows option 1 from the earlier review ( DetailsWhat I checked and found safe
Issues
Tests
On the value question from the earlier review: the PR now states it claims no throughput gain, so the case for merging rests on semantic clarity. With +304/-70 lines, mostly tests, that's a maintainer call. |
| if total_global_valid_tokens is not None: | ||
| num_global_valid_tokens = int(total_global_valid_tokens.item()) | ||
| if num_global_valid_tokens == 0: | ||
| raise ValueError( | ||
| "Validation ran on zero valid tokens; cannot compute an average " | ||
| "validation loss. Ensure the validation batches contain unmasked " | ||
| "labels." | ||
| ) |
There was a problem hiding this comment.
This failure path is no longer SPMD under PP. On the base branch every PP stage built total_global_valid_tokens from the same per-DP-rank data, so all ranks raised this ValueError together. Now only loss owners raise it. Non-owners log -1.0, return to train(), and block on the next PP P2P or FSDP collective until the job is torn down (NCCL timeout, or the torchrun agent killing the group). Under PP, rank 0 is a non-owner, and that is the rank whose logs people usually read, so the actual error is easy to miss.
This is an edge case (fully masked validation labels), but it is the "careful to not hang in collective due to non-SPMD behavior" concern from the earlier review. Some options:
- keep the per-step DP count reduction on every rank, which is a scalar and cheap, and do the zero check everywhere; or
- accept the asymmetry and add a comment saying non-owners can't detect this case.
| owns_loss = not self.parallelism_context.pp_enabled or self.pp_has_last_stage | ||
| if not owns_loss: | ||
| global_loss_token_counts = None | ||
| elif global_loss_token_counts is None: | ||
| raise ValueError( | ||
| "global_loss_token_counts must be provided on loss-owning ranks" | ||
| ) |
There was a problem hiding this comment.
Two things here.
-
engine.pp_has_last_stageis alreadyTruewhen PP is disabled (training_engine.py:408), sonot self.parallelism_context.pp_enabled or self.pp_has_last_stagereduces toself.pp_has_last_stage. This file already uses the bare form at L743/L880/L891, and so doesrl/trainer/trainer.py:303. The same applies totrainer.py:365. Only the validator needs the long form, because itspp_has_last_stageisNonewithout PP. -
Overwriting a caller's tensor with
Noneon non-owners means the engine still accepts the old pattern this PR is removing: local counts passed under a global name.experiments/torchft/trainer.py:390-416still has that exact block (and computesglobal_main_loss_token_countfrom it), so the naming concern from the earlier review is still there in the duplicated FT train step. I'd either update the FT copy to match core and drop the override, keeping only the owner-sideValueError, or explain why this compatibility shim should stay. Discarding the input saves only a tiny H2D copy of a value that is never read on non-last stages.
| vectors over the optimizer step. Routing counts reduce on every MoE stage; | ||
| loss counts reduce only on loss-owning pipeline stages. Non-owners pass no | ||
| loss denominator (`None`) to the engine and retain a `-1` logging sentinel; | ||
| local counts are not substituted for a global loss denominator. The routing | ||
| count collective still runs on every MoE stage during training; this reduces | ||
| its loss payload, not its collective count. Validation skips loss-only | ||
| reductions on non-owners. Each prediction |
There was a problem hiding this comment.
nit: this is trainer and collective bookkeeping ("reduces its loss payload, not its collective count", the -1 logging sentinel) inside the MTP model doc. That belongs in the PR description, or a comment in train_step. Also, Each prediction is now left on its own short line. Something like this would do:
| vectors over the optimizer step. Routing counts reduce on every MoE stage; | |
| loss counts reduce only on loss-owning pipeline stages. Non-owners pass no | |
| loss denominator (`None`) to the engine and retain a `-1` logging sentinel; | |
| local counts are not substituted for a global loss denominator. The routing | |
| count collective still runs on every MoE stage during training; this reduces | |
| its loss payload, not its collective count. Validation skips loss-only | |
| reductions on non-owners. Each prediction | |
| vectors over the optimizer step. Routing counts are reduced on every MoE | |
| pipeline stage; loss counts are reduced only on stages that evaluate the loss. | |
| Each prediction |
Prepared with AI assistance.
Loss normalization belongs to stages that evaluate the objective. Require
denominators on those owners, and discard unused denominators on other ranks
before device conversion or optimizer-step mutation. Preserve existing RL,
TorchFT and direct engine callers instead of requiring an unrelated API change.
Routing denominators remain globally reduced on every MoE stage.
Keep token-seen and auxiliary metric collectives on all ranks; reduce and log
loss/count metrics only on owners. Non-owners retain the existing explicit
loss sentinel. No throughput improvement is claimed for the smaller payload.
Verification
Owning-commit CPU checks: 116 passed, 2 warnings in 17.02s.
Strict relevant Sphinx builds and all non-type repository hooks passed.
Three unchanged main type findings remain attributed, not reported as a clean
full type run. External reviewer and CI acceptance are separate, still pending.
Complete-stack integration (not this PR in isolation): real four-GPU PP2/DP2/
EP2 and PP4/DP1/EP1 ran ten updates with exact model/optimizer tensor parity,
optimizer graphs and sequential validation. A fresh native PP2/PP4 checkpoint
matrix matched resumed state exactly. These are debug-model contract checks,
not 671B throughput or convergence results.