Skip to content

fix(mfsdp): refresh V2 compute weights after optimizer step - #6336

Merged
wujingyue merged 2 commits into
NVIDIA:mainfrom
wujingyue:agent/mfsdp-v2-native-weight-sync
Aug 11, 2026
Merged

wujingyue merged 2 commits into
NVIDIA:mainfrom
wujingyue:agent/mfsdp-v2-native-weight-sync

Conversation

@wujingyue

@wujingyue wujingyue commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Refresh MFSDP V2 BF16 compute weights through FullyShardedOptimizer's native post-step copy hook.
  • Extract sync_model_weights_from_main_weights() so the checkpoint-load path and the fully_shard_optimizer() step post-hook refresh compute weights through one implementation instead of two.
  • Extend the MFSDP V2 optimizer integration test from three to ten steps so missing compute-weight refreshes are detected.

Root cause

#5949 moved main-weight to model-weight synchronization from pre-forward to optimizer post-step. FullyShardedOptimizer inherited the post-step extension point but left its implementation as a no-op, so optimizer weights updated while subsequent forwards continued to use stale compute weights.

The existing three-step integration test used a 1% relative tolerance. The resulting loss drift remained below that threshold, so the test passed despite the missing refresh.

Also fixes a rank-order deadlock in fully_shard_optimizer()

The experimental step_post_hook collected parameter groups into a set and iterated it:

fsdp_parameter_groups: set[FsdpParameterGroup] = set()
...
for parameter_group in fsdp_parameter_groups:
    parameter_group.sync_model_weight_from_main_weight()

FsdpParameterGroup is @dataclass(frozen=True, eq=False), so it hashes by id() and set iteration order varies per process. sync_model_weight_from_main_weight() issues a collective whenever main_weight and model_weight are distinct buffers, so ranks could issue the per-group collectives in different orders and hang.

Routing the hook through the shared helper fixes this as a consequence of the refactor rather than as a special case: the helper iterates the parameters, whose order is identical on every rank, and uses a set only to skip groups already synced.

The bug is latent on main today because it needs both conditions at once — two or more parameter groups and main_weight is not model_weight. No in-tree test combines them: configurations whose optimizer and parameter placements match (e.g. HSDP) alias the two buffers, so the sync early-returns without a collective.

Validation

  • uv run python -m torch.distributed.run --nproc_per_node=2 -m pytest -q tests/unit_tests/distributed/mfsdp_v2/test_mcore_adapter.py::TestMcoreAdapter::test_build_train_and_step
  • Full tests/unit_tests/distributed/mfsdp_v2/test_fully_shard.py on 4x H100: 32 passed. The one failure, test_overlaps_communication_and_compute[default], reproduces identically on unmodified main — it is a launch-bound profiler assertion, unrelated to this change.
  • Deadlock repro: an HFSDP configuration (optimizer [Flat, Flat], parameter [Replicate, Flat]) over three parameter groups on 4 GPUs hung indefinitely before this change and completes in ~9s after it.
  • ruff check and black --check on edited files

Related to #5949 and #6325.

@copy-pr-bot

copy-pr-bot Bot commented Aug 7, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@wujingyue
wujingyue force-pushed the agent/mfsdp-v2-native-weight-sync branch 2 times, most recently from 89e7063 to 1252bd6 Compare August 7, 2026 04:23
@wujingyue
wujingyue marked this pull request as ready for review August 7, 2026 04:26
@wujingyue
wujingyue requested review from a team as code owners August 7, 2026 04:26
@wujingyue
wujingyue requested a review from skyw August 7, 2026 04:26
@wujingyue

Copy link
Copy Markdown
Contributor Author

/ok to test 1252bd6

Comment thread megatron/core/optimizer/fully_sharded_optimizer.py Outdated
for parameter in self.get_parameters()
if (parameter_group := get_containing_parameter_group(parameter)) is not None
}
for parameter_group in parameter_groups:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: single letter "s" difference is hard to spot and easily miss. May consider better name.

