Skip to content

Commit 64df9ee

Browse files
kamran-nvidiaclaude
andcommitted
refactor(model): build GLM-4.5 from a builder-backed model config
Move GLM-4.5 onto the ModelConfig/ModelBuilder path, following Muse Glimmer: - GLM45Bridge maps Hugging Face configs in hf_config_to_model_config_kwargs(). The builder's default MoE layer spec is the GPT decoder block spec, which honors moe_layer_freq; the deprecated provider path keeps its explicit decoder block spec. USE_MODEL_CONFIG_FOR_CONVERSION = True. - All six GLM-4.5 H100 recipes use get_model_config(). Signed-off-by: Kamran Jafari <kjafarisadeg@nvidia.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2f6def0 commit 64df9ee

4 files changed

Lines changed: 203 additions & 46 deletions

File tree

‎src/megatron/bridge/models/glm/glm45_bridge.py‎

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
# limitations under the License.
1414

1515
from functools import partial
16+
from typing import Any
1617

1718
import torch
1819
from megatron.core.models.gpt.gpt_layer_specs import get_gpt_decoder_block_spec
1920
from megatron.core.models.gpt.gpt_model import GPTModel
20-
from transformers import Glm4MoeForCausalLM
21+
from transformers import Glm4MoeForCausalLM, PretrainedConfig
2122

2223
from megatron.bridge.models.conversion.mapping_registry import MegatronMappingRegistry
2324
from megatron.bridge.models.conversion.model_bridge import MegatronModelBridge
@@ -28,8 +29,6 @@
2829
GatedMLPMapping,
2930
QKVMapping,
3031
)
31-
from megatron.bridge.models.gpt_provider import GPTModelProvider
32-
from megatron.bridge.models.hf_pretrained.causal_lm import PreTrainedCausalLM
3332

3433

