Skip to content

Commit d3165d6

Browse files
committed
fix(training): correct hybrid FLOP accounting
Signed-off-by: Philip Petrakian <ppetrakian@nvidia.com>
1 parent 46fb300 commit d3165d6

5 files changed

Lines changed: 311 additions & 12 deletions

File tree

src/megatron/bridge/models/hybrid/hybrid_provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class HybridModelProvider(TransformerConfig, ModelProviderMixin[MCoreHybridModel
105105
params_dtype: torch.dtype = torch.bfloat16
106106
fp16: bool = False
107107
bf16: bool = True
108+
is_hybrid_model: bool = True
108109
num_layers: int | None = None
109110
mamba_num_groups: int = 8
110111
num_attention_heads: int = 1

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

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,20 @@ def calculate_layer_counts():
142142
"""Calculate the number of attention, Mamba, MLP, MoE, and GDN layers."""
143143
if hasattr(cfg.model, "hybrid_layer_pattern") and cfg.model.hybrid_layer_pattern:
144144
counts = {"M": 0, "G": 0, "*": 0, "-": 0, "E": 0}
145+
try:
146+
hybrid_allocation = importlib.import_module("megatron.core.models.hybrid.hybrid_layer_allocation")
147+
layer_counts = hybrid_allocation.get_hybrid_layer_counts(cfg.model.hybrid_layer_pattern)
148+
symbols = hybrid_allocation.Symbols
149+
return (
150+
layer_counts[symbols.ATTENTION],
151+
layer_counts[symbols.MAMBA],
152+
layer_counts[symbols.MLP],
153+
layer_counts[symbols.MOE],
154+
layer_counts[symbols.GDN],
155+
)
156+
except (AttributeError, ImportError, KeyError, ModuleNotFoundError):
157+
pass
158+
145159
try:
146160
parse_hybrid_pattern = importlib.import_module(
147161
"megatron.core.ssm.mamba_hybrid_layer_allocation"
@@ -155,7 +169,7 @@ def calculate_layer_counts():
155169
for layer_type in parsed.mtp_pattern:
156170
if layer_type in counts:
157171
counts[layer_type] += parsed.mtp_num_depths
158-
except (ImportError, ModuleNotFoundError):
172+
except (AttributeError, ImportError, ModuleNotFoundError):
159173
for layer_type in cfg.model.hybrid_layer_pattern:
160174
if layer_type in counts:
161175
counts[layer_type] += 1
@@ -206,19 +220,81 @@ def attn_layer_flops(
206220
num_heads,
207221
gqa_groups=8,
208222
kv_channels=None,
223+
core_seq_factor=None,
209224
):
210225
"""Calculate FLOPs for an attention layer."""
211226
p = (kv_channels * num_heads / hidden_size) if kv_channels else 1
212227
g = gqa_groups
228+
core_seq_factor = seq_len if core_seq_factor is None else core_seq_factor
213229
return (
214-
4
215-
* batch_size
216-
* seq_len
217-
* hidden_size
218-
* p
219-
* (hidden_size + (hidden_size * (g / num_heads)) + (seq_len / 2))
230+
4 * batch_size * seq_len * hidden_size * p * (hidden_size + (hidden_size * (g / num_heads)))
231+
+ 2 * batch_size * seq_len * hidden_size * p * core_seq_factor
220232
)
221233

234+
def calculate_swa_context():
235+
"""Calculate the average causal sliding-window context length."""
236+
window_size = getattr(cfg.model, "window_size", None)
237+
if window_size is None:
238+
return None
239+
if isinstance(window_size, (list, tuple)):
240+
effective_window = window_size[0] + window_size[1] + 1
241+
else:
242+
effective_window = window_size
243+
244+
if effective_window < effective_seq_length:
245+
return effective_window - effective_window * (effective_window - 1) / (2 * effective_seq_length)
246+
return core_attn_seq_factor / 2
247+
248+
def is_swa_layer(layer_number: int) -> bool:
249+
"""Return whether the 1-indexed physical layer uses sliding-window attention."""
250+
window_size = getattr(cfg.model, "window_size", None)
251+
if window_size is None:
252+
return False
253+
window_attn_skip_freq = getattr(cfg.model, "window_attn_skip_freq", None)
254+
if window_attn_skip_freq is None:
255+
return True
256+
if isinstance(window_attn_skip_freq, int):
257+
return layer_number % window_attn_skip_freq != 0
258+
if isinstance(window_attn_skip_freq, list):
259+
return layer_number <= len(window_attn_skip_freq) and bool(window_attn_skip_freq[layer_number - 1])
260+
return False
261+
262+
def count_hybrid_swa_attention_layers(num_attn_layers: int) -> int:
263+
"""Count SWA attention symbols in the main physical hybrid pattern."""
264+
hybrid_pattern = getattr(cfg.model, "hybrid_layer_pattern", None)
265+
if not hybrid_pattern or getattr(cfg.model, "window_size", None) is None:
266+
return 0
267+
try:
268+
hybrid_allocation = importlib.import_module("megatron.core.models.hybrid.hybrid_layer_allocation")
269+
parsed = hybrid_allocation.parse_hybrid_pattern(hybrid_pattern)
270+
main_pattern = parsed.main_pattern or ""
271+
attention_symbol = hybrid_allocation.Symbols.ATTENTION
272+
pipe_symbol = hybrid_allocation.Symbols.PIPE
273+
except (AttributeError, ImportError, ModuleNotFoundError):
274+
try:
275+
parse_hybrid_pattern = importlib.import_module(
276+
"megatron.core.ssm.mamba_hybrid_layer_allocation"
277+
).parse_hybrid_pattern
278+
parsed = parse_hybrid_pattern(hybrid_pattern)
279+
main_pattern = parsed.main_pattern or ""
280+
except (AttributeError, ImportError, ModuleNotFoundError):
281+
main_pattern = hybrid_pattern.split("/", 1)[0]
282+
attention_symbol = "*"
283+
pipe_symbol = "|"
284+
285+
num_swa_layers = 0
286+
num_main_attn_layers = 0
287+
for layer_number, layer_type in enumerate(main_pattern.replace(pipe_symbol, ""), start=1):
288+
if layer_type != attention_symbol:
289+
continue
290+
num_main_attn_layers += 1
291+
if is_swa_layer(layer_number):
292+
num_swa_layers += 1
293+
294+
# MTP attention layers are counted in num_attn_layers, but they do not
295+
# correspond to main-pattern layer numbers for window_attn_skip_freq.
296+
return min(num_swa_layers, num_attn_layers, num_main_attn_layers)
297+
222298
def mamba_layer_flops(
223299
batch_size,
224300
seq_len,
@@ -296,18 +372,38 @@ def hybrid_flops(
296372
gdn_conv_kernel_dim=4,
297373
vocab_size=256000,
298374
mtp_num_layers=0,
375+
core_seq_factor=None,
376+
num_swa_attn_layers=0,
377+
swa_context=None,
299378
):
300379
"""Calculate total FLOPs for the hybrid model."""
301-
flops_fwd = (
302-
num_attn_layers
303-
* attn_layer_flops(
380+
num_full_attn_layers = num_attn_layers - num_swa_attn_layers
381+
full_attn_flops = num_full_attn_layers * attn_layer_flops(
382+
batch_size,
383+
seq_len,
384+
hidden_size,
385+
num_attn_heads,
386+
gqa_groups,
387+
kv_channels,
388+
core_seq_factor=core_seq_factor,
389+
)
390+
swa_attn_flops = 0
391+
if num_swa_attn_layers > 0:
392+
if swa_context is None:
393+
raise ValueError("swa_context must be set when num_swa_attn_layers > 0")
394+
swa_attn_flops = num_swa_attn_layers * attn_layer_flops(
304395
batch_size,
305396
seq_len,
306397
hidden_size,
307398
num_attn_heads,
308399
gqa_groups,
309400
kv_channels,
401+
core_seq_factor=2 * swa_context,
310402
)
403+
404+
flops_fwd = (
405+
full_attn_flops
406+
+ swa_attn_flops
311407
+ num_mlp_layers * mlp_layer_flops(batch_size, seq_len, hidden_size, mlp_expansion, swiglu)
312408
+ num_mamba_layers
313409
* mamba_layer_flops(
@@ -781,8 +877,9 @@ def _compute_vit_flops():
781877
patches_per_image = num_vision_patches / batch_size if batch_size > 0 else num_vision_patches
782878
return vit_flops(cfg, batch_size, patches_per_image)
783879

784-
# Main entrypoint for FLOPs calculation.
785-
if getattr(cfg.model, "is_hybrid_model", False):
880+
# Main entrypoint for FLOPs calculation. Mirror MCore's hybrid detection:
881+
# a physical hybrid pattern is sufficient to select hybrid accounting.
882+
if getattr(cfg.model, "is_hybrid_model", False) or getattr(cfg.model, "hybrid_layer_pattern", None):
786883
# Calculate the number of each type of layer.
787884
num_attn_layers, num_mamba_layers, num_mlp_layers, num_moe_layers, num_gdn_layers = calculate_layer_counts()
788885
mtp_num_layers = getattr(cfg.model, "mtp_num_layers", None)
@@ -809,6 +906,8 @@ def _compute_vit_flops():
809906
num_query_groups = (
810907
cfg.model.num_attention_heads if cfg.model.num_query_groups is None else cfg.model.num_query_groups
811908
)
909+
num_swa_attn_layers = count_hybrid_swa_attention_layers(num_attn_layers)
910+
swa_context = calculate_swa_context() if num_swa_attn_layers > 0 else None
812911

813912
# Compute hybrid model FLOPs.
814913
llm_flops = hybrid_flops(
@@ -848,6 +947,9 @@ def _compute_vit_flops():
848947
gdn_conv_kernel_dim=getattr(cfg.model, "linear_conv_kernel_dim", None) or 4,
849948
vocab_size=padded_vocab_size,
850949
mtp_num_layers=mtp_num_layers,
950+
core_seq_factor=core_attn_seq_factor,
951+
num_swa_attn_layers=num_swa_attn_layers,
952+
swa_context=swa_context,
851953
)
852954
return llm_flops + _compute_vit_flops()
853955
else:

tests/unit_tests/models/gpt_oss/test_gpt_oss_bridges.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def test_provider_bridge_maps_config(self, mock_pretrained):
6464
# Key fields mapped from HF config
6565
assert provider.num_layers == 2 * mock_pretrained.config.num_hidden_layers
6666
assert provider.num_moe_experts == mock_pretrained.config.num_local_experts
67+
assert provider.is_hybrid_model is True
6768
# dtype mapping
6869
assert provider.bf16 is True
6970
assert provider.params_dtype == torch.bfloat16

tests/unit_tests/models/hybrid/test_hybrid_provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def test_hybrid_provider_initialization(self):
3939
assert provider.params_dtype == torch.bfloat16
4040
assert provider.fp16 is False
4141
assert provider.bf16 is True
42+
assert provider.is_hybrid_model is True
4243
assert provider.mamba_num_groups == 8
4344
assert provider.hybrid_layer_pattern is None
4445
assert provider.hybrid_stack_spec is None

0 commit comments

Comments
 (0)