Comment thread megatron/core/optimizer/fully_sharded_optimizer.py Outdated
@svcnvidia-nemo-ci svcnvidia-nemo-ci added the Final Review PR is in the "final review" stage label Aug 7, 2026
@wujingyue
wujingyue force-pushed the agent/mfsdp-v2-native-weight-sync branch from 1252bd6 to 54d9aec Compare August 7, 2026 17:48
@wujingyue
wujingyue force-pushed the agent/mfsdp-v2-native-weight-sync branch from 54d9aec to ad73e1b Compare August 7, 2026 17:52
@wujingyue
wujingyue force-pushed the agent/mfsdp-v2-native-weight-sync branch from ad73e1b to bb85dbd Compare August 7, 2026 17:55
@wujingyue
wujingyue force-pushed the agent/mfsdp-v2-native-weight-sync branch 2 times, most recently from 1e21d17 to 847ee78 Compare August 11, 2026 00:25
wujingyue and others added 2 commits August 11, 2026 00:32
Signed-off-by: Jingyue Wu <jingyuew@nvidia.com>
Extract sync_model_weights_from_main_weights() so the checkpoint load path and
the fully_shard_optimizer() step post-hook refresh compute weights through one
implementation instead of two.

The post-hook previously collected parameter groups into a set and iterated it.
FsdpParameterGroup is @DataClass(frozen=True, eq=False), so it hashes by id()
and set iteration order varies per process. sync_model_weight_from_main_weight()
issues a collective whenever main_weight and model_weight are distinct buffers,
so ranks could issue the per-group collectives in different orders and deadlock.
Reusing the shared helper fixes that: it iterates the parameters, whose order is
identical on every rank, and uses a set only to skip groups already synced.

Reproduced with an HFSDP configuration (optimizer [Flat, Flat], parameter
[Replicate, Flat]) over three parameter groups on 4 GPUs, which hung until this
change; it now completes in ~9s.

Signed-off-by: Jingyue Wu <jingyuew@nvidia.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@wujingyue
wujingyue force-pushed the agent/mfsdp-v2-native-weight-sync branch from 847ee78 to 296a795 Compare August 11, 2026 00:33
wujingyue added a commit to Achyuthan-S/Megatron-LM that referenced this pull request Aug 11, 2026
HFSDP's optimizer placement [Flat, Flat] differs from the parameter placement
[Replicate, Flat], so main_weight and model_weight are distinct buffers. The
test drove a plain torch.optim.SGD, which never refreshes the compute weight,
so every forward re-read the initial weights: across all five steps the loss
and gradient were bit-identical while the optimizer weight advanced. HSDP
passes unwrapped because its optimizer and parameter placements match, so the
two buffers alias and the sync is a no-op.

Wrap the optimizer with fully_shard_optimizer() so its step post-hook refreshes
the compute weight. With that, HFSDP matches the single-rank baseline exactly
(absolute difference 0.0 at every step).

Drop the temporary race-repro probe and the magnitude diagnostic. The failure
was deterministic rather than a race, so neither is needed.

Depends on NVIDIA#6336, which routes that post-hook through
sync_model_weights_from_main_weights(); without it the hook iterates a set
whose order varies per rank and the collectives deadlock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jingyue Wu <jingyuew@nvidia.com>
@wujingyue
wujingyue added this pull request to the merge queue Aug 11, 2026
@svcnvidia-nemo-ci

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started!

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

Merged via the queue into NVIDIA:main with commit 1d402ad Aug 11, 2026
184 checks passed
@wujingyue
wujingyue deleted the agent/mfsdp-v2-native-weight-sync branch August 11, 2026 19:41
ksivaman pushed a commit to ksivaman/Megatron-LM that referenced this pull request Sep 1, 2026
)

Signed-off-by: Jingyue Wu <jingyuew@nvidia.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

This branch was previously deployed

2 inactive deployments
public — 296a795e Deployed Aug 11, 2026 by copy-pr-bot[bot] via finalize / publish-docs #5720
test — 296a795e Deployed Aug 11, 2026 by copy-pr-bot[bot] via cicd-wait-in-queue #18658
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 Run tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants