Skip to content

Commit 567cace

Browse files
committed
Reject unsupported heterogeneous hybrid inference
Signed-off-by: Philip Petrakian <ppetrakian@nvidia.com>
1 parent e344ee2 commit 567cace

6 files changed

Lines changed: 303 additions & 10 deletions

File tree

docs/user-guide/hybrid-model-migration.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,6 @@ remove `--decoder-first-pipeline-num-layers` and
366366
`--decoder-last-pipeline-num-layers`. Express virtual-pipeline segmentation
367367
with additional pipe-delimited segments instead.
368368

369-
The declarative `HybridModelBuilder` currently rejects virtual pipeline
370-
parallelism. Pipe-defined virtual stages are supported by the
371-
`pretrain_hybrid.py` CLI builder, but custom builder users must avoid VPP or use
372-
a path that explicitly supports it.
373-
374369
### Update custom providers and conversion mappings
375370

376371
Custom providers and conversion mappings also need to account for these API and
@@ -398,3 +393,12 @@ Before starting a long run:
398393
parameter counts by layer.
399394
- Save and reload one new checkpoint to confirm that the new optimizer and RNG
400395
state resume correctly.
396+
397+
Direct layer specs are currently training-first. Dynamic inference rejects a
398+
direct architecture when occurrence configurations are incompatible with its
399+
model-global cache or runtime buffers, whether the occurrences differ from one
400+
another or a family-uniform override differs from the base `TransformerConfig`.
401+
Examples include differing Mamba state dimensions and attention KV dimensions
402+
or MoE top-k values that differ from the base config. Legacy
403+
`hybrid_layer_pattern` models retain their existing inference path and do not
404+
use this direct-spec validator.

megatron/core/inference/apis/_llm_base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import torch.distributed as dist
1919

