Skip to content

Fix Megatron-FSDP GDN optimizer checkpoint metadata for FSDP DTensor - #2

Open
conver334 wants to merge 5 commits into
wplf:jinliang/fix-mfsdp-optimizer-offloadfrom
conver334:codex/fsdp-dtensor-explicit-metadata
Open

Fix Megatron-FSDP GDN optimizer checkpoint metadata for FSDP DTensor#2
conver334 wants to merge 5 commits into
wplf:jinliang/fix-mfsdp-optimizer-offloadfrom
conver334:codex/fsdp-dtensor-explicit-metadata

Conversation

@conver334

@conver334 conver334 commented May 26, 2026

Copy link
Copy Markdown

Summary

#4623 fixes the small/dense Megatron-LM optimizer CPU offload repro from #4910, but it does not fix the full Qwen3.5-35B-A3B failure reported in the latest NVIDIA#4910 comment.

The remaining failure happens when saving a Megatron-FSDP fsdp_dtensor optimizer checkpoint with optimizer CPU
offload enabled. The run reaches checkpoint save, then fails while splitting GDN optimizer state in handle_gdn_in_state_dict() because the generated DTensor chunk metadata does not describe the actual FSDP/TP- local shard layout.

This PR adds explicit DCP chunk metadata for those cases instead of letting the generic uneven-DTensor preprocessing recompute it incorrectly.

Problem

The full-model repro in NVIDIA#4910 still fails after merging NVIDIA#4623 into latest dev:

  AssertionError: [Megatron-FSDP] DTensor chunk metadata boundary check failed.
  Offsets: (0, 0, 0), Sizes: (0, 1, 4),
  Global shape: torch.Size([1024, 1, 4]),
  Local shape: torch.Size([0, 1, 4])

Some ranks also have non-empty local shards but still fail the same boundary check. This indicates that the
checkpoint metadata does not correctly describe where each GDN component shard belongs in the global tensor.

The problematic path is specific to FSDP DTensor checkpoint preprocessing for GDN fused parameters and optimizer
state.

Changes

  • Add an explicit metadata marker for DTensors whose DCP chunk metadata has already been set by Megatron-FSDP.
  • Skip generic update_uneven_dtensor_chunk_metadata() for tensors marked with explicit metadata.
  • Attach DCP create_chunk_list and create_write_items hooks to both the DTensor and its local tensor.
  • Add TP-aware offsets for flat optimizer-state DTensors:
    • previous offset: fsdp_slice.start
    • new offset: tp_offset + fsdp_slice.start
  • Compute explicit chunk offsets/sizes when splitting GDN fused tensors into logical component tensors.

Why

For TP-sharded parameters, fsdp_slice.start alone is not enough to describe the global checkpoint offset. Each TP rank owns a different TP-local portion of the logical global tensor, so DCP metadata needs the TP-rank offset as well.

For GDN fused parameters, optimizer state is split into logical components such as query/key/value/z/beta/alpha. After this split, rank-local component shards are not always representable by the generic rank-order uneven-DTensor metadata computation. The checkpoint writer needs explicit offsets and sizes for the component shard that this rank actually owns.

Related Issues / PRs

  • Follow-up to #4623
  • Addresses the remaining full-model failure reported in #4910

Summary by Sourcery

Fix FSDP DTensor optimizer and GDN fused-parameter checkpointing metadata, improve MoE HybridEP compatibility with sequence packing and transport backends, and add end-to-end MoE recipes for DeepSeek-V3 and Qwen3-235B across multiple GPU platforms.

Bug Fixes:

  • Correct DTensor chunk metadata generation for FSDP-sharded optimizer state, including TP-aware offsets for flat tensors.
  • Provide explicit and validated chunk metadata when splitting GDN fused tensors into logical components to avoid incorrect uneven-DTensor preprocessing.
  • Skip uneven-DTensor metadata recomputation for DTensors with explicit Megatron-FSDP chunk metadata to prevent boundary assertion failures.
  • Ensure HybridEP MoE token dispatcher handles THD sequence packing by padding metadata and hidden states to a common token count and trimming after combine.
  • Avoid allocating RDMA buffers for local-only expert-parallel groups when DeepEP may not expose RDMA hints.
  • Reset HybridEP buffers between tests to avoid cross-test contamination.

Enhancements:

  • Treat LinearCrossEntropyModule as column-parallel for the Megatron FSDP adapter to integrate with TP sharding.
  • Extend sequence packing support to flex MoE token dispatcher types in transformer configuration.
  • Add THD sequence-packing helpers and an end-to-end multi-parallelism MoE test covering HybridEP and DeepEP dispatchers.
  • Refine GDN fused-parameter handling to infer TP sharding from metadata and mark TP-local tensors appropriately for DCP planning.

Build:

  • Add YAML-based MoE training recipes for DeepSeek-V3 and Qwen3-235B across GB200/GB300/B200/H100 GPU configurations with MXFP8 and BF16 variants, including CUDA graph, paged stash, and HybridEP/DeepEP options.

Deployment:

  • Add Dockerfile-embedded dependencies and environment configuration to support DeepEP/HybridEP, FA/FA4, TransformerEngine, FlashMLA, and related libraries in new large-scale MoE recipes.

Documentation:

  • Introduce a MoE recipes README indexing new DeepSeek-V3 and Qwen3-235B training recipes and their parallelism/topology characteristics.

Tests:

  • Add an end-to-end packed THD attention + MoE forward/backward test exercising alltoall, DeepEP, and HybridEP token dispatchers under multi-parallelism.

@sourcery-ai

sourcery-ai Bot commented May 26, 2026

Copy link
Copy Markdown

Reviewer's Guide

Fixes Megatron-FSDP DTensor checkpoint metadata for GDN optimizer and adds MoE sequence-packing / HybridEP support and example recipes, by attaching explicit DTensor chunk metadata where the generic uneven-DTensor logic fails, adding TP-aware optimizer offsets, padding THD/HybridEP token flows, and wiring new large-scale DeepSeek/Qwen3 recipes and configs.

Sequence diagram for MoE HybridEP token dispatch with sequence packing

sequenceDiagram
    participant TD as TokenDispatcher
    participant HybridEP as hybrid_ep_dispatch
    participant HybridEPComb as hybrid_ep_combine
    participant Dist as torch.distributed

    TD->>TD: setup_metadata(routing_map, probs)
    TD->>TD: _original_num_tokens = routing_map.shape[0]
    TD->>Dist: all_reduce(max_num_tokens_across_ep)
    TD->>TD: _padded_num_tokens computed
    TD->>TD: pad routing_map, probs to _padded_num_tokens

    TD->>TD: dispatch(hidden_states)
    TD->>TD: pad hidden_states to _padded_num_tokens
    TD->>HybridEP: hybrid_ep_dispatch(x=hidden_states, ...)
    HybridEP-->>TD: dispatched_hidden, handle

    TD->>HybridEPComb: hybrid_ep_combine(..., handle)
    HybridEPComb-->>TD: hidden_states
    TD->>TD: trim to _original_num_tokens
    TD-->>TD: reset _original_num_tokens, _padded_num_tokens
Loading

Flow diagram for FSDP DTensor optimizer checkpoint metadata handling

flowchart LR
    A[make_fsdp_dtensor in split_gdn_fused] --> B[_validate_explicit_dtensor_chunk_metadata]
    B --> C[_set_explicit_dtensor_chunk_metadata]
    C --> D[preprocess_state_dict_for_uneven_dtensor]
    D --> E{_has_explicit_chunk_metadata?}
    E -- Yes --> F[skip update_uneven_dtensor_chunk_metadata]
    E -- No --> G[update_uneven_dtensor_chunk_metadata]

    subgraph Flat_optimizer_state
      H[_pack_hybrid_optimizer_fsdp_state]
      H --> I[_get_flat_fsdp_dtensor_tp_offset]
      I --> J[_set_flat_fsdp_dtensor_chunk_metadata]
      J --> D
    end
Loading

File-Level Changes

Change Details Files
Introduce explicit DTensor chunk metadata and validation for FSDP DTensor checkpoints, including GDN fused tensors and flat optimizer states, and skip generic uneven-DTensor preprocessing when explicit metadata is present.
  • Import ChunkStorageMetadata, MetadataIndex, TensorProperties, TensorWriteData, WriteItem, and WriteItemType to build custom DCP metadata and write plans.
  • Add helpers to set and validate explicit DTensor chunk metadata, attaching create_chunk_list and create_write_items hooks to both DTensors and their local tensors and marking them with a private flag.
  • Update split_gdn_fused to handle TP-aware data_size, compute explicit component shard offsets/sizes across TP/FSDP, mark TP-sharded meta tensors, and create DTensors with explicit, validated chunk metadata instead of relying on rank-order uneven-DTensor logic.
  • Extend DistributedOptimizer FSDP DTensor state handling to build a param-name map, compute TP offsets per parameter, and attach explicit chunk metadata for flat 1D optimizer-state DTensors with tp_offset + fsdp_slice.start offsets, marking them as explicit.
  • Modify uneven_dtensor.preprocess_state_dict_for_uneven_dtensor to detect tensors with explicit chunk metadata and skip update_uneven_dtensor_chunk_metadata for them.
megatron/core/transformer/fsdp_dtensor_checkpoint.py
megatron/core/optimizer/distrib_optimizer.py
megatron/core/distributed/fsdp/src/megatron_fsdp/uneven_dtensor.py
Fix MoE HybridEP/DeepEP token flow with sequence packing by padding routing metadata and hidden states to group-wide maxima and trimming after combine, plus add THD sequence-packing test coverage.
  • Add helpers to compute THD-compatible padded sequence lengths, cumulative lengths, and sharded hidden states that respect CP/TP partitioning and SP, then use them in a new end-to-end packed THD proxy-model test for multiple dispatcher backends.
  • Track original and padded num_tokens in TokenDispatcher, pad routing_map/token_probs and hidden_states to max tokens across the EP group with HYBRIDEP_TOKEN_ALIGNMENT, and trim back to original num_tokens after combine while keeping capacity/budget computations based on padded tokens.
  • Reset HybridEP internal buffers during teardown in MoE dispatcher tests to avoid state leakage between tests.
  • Expose HYBRIDEP_TOKEN_ALIGNMENT as a constant in fused_a2a and only allocate RDMA buffers for EP groups larger than a node’s device count.
tests/unit_tests/transformer/moe/test_token_dispatcher.py
megatron/core/transformer/moe/token_dispatcher.py
megatron/core/transformer/moe/fused_a2a.py
Adjust MoE / transformer plumbing and configs to support sequence packing with flex/HybridEP dispatchers and to register additional TP modules for FSDP.
  • Allow sequence_packing_scheduler to be used with moe_token_dispatcher_type in ("alltoall","flex") instead of only "alltoall".
  • Register LinearCrossEntropyModule as a TP-column module in the FSDP adapter’s module-type registry so it gets correct tensor-parallel handling.
  • Refactor experts.py TEGroupedMLP activation path to use a shared bias_act_func implementation instead of an instance method reference, updating checkpoint activation call sites accordingly.
megatron/core/transformer/transformer_config.py
megatron/core/distributed/fsdp/mcore_fsdp_adapter.py
megatron/core/transformer/moe/experts.py
Add documented large-scale MoE training recipes for DeepSeek-V3 and Qwen3-235B across GB200/GB300/B200/H100 configurations, including Docker build instructions and runtime arguments.
  • Introduce examples/moe_recipes/README.md summarizing all MoE recipes, their parallelism layouts, key features (HybridEP, DeepEP, CUDA graphs, paged stash, offload), and notation.
  • Add DeepSeek-V3 MXFP8 and BF16 recipes for GB200, GB300, B200, and H100, each with pinned base images, TE/FA/FlashMLA/DeepEP versions, cuDNN frontend settings, NCCL/nvte env vars, and Megatron-LM ARGS tuned for specific TP/PP/EP/CP/ETP layouts and batch sizes.
  • Add Qwen3-235B-A22B MXFP8/BF16 recipes for GB200, GB300, and H100 covering full-graph and partial-graph CUDA graph setups, paged stash configurations, HybridEP settings, offload strategies, and precision-aware optimizer options.
examples/moe_recipes/README.md
examples/moe_recipes/deepseek_v3/gb200/mxfp8_256GPU_TP1PP4EP64.yaml
examples/moe_recipes/deepseek_v3/gb300/mxfp8_256GPU_TP1PP4EP64.yaml
examples/moe_recipes/deepseek_v3/b200/mxfp8_256GPU_TP1PP8EP32.yaml
examples/moe_recipes/deepseek_v3/h100/fp8_1024GPU_TP2PP8EP64.yaml
examples/moe_recipes/deepseek_v3/h100/bf16_1024GPU_TP1PP16EP64.yaml
examples/moe_recipes/qwen3_235b/gb200/mxfp8_128GPU_TP1PP1EP64_paged_stash_fullcg_overlap.yaml
examples/moe_recipes/qwen3_235b/gb300/mxfp8_128GPU_TP1PP1EP64_paged_stash_full_cg.yaml
examples/moe_recipes/qwen3_235b/gb200/mxfp8_128GPU_TP1PP1EP64_partial_cg_overlap.yaml
examples/moe_recipes/qwen3_235b/h100/bf16_256GPU_TP2PP8EP32.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In _get_thd_padded_seqlens (test_token_dispatcher), you assume seqlens is non-empty and mutate the last element in-place; consider asserting non-emptiness or handling the empty-sequence case explicitly to avoid a silent IndexError in edge-case tests.
  • For the explicit DTensor chunk metadata helpers (_set_explicit_dtensor_chunk_metadata, _validate_explicit_dtensor_chunk_metadata, and _set_flat_fsdp_dtensor_chunk_metadata), it may be worth centralizing the marker/hook-setting logic to avoid divergence between the different code paths that tag tensors with _megatron_fsdp_explicit_chunk_metadata.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `_get_thd_padded_seqlens` (test_token_dispatcher), you assume `seqlens` is non-empty and mutate the last element in-place; consider asserting non-emptiness or handling the empty-sequence case explicitly to avoid a silent IndexError in edge-case tests.
