fix: whiten advantages over the DP group that includes context parallel - #2235
Open
keepkeen wants to merge 1 commit into
Open
fix: whiten advantages over the DP group that includes context parallel#2235keepkeen wants to merge 1 commit into
keepkeen wants to merge 1 commit into
Conversation
`compute_advantages_and_returns` builds `all_advs` / `all_masks` from this CP rank's zigzag slice of each sequence, then hands them to `distributed_masked_whiten` with `mpu.get_data_parallel_group()` — which defaults to `with_context_parallel=False`. The all-reduce therefore only spans ranks sharing the same cp_rank, so each CP rank normalizes its slice with its own mean/variance and the two halves of one sequence get different affine transforms. Every rank's statistics are also computed over a strict subset of the batch's tokens, so none of them is the intended global whitening. Every other data-parallel group lookup in the codebase passes the flag explicitly; this call site was the only implicit one. The `all_masks.numel() > 0` guard has to go at the same time. A CP rank can legitimately own zero response tokens — for a prompt-heavy sequence both of its chunks fall inside the prompt (e.g. prompt=8000 resp=1000 at cp=2 gives [1000, 0]). With the corrected group that rank would skip a collective its peers are waiting on and hang the step. `distributed_masked_whiten` already handles an empty local tensor: it contributes 0 to all three reduced sums. Adds tests/test_advantage_whiten_cp.py, which spawns dp_size * cp_size gloo workers and asserts the whitened advantage of each sample is identical across (dp, cp) factorizations of the world and equal to the single-rank baseline.
Author
|
Hi @zhuzilin, could you please take a look when you have time? This fixes a reproducible training-correctness issue in advantage whitening under context parallelism, and adds a multi-process CPU regression test covering different DP/CP factorizations and empty local masks. GitHub currently reports the PR as mergeable, and all CI checks pass. Thanks! |
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.
What
--normalize-advantagesis computed with the wrong process group under context parallelism, so advantages are whitened with per-CP-rank statistics instead of global ones.Root cause
compute_advantages_and_returnsbuildsall_advs/all_masksfrom this CP rank's zigzag slice of every sequence (thecp_size != 1branch right above constructs the per-rank mask chunks explicitly), and then reduces over the CP-excluding group:distributed_masked_whitenall-reduces(sum, sum_sq, mask_sum)over that group, so the mean/variance only cover ranks that share the samecp_rank. Two consequences:Every other data-parallel group lookup in the repo is explicit about the flag —
data.py:186,188,model.py:693,loss.py:1295usewith_context_parallel=True,actor.py:106,242useFalse. This call site was the only implicit one.Impact
Hits any run combining context parallelism with
--normalize-advantages. Noteslime_validate_argsrequires--normalize-advantagesforreinforce_plus_plusandreinforce_plus_plus_baseline, and CI already runs--context-parallel-size 2together with--normalize-advantagesintest_qwen3_4B_ppo.py(and the disaggregate / critic-only variants) — nothing there checks the whitened values, which is why it went unnoticed.Driving the real
compute_advantages_and_returnsatcp_size=2, dp_size=1with three sequences(total, resp) = (64,48) (100,90) (40,12)and group-centered rewards+1.5 / -0.5 / -1.0:The two halves of sequence 0 disagree by ~34%, and both are off the correct value.
Why the
numel() > 0guard has to go tooA CP rank can legitimately own zero response tokens: for a prompt-heavy sequence both of its chunks land inside the prompt.
Today
if all_masks.numel() > 0:makes participation in the all-reduce data-dependent. That is already latently unsafe (two DP ranks at the samecp_rankcan disagree under dynamic batching), and it becomes an unconditional hang the moment the group spans CP. So the call is now unconditional —distributed_masked_whitenhandles an empty local tensor, contributing 0 to all three reduced sums, and still raises if the mask is globally empty.Test
tests/test_advantage_whiten_cp.py(new, registered in thecpu-unittestjob) spawnsdp_size * cp_sizegloo workers, runs the realcompute_advantages_and_returns, and asserts the whitened advantage of each sample is the same for every(dp, cp)factorization of the world and equal to the single-rank baseline. It covers(1,1) (2,1) (1,2) (2,2) (1,4) (4,1)and includes a prompt-heavy sequence so some ranks contribute an empty local mask.On
mainit fails:The spawn join is bounded at 180s so that a future rank-dependent branch around the collective surfaces as a test failure rather than a stuck CI job.