20-
from megatron.core.inference.config import InferenceConfig
20+
from megatron.core.inference.config import InferenceConfig, validate_dynamic_inference_model
2121
from megatron.core.inference.contexts.dynamic_context import DynamicInferenceContext
2222
from megatron.core.inference.engines.dynamic_engine import DynamicInferenceEngine, EngineState
2323
from megatron.core.inference.inference_request import DynamicInferenceRequest
@@ -279,6 +279,7 @@ def __init__(
279279
inference_config = InferenceConfig()
280280

281281
# Build the engine pipeline. Mirrors examples/inference/gpt/gpt_dynamic_inference.py.
282+
validate_dynamic_inference_model(model)
282283
context = DynamicInferenceContext(model.config, inference_config)
283284
wrapper = GPTInferenceWrapper(model, context)
284285
controller = TextGenerationController(inference_wrapped_model=wrapper, tokenizer=tokenizer)

megatron/core/inference/config.py

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@
1212
from megatron.core.utils import get_attr_wrapped_model
1313

1414

15+
def validate_dynamic_inference_model(model: MegatronModule) -> None:
16+
"""Reject incompatible direct specs before model-global inference buffers are allocated."""
17+
18+
from megatron.core.models.hybrid.hybrid_architecture import ResolvedHybridArchitecture
19+
20+
try:
21+
architecture = get_attr_wrapped_model(model, "resolved_hybrid_architecture")
22+
except RuntimeError:
23+
return
24+
if not isinstance(architecture, ResolvedHybridArchitecture):
25+
return
26+
if architecture.source != "direct":
27+
return
28+
29+
model_config = get_attr_wrapped_model(model, "config", allow_none=False)
30+
if architecture.has_incompatible_dynamic_inference_shapes(model_config):
31+
raise NotImplementedError(
32+
"Direct HybridModel occurrence configurations are incompatible with dynamic "
33+
"inference's model-global cache or runtime buffers (Mamba state/cache dimensions, "
34+
"attention KV dimensions, or MoE router top-k values)."
35+
)
36+
37+
1538
@dataclass
1639
class MambaInferenceStateConfig:
1740
"""
@@ -49,14 +72,41 @@ def from_model(
4972
model: MegatronModule,
5073
conv_states_dtype: Optional[torch.dtype] = None,
5174
ssm_states_dtype: Optional[torch.dtype] = None,
75+
*,
76+
validate_dynamic_inference: bool = True,
5277
) -> Optional["MambaInferenceStateConfig"]:
53-
"""Returns Mamba inference state config from the model if it is a hybrid model."""
78+
"""Returns Mamba inference state config from the model if it is a hybrid model.
79+
80+
Args:
81+
validate_dynamic_inference: Validate direct specs against the model-global buffers
82+
used by dynamic inference. The explicit legacy static engine disables this
83+
because its Mamba, attention, and MoE state is allocated by each layer.
84+
"""
5485
from megatron.core.models.hybrid.hybrid_layer_allocation import Symbols
5586

87+
if validate_dynamic_inference:
88+
validate_dynamic_inference_model(model)
5689
decoder = get_attr_wrapped_model(model, "decoder")
5790
layer_type_list = getattr(decoder, "layer_type_list", None)
91+
# HybridStack's first-class API exposes stable semantic names, while
92+
# dynamic inference's layer maps intentionally retain legacy symbols.
93+
semantic_to_symbol = {
94+
"mamba": Symbols.MAMBA,
95+
"gdn": Symbols.GDN,
96+
"attention": Symbols.ATTENTION,
97+
"dsa": Symbols.DS_ATTENTION,
98+
"mla": Symbols.MLA,
99+
"mlp": Symbols.MLP,
100+
"moe": Symbols.MOE,
101+
}
102+
if layer_type_list is not None and any(
103+
layer_type in semantic_to_symbol for layer_type in layer_type_list
104+
):
105+
layer_type_list = [
106+
semantic_to_symbol.get(layer_type, layer_type) for layer_type in layer_type_list
107+
]
58108
if layer_type_list is not None and Symbols.MAMBA in layer_type_list:
59-
(mamba_conv_states_shape, mamba_ssm_states_shape) = (
109+
mamba_conv_states_shape, mamba_ssm_states_shape = (
60110
decoder.mamba_state_shapes_per_request()
61111
)
62112
if conv_states_dtype is None:
@@ -73,7 +123,7 @@ def from_model(
73123
elif ssm_states_dtype is None:
74124
ssm_states_dtype = model.config.params_dtype
75125
mamba_chunk_size = 128
76-
for layer_type, layer in zip(decoder.layer_type_list, decoder.layers):
126+
for layer_type, layer in zip(layer_type_list, decoder.layers):
77127
if layer_type == Symbols.MAMBA and hasattr(layer, 'mixer'):
78128
mamba_chunk_size = layer.mixer.chunk_size
79129
break

megatron/core/inference/engines/static_engine.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def __init__(
9494
self.scheduler = Scheduler(max_batch_size=max_batch_size)
9595

9696
mamba_inference_state_config = MambaInferenceStateConfig.from_model(
97-
self.inference_wrapped_model.model
97+
self.inference_wrapped_model.model, validate_dynamic_inference=not legacy
9898
)
9999

100100
try:

megatron/core/models/hybrid/hybrid_model.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,21 @@ def forward(
641641
inference_context = deprecate_inference_params(inference_context, inference_params)
642642

643643
in_inference_mode = InferenceMode.is_active()
644+
resolved_architecture = getattr(self, "resolved_hybrid_architecture", None)
645+
646+
if (
647+
in_inference_mode
648+
and inference_context is not None
649+
and inference_context.is_dynamic_batching()
650+
and resolved_architecture is not None
651+
and resolved_architecture.source == "direct"
652+
and resolved_architecture.has_incompatible_dynamic_inference_shapes(self.config)
653+
):
654+
raise NotImplementedError(
655+
"Direct HybridModel occurrence configurations are incompatible with dynamic "
656+
"inference's model-global cache or runtime buffers (Mamba state/cache "
657+
"dimensions, attention KV dimensions, or MoE router top-k values)."
658+
)
644659

645660
if in_inference_mode:
646661
assert runtime_gather_output, "Inference must always gather TP logits"

0 commit comments

Comments
 (0)