3534
try:
@@ -51,47 +50,58 @@ class GLM45Bridge(MegatronModelBridge):
5150
Example:
5251
>>> from megatron.bridge import AutoBridge
5352
>>> bridge = AutoBridge.from_hf_pretrained("zai-org/GLM-4.5")
54-
>>> provider = bridge.to_megatron_provider()
53+
>>> model_config = bridge.get_model_config()
5554
"""
5655

57-
def provider_bridge(self, hf_pretrained: PreTrainedCausalLM) -> GPTModelProvider:
58-
"""Convert HuggingFace config to GPTModelProvider."""
59-
provider = super().provider_bridge(hf_pretrained)
60-
hf_config = hf_pretrained.config
56+
USE_MODEL_CONFIG_FOR_CONVERSION = True
6157

62-
# Use decoder block spec to properly handle moe_layer_freq (mixed dense/MoE layers)
63-
provider.transformer_layer_spec = partial(get_gpt_decoder_block_spec, use_transformer_engine=HAVE_TE)
64-
provider.normalization = "RMSNorm"
65-
provider.gated_linear_unit = True
66-
provider.add_bias_linear = False
67-
provider.share_embeddings_and_output_weights = False
58+
def hf_config_to_model_config_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
59+
"""Convert a Hugging Face GLM-4.5 config to builder config kwargs.
6860
69-
provider.moe_shared_expert_overlap = True
70-
provider.moe_token_dispatcher_type = "alltoall"
71-
provider.moe_router_load_balancing_type = "seq_aux_loss"
72-
provider.moe_router_pre_softmax = False
73-
provider.moe_grouped_gemm = True
74-
provider.moe_router_score_function = "sigmoid"
75-
provider.moe_permute_fusion = True
76-
provider.moe_router_enable_expert_bias = True
77-
provider.moe_router_dtype = "fp32"
78-
provider.moe_router_bias_update_rate = 0
79-
provider.moe_aux_loss_coeff = 0.001
80-
81-
provider.persist_layer_norm = True
82-
provider.bias_activation_fusion = True
83-
provider.bias_dropout_fusion = True
84-
provider.hidden_dropout = 0.0
85-
provider.autocast_dtype = torch.bfloat16
86-
provider.mtp_num_layers = getattr(hf_config, "num_nextn_predict_layers", None)
87-
provider.mtp_loss_scaling_factor = 0.3
88-
provider.moe_shared_expert_intermediate_size = hf_config.moe_intermediate_size
89-
90-
provider.moe_layer_freq = [0] * hf_config.first_k_dense_replace + [1] * (
91-
hf_config.num_hidden_layers - hf_config.first_k_dense_replace
61+
The builder's default MoE layer spec already uses the GPT decoder block
62+
spec, which honors the mixed dense/MoE ``moe_layer_freq``.
63+
"""
64+
config_kwargs = super().hf_config_to_model_config_kwargs(hf_config)
65+
config_kwargs.update(
66+
normalization="RMSNorm",
67+
gated_linear_unit=True,
68+
add_bias_linear=False,
69+
share_embeddings_and_output_weights=False,
70+
moe_shared_expert_overlap=True,
71+
moe_token_dispatcher_type="alltoall",
72+
moe_router_load_balancing_type="seq_aux_loss",
73+
moe_router_pre_softmax=False,
74+
moe_grouped_gemm=True,
75+
moe_router_score_function="sigmoid",
76+
moe_permute_fusion=True,
77+
moe_router_enable_expert_bias=True,
78+
moe_router_dtype="fp32",
79+
moe_router_bias_update_rate=0,
80+
moe_aux_loss_coeff=0.001,
81+
persist_layer_norm=True,
82+
bias_activation_fusion=True,
83+
bias_dropout_fusion=True,
84+
hidden_dropout=0.0,
85+
autocast_dtype=torch.bfloat16,
86+
masked_softmax_fusion=True,
87+
rope_scaling=False,
88+
rope_scaling_factor=1.0,
89+
mtp_num_layers=getattr(hf_config, "num_nextn_predict_layers", None),
90+
mtp_loss_scaling_factor=0.3,
91+
moe_shared_expert_intermediate_size=hf_config.moe_intermediate_size,
92+
moe_layer_freq=[0] * hf_config.first_k_dense_replace
93+
+ [1] * (hf_config.num_hidden_layers - hf_config.first_k_dense_replace),
9294
)
95+
config_kwargs.setdefault("position_embedding_type", "rope")
96+
return config_kwargs
9397

94-
return provider
98+
def hf_config_to_provider_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
99+
"""Adapt the canonical builder mapping to the deprecated provider path."""
100+
provider_kwargs = self.hf_config_to_model_config_kwargs(hf_config)
101+
# The provider's default layer spec builds homogeneous layers; the decoder
102+
# block spec is needed for mixed dense/MoE layers (moe_layer_freq).
103+
provider_kwargs["transformer_layer_spec"] = partial(get_gpt_decoder_block_spec, use_transformer_engine=HAVE_TE)
104+
return provider_kwargs
95105

96106
def mapping_registry(self) -> MegatronMappingRegistry:
97107
mapping_list = []

‎src/megatron/bridge/recipes/glm/h100/glm45.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def glm45_355b_pretrain_128gpu_h100_bf16_config() -> ConfigContainer:
3232
cfg = _pretrain_common()
3333

3434
# Model config
35-
cfg.model = AutoBridge.from_hf_pretrained("zai-org/GLM-4.5").to_megatron_provider(load_weights=False)
35+
cfg.model = AutoBridge.from_hf_pretrained("zai-org/GLM-4.5").get_model_config()
3636

3737
# Tokenizer - uses NullTokenizer by default
3838
cfg.tokenizer.tokenizer_type = "NullTokenizer"
@@ -165,7 +165,7 @@ def glm45_air_106b_pretrain_32gpu_h100_bf16_config() -> ConfigContainer:
165165
cfg = _pretrain_common()
166166

167167
# Model config
168-
cfg.model = AutoBridge.from_hf_pretrained("zai-org/GLM-4.5-Air").to_megatron_provider(load_weights=False)
168+
cfg.model = AutoBridge.from_hf_pretrained("zai-org/GLM-4.5-Air").get_model_config()
169169

170170
# Tokenizer - uses NullTokenizer by default
171171
cfg.tokenizer.tokenizer_type = "NullTokenizer"
@@ -304,7 +304,7 @@ def glm45_355b_sft_128gpu_h100_bf16_config() -> ConfigContainer:
304304

305305
# Model config from HuggingFace
306306
hf_path = "zai-org/GLM-4.5"
307-
cfg.model = AutoBridge.from_hf_pretrained(hf_path).to_megatron_provider(load_weights=False)
307+
cfg.model = AutoBridge.from_hf_pretrained(hf_path).get_model_config()
308308

309309
# Tokenizer
310310
cfg.tokenizer.tokenizer_model = hf_path
@@ -441,7 +441,7 @@ def glm45_air_106b_sft_32gpu_h100_bf16_config() -> ConfigContainer:
441441

442442
# Model config from HuggingFace
443443
hf_path = "zai-org/GLM-4.5-Air"
444-
cfg.model = AutoBridge.from_hf_pretrained(hf_path).to_megatron_provider(load_weights=False)
444+
cfg.model = AutoBridge.from_hf_pretrained(hf_path).get_model_config()
445445

446446
# Tokenizer
447447
cfg.tokenizer.tokenizer_model = hf_path
@@ -588,7 +588,7 @@ def glm45_355b_peft_16gpu_h100_bf16_config(
588588

589589
# Model config from HuggingFace
590590
hf_path = "zai-org/GLM-4.5"
591-
cfg.model = AutoBridge.from_hf_pretrained(hf_path).to_megatron_provider(load_weights=False)
591+
cfg.model = AutoBridge.from_hf_pretrained(hf_path).get_model_config()
592592

593593
# Tokenizer
594594
cfg.tokenizer.tokenizer_model = hf_path
@@ -732,7 +732,7 @@ def glm45_air_106b_peft_8gpu_h100_bf16_config(
732732

733733
# Model config from HuggingFace
734734
hf_path = "zai-org/GLM-4.5-Air"
735-
cfg.model = AutoBridge.from_hf_pretrained(hf_path).to_megatron_provider(load_weights=False)
735+
cfg.model = AutoBridge.from_hf_pretrained(hf_path).get_model_config()
736736

737737
# Tokenizer
738738
cfg.tokenizer.tokenizer_model = hf_path

‎tests/unit_tests/models/glm/test_glm45_bridge.py‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,84 @@ def test_dtype_mapping_default(self, glm45_355b_config):
330330
assert provider.fp16 is False
331331
assert provider.bf16 is False
332332
assert provider.params_dtype == torch.float32
333+
334+
335+
class TestGLM45BuilderConfig:
336+
"""Builder-backed ModelConfig path for GLM-4.5."""
337+
338+
@staticmethod
339+
def _config(**overrides):
340+
from transformers import Glm4MoeConfig
341+
342+
kwargs = dict(
343+
hidden_size=64,
344+
intermediate_size=128,
345+
moe_intermediate_size=32,
346+
num_hidden_layers=4,
347+
num_attention_heads=8,
348+
num_key_value_heads=4,
349+
n_routed_experts=8,
350+
n_shared_experts=1,
351+
num_experts_per_tok=2,
352+
first_k_dense_replace=1,
353+
max_position_embeddings=4096,
354+
vocab_size=128,
355+
torch_dtype="bfloat16",
356+
)
357+
kwargs.update(overrides)
358+
config = Glm4MoeConfig(**kwargs)
359+
config.architectures = ["Glm4MoeForCausalLM"]
360+
config.num_nextn_predict_layers = 1
361+
return config
362+
363+
def test_provider_bridge_is_inherited_compatibility_only(self):
364+
assert "provider_bridge" not in GLM45Bridge.__dict__
365+
366+
def test_conversion_uses_builder_config(self):
367+
assert GLM45Bridge.USE_MODEL_CONFIG_FOR_CONVERSION is True
368+
369+
def test_hf_config_to_model_config_uses_direct_mapping(self):
370+
from unittest.mock import patch
371+
372+
from megatron.bridge.models.gpt.model_config import BridgeGPTModelConfig
373+
374+
config = self._config()
375+
bridge = GLM45Bridge()
376+
with (
377+
patch.object(bridge, "provider_bridge", side_effect=AssertionError("provider path used")),
378+
patch.object(bridge, "hf_config_to_provider_kwargs", side_effect=AssertionError("provider kwargs used")),
379+
):
380+
result = bridge.hf_config_to_model_config(config)
381+
382+
assert isinstance(result, BridgeGPTModelConfig)
383+
# The builder's default MoE spec is the decoder block spec, so none is configured here.
384+
assert result.transformer_layer_spec is None
385+
assert result.moe_layer_freq == [0, 1, 1, 1]
386+
assert result.moe_shared_expert_intermediate_size == config.moe_intermediate_size
387+
assert result.moe_router_score_function == "sigmoid"
388+
assert result.moe_router_enable_expert_bias is True
389+
assert result.mtp_num_layers == 1
390+
assert result.mtp_loss_scaling_factor == 0.3
391+
assert result.position_embedding_type == "rope"
392+
393+
def test_model_config_matches_provider_runtime_config(self):
394+
from dataclasses import fields
395+
396+
from megatron.core.models.gpt.gpt_layer_specs import get_gpt_decoder_block_spec
397+
398+
config = self._config()
399+
mock_pretrained = Mock(spec=PreTrainedCausalLM)
400+
mock_pretrained.config = config
401+
bridge = GLM45Bridge()
402+
403+
model_config = bridge.hf_config_to_model_config(config)
404+
with pytest.warns(FutureWarning, match=r"deprecated.*get_model_config.*get_model"):
405+
provider = bridge.provider_bridge(mock_pretrained)
406+
407+
provider_fields = {field.name for field in fields(provider)}
408+
model_config_fields = {field.name for field in fields(model_config)}
409+
model_config_fields.update(field.name for field in fields(model_config.transformer))
410+
for field_name in sorted((provider_fields & model_config_fields) - {"transformer_layer_spec"}):
411+
assert getattr(model_config, field_name) == getattr(provider, field_name), field_name
412+
# The provider keeps its explicit decoder block spec for mixed dense/MoE layers.
413+
assert provider.transformer_layer_spec.func is get_gpt_decoder_block_spec

‎tests/unit_tests/recipes/test_glm45_recipes.py‎

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,12 @@ class _FakeBridge:
9898
def __init__(self):
9999
pass
100100

101-
def to_megatron_provider(self, load_weights: bool = False):
101+
def get_model_config(self):
102102
return _FakeModelCfg()
103103

104+
def to_megatron_provider(self, load_weights: bool = False):
105+
raise AssertionError("GLM-4.5 recipes must use get_model_config(), not the legacy provider API")
106+
104107
@staticmethod
105108
def from_hf_pretrained(hf_path: str, **kwargs):
106109
return _FakeBridge()
@@ -112,7 +115,7 @@ class _RealDepthFakeBridge(_FakeBridge):
112115
def __init__(self, num_layers: int):
113116
self.num_layers = num_layers
114117

115-
def to_megatron_provider(self, load_weights: bool = False):
118+
def get_model_config(self):
116119
model = _FakeModelCfg()
117120
model.num_layers = self.num_layers
118121
return model
@@ -546,3 +549,66 @@ def test_glm45_recompute_configuration(monkeypatch: pytest.MonkeyPatch):
546549
assert hasattr(cfg.model, "recompute_granularity")
547550
assert hasattr(cfg.model, "recompute_method")
548551
assert hasattr(cfg.model, "recompute_num_layers")
552+
553+
554+
class _BuilderOnlyBridge:
555+
"""Return a real strict ModelConfig at the recipe's GLM-4.5 depth while rejecting the provider API."""
556+
557+
def __init__(self, num_layers: int):
558+
self.num_layers = num_layers
559+
560+
@staticmethod
561+
def from_hf_pretrained(hf_path: str, **kwargs) -> "_BuilderOnlyBridge":
562+
return _BuilderOnlyBridge(46 if hf_path.endswith("Air") else 92)
563+
564+
def get_model_config(self):
565+
from transformers import Glm4MoeConfig
566+
567+
from megatron.bridge import AutoBridge
568+
569+
config = Glm4MoeConfig(
570+
hidden_size=64,
571+
intermediate_size=128,
572+
moe_intermediate_size=32,
573+
num_hidden_layers=self.num_layers,
574+
num_attention_heads=8,
575+
num_key_value_heads=4,
576+
n_routed_experts=16,
577+
n_shared_experts=1,
578+
num_experts_per_tok=2,
579+
first_k_dense_replace=1,
580+
max_position_embeddings=131072,
581+
vocab_size=151552,
582+
)
583+
config.architectures = ["Glm4MoeForCausalLM"]
584+
config.num_nextn_predict_layers = 1
585+
return AutoBridge.from_hf_config(config).get_model_config()
586+
587+
def to_megatron_provider(self, load_weights: bool = False):
588+
raise AssertionError("GLM-4.5 recipes must not call the legacy provider API")
589+
590+
591+
@pytest.mark.parametrize("recipe_func", _GLM45_RECIPE_FUNCS, ids=lambda func: func.__name__)
592+
def test_each_glm45_recipe_uses_strict_builder_config(recipe_func: Callable, monkeypatch: pytest.MonkeyPatch):
593+
"""Every GLM-4.5 recipe should configure a ModelConfig without provider fallback."""
594+
from megatron.bridge.models.gpt.model_config import BridgeGPTModelConfig
595+
596+
patch_recipe_module_global(monkeypatch, recipe_func, "AutoBridge", _BuilderOnlyBridge)
597+
# Avoid HF I/O for SFT/PEFT tokenizers.
598+
import transformers
599+
600+
monkeypatch.setattr(
601+
transformers,
602+
"AutoTokenizer",
603+
type("FakeAutoTokenizer", (), {"from_pretrained": staticmethod(lambda *args, **kwargs: _FakeTokenizer())}),
604+
)
605+
606+
if "peft" in recipe_func.__name__:
607+
cfg = recipe_func(peft_scheme="lora")
608+
else:
609+
cfg = recipe_func()
610+
611+
assert isinstance(cfg.model, BridgeGPTModelConfig)
612+
assert cfg.model.normalization == "RMSNorm"
613+
assert cfg.model.moe_router_score_function == "sigmoid"
614+
assert cfg.model.mtp_num_layers == 1

0 commit comments

Comments
 (0)