Skip to content

Commit 31f2a61

Browse files
committed
fix(checkpoint): use global FSDP pipeline layer keys
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent a2e8061 commit 31f2a61

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@
139139
except ImportError:
140140
HAVE_MEGATRON_FSDP = False
141141

142+
try:
143+
from megatron.core.transformer.fsdp_dtensor_checkpoint import handle_pipeline_parallel_in_state_dict
144+
except ImportError:
145+
handle_pipeline_parallel_in_state_dict = None
146+
147+
142148
try:
143149
from megatron.core.transformer.fsdp_dtensor_checkpoint import handle_gdn_in_state_dict
144150
except ImportError:
@@ -2291,7 +2297,7 @@ def preprocess_fsdp_dtensor_state_dict(cfg, raw_state_dict: dict[str, Any], mode
22912297
getattr(model_config, "gated_linear_unit", False) and getattr(model_config, "activation_func", None) is F.silu
22922298
)
22932299

2294-
def apply(handler):
2300+
def apply(handler: Callable[..., tuple[dict[str, Any], dict[str, Any] | None]]) -> None:
22952301
"""Run a state dict handler over the model and, when present, the optimizer state."""
22962302
model_state_dict, optimizer_state_dict = handler(model, state_dict["model"], state_dict.get("optimizer"))
22972303
state_dict["model"] = model_state_dict
@@ -2324,6 +2330,15 @@ def apply(handler):
23242330
if handle_mtp_in_state_dict is not None:
23252331
apply(handle_mtp_in_state_dict)
23262332

2333+
# Globalize layer names last: earlier handlers resolve stage-local module paths.
2334+
if handle_pipeline_parallel_in_state_dict is not None:
2335+
apply(handle_pipeline_parallel_in_state_dict)
2336+
elif model_config.pipeline_model_parallel_size > 1:
2337+
raise RuntimeError(
2338+
"FSDP pipeline checkpointing requires a Megatron-Core version with "
2339+
"handle_pipeline_parallel_in_state_dict; older versions collide stage-local layer names."
2340+
)
2341+
23272342
preprocess_state_dict_for_uneven_dtensor(state_dict)
23282343

23292344
return state_dict

‎tests/unit_tests/training/test_checkpointing.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4946,6 +4946,7 @@ def test_preprocess_fsdp_dtensor_state_dict(self):
49464946
mock_model_config = Mock()
49474947
mock_model_config.gated_linear_unit = False
49484948
mock_model_config.num_moe_experts = None
4949+
mock_model_config.pipeline_model_parallel_size = 1
49494950
mock_get_config.return_value = mock_model_config
49504951

49514952
with (
@@ -4956,15 +4957,47 @@ def test_preprocess_fsdp_dtensor_state_dict(self):
49564957
patch("megatron.bridge.training.checkpointing.handle_gdn_in_state_dict", None),
49574958
patch("megatron.bridge.training.checkpointing.handle_mla_down_proj_in_state_dict", None),
49584959
patch("megatron.bridge.training.checkpointing.handle_mtp_in_state_dict", None),
4960+
patch(
4961+
"megatron.bridge.training.checkpointing.handle_pipeline_parallel_in_state_dict",
4962+
side_effect=lambda model, state, optim: (state, optim),
4963+
) as mock_pipeline,
49594964
):
49604965
raw_state_dict = {"model": {"test_param": torch.tensor([1.0])}}
49614966
result = preprocess_fsdp_dtensor_state_dict(mock_cfg, raw_state_dict, mock_model)
49624967

49634968
# Should call FP8 and uneven dtensor preprocessing
49644969
mock_fp8.assert_called_once()
49654970
mock_uneven.assert_called_once()
4971+
mock_pipeline.assert_called_once_with(mock_model, raw_state_dict["model"], None)
49664972
assert "model" in result
49674973

4974+
@pytest.mark.parametrize("pp", [1, 2])
4975+
def test_fsdp_pipeline_checkpoint_requires_core_handler(self, pp: int) -> None:
4976+
"""Old Core remains usable for PP=1 but must not silently corrupt PP>1."""
4977+
from types import SimpleNamespace
4978+
4979+
from megatron.bridge.training import checkpointing
4980+
4981+
model_config = SimpleNamespace(gated_linear_unit=False, num_moe_experts=None, pipeline_model_parallel_size=pp)
4982+
with (
4983+
patch("megatron.core.utils.get_model_config", return_value=model_config),
4984+
patch.object(checkpointing, "handle_fp8_extra_state_case"),
4985+
patch.object(checkpointing, "preprocess_state_dict_for_uneven_dtensor"),
4986+
patch.multiple(
4987+
checkpointing,
4988+
handle_gdn_in_state_dict=None,
4989+
handle_mla_down_proj_in_state_dict=None,
4990+
handle_mtp_in_state_dict=None,
4991+
handle_pipeline_parallel_in_state_dict=None,
4992+
),
4993+
):
4994+
state = {"model": {"weight": torch.ones(1)}}
4995+
if pp > 1:
4996+
with pytest.raises(RuntimeError, match="collide stage-local layer names"):
4997+
checkpointing.preprocess_fsdp_dtensor_state_dict(None, state, None)
4998+
else:
4999+
assert checkpointing.preprocess_fsdp_dtensor_state_dict(None, state, None) == state
5000+
49685001
@patch("megatron.bridge.training.checkpointing.HAVE_MEGATRON_FSDP", True)
49695002
def test_save_fsdp_dtensor_checkpoint_preprocesses_and_saves(self, tmp_path):
49705003
"""Test public FSDP DTensor save helper preprocesses and calls PyTorch DCP."""

0 commit comments

Comments
 (0)