|
13 | 13 | # limitations under the License. |
14 | 14 |
|
15 | 15 | import inspect |
| 16 | +import warnings |
16 | 17 | from dataclasses import dataclass |
17 | 18 | from unittest.mock import Mock, patch |
18 | 19 |
|
|
21 | 22 | import torch.nn.functional as F |
22 | 23 | from megatron.core.models.gpt import GPTModel |
23 | 24 | from megatron.core.transformer import ModuleSpec |
| 25 | +from transformers import Qwen2Config |
24 | 26 |
|
| 27 | +import megatron.bridge.models # noqa: F401 (registers every model-family bridge) |
25 | 28 | from megatron.bridge.models.common.base import ModelConfig |
26 | 29 | from megatron.bridge.models.conversion.model_bridge import MegatronModelBridge, ModelConfigNotSupportedError |
27 | 30 | from megatron.bridge.models.gpt.gpt_builder import GPTModelBuilder |
28 | 31 | from megatron.bridge.models.gpt.model_config import BridgeGPTModelConfig |
29 | 32 | from megatron.bridge.models.llama.llama_bridge import LlamaBridge |
| 33 | +from megatron.bridge.models.muse_glimmer.muse_glimmer_bridge import MuseGlimmerBridge |
| 34 | +from megatron.bridge.models.qwen.qwen2_bridge import Qwen2Bridge |
| 35 | +from megatron.bridge.models.qwen.qwen3_bridge import Qwen3Bridge |
30 | 36 | from megatron.bridge.models.transformer_config import TransformerConfig |
31 | 37 |
|
32 | 38 |
|
@@ -137,22 +143,88 @@ class SpecializedBridge(LlamaBridge): |
137 | 143 | assert model_config.specialized_field is True |
138 | 144 |
|
139 | 145 |
|
140 | | -def test_gpt_model_config_is_the_bridge_default() -> None: |
141 | | - assert MegatronModelBridge.MODEL_CONFIG_CLASS is BridgeGPTModelConfig |
142 | | - assert LlamaBridge.MODEL_CONFIG_CLASS is BridgeGPTModelConfig |
143 | | - assert "MODEL_CONFIG_CLASS" not in LlamaBridge.__dict__ |
| 146 | +def _bridge_subclasses(bridge_class: type[MegatronModelBridge]) -> set[type[MegatronModelBridge]]: |
| 147 | + subclasses = set(bridge_class.__subclasses__()) |
| 148 | + return subclasses.union(*(_bridge_subclasses(subclass) for subclass in subclasses)) |
| 149 | + |
| 150 | + |
| 151 | +def _all_bridge_classes() -> list[type[MegatronModelBridge]]: |
| 152 | + """Return every bridge class registered by importing ``megatron.bridge.models``.""" |
| 153 | + return sorted( |
| 154 | + ( |
| 155 | + bridge_class |
| 156 | + for bridge_class in _bridge_subclasses(MegatronModelBridge) |
| 157 | + if bridge_class.__module__.startswith("megatron.bridge.") |
| 158 | + ), |
| 159 | + key=lambda bridge_class: bridge_class.__name__, |
| 160 | + ) |
| 161 | + |
| 162 | + |
| 163 | +def _has_model_config_mapping(bridge_class: type[MegatronModelBridge]) -> bool: |
| 164 | + return ( |
| 165 | + bridge_class.hf_config_to_model_config_kwargs is not MegatronModelBridge.hf_config_to_model_config_kwargs |
| 166 | + or bridge_class.hf_config_to_model_config is not MegatronModelBridge.hf_config_to_model_config |
| 167 | + ) |
| 168 | + |
| 169 | + |
| 170 | +def test_model_config_is_opt_in_per_family() -> None: |
| 171 | + assert MegatronModelBridge.MODEL_CONFIG_CLASS is None |
144 | 172 | assert MegatronModelBridge.USE_MODEL_CONFIG_FOR_CONVERSION is False |
| 173 | + for bridge_class in (LlamaBridge, Qwen3Bridge): |
| 174 | + assert bridge_class.__dict__["MODEL_CONFIG_CLASS"] is BridgeGPTModelConfig |
| 175 | + assert MuseGlimmerBridge.__dict__["MODEL_CONFIG_CLASS"] is not None |
145 | 176 | assert LlamaBridge.USE_MODEL_CONFIG_FOR_CONVERSION is False |
146 | 177 | assert BridgeGPTModelConfig.transformer_config_class is TransformerConfig |
147 | 178 | assert not hasattr(MegatronModelBridge, "TRANSFORMER_CONFIG_CLASS") |
148 | 179 |
|
149 | 180 |
|
150 | | -def test_bridge_can_explicitly_disable_model_config() -> None: |
151 | | - class UnsupportedBridge(LlamaBridge): |
152 | | - MODEL_CONFIG_CLASS = None |
| 181 | +def test_bridge_subclass_inherits_model_config_opt_in() -> None: |
| 182 | + class DerivedBridge(LlamaBridge): |
| 183 | + pass |
| 184 | + |
| 185 | + assert DerivedBridge.MODEL_CONFIG_CLASS is BridgeGPTModelConfig |
| 186 | + |
| 187 | + |
| 188 | +@pytest.mark.parametrize("bridge_class", _all_bridge_classes(), ids=lambda bridge_class: bridge_class.__name__) |
| 189 | +def test_bridge_can_explicitly_disable_model_config(bridge_class: type[MegatronModelBridge]) -> None: |
| 190 | + if bridge_class.MODEL_CONFIG_CLASS is None: |
| 191 | + # Unmigrated family: the builder path is rejected instead of returning generic defaults. |
| 192 | + assert not _has_model_config_mapping(bridge_class) |
| 193 | + assert bridge_class.USE_MODEL_CONFIG_FOR_CONVERSION is False |
| 194 | + with pytest.raises(ModelConfigNotSupportedError, match=f"{bridge_class.__name__}.*to_megatron_provider"): |
| 195 | + bridge_class().hf_config_to_model_config(object()) |
| 196 | + else: |
| 197 | + # Opted-in family: it must map its Hugging Face config for the builder path. |
| 198 | + assert _has_model_config_mapping(bridge_class) |
| 199 | + |
| 200 | + # A bridge that replaces hf_config_to_model_config() wholesale does not read MODEL_CONFIG_CLASS. |
| 201 | + if bridge_class.hf_config_to_model_config is MegatronModelBridge.hf_config_to_model_config: |
| 202 | + |
| 203 | + class UnsupportedBridge(bridge_class): |
| 204 | + MODEL_CONFIG_CLASS = None |
| 205 | + |
| 206 | + with pytest.raises(ModelConfigNotSupportedError, match="UnsupportedBridge.*to_megatron_provider"): |
| 207 | + UnsupportedBridge().hf_config_to_model_config(object()) |
| 208 | + |
| 209 | + |
| 210 | +def test_unmigrated_bridge_keeps_provider_without_warning() -> None: |
| 211 | + hf_config = Qwen2Config( |
| 212 | + num_hidden_layers=2, |
| 213 | + hidden_size=64, |
| 214 | + intermediate_size=128, |
| 215 | + num_attention_heads=4, |
| 216 | + num_key_value_heads=2, |
| 217 | + vocab_size=256, |
| 218 | + ) |
| 219 | + |
| 220 | + assert Qwen2Bridge.MODEL_CONFIG_CLASS is None |
| 221 | + with warnings.catch_warnings(): |
| 222 | + warnings.simplefilter("error", FutureWarning) |
| 223 | + provider = Qwen2Bridge().provider_bridge(Mock(config=hf_config)) |
153 | 224 |
|
154 | | - with pytest.raises(ModelConfigNotSupportedError, match="sets MODEL_CONFIG_CLASS to None"): |
155 | | - UnsupportedBridge().hf_config_to_model_config(object()) |
| 225 | + assert provider.position_embedding_type == "rope" |
| 226 | + assert provider.add_qkv_bias is True |
| 227 | + assert provider.add_bias_linear is False |
156 | 228 |
|
157 | 229 |
|
158 | 230 | @pytest.mark.skipif( |
|
0 commit comments