Skip to content

Commit a13a9f1

Browse files
authored
Merge branch 'main' into chcui/nvbug-4/fix-lora-plus-warmup
2 parents 834da0f + 5a87644 commit a13a9f1

17 files changed

Lines changed: 659 additions & 174 deletions

File tree

‎.github/workflows/cicd-main.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ jobs:
13881388
notify-nightly-failure:
13891389
name: Notify scheduled CI failure
13901390
runs-on: ubuntu-latest
1391-
environment: main
1391+
environment: public
13921392
needs:
13931393
- ephemeral-runner-routing
13941394
- pre-flight

‎src/megatron/bridge/models/bailing/bailing_moe2_bridge.py‎

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@
3333

3434
import logging
3535
from functools import partial
36+
from typing import Any
3637

3738
from megatron.core.models.gpt.gpt_layer_specs import get_gpt_decoder_block_spec
3839
from megatron.core.models.gpt.gpt_model import GPTModel
39-
from transformers import AutoConfig, AutoModelForCausalLM
40+
from transformers import AutoConfig, AutoModelForCausalLM, PretrainedConfig
4041

4142
from megatron.bridge.models.bailing.configuration_bailing_moe_v2 import BailingMoeV2Config
4243
from megatron.bridge.models.bailing.modeling_bailing_moe_v2 import BailingMoeV2ForCausalLM
@@ -47,8 +48,7 @@
4748
ConcatenatedQKVMapping,
4849
GatedMLPMapping,
4950
)
50-
from megatron.bridge.models.gpt_provider import GPTModelProvider
51-
from megatron.bridge.models.hf_pretrained.causal_lm import PreTrainedCausalLM
51+
from megatron.bridge.models.gpt.model_config import BridgeGPTModelConfig
5252

5353

5454
# Register the Bailing MoE V2 config and model classes with transformers Auto classes.
@@ -76,38 +76,52 @@ class BailingMoeV2Bridge(MegatronModelBridge):
7676
Example:
7777
>>> from megatron.bridge import AutoBridge
7878
>>> bridge = AutoBridge.from_hf_pretrained("inclusionAI/Ling-mini-2.0")
79-
>>> provider = bridge.to_megatron_provider()
79+
>>> model_config = bridge.get_model_config()
8080
"""
8181

82-
def provider_bridge(self, hf_pretrained: PreTrainedCausalLM) -> GPTModelProvider:
83-
provider = super().provider_bridge(hf_pretrained)
84-
hf_config = hf_pretrained.config
85-
86-
provider.transformer_layer_spec = partial(get_gpt_decoder_block_spec, use_transformer_engine=HAVE_TE)
87-
provider.normalization = "RMSNorm"
88-
provider.gated_linear_unit = True
89-
provider.add_bias_linear = False
90-
provider.share_embeddings_and_output_weights = False
91-
provider.qk_layernorm = True
92-
provider.add_qkv_bias = getattr(hf_config, "use_qkv_bias", False)
93-
94-
provider.moe_grouped_gemm = True
95-
provider.moe_router_pre_softmax = True
96-
provider.moe_router_load_balancing_type = "none"
97-
provider.moe_router_score_function = "sigmoid"
98-
provider.moe_router_enable_expert_bias = True
99-
provider.moe_router_dtype = "fp32"
100-
provider.moe_token_dispatcher_type = "alltoall"
101-
provider.moe_permute_fusion = True
102-
103-
provider.hidden_dropout = 0.0
104-
105-
provider.moe_layer_freq = [0] * hf_config.first_k_dense_replace + [1] * (
106-
hf_config.num_hidden_layers - hf_config.first_k_dense_replace
82+
MODEL_CONFIG_CLASS = BridgeGPTModelConfig
83+
USE_MODEL_CONFIG_FOR_CONVERSION = True
84+
85+
def hf_config_to_model_config_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
86+
"""Convert a Hugging Face Ling MoE V2 config to builder config kwargs.
87+
88+
The builder's default MoE layer spec already uses the GPT decoder block
89+
spec, which honors the dense-first ``moe_layer_freq``.
90+
"""
91+
config_kwargs = super().hf_config_to_model_config_kwargs(hf_config)
92+
config_kwargs.update(
93+
normalization="RMSNorm",
94+
gated_linear_unit=True,
95+
add_bias_linear=False,
96+
share_embeddings_and_output_weights=False,
97+
qk_layernorm=True,
98+
add_qkv_bias=getattr(hf_config, "use_qkv_bias", False),
99+
masked_softmax_fusion=True,
100+
rope_scaling=False,
101+
rope_scaling_factor=1.0,
102+
moe_grouped_gemm=True,
103+
moe_router_pre_softmax=True,
104+
moe_router_load_balancing_type="none",
105+
moe_router_score_function="sigmoid",
106+
moe_router_enable_expert_bias=True,
107+
moe_router_dtype="fp32",
108+
moe_token_dispatcher_type="alltoall",
109+
moe_permute_fusion=True,
110+
hidden_dropout=0.0,
111+
moe_layer_freq=[0] * hf_config.first_k_dense_replace
112+
+ [1] * (hf_config.num_hidden_layers - hf_config.first_k_dense_replace),
113+
moe_shared_expert_intermediate_size=hf_config.moe_intermediate_size,
107114
)
108-
provider.moe_shared_expert_intermediate_size = hf_config.moe_intermediate_size
109-
110-
return provider
115+
config_kwargs.setdefault("position_embedding_type", "rope")
116+
return config_kwargs
117+
118+
def hf_config_to_provider_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
119+
"""Adapt the canonical builder mapping to the deprecated provider path."""
120+
provider_kwargs = self.hf_config_to_model_config_kwargs(hf_config)
121+
# The provider's default layer spec builds homogeneous layers; the decoder
122+
# block spec is needed for the dense-first MoE layer pattern.
123+
provider_kwargs["transformer_layer_spec"] = partial(get_gpt_decoder_block_spec, use_transformer_engine=HAVE_TE)
124+
return provider_kwargs
111125

112126
def mapping_registry(self) -> MegatronMappingRegistry:
113127
mapping_list = []

‎src/megatron/bridge/models/exaone/exaone4/exaone4_bridge.py‎

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@
3434
- EXAONE bridge: QK layernorm mapping pattern
3535
"""
3636

37+
import logging
38+
from typing import Any
39+
3740
import torch
3841
import torch.nn.functional as F
3942
from megatron.core.models.gpt.gpt_model import GPTModel
43+
from transformers import PretrainedConfig
4044

4145
from megatron.bridge.models.conversion.mapping_registry import MegatronMappingRegistry
4246
from megatron.bridge.models.conversion.model_bridge import MegatronModelBridge
@@ -46,10 +50,12 @@
4650
QKVMapping,
4751
)
4852
from megatron.bridge.models.exaone.exaone4.exaone4_provider import exaone4_layer_spec
53+
from megatron.bridge.models.gpt.model_config import BridgeGPTModelConfig
4954
from megatron.bridge.models.gpt_provider import GPTModelProvider
50-
from megatron.bridge.models.hf_pretrained.causal_lm import PreTrainedCausalLM
5155

5256

57+
logger = logging.getLogger(__name__)
58+
5359
# Register custom EXAONE modules for AutoMapping weight distribution
5460
# TERowParallelLinearLayerNorm is a row-parallel linear with post-layernorm
5561
# (same pattern as other Post-LN architectures)
@@ -82,51 +88,64 @@ class Exaone4Bridge(MegatronModelBridge):
8288
... "LGAI-EXAONE/EXAONE-4.0-1.2B",
8389
... trust_remote_code=True,
8490
... )
85-
>>> provider = bridge.to_megatron_provider()
91+
>>> model_config = bridge.get_model_config()
8692
"""
8793

88-
def provider_bridge(self, hf_pretrained: PreTrainedCausalLM) -> GPTModelProvider:
89-
"""Convert HuggingFace EXAONE 4.0 config to Megatron GPTModelProvider.
94+
MODEL_CONFIG_CLASS = BridgeGPTModelConfig
95+
USE_MODEL_CONFIG_FOR_CONVERSION = True
9096