- For the explicit DTensor chunk metadata helpers (`_set_explicit_dtensor_chunk_metadata`, `_validate_explicit_dtensor_chunk_metadata`, and `_set_flat_fsdp_dtensor_chunk_metadata`), it may be worth centralizing the marker/hook-setting logic to avoid divergence between the different code paths that tag tensors with `_megatron_fsdp_explicit_chunk_metadata`.

## Individual Comments

### Comment 1
<location path="megatron/core/transformer/fsdp_dtensor_checkpoint.py" line_range="59" />
<code_context>
 from megatron.core.utils import get_attr_wrapped_model


+def _set_explicit_dtensor_chunk_metadata(dtensor: "DTensor", offsets, sizes):
+    """Attach DCP chunk metadata for a DTensor whose local shard is not rank-contiguous."""
+    chunk_meta = ChunkStorageMetadata(offsets=tuple(offsets), sizes=tuple(sizes))
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the DTensor chunk arithmetic and metadata wiring into small reusable helpers to keep split_gdn_fused focused on high-level orchestration.

You can keep the new behavior but reduce the local complexity by pulling the DTensor–chunk wiring into small helpers.

### 1. Encapsulate GDN chunk arithmetic + DTensor construction

The block in `split_gdn_fused` that computes `trailing_numel`, `component_start`, `chunk_offsets`, `chunk_sizes`, and then calls `_validate_explicit_dtensor_chunk_metadata` / `_set_explicit_dtensor_chunk_metadata` can be turned into a focused helper.

For example:

```python
def _build_gdn_component_dtensor(
    comp_data: torch.Tensor,
    meta: torch.Tensor,
    dist_index,
    split_dim: int,
    shard,
    comp_slice,
    per_tp_rank_shape,
    tp_mesh,
) -> "DTensor":
    meta_shape = list(per_tp_rank_shape)
    meta_shape[split_dim] = comp_data.shape[split_dim]

    trailing_numel = math.prod(meta_shape[split_dim + 1 :])
    assert trailing_numel > 0, f"Invalid GDN component shape: {meta_shape}"

    component_start = 0
    if shard.start != shard.stop:
        component_start = shard.start - comp_slice.start
        assert component_start % trailing_numel == 0, (
            f"GDN component shard is not aligned with tensor rows: "
            f"component_start={component_start}, trailing_numel={trailing_numel}, "
            f"meta_shape={meta_shape}, shard={shard}, comp_slice={comp_slice}"
        )
        assert comp_data.numel() % trailing_numel == 0, (
            f"GDN component shard size is not aligned with tensor rows: "
            f"numel={comp_data.numel()}, trailing_numel={trailing_numel}, "
            f"meta_shape={meta_shape}, shard={shard}, comp_slice={comp_slice}"
        )

    chunk_offsets = [0] * len(meta_shape)
    chunk_offsets[split_dim] = component_start // trailing_numel
    chunk_sizes = list(comp_data.shape)

    tp_partition_dim = get_mcore_tensor_parallel_partition_dim(meta)
    if tp_partition_dim is not None:
        tp_rank = dist.get_rank(tp_mesh.get_group())
        chunk_offsets[tp_partition_dim] += tp_rank * meta_shape[tp_partition_dim]

    dtensor = make_fsdp_dtensor(
        comp_data.data,
        meta,
        dist_index=dist_index,
        is_expert_param=False,
        run_check=False,
        update_uneven_dtensor_chunk_meta=False,
    )
    _validate_explicit_dtensor_chunk_metadata(dtensor, chunk_offsets, chunk_sizes)
    _set_explicit_dtensor_chunk_metadata(dtensor, chunk_offsets, chunk_sizes)
    return dtensor
```

Then the body in `split_gdn_fused` is reduced to the higher‑level orchestration:

