Skip to content

Fix MTP CP-boundary roll to communicate on the context-parallel process group - #6746

Merged
liding-nv merged 4 commits into
NVIDIA:mainfrom
liding-nv:liding/mtp-cp-roll-group-fix
Sep 16, 2026
Merged

liding-nv merged 4 commits into
NVIDIA:mainfrom
liding-nv:liding/mtp-cp-roll-group-fix

Conversation

@liding-nv

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes MTP's CP-boundary roll (roll_tensor / _roll_tensor_packed_seq) to communicate on the context-parallel process group instead of the default (world) process group.

Details

Both roll variants compute their neighbor ranks from cp_group but issue isend/irecv without group=, so every CP boundary exchange actually runs on the default process group. When the world equals the CP group (typical unit tests) this is invisible. Whenever the CP group is a strict subgroup of the world (TP > 1 and/or multiple DP replicas), all CP rings in the job share one communicator:

  • eager-init unbatched P2P is serialized against every other op on that group — PyTorch warns: "An unbatched P2P op (send/recv) was called on this ProcessGroup … serialized with all other ops on this ProcessGroup";
  • independent rings' P2P schedules couple through the shared communicator, a hang hazard that grows with world size — especially with packed sequences, where _roll_tensor_packed_seq posts the exchange per packed sequence per microbatch.

The fix addresses the ops to cp_group and batches each rank's sends/receives with batch_isend_irecv.

Testing

  • New unit tests (TestRollTensorWithCPSubgroup) run both roll variants on a CP group that is a strict subgroup of the world (TP2 × CP2), asserting numeric equivalence with the single-rank semantics and that the exchange is addressed to the CP group (the latter fails on the pre-fix code).
  • Full test_multi_token_prediction.py run before/after the fix on 4 GPUs produces identical results (failing cases on 4 GPUs are pre-existing world-size/fp8 environment mismatches, unchanged by this PR).
  • Loss parity verified in a 5-node MTP+CP training A/B (fixed vs unfixed): loss trajectories match to ~1e-6 relative.
  • tools/autoformat.sh run; black/isort/pylint/ruff clean (mypy findings are pre-existing, outside the changed lines).

…ss group

Signed-off-by: Li Ding <liding@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@liding-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 360e152

@liding-nv

Copy link
Copy Markdown
Contributor Author

/claude fix

Signed-off-by: svcnvidia-nemo-ci <svcnvidia-nemo-ci@nvidia.com>
@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🛠️ Claude fix commit bb6d5b2426d6 (attempt 1)

⚠️ This explanation is AI-generated and may be inaccurate; the exact commit is authoritative.

What changed
Resolved the sole merge conflict in tests/unit_tests/transformer/test_multi_token_prediction.py by keeping both independently-added test classes: TestRollTensorWithCPSubgroup (PR side) and TestLearnedOutputContract (base side), each with its own setup_method/teardown_method. Verified no conflict markers remain, the file parses, both classes are byte-identical to their original sides, imports were already merged, and formatting seams are clean. No other file touched; nothing staged or committed.

Files changed by Claude

  • tests/unit_tests/transformer/test_multi_token_prediction.py

Why
Add/add conflict at end of file: both branches appended a different new test class after the existing Mamba test. Both classes open with an identically-shaped setup_method/teardown_method pair, so git anchored on the shared teardown_method and interleaved them into one broken hunk instead of treating them as separate additions. Keeping both was correct. Could not run black/isort or the tests: uv and formatters are not installed, torch is absent, no GPU (tests need 4), network blocked.

DCO
@liding-nv, please fix any DCO failures on your commits before merge. DCO does not block this workflow.

View exact commit

Sanitized and posted by svcnvidia-nemo-ci.

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

/ok to test bb6d5b2

@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

❌ Claude fix stopped because exact-SHA CI did not complete in time. View exact-SHA CI.

@liding-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 0fed350

@liding-nv

Copy link
Copy Markdown
Contributor Author

/ok to test b11538c



class TestRollTensorWithCPSubgroup:
"""Roll-tensor CP paths on a CP group that is a strict subgroup of WORLD.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing packed-sequence test uses TP1 × CP2 and checks numerical correctness without checking communicator selection. When run with two ranks, CP and WORLD contain the same ranks, hiding the communicator mismatch from numerical checks.

This test uses TP2 × CP2 to guarantee CP is a strict subgroup of WORLD, and explicitly checks that boundary exchanges use cp_group. The explicit group assertion detects the regression

full = torch.arange(2 * 16, dtype=torch.float32, device="cuda").reshape(2, 16)
# Oracle: the documented single-rank semantics on the full sequence.
expected_full, _ = roll_tensor(full.clone(), shifts=-1, dims=-1)
expected_local = self._cp_shard(expected_full, cp_rank, cp_size)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We build the reference by rolling the full sequence without distributed communication, then extracting this rank’s CP shard.
The actual path shards the input first, then rolls it with cross-rank communication. Both paths should produce identical local values.

rolled, rolled_sum = roll_tensor(local, shifts=-1, dims=-1, cp_group=cp_group)

assert torch.equal(rolled, expected_local)
assert rolled_sum == expected_local.sum()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These check the local output values—including exchanged boundary tokens and zero padding—and the returned local sum. They verify numerical behavior is preserved. They can also pass before the fix because the old implementation communicates with the correct peers through WORLD.

@svcnvidia-nemo-ci svcnvidia-nemo-ci added the Final Review PR is in the "final review" stage label Sep 15, 2026
@svcnvidia-nemo-ci svcnvidia-nemo-ci added Approved All necessary approvals have been made and removed Final Review PR is in the "final review" stage labels Sep 15, 2026
@liding-nv
liding-nv added this pull request to the merge queue Sep 15, 2026
@nemo-automation-bot

Copy link
Copy Markdown

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/35040222266

@nemo-automation-bot

Copy link
Copy Markdown

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/35043683828

@nemo-automation-bot

Copy link
Copy Markdown

🔄 Merge queue validation started!

You can track the progress here: https://github.com/NVIDIA/Megatron-LM/actions/runs/35044129580

Merged via the queue into NVIDIA:main with commit 1c9a7dc Sep 16, 2026
108 of 110 checks passed
@liding-nv
liding-nv deleted the liding/mtp-cp-roll-group-fix branch September 16, 2026 03:52

This branch was previously deployed

2 inactive deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Approved All necessary approvals have been made complexity: low

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants