Skip to content

Commit 1e5fbe5

Browse files
cuichenxyaoyu-33
andcommitted
fix(training): preserve compact LayerWise workflows
Propagate the LayerWise parameter-layout choice through standard and MIMO model wrapping, and keep the ordinary distributed-optimizer DDP flag disabled for compact layouts. Detect nested distributed optimizer leaves when choosing checkpoint metadata and cover DP-resize resume behavior. Co-authored-by: ykarnati <ykarnati@nvidia.com> Co-authored-by: yaoyu-33 <yaoyu.094@gmail.com> Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent ee73562 commit 1e5fbe5

18 files changed

Lines changed: 404 additions & 39 deletions

‎docs/training/optimizer-scheduler.md‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ layer-wise Muon. The current Core implementation requires one distributed
4949
optimizer instance and does not support `overlap_param_gather_with_optimizer_step`
5050
or either FSDP implementation with Muon.
5151

52+
By default, `use_layer_wise_param_layout=True` builds shard-aligned DDP buffers
53+
for the LayerWise optimizer. Set it to `False` to retain compact DDP buffers and
54+
use LayerWise's variable-size parameter synchronization path. This reduces
55+
layout padding while preserving the configured parameter-gather behavior.
56+
5257
Resuming Muon training restores Muon optimizer state. When changing optimization
5358
algorithms between runs, load model weights with fresh optimizer state using
5459
`checkpoint.load_optim=False` or the appropriate finetuning configuration;
@@ -145,4 +150,4 @@ These scheduling options control how the weight decay coefficient changes over t
145150
|---------------|-----------------------------------------------------------------------------|
146151
| **Constant** | Fixed weight decay throughout training. |
147152
| **Linear** | Linear progression from start to end weight decay. |
148-
| **Cosine** | Cosine progression from start to end weight decay. |
153+
| **Cosine** | Cosine progression from start to end weight decay. |

‎src/megatron/bridge/models/megatron_mimo/build_model.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def build_megatron_mimo_model(
4141
wrap_with_ddp: bool = True,
4242
data_parallel_random_init: bool = True,
4343
use_layer_wise_distributed_optimizer: bool = False,
44+
use_layer_wise_param_layout: bool = True,
4445
) -> tuple["MimoModel", "MegatronMIMOInfra"]:
4546
"""Build a distributed MegatronMIMO model and return ``(model, infra)``.
4647
@@ -78,6 +79,7 @@ def build_megatron_mimo_model(
7879
wrap_with_ddp=wrap_with_ddp,
7980
data_parallel_random_init=data_parallel_random_init,
8081
use_layer_wise_distributed_optimizer=use_layer_wise_distributed_optimizer,
82+
use_layer_wise_param_layout=use_layer_wise_param_layout,
8183
)
8284
mimo_model = model_list[0]
8385

‎src/megatron/bridge/models/megatron_mimo/megatron_mimo_ddp.py‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from megatron.core.models.mimo.config.role import MIMO_LANGUAGE_MODULE_KEY
1515
from torch import nn
1616

17+
from megatron.bridge.utils.common_utils import warn_rank_0
18+
1719

1820
if TYPE_CHECKING:
1921
from megatron.core.distributed import (
@@ -36,6 +38,7 @@ def wrap_megatron_mimo_model_distributed(
3638
pg_collections: Dict[str, Optional["ProcessGroupCollection"]],
3739
*,
3840
use_layer_wise_distributed_optimizer: bool = False,
41+
use_layer_wise_param_layout: bool = True,
3942
) -> "MimoModel":
4043
"""Wrap MegatronMIMO model's submodules with DDP.
4144
@@ -48,6 +51,8 @@ def wrap_megatron_mimo_model_distributed(
4851
grids: Module name to HyperCommGrid mapping.
4952
pg_collections: Module name to ProcessGroupCollection mapping.
5053
use_layer_wise_distributed_optimizer: Build shard-aligned layouts for layer-wise optimizers.
54+
use_layer_wise_param_layout: Use shard-aligned DDP buffers for LayerWise optimizers.
55+
False retains compact DDP buffers and variable-size parameter synchronization.
5156
5257
Returns:
5358
The same megatron_mimo_model with wrapped submodules.
@@ -61,14 +66,26 @@ def wrap_megatron_mimo_model_distributed(
6166
# Lazy import to avoid circular dependency (models layer loads before training layer)
6267
from megatron.bridge.training.megatron_mimo_parallel_utils import is_current_rank_in_grid
6368

69+
disable_encoder_overlap = ddp_config.overlap_grad_reduce is True or ddp_config.overlap_param_gather is True
70+
has_modality_grid = any(
71+
module_name != MIMO_LANGUAGE_MODULE_KEY and grid is not None for module_name, grid in grids.items()
72+
)
73+
if disable_encoder_overlap and has_modality_grid:
74+
# Emit before per-grid rank filtering so global rank zero reports the
75+
# override even when it does not participate in an encoder grid.
76+
warn_rank_0(
77+
"Disabling gradient-reduce and parameter-gather overlap for modality encoders because "
78+
"not every rank is guaranteed to execute an encoder backward pass."
79+
)
80+
6481
def wrap_module(
6582
module: nn.Module,
6683
config: "TransformerConfig",
6784
pg: "ProcessGroupCollection",
6885
module_ddp_config: "DistributedDataParallelConfig",
6986
) -> "DistributedDataParallel":
7087
layout = None
71-
if use_layer_wise_distributed_optimizer:
88+
if use_layer_wise_distributed_optimizer and use_layer_wise_param_layout:
7289
module_ddp_config.use_distributed_optimizer = True
7390
tag_params_for_buffer_routing([module])
7491
groups = pg.setup_process_groups_for_ddp(pg, config, module_ddp_config)
@@ -144,10 +161,12 @@ def wrap_module(
144161
f"a 'config' attribute. Encoders must be MegatronModule subclasses."
145162
)
146163

147-
# An encoder rank that gets no media in a step runs no encoder forward or backward,
148-
# so the DDP hooks would not issue its overlapped param all-gather or grad reduce.
149-
encoder_ddp_config = dataclasses.replace(
150-
ddp_config, overlap_grad_reduce=False, overlap_param_gather=False
164+
# Uneven input participation can leave encoder replicas
165+
# unsynchronized when communication is hook-driven.
166+
encoder_ddp_config = (
167+
dataclasses.replace(ddp_config, overlap_grad_reduce=False, overlap_param_gather=False)
168+
if disable_encoder_overlap
169+
else ddp_config
151170
)
152171
wrapped = wrap_module(
153172
submodule,

‎src/megatron/bridge/models/megatron_mimo/megatron_mimo_provider.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@ def provide_distributed_model(
511511
post_wrap_hook: Optional[Callable[[List[MegatronModule]], List[MegatronModule]]] = None,
512512
*,
513513
use_layer_wise_distributed_optimizer: bool = False,
514+
use_layer_wise_param_layout: bool = True,
514515
) -> List[MegatronModule]:
515516
"""Build MegatronMIMO model with heterogeneous parallelism and DDP wrapping.
516517
@@ -545,6 +546,8 @@ def provide_distributed_model(
545546
pre_wrap_hook: Callable(s) to modify model before wrapping.
546547
post_wrap_hook: Callable to modify model after wrapping.
547548
use_layer_wise_distributed_optimizer: Build shard-aligned layouts for layer-wise optimizers.
549+
use_layer_wise_param_layout: Use shard-aligned DDP buffers for LayerWise optimizers.
550+
False retains compact DDP buffers and variable-size parameter synchronization.
548551
549552
Returns:
550553
List containing the wrapped MimoModel.
@@ -626,6 +629,7 @@ def provide_distributed_model(
626629
grids=infra.module_to_grid_map,
627630
pg_collections=infra.pg_collections,
628631
use_layer_wise_distributed_optimizer=use_layer_wise_distributed_optimizer,
632+
use_layer_wise_param_layout=use_layer_wise_param_layout,
629633
)
630634
for m in model_list
631635
]

‎src/megatron/bridge/models/model_provider.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ def provide_distributed_model(
215215
pg_collection: ProcessGroupCollection | None = None,
216216
*,
217217
use_layer_wise_distributed_optimizer: bool = False,
218+
use_layer_wise_param_layout: bool = True,
218219
) -> list[ModelT]:
219220
"""Instantiate and wrap the model for distributed training.
220221
@@ -225,6 +226,8 @@ def provide_distributed_model(
225226
226227
Args:
227228
use_layer_wise_distributed_optimizer: Build DDP layouts for layer-wise optimizer state.
229+
use_layer_wise_param_layout: Use shard-aligned DDP buffers for LayerWise optimizers.
230+
False retains compact DDP buffers and variable-size parameter synchronization.
228231
ddp_config: Configuration for distributed data parallel.
229232
model_type: Type of model (encoder, decoder, or both).
230233
overlap_param_gather_with_optimizer_step: Whether to overlap param gathering.
@@ -307,6 +310,7 @@ def composed_pre_wrap_hook(model: list[MegatronModule]) -> list[MegatronModule]:
307310
mixed_precision_wrapper=mixed_precision_wrapper,
308311
pg_collection=pg_collection,
309312
use_layer_wise_distributed_optimizer=use_layer_wise_distributed_optimizer,
313+
use_layer_wise_param_layout=use_layer_wise_param_layout,
310314
)
311315

312316
if final_post_wrap_hook:
@@ -612,6 +616,7 @@ def get_model(
612616
*,
613617
pg_collection: ProcessGroupCollection,
614618
use_layer_wise_distributed_optimizer: bool = False,
619+
use_layer_wise_param_layout: bool = True,
615620
) -> list[MegatronModule]:
616621
"""Create and configure a model for distributed training.
617622
@@ -635,6 +640,8 @@ def get_model(
635640
use_megatron_fsdp: Use Megatron's Fully Sharded Data Parallel
636641
use_torch_fsdp2: Use PyTorch's Fully Sharded Data Parallel v2
637642
use_layer_wise_distributed_optimizer: Build shard-aligned DDP layouts for layer-wise optimizers.
643+
use_layer_wise_param_layout: Use shard-aligned DDP buffers for LayerWise optimizers.
644+
False retains compact DDP buffers and variable-size parameter synchronization.
638645
wrap_with_ddp: Whether to wrap the model with DDP
639646
data_parallel_random_init: Whether to use random initialization for
640647
data parallel ranks (vs broadcasting from rank 0)
@@ -740,6 +747,7 @@ def get_model(
740747
use_megatron_fsdp=use_megatron_fsdp,
741748
use_torch_fsdp2=use_torch_fsdp2,
742749
use_layer_wise_distributed_optimizer=use_layer_wise_distributed_optimizer,
750+
use_layer_wise_param_layout=use_layer_wise_param_layout,
743751
pg_collection=pg_collection,
744752
)
745753

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

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,9 +1394,10 @@ def save_checkpoint(
13941394
rank = torch.distributed.get_rank() if torch.distributed.is_initialized() else 0
13951395

13961396
# Collect cfg, model, RNG.
1397-
sharded_sd_metadata = _build_sharded_state_dict_metadata(uses_distributed_optimizer(cfg.optimizer), ckpt_cfg)
1397+
use_distributed_optimizer = _uses_distributed_optimizer(cfg.optimizer, optimizer)
1398+
sharded_sd_metadata = _build_sharded_state_dict_metadata(use_distributed_optimizer, ckpt_cfg)
13981399
sharded_sd_metadata["dp_cp_group"] = _checkpoint_dp_cp_group(pg_collection)
1399-
if uses_distributed_optimizer(cfg.optimizer):
1400+
if use_distributed_optimizer:
14001401
print_rank_0(
14011402
f"Storing distributed optimizer sharded state of type {sharded_sd_metadata['distrib_optim_sharding_type']}"
14021403
)
@@ -2913,6 +2914,7 @@ def _load_checkpoint_from_path(
29132914
model = unwrap_model(model)
29142915
pg_collection = pg_collection or get_pg_collection(model)
29152916
ckpt_format = cfg.checkpoint.ckpt_format
2917+
use_distributed_optimizer = _uses_distributed_optimizer(cfg.optimizer, optimizer)
29162918

29172919
# HuggingFace-format directories are only honored for pretrained model
29182920
# initialization. Resume training via ``checkpoint.load`` should use a
@@ -3048,9 +3050,7 @@ def _load_checkpoint_from_path(
30483050

30493051
if ckpt_type == CheckpointType.LOCAL:
30503052
# Local checkpoints don't store content metadata in common.pt.
3051-
sharded_sd_metadata = _build_sharded_state_dict_metadata(
3052-
uses_distributed_optimizer(cfg.optimizer), cfg.checkpoint
3053-
)
3053+
sharded_sd_metadata = _build_sharded_state_dict_metadata(use_distributed_optimizer, cfg.checkpoint)
30543054
else:
30553055
sharded_sd_metadata = dist_checkpointing.load_content_metadata(preloaded_state_dict=state_dict)
30563056
print_rank_0(f"sharded_state_dict metadata loaded from the checkpoint: {sharded_sd_metadata}")
@@ -3065,23 +3065,23 @@ def _load_checkpoint_from_path(
30653065
gen_sd_optim = optimizer
30663066
gen_sd_opt_param_scheduler = opt_param_scheduler
30673067

3068-
if uses_distributed_optimizer(cfg.optimizer):
3068+
if use_distributed_optimizer:
30693069
if sharded_sd_metadata is None:
30703070
sharded_sd_metadata = {
30713071
"distrib_optim_sharding_type": (
30723072
"fully_sharded_model_space"
3073-
if run_config["checkpoint"]["fully_parallel_save"]
3073+
if not cfg.optimizer.use_distributed_optimizer
3074+
or run_config["checkpoint"]["fully_parallel_save"]
30743075
else "dp_zero_gather_scatter"
30753076
),
30763077
}
3077-
if (
3078-
not tp_pp_match
3079-
and sharded_sd_metadata["distrib_optim_sharding_type"]
3080-
not in DistributedOptimizer.checkpoint_fully_reshardable_formats
3081-
):
3078+
# Older mixed-optimizer checkpoints omitted this key and used
3079+
# DistributedOptimizer's model-space default.
3080+
sharding_type = sharded_sd_metadata.get("distrib_optim_sharding_type", "fully_sharded_model_space")
3081+
if not tp_pp_match and sharding_type not in DistributedOptimizer.checkpoint_fully_reshardable_formats:
30823082
raise RuntimeError(
30833083
f"{mismatch_msg}: not supported for DistributedOptimizer with sharding type"
3084-
f" {sharded_sd_metadata['distrib_optim_sharding_type']}."
3084+
f" {sharding_type}."
30853085
f" Please use `checkpoint_config.fully_parallel_save=True` for checkpoint saving."
30863086
)
30873087
else:
@@ -3199,7 +3199,7 @@ def _load_checkpoint_from_path(
31993199
print_rank_0("Optimizer state was not saved in the FSDP checkpoint; using a fresh optimizer")
32003200

32013201
optim_sd_kwargs = dict(
3202-
metadata=_build_sharded_state_dict_metadata(uses_distributed_optimizer(cfg.optimizer), cfg.checkpoint),
3202+
metadata=_build_sharded_state_dict_metadata(use_distributed_optimizer, cfg.checkpoint),
32033203
is_loading=True,
32043204
)
32053205
# DTensor optimizer state does not contain dtype-keyed FQNs.
@@ -4149,6 +4149,20 @@ def load_fsdp_dtensor_checkpoint(
41494149
return state_dict, checkpoint_name, release, CheckpointType.FSDP_DTENSOR
41504150

41514151

4152+
def _has_distributed_optimizer(optimizer: MegatronOptimizer | None) -> bool:
4153+
"""Find tensor-sharded DistributedOptimizer leaves inside optimizer wrappers."""
4154+
if isinstance(optimizer, DistributedOptimizer):
4155+
return True
4156+
if not isinstance(optimizer, MegatronOptimizer):
4157+
return False
4158+
return any(_has_distributed_optimizer(child) for child in getattr(optimizer, "chained_optimizers", ()))
4159+
4160+
4161+
def _uses_distributed_optimizer(config: Any, optimizer: MegatronOptimizer | None) -> bool:
4162+
"""Combine declared LayerWise mode with the constructed optimizer tree."""
4163+
return uses_distributed_optimizer(config) or _has_distributed_optimizer(optimizer)
4164+
4165+
41524166
def _build_sharded_state_dict_metadata(use_distributed_optimizer: bool, cfg: CheckpointConfig) -> dict:
41534167
"""Builds metadata used for sharded_state_dict versioning.
41544168

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ class OptimizerConfig(MCoreOptimizerConfig):
134134
for field modifications after construction but before computed fields are calculated.
135135
"""
136136

137+
use_layer_wise_param_layout: bool = True
138+
"""Use the precomputed shard-aligned layout for the LayerWise optimizer.
139+
140+
Set to ``False`` to retain compact DDP buffers and use LayerWise's
141+
variable-size parameter synchronization path. This setting has no effect unless
142+
``use_layer_wise_distributed_optimizer`` is enabled.
143+
"""
144+
137145
def __post_init__(self) -> None:
138146
"""Skip MCore post_init during initial construction.
139147
@@ -2359,9 +2367,10 @@ def _validate_and_sync_distributed_optimizer_settings(config: ConfigContainer) -
23592367
if uses_layer_wise_optimizer(config.optimizer):
23602368
if config.ddp.num_distributed_optimizer_instances != 1:
23612369
raise ValueError("Layer-wise optimizers require num_distributed_optimizer_instances=1.")
2362-
# Both distributed implementations use reduce-scatter. Model setup must
2363-
# additionally pass layer-wise intent so Core chooses the correct layout.
2364-
config.ddp.use_distributed_optimizer = True
2370+
# This flag selects the ordinary DistributedOptimizer DDP layout. The
2371+
# model wrapper enables it only when a shard-aligned LayerWise layout is
2372+
# requested; compact LayerWise mode intentionally leaves it disabled.
2373+
config.ddp.use_distributed_optimizer = False
23652374
elif ddp_setting or optimizer_setting:
23662375
if ddp_setting != optimizer_setting:
23672376
warn_rank_0(

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
OptimizerConfigOverrideProviderContext,
3636
SchedulerConfig,
3737
)
38+
from megatron.bridge.training.utils.optimizer_utils import normalize_optimizer_config
3839

3940

4041
G_LOGGER = logging.getLogger(__name__)
@@ -60,6 +61,11 @@ def setup_optimizer(
6061
Returns:
6162
tuple containing the optimizer and scheduler
6263
"""
64+
if not hasattr(optimizer_config, "provide"):
65+
# setup_optimizer is also used by callers that construct Core's config
66+
# directly, without ConfigContainer validation.
67+
normalize_optimizer_config(optimizer_config)
68+
6369
if optimizer_config_override_provider is None:
6470
optimizer_config_override_provider = OptimizerConfigOverrideProvider()
6571

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,7 @@ def _build_distributed_model(cfg: ConfigContainer, pg_collection: ProcessGroupCo
633633
use_megatron_fsdp=cfg.dist.use_megatron_fsdp,
634634
use_torch_fsdp2=cfg.dist.use_torch_fsdp2,
635635
data_parallel_random_init=cfg.rng.data_parallel_random_init,
636+
use_layer_wise_param_layout=cfg.optimizer.use_layer_wise_param_layout,
636637
)
637638
else:
638639
model = model_config.provide_distributed_model(
@@ -641,6 +642,7 @@ def _build_distributed_model(cfg: ConfigContainer, pg_collection: ProcessGroupCo
641642
use_torch_fsdp2=cfg.dist.use_torch_fsdp2,
642643
overlap_param_gather_with_optimizer_step=cfg.optimizer.overlap_param_gather_with_optimizer_step,
643644
use_layer_wise_distributed_optimizer=uses_layer_wise_optimizer(cfg.optimizer),
645+
use_layer_wise_param_layout=cfg.optimizer.use_layer_wise_param_layout,
644646
data_parallel_random_init=cfg.rng.data_parallel_random_init,
645647
pg_collection=pg_collection,
646648
)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ def _update_megatron_mimo_model_config_funcs(
138138
def setup_megatron_mimo(
139139
state: GlobalState,
140140
build_data_iterators_fn: Optional[Callable] = None,
141+
*,
142+
use_layer_wise_param_layout: Optional[bool] = None,
141143
) -> MegatronMIMOSetupOutput:
142144
"""MegatronMIMO-specific setup helper.
143145
@@ -156,6 +158,8 @@ def setup_megatron_mimo(
156158
create the optimizer.
157159
build_data_iterators_fn: Optional function to build data iterators.
158160
Should have signature: (cfg, megatron_mimo_infra) -> (train_iter, valid_iter)
161+
use_layer_wise_param_layout: Override the optimizer config's LayerWise layout choice.
162+
False retains compact DDP buffers and variable-size parameter synchronization.
159163
160164
Returns:
161165
MegatronMIMOSetupOutput containing all components for training.
@@ -187,13 +191,16 @@ def setup_megatron_mimo(
187191
from megatron.bridge.models.megatron_mimo import build_megatron_mimo_model
188192

189193
megatron_mimo_provider = cfg.model
194+
if use_layer_wise_param_layout is None:
195+
use_layer_wise_param_layout = cfg.optimizer.use_layer_wise_param_layout
190196
model, megatron_mimo_infra = build_megatron_mimo_model(
191197
megatron_mimo_provider,
192198
ddp_config=cfg.ddp,
193199
use_layer_wise_distributed_optimizer=uses_layer_wise_optimizer(cfg.optimizer),
194200
fp16=getattr(cfg.model, "fp16", False),
195201
bf16=getattr(cfg.model, "bf16", True),
196202
seed=cfg.rng.seed,
203+
use_layer_wise_param_layout=use_layer_wise_param_layout,
197204
)
198205

199206
logger.info(f"Rank {dist.get_rank()}: Creating multimodule communicator")

0 commit comments

Comments
 (0)