91-
Maps HF config fields to Megatron TransformerConfig parameters and sets
92-
EXAONE-specific options including Post-LN, QK norm, and RoPE scaling.
97+
# Megatron Core applies llama3 RoPE scaling with these fixed values; only the factor is configurable.
98+
_MCORE_LLAMA3_ROPE_SCALING = {
99+
"low_freq_factor": 1.0,
100+
"high_freq_factor": 4.0,
101+
"original_max_position_embeddings": 8192,
102+
}
93103

94-
Args:
95-
hf_pretrained: HuggingFace PreTrainedCausalLM containing the EXAONE config
104+
def hf_config_to_model_config_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
105+
"""Convert a Hugging Face EXAONE 4.0 config to builder config kwargs.
96106
97-
Returns:
98-
GPTModelProvider configured for EXAONE 4.0 architecture
107+
Sets EXAONE-specific options including Post-LN, QK norm, and RoPE scaling.
108+
Size-dependent fields are populated by the shared HF config mapping.
99109
"""
100-
hf_config = hf_pretrained.config
101-
102-
provider = super().provider_bridge(hf_pretrained)
103-
104-
# EXAONE-specific architecture settings. Size-dependent fields are
105-
# populated by the shared HF config mapping in the base bridge.
106-
provider.normalization = "RMSNorm"
107-
provider.activation_func = F.silu
108-
provider.gated_linear_unit = True
109-
provider.position_embedding_type = "rope"
110-
provider.add_bias_linear = False
111-
provider.add_qkv_bias = False
112-
provider.qk_layernorm = True
113-
provider.hidden_dropout = 0.0
114-
provider.attention_dropout = 0.0
115-
provider.transformer_layer_spec = exaone4_layer_spec
116-
provider.autocast_dtype = torch.bfloat16
110+
config_kwargs = super().hf_config_to_model_config_kwargs(hf_config)
111+
config_kwargs.update(
112+
normalization="RMSNorm",
113+
activation_func=F.silu,
114+
gated_linear_unit=True,
115+
position_embedding_type="rope",
116+
add_bias_linear=False,
117+
add_qkv_bias=False,
118+
qk_layernorm=True,
119+
hidden_dropout=0.0,
120+
attention_dropout=0.0,
121+
transformer_layer_spec=exaone4_layer_spec,
122+
autocast_dtype=torch.bfloat16,
123+
masked_softmax_fusion=True,
124+
rope_scaling=False,
125+
rope_scaling_factor=1.0,
126+
)
117127

118128
# RoPE scaling for EXAONE 4.0 (llama3-style)
119129
hf_rope_scaling = getattr(hf_config, "rope_scaling", None)
120130
if hf_rope_scaling is not None and hf_rope_scaling.get("rope_type") == "llama3":
121-
provider.rope_scaling = True
122-
provider.rope_scaling_factor = hf_rope_scaling.get("factor", 16.0)
123-
provider.rope_scaling_low_freq_factor = hf_rope_scaling.get("low_freq_factor", 1.0)
124-
provider.rope_scaling_high_freq_factor = hf_rope_scaling.get("high_freq_factor", 4.0)
125-
provider.rope_scaling_original_max_position_embeddings = hf_rope_scaling.get(
126-
"original_max_position_embeddings", 8192
127-
)
131+
config_kwargs.update(rope_scaling=True, rope_scaling_factor=hf_rope_scaling.get("factor", 16.0))
132+
unsupported = {
133+
key: hf_rope_scaling[key]
134+
for key, value in self._MCORE_LLAMA3_ROPE_SCALING.items()
135+
if key in hf_rope_scaling and hf_rope_scaling[key] != value
136+
}
137+
if unsupported:
138+
logger.warning(
139+
"EXAONE 4.0 llama3 RoPE scaling values %s differ from the fixed values Megatron Core applies (%s).",
140+
unsupported,
141+
self._MCORE_LLAMA3_ROPE_SCALING,
142+
)
143+
144+
return config_kwargs
128145

129-
return provider
146+
def hf_config_to_provider_kwargs(self, hf_config: PretrainedConfig) -> dict[str, Any]:
147+
"""Adapt the canonical builder mapping to the deprecated provider path."""
148+
return self.hf_config_to_model_config_kwargs(hf_config)
130149