```python
meta_shape = list(per_tp_rank_shape)
meta_shape[split_dim] = s
meta = torch.empty(*meta_shape, device="meta")
copy_tensor_model_parallel_attributes(meta, dist_param)
if (
    tp_mesh.mesh.numel() > 1
    and split_dim == 0
    and global_shape[split_dim] == total_split
    and not is_mcore_tensor_model_parallel(meta)
):
    meta._tensor_parallel_mode = "column"

dtensor = _build_gdn_component_dtensor(
    comp_data=comp_data,
    meta=meta,
    dist_index=dist_index,
    split_dim=split_dim,
    shard=shard,
    comp_slice=comp_slice,
    per_tp_rank_shape=per_tp_rank_shape,
    tp_mesh=tp_mesh,
)
results.append(dtensor)
```

This keeps all the same checks/behavior but pushes the low‑level arithmetic and validations into a single well‑named helper.

### 2. Deduplicate DTensor + local‑tensor metadata wiring

`_set_explicit_dtensor_chunk_metadata` mirrors the pattern used in your optimizer’s `_set_flat_fsdp_dtensor_chunk_metadata`. You can share the mechanics of “attach closures + flag to DTensor and its local tensor” while allowing different metadata/content.

For example, introduce a generic attachment helper:

```python
def _attach_chunk_metadata_to_dtensor(dtensor: "DTensor", chunk_list_fn, write_items_fn):
    dtensor.__create_chunk_list__ = chunk_list_fn
    dtensor.__create_write_items__ = write_items_fn
    dtensor._megatron_fsdp_explicit_chunk_metadata = True

    local = dtensor._local_tensor
    local.__create_chunk_list__ = chunk_list_fn
    local.__create_write_items__ = write_items_fn
    local._megatron_fsdp_explicit_chunk_metadata = True
```

Then `_set_explicit_dtensor_chunk_metadata` becomes:

```python
def _set_explicit_dtensor_chunk_metadata(dtensor: "DTensor", offsets, sizes):
    chunk_meta = ChunkStorageMetadata(offsets=tuple(offsets), sizes=tuple(sizes))

    def _chunk_list():
        return [chunk_meta]

    def _write_items(fqn: str, tensor: "DTensor"):
        if tensor.to_local().numel() == 0:
            return []
        return [
            WriteItem(
                type=WriteItemType.SHARD,
                index=MetadataIndex(fqn, chunk_meta.offsets),
                tensor_data=TensorWriteData(
                    chunk=chunk_meta,
                    properties=TensorProperties.create_from_tensor(tensor.to_local()),
                    size=tensor.size(),
                ),
            )
        ]

    _attach_chunk_metadata_to_dtensor(dtensor, _chunk_list, _write_items)
```

The optimizer helper can call `_attach_chunk_metadata_to_dtensor` with its own closures as well, so the low‑level DTensor/local‑tensor wiring is defined once and reused.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread megatron/core/transformer/fsdp_dtensor_checkpoint.py Outdated
@wplf
wplf force-pushed the jinliang/fix-mfsdp-optimizer-offload branch 2 times, most recently from 2f2442b to 21ba0b9 Compare May 27, 2026 03:54
@wplf

wplf commented May 27, 2026

Copy link
Copy Markdown
Owner

review

wplf added a commit that referenced this pull request May 29, 2026
… split

Mirror PR #2's GDN fix in split_gdn_fused over to split_swiglu_linear_fc1:
both helpers wrap fused-parameter component shards as DTensors during
checkpoint save, and both previously called make_fsdp_dtensor with
run_check=True, update_uneven_dtensor_chunk_meta=True. That path issues a
blocking all_gather_object inside the checkpoint preprocessing, which
deadlocks under MFSDP + HybridDeviceOptimizer (issue NVIDIA#4910).

PR #2 fixed split_gdn_fused but left split_swiglu_linear_fc1 untouched.
Qwen3.5-35B-A3B has both GDN (1/4 of layers via linear_attention_freq=4)
and SWiGLU MLP (the other 3/4), so save still hangs in the SWiGLU
branch. Apply the same explicit chunk metadata pattern there.
wplf added a commit that referenced this pull request May 29, 2026
The split_gdn_fused() fast-path for already-TP-local DTensors (added by
NVIDIA#4799) calls split_dtensor(..., update_uneven_dtensor_chunk_meta=True).
split_dtensor() itself unconditionally calls gather_and_compute_chunk_metadata
(line 457 in uneven_dtensor.py), which issues an all_gather_object on the
DTensor's shard groups.

Inside checkpoint save preprocessing, this triggers the same deadlock as
issue NVIDIA#4910: ranks enter collectives on different mesh groups in different
orders and the NCCL watchdog aborts after 10 min.

PR #2's explicit-chunk-metadata fix only covered the slow rebuild path; the
fast-path was untouched. Disable it so GDN model state falls through to the
slow path.
Signed-off-by: conver334 <conver334@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants