Skip to content

[DO NOT MERGE] Draft mfsdp_refactor to dev branch#5665

Draft
shjwudp wants to merge 36 commits into
NVIDIA:devfrom
shjwudp:mfsdp_refactor_dev_upstream
Draft

[DO NOT MERGE] Draft mfsdp_refactor to dev branch#5665
shjwudp wants to merge 36 commits into
NVIDIA:devfrom
shjwudp:mfsdp_refactor_dev_upstream

Conversation

@shjwudp

@shjwudp shjwudp commented Jul 6, 2026

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

What does this PR do?

⚠️ 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.

Autumn1998 and others added 30 commits May 28, 2026 07:53
…Tongliu)

- Add FSDP v2 mixed precision policy with MXFP8 support
- Add param group for mixed precision parameter management
- Add FSDP v2 hooks for mixed precision workflow
- Add bucket allocator for mixed precision buffer management
… core (by Jack Chang)

- Add FSDP v2 checkpoint module with save/load and online format conversion
- Add DP buffer, FSDP module, fully_shard API, and utils for v2
- Add NVFP4 design doc and checkpoint design doc
- Add FSDP v2 hooks in mcore_fsdp_adapter for v2 integration
- Add use_megatron_fsdp_v2 flag in DDP config and arguments
- Wire FSDP v2 into distrib_optimizer state_dict/load paths
- Update checkpointing.py with v2 import, loading, and format support
- Add v2 unit tests (checkpoint, fully_shard, nd_parallel, param_group, allocator)
- Update uneven_dtensor, megatron_fsdp, and fsdp_dtensor_checkpoint for v2
…upport

- README: fix architecture diagram (add missing files), replace stale
  unit test references, remove false limitations (state_dict_for_save_checkpoint
  and stop_communication are already wired), add FullyShardMixedPrecisionPolicy
  docs, add NVFP4 non-distributed path limitation, fix installation and toy
  example commands
- tp_support_design.md: draft TP support plan referencing v1 patterns
  (two-step DTensor construction, 2D DeviceMesh, gradient decoupling)
- fsdp_toy.py: add --activation-checkpoint flag and ToyBlock checkpointing
- test_fully_shard.py: add 6 activation checkpointing tests (forward/backward,
  multi-step stability, overlap compatibility, nested FSDP, numerical
  equivalence, per-layer mixed checkpointing)
- fsdp_toy.py: use Megatron get_state_dict in AppState to attach
  uneven DTensor chunk metadata during checkpoint save; remove
  dead sys.modules hack; add warnings import fallback
- checkpoint.py: rename load_torch_dist_into_fsdp_v2 to
  _load_torch_dist_into_megatron_fsdp_v2 (private convention)
- checkpointing.py: add import of _load_torch_dist_into_megatron_fsdp_v2
- test_param_group.py: fix dimension comments and simplify assertion
…rename

- Rename FullyShardMixedPrecisionPolicy to MixedPrecisionPolicy across all
  imports, exports, docs, and tests
- Add ZeRO-1 (optim) and ZeRO-2 (optim_grads) sharding strategy support
  alongside existing ZeRO-3 (optim_grads_params); reject no_shard in v2
- Refactor DataParallelBuffer.unshard() to support replicated buffer
  refresh via _dirty flag for ZeRO-1/2 weight consistency after optimizer
- Refactor DataParallelBuffer.reduce_grad() to handle all three ZeRO
  strategies (reduce-scatter vs all-reduce vs delayed reduce-scatter)
- Add FSDPModule.finish_grad_sync() for delayed ZeRO-1 reduce-scatter
  at iteration boundary; delegate finish_grad_sync through adapter
- Handle empty trace pools gracefully in allocator.plan()
- Add strict iter-equivalence tests for ZeRO strategies with parameter
  snapshot comparison
- Add MXFP8 ZeRO-1/2 smoke tests for replicated quantized buffer refresh
- Update design.md with ZeRO-1/2 workflow docs and replicated weight
  refresh design
…en dtensor split, improve checkpoint inspector

- Add _sync_module_states_after_load to FSDPModule and call it after loading
  Megatron FSDP v2 checkpoints to ensure model weights are consistent
- Add get_root_module to _FSDPRootContext and FSDPModule
- Harden split_dtensor: assert __create_chunk_list__ exists instead of
  falling back to gather_and_compute_chunk_metadata
- Rewrite compare_two_checkpoint in checkpoint_inspector with batched,
  memory-aware loading and DeviceMesh support for large distributed ckpts
- Improve print_tensor output with L2 norm and local shard data display
Restructure step() to fire all grad gathers up front (so later gathers
overlap earlier params' Newton-Schulz) instead of the prior depth-2
prefetch pipeline; gather Work handles renamed handles->reqs, the inner
ParameterGroup local renamed pg->param_group. Trim the step() / class
docstrings to a concise step-by-step description and drop the stale
module docstring that still described the removed reduce-scatter-stream
prefetch design. Add an assert that no closure is passed (Muon needs none).

No behavior change: 4-rank GB200 e2e (hidden=8192, optim_grads_params)
trains 10/10 iters, finite decreasing loss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Restructure test_dist_muon.py around a single five-step matches-reference
test and parametrize it over shape scale (small/medium/large, each sized so
matrices straddle a shard boundary and exercise the gather/scatter), nesterov,
and sharding strategy. Fold the 1D-bias skip check into the same param set so
the bit-exact comparison also asserts Muon never touches non-2D params.

Make the reference mirror step()'s exact ops so torch.equal holds bit for bit:
the nesterov look-ahead is a single alpha-add (g + m*buf), not g + (m*buf).
Verified 18/18 on a 4-rank GB200 run, stable across two consecutive runs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…olicy

- Mark optim (ZeRO-1) and optim_grads (ZeRO-2) as Supported, note no param-gather overlap
- Clarify FullyShardFP8Policy / FullyShardNVFP4Policy are recipe dataclasses embedded in MixedPrecisionPolicy, not standalone policies
- Add NVFP4 usage example
- Update Known Limitations to reflect actual support status
Steps so far (step() not yet rewired — still uses the back-ref path):
- FullyShardV2Muon now takes an explicit `grads` arg (DTensor per param,
  replacing the unused `pg_collection`); asserts params and grads are all
  DTensors and aligned 1:1.
- Init reconstructs every DP rank's (flat_offset, size) shard range for each
  param purely from the grad DTensor sharding + one all_gather per dp_group
  (_gather_shard_ranges), with a temporary cross-check against the old
  ParameterGroup _grad_gather_plans to prove equivalence.
- master(fp32) -> model(bf16) cast moved out of the inner step() into the
  FullyShardV2MuonOptimizer wrapper, calling the v2 FSDPModule
  _copy_main_weights_to_model_weights() (not the v1 param_and_grad_buffer shim).
- Factory builds ONE Muon over all model chunks (global root balance / per-layer
  packaging / a2a pipelining see every layer together); the wrapper now pulls the
  2D-matrix dist_params + grad DTensors straight from the FSDP v2 _fsdp_param_groups
  in layer order, so the factory no longer news the inner optimizer or uses back-refs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrite FullyShardV2Muon's gather/scatter to per-package
batch_isend_irecv that reads/writes the grad, full-grad and orth
tensors directly, removing all pack/unpack buffers and copies:

- gather/scatter plans are static (group, send, recv), built once;
  the P2Ps irecv straight into the preallocated full grad (split
  roots) and isend straight from the post-momentum shard.
- split-root full grads are preallocated once; single-holder params
  view their grad shard as the full grad (no buffer, no P2P).
- Phase 1 writes the post-momentum shard into its final location
  (full-grad slice / grad shard / momentum buffer), so no copy.
- Phase 3b reads each root's own orth segment straight from the orth
  it computed; non-root holders read the scatter-delivered grad.
- gather(i+1) is issued before NS(i) so the P2Ps overlap the NS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- fully_shard_v2_muon.py: trim duplicated/over-explaining comments
  (no logic change).
- param_group.py / dp_buffer.py: remove the dead single-root Muon
  plumbing the copy-free DTensor optimizer no longer uses —
  ParameterGroup.unshard_grad_to_root_async / scatter_grad_from_root_async,
  the per-init _grad_gather_plans build, the dist_param._fsdp_param_group /
  _fsdp_orig_param back-refs, and DataParallelBuffer.item_root_ranks /
  _build_item_root_ranks. Confirmed a closed dead cluster (only referenced
  each other); the Adam/FSDP path never touched them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CUDA Graph:
- Add FSDPCudaGraphRunner for per-module CUDA graph capture/replay
- Support enable_cuda_graph on leaf FSDP modules only
- Integrate with TracePoolAllocator for stable buffer addresses
- Mark as experimental feature with FutureWarning and docs
- Add cuda_graph_design.md

TracePoolAllocator:
- Implement trace/plan/optimized lifecycle with interval coloring
- Add allocator_design.md with CUDA graph integration lifecycle
- Stable slot-based allocation for fixed addresses across micro-batches

Checkpoint:
- Add chunk metadata source tags (init/preprocess/split/propagate:*) for
  provenance tracing when debugging numel mismatches
- Add _verify_chunk_metadata with numel consistency check and detailed
  failure diagnostics (key, shapes, chunk_list, source, device_mesh)
- Fix _maybe_wrap_as_uneven_dtensor dead isinstance(DTensor) check
- Add warning for handle_experts_in_state_dict identity transform on
  non-local experts (local_expert_start=0)
- Make model an explicit parameter to _preprocess_and_verify_v2_state_dict
- Guard Phase 4 slicing for _extra_state tensors without chunk metadata
- Update mcore_fsdp_checkpoint_design.md: clarify v1/v2 split, fix
  feature matrix, annotate unchecked PP/multi-optimizer items

DP Buffer:
- Move _dirty flag from buffer attribute to tensor attribute
- Refactor is_unsharded() and unshard() for non-distributed buffers
- Change _get_item_global_range to return (start, end) for consistency
  with _get_item_self_range and _get_item_local_range

Hooks:
- Merge _register_fine_grained_forward_pre_hooks into
  _register_forward_pre_hook with fine_grained parameter
- Handle CUDA graph activation correctly for root vs child modules

Distributed Muon:
- Refactor toward DTensor-driven, ParameterGroup-free design
- Make copy-free via batched isend/irecv
- Clean up comments and dead single-root plumbing

Docs:
- Update v2/README.md with experimental CUDA graph note, fix
  cuda_graph_active replay description, add known limitations
- Add experimental note to examples/megatron_fsdp/README.md
- Update checkpoint design doc across multiple sections
…2-muon-refactor

# Conflicts:
#	megatron/core/distributed/fsdp/src/megatron_fsdp/v2/README.md
#	megatron/core/distributed/fsdp/src/megatron_fsdp/v2/mixed_precision.py
Skip no-op unshard for clean replicated buffers
Initialize clean replicated FSDP weight buffers
shjwudp added 6 commits June 12, 2026 19:06
…/resume

- Lazy gradient buffer: _init_dist_grads() defers main_grad_buffer.data
  allocation to first backward; _maybe_free_grad_data() frees between steps;
  _grad_buffer_is_fresh flag ensures overwrite (not accumulate) on first
  reduce-scatter after alloc or zero_grad.  torch.empty + overwrite avoids
  zero-init cost.  Saves ~35 GB per rank for 70B/bf16/dp=8 during forward
  and between training steps.

- CPU offload infrastructure: _is_on_cpu / _ensure_data_on_gpu / _move_data_to
  primitives on DataParallelBuffer; _ensure_buffers_on_gpu auto-reload
  at every FSDP entry point; _rebuild_dist_views updates _local_tensor
  in-place after device moves.

- TracePoolAllocator lifecycle: release() frees slot tensors while preserving
  plan metadata; _auto_resume() re-allocates on next alloc/free; resume()
  for explicit restore.  FSDPModule.release_memory_pool() tears down CUDA
  graphs, clears sentinels, and releases allocator slots.

- get_state_dict zero-collective upgrade: copy chunk metadata from model
  dist_params instead of all_gather_object.

- CLI: --fsdp-trace-pool flag; adapter wires both fsdp_double_buffer and
  fsdp_trace_pool to enable_trace_pool.  fsdp_toy.py --release-memory-pool.

- CI: unblock checkpoint unit tests in test_fully_shard.py; add fsdp_trace_pool
  coverage to test_mcore_nd_parallel and test_mcore_checkpoint.

- Docs: lazy_grad_buffer_design.md with memory lifecycle, edge cases,
  and torch.empty bug analysis.

- _is_torchdynamo_compiling() helper guards free_storage/alloc_storage.
…, CPU offload, and design docs

## Overview

21 files, +1,473 / -336. Integrates M-FSDP v2 into the 1F1B pipeline schedule,
adds lazy gradient buffer allocation, CPU offload/reload infrastructure,
and reorganizes design documentation.

## Key Changes

### 1F1B + FSDP v2 Integration
- Refactored hooks from closures to standalone functions (pre/post-forward,
  pre/post-backward) with `skip_final_callback` parameter for EP overlap
  schedules to suppress auto-enqueue and trigger manually.
- Unified fine-grained hook registration replaces the old EP-overlap system;
  `fine_grained_hooks`, `skip_backward_callback`, `skip_final_backward_callback`
  flags decompose the monolithic `enable_ep_overlap`.
- combined_1f1b.py: dispatch pre/post-backward setup, reshard hooks, and
  post-backward final callback conditionally for v1 vs v2 FSDP.
- Root detection now raises RuntimeError when v2 modules exist but none
  is marked `_is_root` (previously silently disabled FSDP).

### Lazy Gradient Buffers
- `_init_dist_grads()` allocates gradient buffers on first use instead of
  eagerly at init, using `torch.empty` for zero-cost allocation.
- `_grad_buffer_is_fresh` flag replaces `is_zero_grad`; correctly resolves
  stale-data FIXME when `set_to_none=True` is used for zero_grad.

### CPU Offload / Reload
- `offload_to_cpu()`: moves buffers to CPU sorted largest-first, with
  optional `max_cpu_bytes` budget and `pin_memory` support.
- `reload_to_gpu()`: explicit pre-warm to hide first-touch latency.
- `release_memory_pool()`: tears down CUDA graphs and releases allocator.
- `_ensure_buffers_on_gpu()` auto-reloads on any GPU access path.

### Design Docs
- Moved all design docs to `v2/design/` directory.
- New: `1f1b_ep_overlap_fsdp_design.md` (544 lines), `hooks_api.md` (136).
- Existing: `lazy_grad_buffer_design.md`, `allocator_design.md`,
  `cuda_graph_design.md`, `nvfp4_design.md`, `tp_support_design.md`,
  `mcore_fsdp_checkpoint_design.md` relocated.

### Removals
- `enable_ep_overlap` flag and `_register_ep_overlap_hooks` deleted.
- `_ep_submodule_fwd_total/bwd_total/bwd_done` counters removed from
  `_FSDPState`.
- No callers remain in the repo for the removed API.
…ooks` for MFSDP v2 (#21)

Replace the custom FSDPCudaGraphRunner with a batch CUDA graph capture
approach built on te-graph-runtime (https://github.com/buptzyb/te-graph-runtime)
vendored at v2/te_graph_runtime/.

Core changes
------------
- cuda_graph_runner.py: rewritten — CudaGraphRunner records sample args
  per module, batch-captures all graphs in a single make_graphed_callables.
- capture_time_hooks run FSDP unshard/reshard outside CUDA graph capture.
- hooks.py: @torch.compiler.disable on all 5 FSDP hook functions; CG
  recording logic replaces per-module capture; capture trigger in
  post_backward_final_callback guarded by TracePoolAllocator asserts.
- te_graph_runtime/: vendored with local modifications (None-safe guards,
  positional arg replay, empty_cache between warmup/capture, optional
  capture_stream, separate warmup stream for torch.compile).
- fully_shard.py: _fsdp_class_cache avoids torch.compile recompilation
  from dynamically-created class id changes.

Examples & docs
---------------
- test_qwenimage.py: end-to-end training test for QwenImage (FA2/FA3,
  per-block compile, CG+trace pool, memory tracking, OOM dump, benchmarks).
- README.md / design/cuda_graph_design.md: updated for te-graph-runtime
  architecture; TracePoolAllocator highlighted as CG enabler.

Benchmarks (QwenImageTransformer2DModel, bs=4, bf16, torch.compile, FA2)
-----------------------------------------------------------------------
| Backend    | 8×H100         | 4×GB200        |
|------------|----------------|----------------|
| fsdp1      | 729ms / 60.2GB | 679ms / 75.4GB |
| mfsdpv2    | 769ms / 59.3GB | 647ms / 74.7GB |
| mfsdpv2+cg | 674ms / 68.3GB | 364ms / 88.7GB |

Files: 18 changed, +4199 / -721
"
From github.com:shjwudp/Megatron-LM
 * branch                mfsdp_refactor -> FETCH_HEAD
[mfsdp_refactor_diffuser 2234409e3] CUDA graph capture via vendored te-graph-runtime with capture_time_hooks for MFSDP v2
 18 files changed, 4199 insertions(+), 721 deletions(-)
 create mode 100644 examples/qwenimage_mfsdp/README.md
 create mode 100644 examples/qwenimage_mfsdp/test_qwenimage.py
 create mode 100644 megatron/core/distributed/fsdp/src/megatron_fsdp/v2/cuda_graph_memory_analysis.md
 create mode 100644 megatron/core/distributed/fsdp/src/megatron_fsdp/v2/te_graph_runtime/README.md
 create mode 100644 megatron/core/distributed/fsdp/src/megatron_fsdp/v2/te_graph_runtime/__init__.py
 create mode 100644 megatron/core/distributed/fsdp/src/megatron_fsdp/v2/te_graph_runtime/graph.py
…
Squash of mfsdp_refactor_cg_test:
- fsdp_toy.py: add --use-real-data (teacher-student regression) with
  per-step loss + final convergence assert; default CUDA graph off to
  align Megatron-FSDP and PyTorch FSDP2 paths; scientific-notation loss.
- test_qwenimage.py: add --real-data (fixed flow-matching overfit) with
  cross-rank mean loss and convergence assert.
…tream

# Conflicts:
#	megatron/core/distributed/distributed_data_parallel_config.py
#	megatron/core/distributed/fsdp/src/megatron_fsdp/distributed_data_parallel_config.py
#	megatron/core/optimizer/clip_grads.py
#	megatron/core/pipeline_parallel/combined_1f1b.py
#	megatron/core/transformer/moe/router.py
@copy-pr-bot

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

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