131150
@classmethod
132151
def megatron_to_hf_config(cls, provider: GPTModelProvider) -> dict:
@@ -149,9 +168,11 @@ def megatron_to_hf_config(cls, provider: GPTModelProvider) -> dict:
149168
hf_config["rope_scaling"] = {
150169
"rope_type": "llama3",
151170
"factor": provider.rope_scaling_factor,
152-
"low_freq_factor": provider.rope_scaling_low_freq_factor,
153-
"high_freq_factor": provider.rope_scaling_high_freq_factor,
154-
"original_max_position_embeddings": provider.rope_scaling_original_max_position_embeddings,
171+
# Megatron Core's fixed llama3 values, unless a caller set them explicitly.
172+
**{
173+
key: getattr(provider, f"rope_scaling_{key}", value)
174+
for key, value in cls._MCORE_LLAMA3_ROPE_SCALING.items()
175+
},
155176
}
156177

157178
return hf_config

‎src/megatron/bridge/models/hf_pretrained/state.py‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,13 +196,26 @@ def __init__(self, source: Dict[str, torch.Tensor] | "StateSource"):
196196
raise TypeError(f"StateDict source must be a dict or a StateSource, got {type(source)}")
197197

198198
self.source = source
199+
self._key_set: set[str] | None = None
200+
self._key_set_source: List[str] | None = None
199201

200202
def _get_all_keys(self) -> List[str]:
201203
"""
202204
Get all available tensor keys from the underlying source.
203205
"""
204206
return self.source.get_all_keys()
205207

208+
def _get_key_set(self) -> set[str]:
209+
# Membership checks run once per tensor during conversion; a list scan made
210+
# them quadratic in the number of checkpoint keys. Sources return a cached
211+
# list, so the set is rebuilt only if they hand back a different one (e.g.
212+
# while a bridge temporarily wraps ``get_all_keys`` with virtual keys).
213+
keys = self._get_all_keys()
214+
if self._key_set_source is not keys:
215+
self._key_set = set(keys)
216+
self._key_set_source = keys
217+
return self._key_set
218+
206219
def _load_tensors(self, keys_to_load: List[str]) -> Dict[str, torch.Tensor]:
207220
"""
208221
Load specified tensors from the underlying source.
@@ -221,7 +234,7 @@ def _match_keys(self, pattern: Union[str, Pattern]) -> List[str]:
221234
return [k for k in all_keys if fnmatch.fnmatch(k, pattern)]
222235
else:
223236
# Exact match
224-
return [pattern] if pattern in all_keys else []
237+
return [pattern] if pattern in self._get_key_set() else []
225238

226239
@overload
227240
def __getitem__(self, key: str) -> Union[torch.Tensor, Dict[str, torch.Tensor]]: ...
@@ -299,11 +312,11 @@ def __getitem__(self, key: Union[str, List[str], Pattern]) -> Union[torch.Tensor
299312
raise KeyError(f"No keys match pattern: {key}")
300313
return self._load_tensors(matched_keys)
301314
else:
302-
if key not in self._get_all_keys():
315+
if key not in self._get_key_set():
303316
raise KeyError(f"Key not found: {key}")
304317
return self._load_tensors([key])[key]
305318
elif isinstance(key, list):
306-
all_keys_set = set(self._get_all_keys())
319+
all_keys_set = self._get_key_set()
307320
missing_keys = [k for k in key if k not in all_keys_set]
308321
if missing_keys:
309322
raise KeyError(f"Keys not found: {missing_keys}")
@@ -334,7 +347,7 @@ def keys(self) -> List[str]:
334347

335348
def __contains__(self, key: str) -> bool:
336349
"""Check if a key exists in the state dict."""
337-
return key in self._get_all_keys()
350+
return key in self._get_key_set()
338351

339352
def __repr__(self) -> str:
340353
"""String representation."""
@@ -350,7 +363,7 @@ def get(self, key: str, default=None) -> Optional[torch.Tensor]:
350363
Returns `default` if the key is not found.
351364
Note: This method is for single key lookup and does not support patterns.
352365
"""
353-
if key in self._get_all_keys():
366+
if key in self._get_key_set():
354367
return self._load_tensors([key])[key]
355368
return default
356369

@@ -500,6 +513,7 @@ def __init__(
500513
# DeepSeek-V4-Pro). Both can be disabled through environment variables.
501514
self._small_tensor_cache: Dict[str, torch.Tensor] = {}
502515
self._open_handles: Dict[str, Any] = {}
516+
self._open_handle_keys: dict[str, set[str]] = {}
503517
self._cache_small = os.environ.get("MBRIDGE_STATE_CACHE_SMALL_TENSORS", "1") == "1"
504518
self._reuse_handles = os.environ.get("MBRIDGE_STATE_REUSE_HANDLES", "1") == "1"
505519
self._small_numel = int(os.environ.get("MBRIDGE_STATE_SMALL_TENSOR_NUMEL", str(16 * 1024 * 1024)))
@@ -687,6 +701,17 @@ def _get_handle(self, file_path: Path):
687701
self._open_handles[key] = handle
688702
return handle
689703

704+
def _get_handle_keys(self, file_path: Path, handle: Any) -> set[str]:
705+
"""Return the key set of ``handle``, cached alongside reused handles."""
706+
if not self._reuse_handles:
707+
return set(handle.keys())
708+
key = str(file_path)
709+
keys = self._open_handle_keys.get(key)
710+
if keys is None:
711+
keys = set(handle.keys())
712+
self._open_handle_keys[key] = keys
713+
return keys
714+
690715
def _maybe_cache(self, key: str, tensor: torch.Tensor) -> None:
691716
# Keep a private copy: callers may modify the tensors they get back in place.
692717
if self._cache_small and tensor.numel() <= self._small_numel:
@@ -695,6 +720,7 @@ def _maybe_cache(self, key: str, tensor: torch.Tensor) -> None:
695720
def _invalidate_handle(self, file_path: Path) -> None:
696721
"""Drop a reused handle so the next access re-reads the shard header (e.g. after a retry)."""
697722
self._open_handles.pop(str(file_path), None)
723+
self._open_handle_keys.pop(str(file_path), None)
698724

699725
def load_tensors(self, keys_to_load: List[str]) -> Dict[str, torch.Tensor]:
700726
if not keys_to_load:
@@ -744,7 +770,7 @@ def load_tensors(self, keys_to_load: List[str]) -> Dict[str, torch.Tensor]:
744770
for attempt in range(max_retries):
745771
f = self._get_handle(file_path)
746772
if True: # the handle may be shared across calls; do not close it here
747-
file_keys = set(f.keys())
773+
file_keys = self._get_handle_keys(file_path, f)
748774
for key in keys_in_file:
749775
source_key = (self.key_prefix or "") + key
750776
if source_key in file_keys and key not in loaded_tensors:

‎src/megatron/bridge/recipes/exaone/h100/exaone4.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def _set_optimizer_precision(cfg: ConfigContainer) -> None:
3535

3636

3737
def _apply_exaone4_common(cfg: ConfigContainer) -> None:
38-
cfg.model = AutoBridge.from_hf_pretrained(_HF_PATH).to_megatron_provider(load_weights=False)
38+
cfg.model = AutoBridge.from_hf_pretrained(_HF_PATH).get_model_config()
3939
cfg.tokenizer.tokenizer_model = _HF_PATH
4040

4141
cfg.model.tensor_model_parallel_size = 1

‎src/megatron/bridge/recipes/qwen/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
qwen35_text_9b_pretrain_8gpu_gb200_bf16_deterministic_config,
2424
qwen35_text_35b_a3b_pretrain_8gpu_gb200_bf16_config,
2525
qwen35_text_35b_a3b_sft_8gpu_gb200_bf16_dynamic_cp_config,
26+
qwen35_text_35b_a3b_sft_long_context_16gpu_gb200_bf16_config,
2627
qwen35_text_35b_a3b_sft_long_context_16gpu_gb200_fp8mx_config,
2728
)
2829

@@ -182,5 +183,6 @@
182183
"qwen35_text_35b_a3b_pretrain_config",
183184
"qwen35_text_35b_a3b_pretrain_8gpu_gb200_bf16_config",
184185
"qwen35_text_35b_a3b_sft_8gpu_gb200_bf16_dynamic_cp_config",
186+
"qwen35_text_35b_a3b_sft_long_context_16gpu_gb200_bf16_config",
185187
"qwen35_text_35b_a3b_sft_long_context_16gpu_gb200_fp8mx_config",
186188
]

0 commit comments

Comments
 (0)