Skip to content

Commit 4950254

Browse files
committed
fix(models): support Transformers 5.15 conversions
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
1 parent ea1fa3d commit 4950254

4 files changed

Lines changed: 50 additions & 13 deletions

File tree

src/megatron/bridge/training/utils/config_utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ def _convert_pretrained_config_to_dict(
102102

103103
if include_target and is_dataclass(value):
104104
field_names = {field.name for field in dataclass_fields(value) if not field.name.startswith("_")}
105+
raw_values = vars(value)
105106
config_items = [
106-
(field.name, getattr(value, field.name))
107+
(
108+
field.name,
109+
raw_values[field.name] if field.name in raw_values else object.__getattribute__(value, field.name),
110+
)
107111
for field in dataclass_fields(value)
108112
if not field.name.startswith("_")
109113
]

tests/functional_tests/test_groups/models/deepseek/test_deepseek_conversion.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
"num_attention_heads": 32,
4343
"num_experts_per_tok": 4,
4444
"num_hidden_layers": 2,
45-
"num_key_value_heads": 4,
45+
# DeepSeek MLA expands compressed KV latents to every attention head before
46+
# dispatching to the HF attention backend, so it uses MHA rather than GQA.
47+
"num_key_value_heads": 32,
4648
"num_nextn_predict_layers": 0,
4749
"q_lora_rank": 512,
4850
"topk_group": 2,

tests/functional_tests/test_groups/models/nemotron_omni/test_nemotron_omni_conversion.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,10 @@
4040
# 52-layer pattern, which keeps a representative mamba (M), MoE-MLP (E) and
4141
# attention (*) layer mix.
4242
_LLM_LAYER_TYPES = ["mamba", "moe", "mamba", "moe", "mamba", "attention"]
43-
# Transformers 5.15 serializes the same hybrid blocks under their canonical names.
44-
_SERIALIZED_LLM_LAYER_TYPES = [
45-
"linear_attention",
46-
"moe",
47-
"linear_attention",
48-
"moe",
49-
"linear_attention",
50-
"full_attention",
51-
]
43+
_LLM_LAYER_TYPE_ALIASES = {
44+
"linear_attention": "mamba",
45+
"full_attention": "attention",
46+
}
5247
_LLM_OVERRIDES = {
5348
"hybrid_override_pattern": "MEMEM*",
5449
"layer_types": _LLM_LAYER_TYPES,
@@ -72,6 +67,11 @@ def _apply_overrides(sub_config, overrides: dict) -> None:
7267
setattr(sub_config, key, value)
7368

7469

70+
def _normalize_llm_layer_types(layer_types: list[str]) -> list[str]:
71+
"""Normalize equivalent remote-config names to the toy model's layer vocabulary."""
72+
return [_LLM_LAYER_TYPE_ALIASES.get(layer_type, layer_type) for layer_type in layer_types]
73+
74+
7575
def _fix_tied_weights_keys(model: nn.Module) -> None:
7676
"""Convert _tied_weights_keys from list to dict for transformers 5.x compatibility."""
7777
for module in model.modules():
@@ -223,11 +223,23 @@ def test_nemotron_omni_conversion_roundtrip(self, nemotron_omni_toy_model_path,
223223
with open(config_file) as f:
224224
saved_config = json.load(f)
225225

226+
source_config_file = Path(hf_model_id) / "config.json"
227+
if source_config_file.exists():
228+
with open(source_config_file) as f:
229+
source_config = json.load(f)
230+
else:
231+
source_config = AutoConfig.from_pretrained(hf_model_id, trust_remote_code=True).to_dict()
232+
source_llm_config = source_config["llm_config"]
233+
226234
assert saved_config["architectures"][0] == "NemotronH_Nano_Omni_Reasoning_V3"
227235
assert saved_config["model_type"] == "NemotronH_Nano_Omni_Reasoning_V3"
228236
assert "llm_config" in saved_config
229237
assert "vision_config" in saved_config
230238
assert "sound_config" in saved_config
231239
assert saved_config["llm_config"]["num_hidden_layers"] == 6
232-
assert saved_config["llm_config"]["layer_types"] == _SERIALIZED_LLM_LAYER_TYPES
233-
assert saved_config["llm_config"]["layers_block_type"] == _SERIALIZED_LLM_LAYER_TYPES
240+
expected_layer_types = _normalize_llm_layer_types(source_llm_config["layer_types"])
241+
expected_block_types = _normalize_llm_layer_types(source_llm_config["layers_block_type"])
242+
assert expected_layer_types == _LLM_LAYER_TYPES
243+
assert expected_block_types == _LLM_LAYER_TYPES
244+
assert _normalize_llm_layer_types(saved_config["llm_config"]["layer_types"]) == expected_layer_types
245+
assert _normalize_llm_layer_types(saved_config["llm_config"]["layers_block_type"]) == expected_block_types

tests/unit_tests/training/utils/test_config_utils.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import pytest
2626
import torch
2727
from megatron.core.msc_utils import MultiStorageClientFeature
28+
from transformers import PreTrainedConfig
2829

2930
from megatron.bridge.models.common import Serializable
3031
from megatron.bridge.training.utils.config_utils import _ConfigContainerBase, create_ddp_config
@@ -774,6 +775,24 @@ class ConfigWithNone(_ConfigContainerBase):
774775
assert result["optional_field"] is None
775776
assert result["required_field"] == "required"
776777

778+
def test_dataclass_pretrained_config_reads_raw_fields(self):
779+
"""Dataclass config serialization must not invoke guarded dynamic access."""
780+
781+
@dataclass
782+
class HeterogeneousConfig(PreTrainedConfig):
783+
num_key_value_heads: int = 8
784+
785+
def __getattribute__(self, name):
786+
if name == "num_key_value_heads":
787+
raise RuntimeError("read this value from the per-layer config")
788+
return super().__getattribute__(name)
789+
790+
config = HeterogeneousConfig()
791+
792+
serialized = _ConfigContainerBase._convert_pretrained_config_to_dict(config, include_target=True)
793+
794+
assert serialized["num_key_value_heads"] == 8
795+
777796
def test_config_with_complex_nested_types(self):
778797
"""Test ConfigContainer with complex nested types."""
779798

0 commit comments

Comments
 (0)