Skip to content

[Dev] Align decoupled Muon FP8 parameter-gather paths with main - #6015

Open
Wohox wants to merge 2 commits into
NVIDIA:devfrom
Wohox:wohox/fix-dp-reshardable-empty-bucket
Open

[Dev] Align decoupled Muon FP8 parameter-gather paths with main#6015
Wohox wants to merge 2 commits into
NVIDIA:devfrom
Wohox:wohox/fix-dp-reshardable-empty-bucket

Conversation

@Wohox

@Wohox Wohox commented Jul 24, 2026

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

Summary

This PR is now the dev-branch alignment counterpart of the decoupled Muon FP8
parameter-gather work in #5479 on main. It keeps the original #6015 LayerWise
checkpoint/param-gather fixes and ports the subsequent implementation cleanups and
performance work so the two branches use the same primitives and validation boundaries.

The refactor also incorporates the batched MXFP8 copy-back optimization from #6094.
megatron/training/training.py remains unchanged; the shared behavior lives in the DDP,
FP8, and optimizer layers that own it.

Changes

FP8 parameter staging and copy-back

  • Reuse the batched MXFP8 copy-back path from Perf: skip per-param copy_ dispatch in the MXFP8 param copy-back #6094, invoking the quantizer directly
    instead of routing every parameter through aten::copy_ / __torch_dispatch__.
  • Stage LayerWise FP32 masters directly into one flat BF16 transport tensor instead of
    allocating one BF16 tensor per parameter and flattening the temporary copies.
  • Use one batched helper for BF16 -> MXFP8/blockwise copy-back, while preserving the
    single-parameter compatibility wrapper.
  • Centralize consumption and clearing of Transformer Engine's high-precision
    initialization value in pop_high_precision_init_val, shared by DDP, Distributed
    Optimizer, LayerWise Optimizer, Float16 Optimizer, and Megatron FSDP call sites.
  • Keep the Megatron FSDP fallback behavior independent: it reuses the shared helper but
    does not opt into the LayerWise gather topology.

Validation and routing

  • Detect only the LayerWise-supported parameter storage types (plain MXFP8 and
    blockwise FP8) instead of treating every generic Float8/GroupedTensor representation
    as supported.
  • Reject --moe-single-grouped-weight for the compact LayerWise FP8 gather because the
    batched copy-back does not support Transformer Engine GroupedTensor storage.
  • Preserve native FP32 routing and mixed BF16/FP8 bucket handling.
  • Correct the overlap assertion for chunked optimizer-state/master-weight offload: a
    child-level pre-forward requirement does not imply that every bucket owned by that
    child has already been dispatched.

Existing #6015 fixes retained

  • Handle empty/padding-only dp_reshardable bucket shards in the decoupled compact
    LayerWise configuration.
  • Avoid slot-0 assumptions in LayerWise parameter gather when another rank owns the
    first real parameter.
  • Keep the FP8 parameter-gather tests non-vacuous and exercise deferred overlap paths.

Checkpoint redesign boundary

The existing checkpoint implementation is retained by this PR; the cleaner
padding-free redesign is intentionally not implemented here.

The follow-up design has two relevant rank-local cases (Distributed Optimizer ownership
is already resolved before these buffers are constructed, so an "unowned bucket" case
is not part of the design):

  1. A shard wholly beyond numel_unpadded is trailing DP-alignment padding and should be
    omitted from save and load.
  2. A shard wholly inside an intra-parameter alignment gap is still inside the current
    dense bucket checkpoint extent. It should be omitted by making the coverage contract
    padding-aware, not by synthesizing checkpoint tensors. The change must preserve
    explicit bucket identity and construct the corresponding load request without relying
    on list position, so multi-bucket checkpoints round-trip by value.

This must be a scoped coverage rule for declared padding regions; globally disabling DCP
coverage validation would also accept missing optimizer state and is not safe.

Test plan

H100:

  • tests/unit_tests/test_muon_decouple_fp8_param_gather.py: 8 passed, 9 skipped
    per rank.
  • tests/unit_tests/test_fp8_utils.py: 2 passed, 3 skipped per rank.
  • tests/unit_tests/distributed/test_fp8_param.py: 10 passed, 12 skipped per rank.
  • tests/unit_tests/distributed/mfsdp_v1/test_mfsdp_fully_shard.py:
    197 passed, 125 skipped, 6 xfailed per rank. The six results are expected XFAILs,
    not failures introduced by this PR.
  • tests/unit_tests/dist_checkpointing/test_layer_wise_optimizer.py:
    292 passed, 161 skipped per rank.

GB200:

  • tests/unit_tests/test_fp8_utils.py: 5 passed per rank, including the real MXFP8
    batched copy-back path.
  • The full Muon file exposes two pre-existing test/baseline issues: chunked-offload
    numerical drift also reproduces on the pre-refactor b9086f1a7 baseline, and the
    pending-gather test calls finish_grad_sync() after manually waiting the parameter
    gather handle. Neither is caused by this refactor.

Formatting and static checks:

  • Black check passed for all changed Python files.
  • tools/autoformat.sh scope passed Black, isort, pylint, and ruff.
  • Copyright checks passed.
  • git diff --check passed.

Related

@copy-pr-bot

copy-pr-bot Bot commented Jul 24, 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.

@Wohox
Wohox force-pushed the wohox/fix-dp-reshardable-empty-bucket branch from 94362fc to 56f71b5 Compare July 28, 2026 06:48
@Wohox
Wohox force-pushed the wohox/fix-dp-reshardable-empty-bucket branch from 593ce3b to da968db Compare July 30, 2026 02:09
Wohox added a commit to Wohox/Megatron-LM that referenced this pull request Jul 30, 2026
Four defects found while reviewing this PR. All four originate in the dev-side commits
this PR ports (NVIDIA#6015 `56f71b517`/`593ce3b10`, NVIDIA#5470 `0866f3511`), so they apply to `dev`
as well.

1. `sharded_param_state_fs_model_space` rejected every plain `ShardedTensor`.
   The guard asserted `isinstance(sharded_metadata, ShardedTensorFactory)` per param, using
   "is a factory" as a proxy for "is the decoupled LayerWise layout" -- unrelated properties.
   Only gated-MLP fc1 yields a factory, so this aborted `fully_sharded_model_space` for
   essentially every param of every ordinary DistributedOptimizer run (including resume from
   checkpoints whose metadata selects that format), with a misleading Muon message, and made
   the pre-existing two-branch handling below it dead code. Rejected once up front on the real
   condition instead, leaving the common path untouched.

2. `_allgather_helper` indexed rank 0's list blindly (`params_list[0][0].device`).
   Its fp8 twin already guards this, but the native-fp32 dispatch added by NVIDIA#5470 feeds it a
   per-rank split in which rank 0's list is empty whenever rank 0 owns no `keep_in_fp32`
   param -- an `IndexError` on every rank, on the first `allgather_params()`. Mirrored the fp8
   twin's lookup and its no-owner early return.

3. The padding template was rebuilt per save from a hardcoded Adam key set and
   `main_params_dtype`. Lion (and Muon's `muon_scalar_optimizer`) have no `exp_avg_sq`, and
   `'param'`'s dtype is not `main_params_dtype`, so a rank owning nothing in any bucket emitted
   padding whose keys/dtypes disagreed with the real shards its peers wrote under the same key.
   That matters even though the padding is discarded on load: it is a real `ShardedTensor`
   tiling the same `(gbuf_world_numel_unpadded,)` global tensor, and
   `dist_checkpointing/validation.py` requires one dtype per key, so the save aborts.

   Record the key set and dtypes once in `__init__` (`_ckpt_pad_dtypes`) from
   `optimizer_state_keys` / `_get_state_key_dtype` -- the same helpers every other call site
   uses. They are a property of the optimizer and its config, hence identical on every DP rank,
   so no cross-DP reconciliation is needed. Where a rank does hold state we cross-check the two
   agree, so a wrong derivation surfaces on that rank instead of as a checkpointing dtype error
   on whichever rank happened to own nothing.

4. A 101-character line in `megatron/core` would fail the pylint `C0301` pre-commit hook
   (`max-line-length=100`, `files: ^megatron/core/.*`). Reflowed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Pingtian Li <pingtianl@nvidia.com>
@Wohox
Wohox force-pushed the wohox/fix-dp-reshardable-empty-bucket branch 2 times, most recently from 1433ad0 to c89716a Compare July 31, 2026 17:40
@Wohox Wohox changed the title [Dev] Fix dp_reshardable optimizer checkpoint save for empty/padding-only shards [Dev] Muon decoupled-optimizer bug fixes (dp_reshardable checkpoint + LayerWise param-gather) Aug 1, 2026
@Wohox

Wohox commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

/claude strict-review

@claude claude Bot 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.

Strict review passed — no significant issues found.

Traced the full data flow across all five changed files:

distrib_optimizer.py (main fix) — The store-back line gbuf_range_map_for_all_buckets[bucket_idx] = bucket_state correctly re-binds the synthesized padding shard into the returned state; without it the shard is dropped and DCP global-plan validation fails (chunks_volume < tensor_volume). Verified the synthesis coverage math by hand: the ShardedTensor covers exactly [world_shard_start, world_shard_start + pad_len) within [0, numel_unpadded), and the downstream padding-insertion loop adds no spurious trailing padding.

Collective-safety / backward compat — The all_gather_object is gated on use_layer_wise_distributed_optimizer, a config value identical on every DP rank, so all ranks take the same branch and an ordinary DistributedOptimizer keeps its communication-free save/load path. The relaxed empty bucket assert only changes behavior in a previously-crashing case (empty state + empty param_map); ordinary DistOpt still hits the strict param_map assertion. self.data_parallel_group is an existing instance attribute, not a new global-process-group read.

layer_wise_optimizer.py_allgather_helper now derives device/dtype from the first non-empty per-rank list instead of indexing rank 0 blindly; this is symmetric across ranks (same partition on all), so the early return is collective-safe and mirrors the fp8 twin. The not any(...) expert-parallel guards correctly generalize the [0]-only emptiness check.

Mandatory unused-variable check_optimizer_model_params, pad_templates/pad_device, and all new test params have real read/use paths.

Tests — Genuinely convert vacuous {} == {} asserts into meaningful ones and add store-back regression coverage.

LGTM.

@Wohox
Wohox force-pushed the wohox/fix-dp-reshardable-empty-bucket branch 3 times, most recently from 85caba9 to 4516fe2 Compare August 3, 2026 05:27
@Wohox
Wohox marked this pull request as ready for review August 3, 2026 05:27
@Wohox
Wohox requested review from a team as code owners August 3, 2026 05:27
@Wohox

Wohox commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 4516fe2

@Wohox

Wohox commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test d05dd1f

@Wohox

Wohox commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 7db50e2

@Wohox Wohox changed the title [Dev] Muon decoupled-optimizer bug fixes (dp_reshardable checkpoint + LayerWise param-gather) [Dev] Align decoupled Muon FP8 parameter-gather paths with main Aug 19, 2026
Batch MXFP8 copy-back through existing quantizers, share conversion helpers
across DDP and MCore FSDP, and tighten validation plus offload-overlap
handling. Make gather ownership checks robust to empty rank slots and add
coverage for copy-back, overlap, and quantized parameter comparisons.

Co-authored-by: Shiqing Fan <shiqingf@nvidia.com>
Signed-off-by: Shiqing Fan <shiqingf@nvidia.com>
Signed-off-by: Pingtian Li <pingtianl@nvidia.com>
@Wohox
Wohox force-pushed the wohox/fix-dp-reshardable-empty-bucket branch from abbe881 to a3db0b5 Compare August 19, 2026 07:33
Persist exact intra-parameter padding ranges in checkpoint content metadata
so DCP validation can distinguish intentional sparse coverage from missing
optimizer state. Preserve multi-bucket indices during load and cover trailing
plus intra-parameter padding with value-level round-trip tests.

Signed-off-by: Pingtian Li <pingtianl@nvidia.com>
@Wohox
Wohox force-pushed the wohox/fix-dp-reshardable-empty-bucket branch from a3db0b5 to 7610db8 Compare August 19, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant