Skip to content

[Main] Fix Numerical issue for GTP + Muon + MXFP8 param gather + use layerwise optimizer layout - #6754

Merged
zhongbozhu merged 5 commits into
NVIDIA:mainfrom
zhongbozhu:fix_muon_reuse_grad_buf
Aug 27, 2026
Merged

[Main] Fix Numerical issue for GTP + Muon + MXFP8 param gather + use layerwise optimizer layout #6754
zhongbozhu merged 5 commits into
NVIDIA:mainfrom
zhongbozhu:fix_muon_reuse_grad_buf

Conversation

@zhongbozhu

@zhongbozhu zhongbozhu commented Aug 21, 2026

Copy link
Copy Markdown
Contributor
  • I, the PR author, have personally reviewed every line of this PR.

What does this PR do?

This PR fixed a numerical issue under the specific combination as shown in the title: GTP + Muon + MXFP8 param gather + use layerwise optimizer layout

The fix is simple, let use_layer_wise_param_layout for Muon layerwise optimizer respect reuse_grad_buf_for_mxfp8_param_ag and choose not to remap parameters into the flat DDP buffer and reuse the gradient buffer for AG.

This bug also fixed the corner case where Muon + layerwise optimizer + mxfp8 primary weight used to only work with --overlap-param-gather, so we need to make sure overlap False also just works.

How _stage_layerwise_mxfp8_params works for distributed layerwise optimizer for muon

1. Overlap ON

Optimizer step
└─ Update locally owned FP32 masters
   └─ Do not synchronize parameters yet                 # step_with_ready_grads

Next forward:

Bucket group N becomes needed                           # DDP forward pre-hook
├─ If not already dispatched:
│  ├─ Stage every bucket in group N                     # _stage_layerwise_mxfp8_params
│  └─ Launch asynchronous all-gather                    # dist_all_gather_func
├─ Wait for group N                                     # finish_param_sync
├─ Stage and launch group N+1
├─ Publish group N into TE MXFP8 storage                # _post_param_sync
└─ Compute with group N
       └─ overlaps with group N+1 all-gather
Forward pre-hooks and the next-group link progressively synchronize all bucket groups.


2. Overlap OFF

Optimizer step
├─ Update locally owned FP32 masters
└─ Iterate over every LayerWise-owned bucket group      # start_param_sync_for_bucket_group_subset
   ├─ Stage every bucket in the current group           # _stage_layerwise_mxfp8_params
   ├─ Run synchronous all-gather                        # dist_all_gather_func
   ├─ Publish gathered values into TE MXFP8 storage     # _post_param_sync
   └─ Continue with the next bucket group

All groups complete
└─ Optimizer step returns

Both modes synchronize every bucket group; 

overlap ON lets forward pre-hooks drive them incrementally, 

while overlap OFF must traverse all groups eagerly before the next forward begins.

Unit test:

torchrun --standalone --nproc-per-node=4 \
  --log-dir /tmp/gtp-test-logs \
  --redirects 3 --tee 0:3 \
  -m pytest -q --capture=fd \
  tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_mxfp8_fp8_param_gather[tp_case0-dp_overlap0]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_mxfp8_fp8_param_gather[tp_case0-dp_overlap1]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_mxfp8_fp8_param_gather[tp_case1-dp_overlap0]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_mxfp8_fp8_param_gather[tp_case1-dp_overlap1]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_mxfp8_moe_fp8_param_gather
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_gdp_adamw_moe_muon_mxfp8_sync[False]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_gdp_adamw_moe_muon_mxfp8_sync[True]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_muon_layout_and_mxfp8_param_gather_parity[False]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_muon_layout_and_mxfp8_param_gather_parity[True]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_moe_grouped_tensor_op_fuser_layerwise_mxfp8_sync_parity
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gdp_adamw_moe_muon_mxfp8_param_gather[False]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gdp_adamw_moe_muon_mxfp8_param_gather[True]
[default0]:PASSED tests/unit_tests/generalized_tensor_parallel/test_gtp_fp8_param_gather.py::TestGTPFp8ParamGather::test_gtp_mxfp8_save_does_not_perturb_training

⚠️ For major changes (either in lines of code or in its impact), please make sure to first share a design doc with the team. If you're unsure what's the best way to do so, contact @NVIDIA/mcore-oncall.

Issue tracking

For PRs from open-source community contributors:

  • New features: a linked issue is required. Please open a feature request and reference it here before submitting the PR.
  • Small updates (bug fixes, minor improvements): a linked issue is recommended and will accelerate the PR review process.

