Skip to content

Commit f1661bf

Browse files
Address review: refuse a Megatron-built GLM-5 MTP, drop the KPool indexer export
- GLM-5's MTP is only copied from the source checkpoint; raise on every rank if Megatron built one instead of exporting it under mtp.* names, and drop the unused mtp.* rules. - Leave the KPool indexer parameters to the GLM-5.3-Flash PR that adds their rules. - Check the restored DSA K/V quantizers stay enabled (V is unused in the forward). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
1 parent 241e3f6 commit f1661bf

4 files changed

Lines changed: 37 additions & 13 deletions

File tree

‎modelopt/torch/export/plugins/mcore_glm.py‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"""Custom mappings from Megatron Core models to GLM-5.x Hugging Face models.
1717
1818
GLM-5 / GLM-5.2 (``glm_moe_dsa``) is DeepSeek-V3-style MLA with a DSA indexer. It keeps the MTP
19-
layer at HF index ``num_hidden_layers`` under the decoder's names.
19+
layer at HF index ``num_hidden_layers`` under the decoder's names; Megatron-Bridge does not build
20+
it, so the exporter copies it from the source checkpoint.
2021
"""
2122

2223
from .mcore_custom import (
@@ -37,8 +38,4 @@
3738
"indexer.linear_weights_proj": NameRemapping("model.layers.{}.self_attn.indexer.weights_proj."),
3839
"experts.linear_fc1": GroupedGatedMLPSlicing("model.layers.{}.mlp.experts.{{}}"),
3940
"experts.linear_fc2": GroupedMLPSlicing("model.layers.{}.mlp.experts.{{}}.down_proj"),
40-
"mtp.enorm": NameRemapping("model.layers.{}.enorm."),
41-
"mtp.hnorm": NameRemapping("model.layers.{}.hnorm."),
42-
"mtp.eh_proj": NameRemapping("model.layers.{}.eh_proj."),
43-
"mtp.final_layernorm": NameRemapping("model.layers.{}.shared_head.norm."),
4441
}

‎modelopt/torch/export/unified_export_megatron.py‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ def __init__(
205205
self.all_rules = self._populate_rule_book()
206206
self.rules = self.all_rules[self.arch]
207207
self.all_mcore_mappings = all_mcore_hf_export_mapping[self.arch]
208+
if self.rules.get("mtp_in_decoder_layers", False) and language_model.config.mtp_num_layers:
209+
# Only the copy from the source checkpoint is supported (the bridge builds no MTP).
210+
raise NotImplementedError(
211+
f"Exporting a Megatron-built MTP for {self.arch} is not supported yet."
212+
)
208213
# The vision tower is copied through unquantized, so deployments must not treat it as such.
209214
self.exclude_modules = [
210215
prefix.removesuffix(".") + "*" for prefix in self.vision_passthrough_prefixes or ()
@@ -990,18 +995,11 @@ def _get_gated_delta_net_state_dict(self, layer, layer_id, is_mtp=False):
990995
self.rules["linear_attn.out_proj"](gdn.out_proj, layer_id, is_mtp=is_mtp)
991996

992997
def _get_dsa_indexer_state_dict(self, indexer, layer_id, is_mtp=False):
993-
"""Export the DSA kpool indexer of a sparse MLA layer."""
998+
"""Export the DSA indexer of a sparse MLA layer."""
994999
if "indexer.linear_wq_b" not in self.rules:
9951000
raise NotImplementedError(f"No export rule for the DSA indexer of {self.arch}.")
9961001
for name in ("linear_wq_b", "linear_wk", "k_norm", "linear_weights_proj"):
9971002
self.rules[f"indexer.{name}"](getattr(indexer, name), layer_id, is_mtp=is_mtp)
998-
# KPool (GLM-5.3-Flash) only; plain DSA indexers (GLM-5.2) have no compression params.
999-
for name in ("index_kpool_compress_ape", "index_kpool_compress_gate"):
1000-
param = getattr(indexer, name, None)
1001-
if param is not None:
1002-
self.rules[f"indexer.{name}"](
1003-
param.detach().to(self.dtype), layer_id, is_mtp=is_mtp
1004-
)
10051003

10061004
def _get_mamba_layer_state_dict(self, layer, layer_id, is_mtp=False):
10071005
if not isinstance(layer.norm, IdentityOp):

‎tests/gpu_megatron/torch/export/test_unified_export_megatron.py‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,30 @@ def test_mtp_state_dict_index_file(tmp_path):
10541054
assert "mtp*" in exporter.exclude_modules
10551055

10561056

1057+
def _test_live_decoder_mtp_export_rejected(model_dir, rank, size):
1058+
model = get_mcore_gpt_model(
1059+
tensor_model_parallel_size=size,
1060+
pipeline_model_parallel_size=1,
1061+
initialize_megatron=True,
1062+
num_layers=2,
1063+
hidden_size=64,
1064+
num_attention_heads=4,
1065+
vocab_size=128,
1066+
max_sequence_length=32,
1067+
mtp_num_layers=1,
1068+
).cuda()
1069+
with pytest.raises(NotImplementedError, match="Megatron-built MTP"):
1070+
GPTModelExporter(model, str(model_dir))
1071+
1072+
1073+
def test_live_decoder_mtp_export_rejected(dist_workers_size_1, tmp_path):
1074+
"""GLM-5 MTP is only copied from the source; a Megatron-built one must not export misnamed."""
1075+
transformers.GlmMoeDsaConfig(
1076+
num_hidden_layers=2, architectures=["GlmMoeDsaForCausalLM"]
1077+
).save_pretrained(tmp_path)
1078+
dist_workers_size_1.run(partial(_test_live_decoder_mtp_export_rejected, tmp_path))
1079+
1080+
10571081
def test_mtp_state_dict_copies_decoder_mtp_layers(tmp_path):
10581082
"""GLM-5 keeps MTP as an extra decoder layer; copy it dequantized when Megatron did not build it."""
10591083
model_dir = tmp_path / "fake_glm5"

‎tests/gpu_megatron/torch/quantization/plugins/test_megatron.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,6 +2176,11 @@ def _test_dsa_kv_cache_quant_helper(tmp_path, rank, size):
21762176
if ".indexer." in name:
21772177
param.requires_grad_(False)
21782178
sharded_state_dict_test_helper(tmp_path, model, model_test, forward)
2179+
# The helper compares every restored tensor (incl. K/V amax), but V is unused in the forward
2180+
# pass, so also check both quantizers came back enabled.
2181+
restored = [m for m in model_test.modules() if isinstance(m, DSAttention)]
2182+
assert len(restored) == len(dsa_modules)
2183+
assert all(m.k_bmm_quantizer.is_enabled and m.v_bmm_quantizer.is_enabled for m in restored)
21792184

21802185

21812186
def test_dsa_kv_cache_quant(dist_workers_size_1, tmp_path):

0 commit comments

Comments
 (0)