Skip to content

Commit 567f591

Browse files
refactor(model): add Qwen3 builder-backed model config with YaRN support (#6365)
Signed-off-by: Kamran Jafari <kjafarisadeg@nvidia.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ae22c0d commit 567f591

9 files changed

Lines changed: 393 additions & 91 deletions

File tree

‎docs/bridge-guide.md‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,13 @@ The provider pattern is especially useful when you need to:
106106
- Configure advanced features like MoE, activation recomputation, or mixed precision
107107
- Set up distributed training parameters
108108

109-
### Using Builder-backed Llama Configs
109+
### Using Builder-backed Model Configs
110110

111-
Llama also supports the builder-backed configuration path. This keeps model
112-
configuration as serializable data and leaves construction to Megatron Core's
113-
`GPTModelBuilder`. The provider API remains available for compatibility while
114-
other model families migrate. Calling the legacy provider API for a
111+
Model families are migrating to the builder-backed configuration path. It keeps
112+
model configuration as serializable data and leaves construction to the
113+
family's Megatron Core `ModelBuilder`, such as `GPTModelBuilder` or
114+
`HybridModelBuilder`. The provider API remains available for compatibility
115+
while other model families migrate. Calling the legacy provider API for a
115116
builder-backed family emits a deprecation warning.
116117

117118
```python
@@ -134,9 +135,10 @@ Use `load_weights=False` for random initialization. A bridge created with
134135
`from_hf_config()` has no weights, so it requires `load_weights=False` or an
135136
explicit `hf_path`.
136137

137-
Llama training recipes store the result of `get_model_config()` in
138-
`ConfigContainer.model`. The training setup recognizes `ModelConfig` and calls
139-
its `ModelBuilder` directly; it does not create a legacy model provider.
138+
Training recipes for builder-backed families store the result of
139+
`get_model_config()` in `ConfigContainer.model`. The training setup recognizes
140+
`ModelConfig` and calls its `ModelBuilder` directly; it does not create a
141+
legacy model provider.
140142

141143
## Check Supported Models
142144

‎src/megatron/bridge/models/gpt_provider.py‎

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,7 @@ class GPTModelProvider(TransformerConfig, ModelProviderMixin[MCoreGPTModel]):
148148
rotary_scaling_factor: Optional[float] = None
149149
seq_len_interpolation_factor: Optional[float] = None
150150

151-
# YARN (Yet Another RoPE extensioN) position embedding parameters
152-
# Used when position_embedding_type == "yarn"
153-
yarn_rotary_scaling_factor: Optional[float] = None
154-
yarn_original_max_position_embeddings: Optional[int] = None
155-
yarn_beta_fast: Optional[float] = None
156-
yarn_beta_slow: Optional[float] = None
157-
yarn_mscale: Optional[float] = None
158-
yarn_mscale_all_dim: Optional[float] = None
159-
yarn_correction_range_round_to_int: Optional[bool] = None
151+
# YaRN parameters (yarn_*) are declared on the base TransformerConfig.
160152

161153
seq_length: int = 1024
162154
attention_softmax_in_fp32: bool = False
@@ -405,37 +397,3 @@ def mtp_block_spec(config: "GPTModelProvider", vp_stage: Optional[int] = None) -
405397
return get_gpt_mtp_block_spec(config, spec, use_transformer_engine=True, vp_stage=vp_stage)
406398
else:
407399
return None
408-
409-
410-
def _patch_yarn_concentration_factor():
411-
"""Patch MCore _yarn_get_concentration_factor_from_config for None handling.
412-
413-
GPTModelProvider defines yarn_rotary_scaling_factor as Optional[float] = None,
414-
but MCore uses hasattr() which returns True for dataclass fields set to None.
415-
This causes a crash for non-YARN models. Use getattr + is not None instead.
416-
417-
TODO: Remove once upstream MCore merges the fix.
418-
"""
419-
try:
420-
import megatron.core.models.common.embeddings.yarn_rotary_pos_embedding as _yarn_mod
421-
import megatron.core.transformer.attention as _attn_mod
422-
423-
_get_factor = _yarn_mod._yarn_get_concentration_factor
424-
425-
def _fixed_from_config(config):
426-
yarn_scaling = getattr(config, "yarn_rotary_scaling_factor", None)
427-
if yarn_scaling is not None:
428-
return _get_factor(
429-
yarn_scaling,
430-
getattr(config, "yarn_mscale", None),
431-
getattr(config, "yarn_mscale_all_dim", None),
432-
)
433-
return 1.0
434-
435-
_yarn_mod._yarn_get_concentration_factor_from_config = _fixed_from_config
436-
_attn_mod._yarn_get_concentration_factor_from_config = _fixed_from_config
437-
except ImportError:
438-
pass
439-
440-
441-
_patch_yarn_concentration_factor()

‎src/megatron/bridge/models/qwen/qwen3_bridge.py‎

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing import Any
16+
1517
import torch
1618
from megatron.core.models.gpt.gpt_model import GPTModel
17-
from transformers import Qwen3ForCausalLM
19+
from transformers import PretrainedConfig, Qwen3ForCausalLM
1820

1921
from megatron.bridge.models.conversion.mapping_registry import MegatronMappingRegistry
2022
from megatron.bridge.models.conversion.model_bridge import MegatronModelBridge
@@ -37,22 +39,39 @@ class Qwen3Bridge(MegatronModelBridge):
3739
Example:
3840
>>> from megatron.bridge import AutoBridge
3941
>>> bridge = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B")
40-
>>> provider = bridge.to_megatron_provider()
42+
>>> model_config = bridge.get_model_config()
4143
"""
4244

43-
def provider_bridge(self, hf_pretrained):
44-
"""Convert HuggingFace Qwen3 config to GPTModelProvider."""
45-
provider = super().provider_bridge(hf_pretrained)
45+
USE_MODEL_CONFIG_FOR_CONVERSION = True
4646

47-
provider.normalization = "RMSNorm"
48-
provider.gated_linear_unit = True
49-
provider.add_bias_linear = False
50-
provider.add_qkv_bias = False # Qwen3 does NOT have QKV bias (unlike Qwen2)
51-
provider.hidden_dropout = 0.0
52-
provider.qk_layernorm = True # Qwen3 uses QK layernorm
53-
provider.autocast_dtype = torch.bfloat16
47+
def hf_config_to_model_config_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
48+
"""Convert a Hugging Face Qwen3 config to builder config kwargs."""
49+
config_kwargs = super().hf_config_to_model_config_kwargs(hf_config)
50+
config_kwargs.update(
51+
normalization="RMSNorm",
52+
gated_linear_unit=True,
53+
add_bias_linear=False,
54+
add_qkv_bias=False, # Qwen3 does NOT have QKV bias (unlike Qwen2)
55+
hidden_dropout=0.0,
56+
qk_layernorm=True, # Qwen3 uses QK layernorm
57+
autocast_dtype=torch.bfloat16,
58+
masked_softmax_fusion=True,
59+
rope_scaling=False,
60+
rope_scaling_factor=1.0,
61+
)
62+
# The shared mapping already selects "yarn" for YaRN-scaled checkpoints.
63+
config_kwargs.setdefault("position_embedding_type", "rope")
64+
if config_kwargs["position_embedding_type"] == "yarn":
65+
# Qwen3 YaRN configs usually omit these keys; apply the Hugging Face defaults
66+
# instead of passing None to Megatron Core.
67+
config_kwargs.setdefault("yarn_beta_fast", 32.0)
68+
config_kwargs.setdefault("yarn_beta_slow", 1.0)
69+
config_kwargs.setdefault("yarn_correction_range_round_to_int", True)
70+
return config_kwargs
5471

55-
return provider
72+
def hf_config_to_provider_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
73+
"""Adapt the canonical builder mapping to the deprecated provider path."""
74+
return self.hf_config_to_model_config_kwargs(hf_config)
5675

5776
def mapping_registry(self) -> MegatronMappingRegistry:
5877
"""Return the MegatronMappingRegistry for Qwen3 parameter conversion.

‎src/megatron/bridge/models/transformer_config.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,41 @@ def _uses_legacy_full_iteration(value: object) -> bool:
145145
setattr(config, padding_field, True)
146146

147147

148+
def _patch_yarn_concentration_factor() -> None:
149+
"""Patch MCore _yarn_get_concentration_factor_from_config for None handling.
150+
151+
TransformerConfig declares yarn_rotary_scaling_factor as ``float | None = None``,
152+
but MCore uses hasattr(), which returns True for dataclass fields set to None.
153+
This causes a crash for non-YaRN models. Use getattr + is not None instead.
154+
155+
TODO: Remove once upstream MCore merges the fix.
156+
"""
157+
try:
158+
import megatron.core.models.common.embeddings.yarn_rotary_pos_embedding as _yarn_mod
159+
import megatron.core.transformer.attention as _attn_mod
160+
161+
_get_factor = _yarn_mod._yarn_get_concentration_factor
162+
163+
def _fixed_from_config(config):
164+
yarn_scaling = getattr(config, "yarn_rotary_scaling_factor", None)
165+
if yarn_scaling is not None:
166+
return _get_factor(
167+
yarn_scaling,
168+
getattr(config, "yarn_mscale", None),
169+
getattr(config, "yarn_mscale_all_dim", None),
170+
)
171+
return 1.0
172+
173+
_yarn_mod._yarn_get_concentration_factor_from_config = _fixed_from_config
174+
_attn_mod._yarn_get_concentration_factor_from_config = _fixed_from_config
175+
except ImportError:
176+
pass
177+
178+
179+
# Applied at import so every config that declares the YaRN fields below is covered.
180+
_patch_yarn_concentration_factor()
181+
182+
148183
@dataclass
149184
class TransformerConfig(MCoreTransformerConfig):
150185
"""Megatron Core TransformerConfig with deferred post-init.
@@ -175,6 +210,17 @@ class TransformerConfig(MCoreTransformerConfig):
175210
gtp_weight_remat_size: int = field(init=False, default=1)
176211
expert_gtp_weight_remat_size: int = field(init=False, default=1)
177212

213+
# YaRN (Yet another RoPE extensioN) parameters, used when position_embedding_type == "yarn".
214+
# Megatron Core models read these from their transformer config, so builder-backed
215+
# configs declare them here rather than on the outer model config.
216+
yarn_rotary_scaling_factor: float | None = None
217+
yarn_original_max_position_embeddings: int | None = None
218+
yarn_beta_fast: float | None = None
219+
yarn_beta_slow: float | None = None
220+
yarn_mscale: float | None = None
221+
yarn_mscale_all_dim: float | None = None
222+
yarn_correction_range_round_to_int: bool | None = None
223+
178224
def __post_init__(self) -> None:
179225
"""Skip MCore post_init during initial construction.
180226

‎src/megatron/bridge/recipes/qwen/h100/qwen3.py‎

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def qwen3_600m_pretrain_1gpu_h100_bf16_config() -> ConfigContainer:
3737
cfg = _pretrain_common()
3838

3939
# Model config
40-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-0.6B").to_megatron_provider(load_weights=False)
40+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-0.6B").get_model_config()
4141

4242
# Tokenizer (--tokenizer-model)
4343
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-0.6B"
@@ -122,7 +122,7 @@ def qwen3_1p7b_pretrain_1gpu_h100_bf16_config() -> ConfigContainer:
122122
cfg = _pretrain_common()
123123

124124
# Model config
125-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B").to_megatron_provider(load_weights=False)
125+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B").get_model_config()
126126

127127
# Tokenizer
128128
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-1.7B"
@@ -207,7 +207,7 @@ def qwen3_4b_pretrain_2gpu_h100_bf16_config() -> ConfigContainer:
207207
cfg = _pretrain_common()
208208

209209
# Model config
210-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-4B").to_megatron_provider(load_weights=False)
210+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-4B").get_model_config()
211211

212212
# Tokenizer
213213
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-4B"
@@ -292,9 +292,7 @@ def qwen3_8b_pretrain_4gpu_h100_bf16_config() -> ConfigContainer:
292292
cfg = _pretrain_common()
293293

294294
# Model config
295-
cfg.model = AutoBridge.from_hf_pretrained(
296-
_QWEN3_8B_MODEL_ID, revision=_QWEN3_8B_MODEL_REVISION
297-
).to_megatron_provider(load_weights=False)
295+
cfg.model = AutoBridge.from_hf_pretrained(_QWEN3_8B_MODEL_ID, revision=_QWEN3_8B_MODEL_REVISION).get_model_config()
298296

299297
# Tokenizer
300298
cfg.tokenizer.tokenizer_model = _QWEN3_8B_MODEL_ID
@@ -435,7 +433,7 @@ def qwen3_14b_pretrain_8gpu_h100_bf16_config() -> ConfigContainer:
435433
cfg = _pretrain_common()
436434

437435
# Model config
438-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-14B").to_megatron_provider(load_weights=False)
436+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-14B").get_model_config()
439437

440438
# Tokenizer
441439
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-14B"
@@ -519,7 +517,7 @@ def qwen3_32b_pretrain_16gpu_h100_bf16_config() -> ConfigContainer:
519517
"""
520518
cfg = _pretrain_common()
521519

522-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-32B").to_megatron_provider(load_weights=False)
520+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-32B").get_model_config()
523521

524522
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-32B"
525523

@@ -597,7 +595,7 @@ def qwen3_600m_sft_1gpu_h100_bf16_config() -> ConfigContainer:
597595
cfg = _sft_common()
598596

599597
# Model config
600-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-0.6B").to_megatron_provider(load_weights=False)
598+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-0.6B").get_model_config()
601599

602600
# Tokenizer
603601
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-0.6B"
@@ -741,7 +739,7 @@ def qwen3_600m_sft_8gpu_h100_bf16_yarn_128k_config() -> ConfigContainer:
741739
hf_path = "Qwen/Qwen3-0.6B"
742740

743741
# Model and tokenizer use the same HF checkpoint so the chat template matches the dataset.
744-
cfg.model = AutoBridge.from_hf_pretrained(hf_path).to_megatron_provider(load_weights=False)
742+
cfg.model = AutoBridge.from_hf_pretrained(hf_path).get_model_config()
745743
cfg.tokenizer.tokenizer_model = hf_path
746744

747745
# 128K context parallelism.
@@ -817,7 +815,7 @@ def qwen3_1p7b_sft_1gpu_h100_bf16_config() -> ConfigContainer:
817815
cfg = _sft_common()
818816

819817
# Model config
820-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B").to_megatron_provider(load_weights=False)
818+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B").get_model_config()
821819

822820
# Tokenizer
823821
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-1.7B"
@@ -909,7 +907,7 @@ def qwen3_4b_sft_2gpu_h100_bf16_config() -> ConfigContainer:
909907
cfg = _sft_common()
910908

911909
# Model config
912-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-4B").to_megatron_provider(load_weights=False)
910+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-4B").get_model_config()
913911

914912
# Tokenizer
915913
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-4B"
@@ -1001,9 +999,7 @@ def qwen3_8b_sft_4gpu_h100_bf16_config() -> ConfigContainer:
1001999
cfg = _sft_common()
10021000

10031001
# Model config
1004-
cfg.model = AutoBridge.from_hf_pretrained(
1005-
_QWEN3_8B_MODEL_ID, revision=_QWEN3_8B_MODEL_REVISION
1006-
).to_megatron_provider(load_weights=False)
1002+
cfg.model = AutoBridge.from_hf_pretrained(_QWEN3_8B_MODEL_ID, revision=_QWEN3_8B_MODEL_REVISION).get_model_config()
10071003

10081004
# Tokenizer
10091005
cfg.tokenizer.tokenizer_model = _QWEN3_8B_MODEL_ID
@@ -1149,7 +1145,7 @@ def qwen3_14b_sft_8gpu_h100_bf16_config() -> ConfigContainer:
11491145
cfg = _sft_common()
11501146

11511147
# Model config
1152-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-14B").to_megatron_provider(load_weights=False)
1148+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-14B").get_model_config()
11531149

11541150
# Tokenizer
11551151
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-14B"
@@ -1242,7 +1238,7 @@ def qwen3_32b_sft_16gpu_h100_bf16_config() -> ConfigContainer:
12421238
cfg = _sft_common()
12431239

12441240
# Model config
1245-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-32B").to_megatron_provider(load_weights=False)
1241+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-32B").get_model_config()
12461242

12471243
# Tokenizer
12481244
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-32B"
@@ -1344,7 +1340,7 @@ def qwen3_600m_peft_1gpu_h100_bf16_config(peft_scheme: str | PEFT = "lora") -> C
13441340
cfg = _peft_common()
13451341

13461342
# Model config
1347-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-0.6B").to_megatron_provider(load_weights=False)
1343+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-0.6B").get_model_config()
13481344

13491345
# Tokenizer
13501346
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-0.6B"
@@ -1442,7 +1438,7 @@ def qwen3_1p7b_peft_1gpu_h100_bf16_config(peft_scheme: str | PEFT = "lora") -> C
14421438
cfg = _peft_common()
14431439

14441440
# Model config
1445-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B").to_megatron_provider(load_weights=False)
1441+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-1.7B").get_model_config()
14461442

14471443
# Tokenizer
14481444
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-1.7B"
@@ -1540,7 +1536,7 @@ def qwen3_4b_peft_1gpu_h100_bf16_config(peft_scheme: str | PEFT = "lora") -> Con
15401536
cfg = _peft_common()
15411537

15421538
# Model config
1543-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-4B").to_megatron_provider(load_weights=False)
1539+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-4B").get_model_config()
15441540

15451541
# Tokenizer
15461542
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-4B"
@@ -1638,9 +1634,7 @@ def qwen3_8b_peft_1gpu_h100_bf16_config(peft_scheme: str | PEFT = "lora") -> Con
16381634
cfg = _peft_common()
16391635

16401636
# Model config
1641-
cfg.model = AutoBridge.from_hf_pretrained(
1642-
_QWEN3_8B_MODEL_ID, revision=_QWEN3_8B_MODEL_REVISION
1643-
).to_megatron_provider(load_weights=False)
1637+
cfg.model = AutoBridge.from_hf_pretrained(_QWEN3_8B_MODEL_ID, revision=_QWEN3_8B_MODEL_REVISION).get_model_config()
16441638

16451639
# Tokenizer
16461640
cfg.tokenizer.tokenizer_model = _QWEN3_8B_MODEL_ID
@@ -1765,7 +1759,7 @@ def qwen3_14b_peft_1gpu_h100_bf16_config(peft_scheme: str | PEFT = "lora") -> Co
17651759
cfg = _peft_common()
17661760

17671761
# Model config
1768-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-14B").to_megatron_provider(load_weights=False)
1762+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-14B").get_model_config()
17691763

17701764
# Tokenizer
17711765
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-14B"
@@ -1864,7 +1858,7 @@ def qwen3_32b_peft_1gpu_h100_bf16_config(peft_scheme: str | PEFT = "lora") -> Co
18641858
cfg = _peft_common()
18651859

18661860
# Model config
1867-
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-32B").to_megatron_provider(load_weights=False)
1861+
cfg.model = AutoBridge.from_hf_pretrained("Qwen/Qwen3-32B").get_model_config()
18681862

18691863
# Tokenizer
18701864
cfg.tokenizer.tokenizer_model = "Qwen/Qwen3-32B"

0 commit comments

Comments
 (0)