Linked issue:

Contribution process

Pre-checks

  • I have added relevant unit tests
  • I have added relevant functional tests
  • I have added proper typing to my code Typing guidelines
  • I have added relevant documentation
  • I have run the autoformatter.sh on my PR

Code review

Feel free to message or comment @NVIDIA/mcore-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!

All PRs start as draft. If you open a non-draft PR, it will be automatically converted to draft.

Step 1: Mark PR as "Ready for Review"

  1. When your PR is ready, click Ready for Review.
  2. An oncall reviewer is auto-assigned and expert reviewers are notified based on your changes.
    • Some PRs may jump straight to step 2. This is determined by .github/CODEOWNERS.

⚠️ Only mark as ready once merge-conflicts are resolved and the CI is passing.
Final Review might get declined if these requirements are not fulfilled.

Step 2: Final Review

For PRs that change megatron/core, once all expert reviewers have approved, the Final Review label is applied automatically and final reviewers are assigned.

For PRs outside megatron/core, this step is skipped.

Step 3: Approved

Once all required reviewers have approved, the Approved label is applied automatically.

Merge

Any member of mcore-engineers will be able to merge your PR.

@zhongbozhu
zhongbozhu requested review from a team as code owners August 21, 2026 09:37
@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.

@svcnvidia-nemo-ci
svcnvidia-nemo-ci marked this pull request as draft August 21, 2026 09:37
@github-actions

Copy link
Copy Markdown
Contributor

This PR has been automatically converted to draft because all PRs must start as drafts.

When you are ready for review, click Ready for Review to begin the review process. This will:

  1. Add the oncall reviewer (optional reviewer)
  2. Add required review teams based on your changes

See the contribution guide for more details.

@zhongbozhu

Copy link
Copy Markdown
Contributor Author

/ok to test 3f1ea27

if len(buffers) == 0:
return []

# LayerWiseDistributedOptimizer and DistributedOptimizer classify a whole bucket group from

@zhongbozhu zhongbozhu Aug 21, 2026

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.

Note: this change is necessary to pass the --over-param-gather = False unit test, otherwise the numerical error will be very high.

With overlap=False, LayerWise launches parameter AG immediately after the Muon step, before Adam fallback has stepped. Without ownership partitioning, the mixed group starts with a Muon bucket, so the entire group—including Adam parameters—is gathered with stale Adam values. DistOpt later skips that group, so updated Adam shards are never gathered.

With overlap=True, AG is deferred to the next forward pass, after both Muon and Adam have stepped. Therefore, the mixed group happens to contain current values.

partition_buckets removes this timing dependency by keeping Muon-owned and Adam-owned buckets in separate groups.

@fanshiqing

Copy link
Copy Markdown
Member

/claude strict-review

@zhongbozhu
zhongbozhu requested a review from a team as a code owner August 22, 2026 04:26
@zhongbozhu

Copy link
Copy Markdown
Contributor Author

/ok to test 1c46170

@fanshiqing

Copy link
Copy Markdown
Member

/claude strict-review

Comment thread megatron/core/distributed/param_and_grad_buffer.py
Comment thread megatron/core/optimizer/optimizer.py Outdated
Comment thread megatron/core/optimizer/optimizer.py
Comment thread megatron/core/distributed/param_and_grad_buffer.py Outdated

@Wohox Wohox left a comment

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.

LGTM!
This PR is highly similar to #5479 regarding partition_buckets & high prec init value & copying back to bf16 staging buffer for AG. Discussed with @zhongbozhu #5479 need to rebase current PR and then decide the scope for change.

@Wohox

Wohox commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

@zhongbozhu Can you help double check with this PR, the fp8 param gather also utilizes the multi-tensor copy optimization Shiqing developed?

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
@zhongbozhu
zhongbozhu force-pushed the fix_muon_reuse_grad_buf branch from 1c46170 to 814cfdd Compare August 26, 2026 07:40
if self.ddp_config is not None
else config.overlap_param_gather
)
if self.overlap_param_gather and not self.use_buffer_param_sync:

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.

Note: to fix the numerical bug, this has to be

if not self.use_buffer_param_sync and (
            self.overlap_param_gather or config.reuse_grad_buf_for_mxfp8_param_ag
        ):

which is them simplified to be layerwise_param_sync_via_bucket_group flag created at init time

# All-gather updated params. If overlap_param_gather is True, the all-gather
# is deferred to the forward pre-hooks via DDP bucket infrastructure.
if not self.overlap_param_gather:
if self.use_buffer_param_sync:

