Skip to content

Commit 809f0e6

Browse files
wujingyueclaude
andauthored
fix(fsdp): drop stale MFSDP v2 guard rails for clip_grad, precision-aware optimizer, HSDP, and optimizer CUDA graphs (#5933)
Signed-off-by: Jingyue Wu <wujingyue@gmail.com> Signed-off-by: Jingyue Wu <jingyuew@nvidia.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 5531d7b commit 809f0e6

8 files changed

Lines changed: 95 additions & 61 deletions

File tree

‎src/megatron/bridge/models/conversion/utils.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,18 @@ def unwrap_model(model, module_instances=None):
4646
if module_instances is None:
4747
from megatron.core.distributed import DistributedDataParallel as DDP
4848
from megatron.core.distributed import TorchFullyShardedDataParallel as torch_FSDP
49+
from megatron.core.distributed.fsdp.mcore_fsdp_adapter import (
50+
FullyShardedDataParallelV1,
51+
FullyShardedDataParallelV2,
52+
)
4953
from megatron.core.distributed.fsdp.src.megatron_fsdp.megatron_fsdp import MegatronFSDP
5054
from megatron.core.transformer.module import Float16Module
5155

52-
from megatron.bridge.training.fsdp_compat import MEGATRON_FSDP_TYPES
53-
5456
module_instances = (
5557
DDP,
5658
torch_FSDP,
57-
*MEGATRON_FSDP_TYPES,
59+
FullyShardedDataParallelV1,
60+
FullyShardedDataParallelV2,
5861
Float16Module,
5962
MegatronFSDP,
6063
)

‎src/megatron/bridge/training/config.py‎

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@
7171
from megatron.bridge.peft.base import PEFT
7272
from megatron.bridge.training.comm_overlap import CommOverlapConfig
7373
from megatron.bridge.training.flex_dispatcher_backend import validate_flex_dispatcher_backend
74-
from megatron.bridge.training.fsdp_compat import MCORE_HAS_MEGATRON_FSDP_V2
7574
from megatron.bridge.training.mixed_precision import MixedPrecisionConfig, get_mixed_precision_config
7675
from megatron.bridge.training.tokenizers.config import TokenizerConfig
7776
from megatron.bridge.training.utils.config_utils import _ConfigContainerBase as Container
@@ -1082,8 +1081,8 @@ def _validate_and_apply_megatron_fsdp_configs(self) -> None:
10821081
self.dist.use_megatron_fsdp = True
10831082
self.ddp.use_megatron_fsdp = True
10841083

1085-
megatron_fsdp_version = getattr(self.ddp, "megatron_fsdp_version", 1)
1086-
if not MCORE_HAS_MEGATRON_FSDP_V2 or megatron_fsdp_version == 1:
1084+
megatron_fsdp_version = self.ddp.megatron_fsdp_version
1085+
if megatron_fsdp_version == 1:
10871086
self._validate_and_apply_megatron_fsdp_v1_configs()
10881087
elif megatron_fsdp_version == 2:
10891088
self._validate_and_apply_megatron_fsdp_v2_configs()
@@ -1173,32 +1172,20 @@ def _validate_and_apply_megatron_fsdp_v2_configs(self) -> None:
11731172
raise ValueError("MFSDP V2 does not support use_tp_pp_dp_mapping.")
11741173
if self.rng.data_parallel_random_init:
11751174
raise ValueError("MFSDP V2 does not support data_parallel_random_init.")
1176-
if self.ddp.num_distributed_optimizer_instances != 1:
1177-
raise ValueError("MFSDP V2 does not currently support HSDP.")
1178-
if self.ddp.outer_dp_sharding_strategy != "no_shard":
1179-
raise ValueError("MFSDP V2 does not currently support outer DP sharding.")
11801175
if self.checkpoint.save is not None or self.checkpoint.load is not None:
11811176
raise ValueError("MFSDP V2 checkpoint save and load are not yet supported.")
11821177
if self.checkpoint.pretrained_checkpoint is not None:
11831178
raise ValueError("MFSDP V2 checkpoint loading is not yet supported.")
11841179
if self.optimizer.loss_scale is not None:
11851180
raise ValueError("MFSDP V2 does not support loss scaling.")
1186-
if self.optimizer.clip_grad > 0.0:
1187-
raise ValueError("MFSDP V2 does not currently support gradient clipping.")
1188-
if self.optimizer.use_precision_aware_optimizer:
1189-
raise ValueError("MFSDP V2 does not support precision-aware optimizer.")
11901181
if self.optimizer.optimizer_cpu_offload:
11911182
raise ValueError("MFSDP V2 does not support optimizer CPU offload.")
11921183
if self.optimizer.use_layer_wise_distributed_optimizer:
11931184
raise ValueError("MFSDP V2 does not support layer-wise distributed optimizer.")
1194-
if self.optimizer.optimizer_cuda_graph:
1195-
raise ValueError("MFSDP V2 does not support optimizer CUDA graphs.")
11961185
if self.model.calculate_per_token_loss:
11971186
raise ValueError("MFSDP V2 does not support per-token loss normalization.")
11981187
if self.model.fp8 or self.model.fp4 or self.ddp.fp8_param_gather or self.ddp.fp4_param_gather:
11991188
raise ValueError("MFSDP V2 does not support FP8 or FP4.")
1200-
if self.model.cuda_graph_impl != "none" or self.ddp.megatron_fsdp_cuda_graph_mode:
1201-
raise ValueError("MFSDP V2 does not support CUDA graphs.")
12021189

12031190
self.ddp.data_parallel_sharding_strategy = "optim_grads_params"
12041191
self.ddp.use_distributed_optimizer = False

‎src/megatron/bridge/training/fsdp_compat.py‎

Lines changed: 0 additions & 29 deletions
This file was deleted.

‎src/megatron/bridge/training/setup.py‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
from megatron.core import tensor_parallel
2828
from megatron.core.config import set_experimental_flag
2929
from megatron.core.distributed import DistributedDataParallel, DistributedDataParallelConfig, finalize_model_grads
30+
from megatron.core.distributed.fsdp.mcore_fsdp_adapter import (
31+
FullyShardedDataParallelV1,
32+
FullyShardedDataParallelV2,
33+
)
3034
from megatron.core.jit import disable_jit_fuser
3135
from megatron.core.optimizer import MegatronOptimizer
3236
from megatron.core.optimizer_param_scheduler import OptimizerParamScheduler
@@ -58,7 +62,6 @@
5862
maybe_load_dataloader_state,
5963
)
6064
from megatron.bridge.training.config import ConfigContainer
61-
from megatron.bridge.training.fsdp_compat import MEGATRON_FSDP_TYPES
6265
from megatron.bridge.training.gtp import (
6366
classify_gtp_remat_chains,
6467
configure_gtp_remat,
@@ -632,10 +635,19 @@ def _update_model_config_funcs(
632635
peft_enabled: bool = False,
633636
) -> None:
634637
"""Update model config sync funcs based on initialized model."""
635-
if isinstance(model[0], (DistributedDataParallel, *MEGATRON_FSDP_TYPES)) and ddp_config.overlap_grad_reduce:
638+
# DDP and MFSDP v1 only reduce during backward when overlap_grad_reduce is on, so
639+
# without it there is nothing to suppress. MFSDP v2 always reduces in backward, so
640+
# no_sync is how the schedule marks a non-final microbatch -- a correctness
641+
# requirement, not an overlap optimization (mirrors Megatron-LM #7186). Without it,
642+
# every backward finalizes the DP-outer axis and only the last microbatch's gradient
643+
# reaches the optimizer.
644+
if isinstance(model[0], FullyShardedDataParallelV2) or (
645+
isinstance(model[0], (DistributedDataParallel, FullyShardedDataParallelV1))
646+
and ddp_config.overlap_grad_reduce
647+
):
636648
assert model_config.no_sync_func is None, (
637-
"When overlap_grad_reduce is True, config.no_sync_func must be None; "
638-
"a custom no_sync_func is not supported when overlapping grad-reduce"
649+
"config.no_sync_func must be None when the wrapper supplies its own no_sync "
650+
"(overlap_grad_reduce, or Megatron-FSDP v2); a custom no_sync_func is not supported"
639651
)
640652
model_config.no_sync_func = [model_chunk.no_sync for model_chunk in model]
641653
if len(model) == 1:

‎src/megatron/bridge/training/train.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
import torch
2525
import torch.profiler
2626
from megatron.core.distributed import DistributedDataParallel as DDP
27+
from megatron.core.distributed.fsdp.mcore_fsdp_adapter import (
28+
FullyShardedDataParallelV1,
29+
FullyShardedDataParallelV2,
30+
)
2731
from megatron.core.full_cuda_graph import FullCudaGraphWrapper
2832
from megatron.core.num_microbatches_calculator import (
2933
get_current_global_batch_size,
@@ -73,7 +77,6 @@
7377
from megatron.bridge.training.config import ConfigContainer
7478
from megatron.bridge.training.eval import evaluate_and_print_results
7579
from megatron.bridge.training.forward_step_func_types import ForwardStepCallable
76-
from megatron.bridge.training.fsdp_compat import MEGATRON_FSDP_TYPES
7780
from megatron.bridge.training.gtp import get_data_distribution_group
7881
from megatron.bridge.training.initialize import destroy_global_state
7982
from megatron.bridge.training.nvrx_straggler import (
@@ -1741,7 +1744,7 @@ def _maybe_register_fsdp_buffers(
17411744
):
17421745
print_rank_0("[Megatron-FSDP] Registering FSDP communication buffers manually")
17431746
for model_chunk in model:
1744-
if isinstance(model_chunk, MEGATRON_FSDP_TYPES) and getattr(
1747+
if isinstance(model_chunk, (FullyShardedDataParallelV1, FullyShardedDataParallelV2)) and getattr(
17451748
model_chunk.ddp_config, "fsdp_manual_registration", False
17461749
):
17471750
fsdp_param_and_grad_buffer = getattr(model_chunk, "param_and_grad_buffer", None)

‎tests/functional_tests/test_groups/megatron_fsdp/test_megatron_fsdp.py‎

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import pytest
2020
import torch
2121
import torch.nn.functional as F
22+
from megatron.core.tensor_parallel.random import initialize_rng_tracker
2223
from megatron.core.transformer.enums import AttnBackend
2324

2425
from megatron.bridge.models.gpt_provider import GPTModelProvider
@@ -38,7 +39,6 @@
3839
ValidationConfig,
3940
runtime_config_update,
4041
)
41-
from megatron.bridge.training.fsdp_compat import MCORE_HAS_MEGATRON_FSDP_V2
4242
from megatron.bridge.training.gpt_step import forward_step
4343
from megatron.bridge.training.pretrain import pretrain
4444
from megatron.bridge.training.state import GlobalState
@@ -422,19 +422,47 @@ def test_fsdp_pretrain_basic(self):
422422
torch.distributed.barrier()
423423

424424
@pytest.mark.run_only_on("GPU")
425-
@pytest.mark.skipif(not MCORE_HAS_MEGATRON_FSDP_V2, reason="MFSDP v2 is unavailable in this MCore revision")
426425
def test_fsdp_v2_dense_hybrid_pretrain_smoke(self):
427-
"""Train a dense two-layer HybridModel with the experimental MFSDP V2 path."""
426+
"""Train a dense two-layer HybridModel with MFSDP V2 in eager mode."""
428427
initialize_distributed()
429428
torch.distributed.barrier()
430429

431430
cfg = create_fsdp_config_container(
432431
seq_length=128,
433432
train_iters=10,
434-
optimizer={"clip_grad": 0.0},
433+
optimizer={"use_precision_aware_optimizer": True},
434+
)
435+
cfg.model = create_dense_hybrid_smoke_model_config()
436+
cfg.ddp.megatron_fsdp_version = 2
437+
438+
pretrain(cfg, forward_step)
439+
440+
torch.distributed.barrier()
441+
442+
@pytest.mark.run_only_on("GPU")
443+
def test_fsdp_v2_dense_hybrid_cuda_graph_pretrain_smoke(self):
444+
"""Train a dense HybridModel with MFSDP V2 full-iteration and optimizer graphs."""
445+
initialize_distributed()
446+
torch.distributed.barrier()
447+
448+
# Replace the tracker left by eager tests with MCore's graph-safe RNG tracker.
449+
initialize_rng_tracker(use_cudagraphable_rng=True, force_reset=True)
450+
451+
cfg = create_fsdp_config_container(
452+
seq_length=128,
453+
train_iters=10,
454+
optimizer={"use_precision_aware_optimizer": True, "optimizer_cuda_graph": True},
435455
)
436456
cfg.model = create_dense_hybrid_smoke_model_config()
437457
cfg.ddp.megatron_fsdp_version = 2
458+
# Full-iteration graphs over a dense model: Megatron-LM #7075 made these work
459+
# under MFSDP v2. MoE stays out, its dispatch shapes are not capturable.
460+
cfg.model.cuda_graph_impl = "full_iteration"
461+
# The loss NaN check reads a GPU value on the host and cannot be captured.
462+
cfg.rerun_state_machine.check_for_nan_in_loss = False
463+
# Capturable TE FusedAdam requires the main-gradient and main-weight dtypes
464+
# to match (https://github.com/NVIDIA/TransformerEngine/issues/3358).
465+
cfg.ddp.megatron_fsdp_main_grads_dtype = torch.float32
438466

439467
pretrain(cfg, forward_step)
440468

@@ -455,6 +483,32 @@ def test_fsdp_v2_moe_ep2_pretrain_smoke(self):
455483
cfg.ddp.megatron_fsdp_version = 2
456484

457485
pretrain(cfg, forward_step)
486+
487+
torch.distributed.barrier()
488+
489+
@pytest.mark.run_only_on("GPU")
490+
@pytest.mark.parametrize("outer_dp_sharding_strategy", ["no_shard", "optim"], ids=["hsdp", "hfsdp"])
491+
def test_fsdp_v2_hybrid_dp_pretrain_smoke(self, outer_dp_sharding_strategy):
492+
"""Train the dense HybridModel over an outer data-parallel axis.
493+
494+
no_shard outer over the optim_grads_params inner axis is HSDP, optim outer is HFSDP.
495+
"""
496+
initialize_distributed()
497+
torch.distributed.barrier()
498+
499+
cfg = create_fsdp_config_container(
500+
seq_length=128,
501+
train_iters=10,
502+
ddp={
503+
"num_distributed_optimizer_instances": 2,
504+
"outer_dp_sharding_strategy": outer_dp_sharding_strategy,
505+
},
506+
)
507+
cfg.model = create_dense_hybrid_smoke_model_config()
508+
cfg.ddp.megatron_fsdp_version = 2
509+
510+
pretrain(cfg, forward_step)
511+
458512
torch.distributed.barrier()
459513

460514
@pytest.mark.run_only_on("GPU")

‎tests/unit_tests/models/test_conversion_unwrap_utils.py‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ class FSDPV2(FSDPV1):
2727
pass
2828

2929
monkeypatch.setattr(
30-
"megatron.bridge.training.fsdp_compat.MEGATRON_FSDP_TYPES",
31-
(FSDPV1, FSDPV2),
30+
"megatron.core.distributed.fsdp.mcore_fsdp_adapter.FullyShardedDataParallelV1",
31+
FSDPV1,
32+
)
33+
monkeypatch.setattr(
34+
"megatron.core.distributed.fsdp.mcore_fsdp_adapter.FullyShardedDataParallelV2",
35+
FSDPV2,
3236
)
3337

3438
module = torch.nn.Linear(1, 1)

‎tests/unit_tests/training/test_train.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
from unittest.mock import Mock, patch
2121

2222
import pytest
23+
from megatron.core.distributed.fsdp.mcore_fsdp_adapter import FullyShardedDataParallelV1
2324
from megatron.core.optimizer.distrib_optimizer import DistributedOptimizer
2425

25-
from megatron.bridge.training.fsdp_compat import MEGATRON_FSDP_TYPES
2626
from megatron.bridge.training.state import GlobalState
2727
from megatron.bridge.training.train import (
2828
_delete_cuda_graphs,
@@ -312,7 +312,7 @@ def test_maybe_register_fsdp_buffers_execution(self):
312312
config.ddp.fsdp_manual_registration = True
313313

314314
# Mock model chunk
315-
model_chunk = Mock(spec=MEGATRON_FSDP_TYPES[0])
315+
model_chunk = Mock(spec=FullyShardedDataParallelV1)
316316
# Mock ddp_config on the chunk
317317
model_chunk.ddp_config = Mock()
318318
model_chunk.ddp_config.fsdp_manual_registration = True

0 commit comments

Comments
 (0)