@zhongbozhu zhongbozhu Aug 26, 2026

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.

Note: To fix the numerical bug, this has to be

if self.use_buffer_param_sync or self.config.reuse_grad_buf_for_mxfp8_param_ag:

which is then further simplified to be layerwise_param_sync_via_bucket_group, essentially if the bucket group should initiate the param sync (instead of letting optimizer step do the param all-gather, and then in this case we don't have all-gather & fwd pass overlap)

bucket group initiating param sync means that the bucket group chooses the buffer, launches/waits for the collective, and supports forward-hook overlap—instead of the optimizer calling allgather_params() directly, which has no param AG overlap.

The reuse_grad_buf_for_mxfp8_param_ag toggle also requires bucket-group parameter sync because the bucket group owns grad_data and performs the complete FP32 master → BF16 grad buffer → all-gather → TE MXFP8 quant workflow, so it has to be bucket group initiating the param sync, even when overlap_param_gather is False.

@zhongbozhu

Copy link
Copy Markdown
Contributor Author

/ok to test 814cfdd

@fanshiqing fanshiqing left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
@zhongbozhu

Copy link
Copy Markdown
Contributor Author

/ok to test 277eceb

@svcnvidia-nemo-ci svcnvidia-nemo-ci added the Final Review PR is in the "final review" stage label Aug 26, 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 Aug 27, 2026
@zhongbozhu
zhongbozhu added this pull request to the merge queue Aug 27, 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/33047704410

Merged via the queue into NVIDIA:main with commit af7e4f6 Aug 27, 2026
145 of 150 checks passed
@zhongbozhu
zhongbozhu deleted the fix_muon_reuse_grad_buf branch August 27, 2026 08:58
xuwchen pushed a commit to xuwchen/Megatron-LM that referenced this pull request Aug 27, 2026
…layerwise optimizer layout (NVIDIA#6754)

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
(cherry picked from commit af7e4f6)
xuwchen pushed a commit to xuwchen/Megatron-LM that referenced this pull request Aug 27, 2026
…layerwise optimizer layout (NVIDIA#6754)

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
(cherry picked from commit af7e4f6)
Wohox added a commit to Wohox/Megatron-LM that referenced this pull request Aug 28, 2026
Rebased onto main after NVIDIA#6754 ([Main] Fix Numerical issue for GTP + Muon +
MXFP8 param gather + use layerwise optimizer layout).

Conflict resolution notes:

* ``use_layer_wise_param_layout`` (bool) becomes ``layer_wise_param_layout``
  (``'padded' | 'decoupled' | 'legacy'``, default ``'decoupled'``). NVIDIA#6754 gave
  the old ``False`` value -- the legacy non-DistOpt single-buffer path -- real
  MXFP8 param-gather support and a padded-vs-legacy parity test, so this PR no
  longer reuses that value for the new compact decoupled layout.
* Dropped the "padded layout does not support FP8/FP4 parameter gather"
  validation guard: NVIDIA#6754 added that support.
* Dropped this PR's copy of the ``get_high_precision_init_val`` FP32-master
  seeding in ``Float16OptimizerWithFloat16Params``; NVIDIA#6754 landed the same fix.
  Kept the shared ``pop_high_precision_init_val()`` helper so optimizer.py,
  distrib_optimizer.py and Megatron-FSDP all use one implementation.
* Dropped this PR's ``partition_buckets`` owner-splitting in the
  force_single_bucket_group branch and the ``_bucket_distopt`` /
  ``_merged_use_distributed_optimizer`` helpers: NVIDIA#6754's partition-by-optimizer-
  owner already separates LayerWise from DistOpt buffers before any merging
  strategy runs. Kept the per-buffer ``ddp_config`` threading in the fp8-merge
  branch and reduced the rest to one invariant assert.
* ``layerwise_param_sync_via_bucket_group`` (NVIDIA#6754) is forced to
  ``overlap_param_gather`` on the decoupled layout: a compact-layout param's
  ping-pong owner is unrelated to its DDP-buffer offset, so the DistOpt-style
  fixed-shard bucket-group gather does not apply there.
* Golden values: kept this PR's regenerated ``dist_dist_muon`` values (the
  default is still the decoupled layout) and applied main's NVIDIA#6539 convention of
  dropping the leading NaN iteration-time entry.
* Reverted the unrelated autoformatter churn in ``models/{gpt,hybrid,base}.py``
  and ``models/dist_utils.py`` that main has not applied.
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 nemotron

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] GTP+Muon

7 participants