Skip to content
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6ff2fcc
Prepare the Gated DeltaNet base for channel-wise variants
fkuner Aug 17, 2026
16f448c
Add Kimi Delta Attention to hybrid models
fkuner Aug 17, 2026
72cefc3
Honor GDN projection metadata in FSDP checkpoints
fkuner Aug 15, 2026
ed853d3
Add head-wise output gating to MLA
fkuner Aug 15, 2026
fe92b2d
Include nested MTP modules in QK clipping
fkuner Aug 15, 2026
2ec2297
Add a Ling-V3 Tiny hybrid model spec
fkuner Aug 15, 2026
5fe61a8
Add a Ling-V3 Tiny pretraining example
fkuner Aug 15, 2026
8296e8b
Account for KDA and MLA in training metrics
fkuner Aug 15, 2026
7f0ae9f
Fix Hybrid MTP z-loss metric indexing
fkuner Aug 17, 2026
7ff158e
Add Ling-V3 Tiny functional regression
fkuner Aug 15, 2026
529e5e4
Apply suggestions from code review
fkuner Aug 20, 2026
c3bf414
Rename Ling-V3 Tiny functional test
fkuner Aug 20, 2026
f08a989
Fix MLA output gate compilation and tests
fkuner Aug 20, 2026
545c761
Validate GDN norm recompute for hybrid models
fkuner Aug 20, 2026
27f29bf
Tighten MLA output gate VJP tolerance
fkuner Aug 20, 2026
22458cd
Validate KDA safe-gate bounds
fkuner Aug 21, 2026
4b03bfb
refactor: Align KDA structure with GatedDeltaNet
yuzhongw-nvidia Aug 21, 2026
a7855d9
feat: Add attention output gate granularity
yuzhongw-nvidia Aug 21, 2026
7f3c18b
Merge pull request #1 from yuzhongw-nvidia/yuzhongw-nvidia/align-kda-gdn
fkuner Aug 22, 2026
f9e20e5
Remove Ling-specific onboarding artifacts
fkuner Aug 22, 2026
fe1caca
Remove FSDP DTensor support from Ling onboarding
fkuner Aug 22, 2026
611c8ac
Use the generic Hybrid spec in functional coverage
fkuner Aug 22, 2026
1f6ffd1
Refine KDA and hybrid FLOPs test contracts
fkuner Aug 22, 2026
4a68b8a
Correct MLA headwise gate VJP reference
fkuner Aug 22, 2026
87c96f8
Correct KDA source attribution
fkuner Aug 23, 2026
be824cc
Update megatron/training/training.py
fkuner Aug 24, 2026
aa516dc
Update megatron/core/transformer/multi_latent_attention.py
fkuner Aug 24, 2026
bd98edc
Fix hybrid layer count doctest examples
fkuner Aug 24, 2026
f4e113e
Fix Ling-V3 Tiny CI assertions
fkuner Aug 24, 2026
14e28c6
Remove unused KDA parameter markers
fkuner Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions megatron/core/inference/contexts/dynamic_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -350,14 +350,16 @@ def __init__(self, model_config: TransformerConfig, inference_config: InferenceC
# For hybrid models, the layer map converts the global layer index to the
# corresponding attention layer index or Mamba layer index depending on the
# layer type.
attention_layer_map, dsa_layer_map, gdn_layer_map, mamba_layer_map = (
attention_layer_map, dsa_layer_map, gdn_layer_map, kda_layer_map, mamba_layer_map = (
operator.itemgetter(
Symbols.ATTENTION, Symbols.DS_ATTENTION, Symbols.GDN, Symbols.MAMBA
Symbols.ATTENTION, Symbols.DS_ATTENTION, Symbols.GDN, Symbols.KDA, Symbols.MAMBA
)(get_layer_maps_from_layer_type_list(mamba_inference_state_config.layer_type_list))
)

if len(gdn_layer_map) > 0:
raise NotImplementedError("GDN layers are not supported for inference.")
if len(kda_layer_map) > 0:
raise NotImplementedError("KDA layers are not supported for inference.")

self.num_attention_layers = len(attention_layer_map) + len(dsa_layer_map)
self.num_mamba_layers = len(mamba_layer_map)
Expand All @@ -384,7 +386,7 @@ def __init__(self, model_config: TransformerConfig, inference_config: InferenceC
model_config, vp_stage=None, pp_rank=pp_rank
)
self.num_mamba_layers = 0
(self.mamba_conv_states_shape, self.mamba_ssm_states_shape) = (None, None)
self.mamba_conv_states_shape, self.mamba_ssm_states_shape = (None, None)
self.layer_map = {i: i for i in range(self.num_attention_layers)}

if self.num_attention_layers == 0:
Expand Down Expand Up @@ -2663,7 +2665,7 @@ def check_availability(self, req: DynamicInferenceRequest) -> Tuple[bool, bool,
self.total_request_count < self.max_requests and self.paused_request_count == 0
)

(_, num_blocks_from_pool, _, _, _, effective_prefill_chunk_length) = (
_, num_blocks_from_pool, _, _, _, effective_prefill_chunk_length = (
self._compute_prefix_match(req, req.remaining_prompt_length)
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

import warnings
from typing import List, Optional

from megatron.core.fusions.fused_bias_dropout import get_bias_dropout_add
from megatron.core.models.backends import BackendSpecProvider
from megatron.core.ssm.gated_delta_net import GatedDeltaNet, GatedDeltaNetSubmodules
from megatron.core.ssm.gated_delta_net import (
GatedDeltaNet,
GatedDeltaNetSubmodules,
KimiDeltaAttention,
KimiDeltaAttentionSubmodules,
)
from megatron.core.transformer.enums import AttnMaskType, LayerType
from megatron.core.transformer.experimental_attention_variant.absorbed_mla import (
AbsorbedMLASelfAttention,
Expand Down Expand Up @@ -66,6 +72,17 @@
HAVE_KITCHEN = False


##########
# Experimental Attention Variant Names
##########

# Canonical ``experimental_attention_variant`` names served by the gated delta net family.
GDN_ATTENTION_VARIANTS = ("gdn", "kda")

# Deprecated ``experimental_attention_variant`` spellings mapped to their canonical name.
_DEPRECATED_ATTENTION_VARIANT_ALIASES = {"gated_delta_net": "gdn"}


##########
# Experimental Attention Variant Module Specs
##########
Expand All @@ -74,21 +91,33 @@
def get_gated_delta_net_module_spec(
config: TransformerConfig, backend: BackendSpecProvider = None
) -> ModuleSpec:
"""Build module spec for GatedDeltaNet attention."""
"""Build a module spec for a GDN-family attention variant."""

if backend is None:
backend = _get_backend_spec_provider(config=config)

rms_norm = config.normalization == "RMSNorm"
attention = ModuleSpec(
module=GatedDeltaNet,
submodules=GatedDeltaNetSubmodules(
in_proj=backend.column_parallel_layer_norm_linear(),
out_norm=backend.layer_norm(rms_norm=rms_norm, for_qk=False),
out_proj=backend.row_parallel_linear(),
),
metainfo={"fuse_input_layernorm": True},
)
if config.experimental_attention_variant == "kda":
attention = ModuleSpec(
module=KimiDeltaAttention,
submodules=KimiDeltaAttentionSubmodules(
in_proj=backend.column_parallel_linear(),
beta_proj=backend.column_parallel_linear(),
out_norm=backend.layer_norm(rms_norm=rms_norm, for_qk=False),
out_proj=backend.row_parallel_linear(),
),
metainfo={"fuse_input_layernorm": False},
)
else:
attention = ModuleSpec(
module=GatedDeltaNet,
submodules=GatedDeltaNetSubmodules(
in_proj=backend.column_parallel_layer_norm_linear(),
out_norm=backend.layer_norm(rms_norm=rms_norm, for_qk=False),
out_proj=backend.row_parallel_linear(),
),
metainfo={"fuse_input_layernorm": True},
)
return attention


Expand Down Expand Up @@ -209,7 +238,7 @@ def get_experimental_attention_variant_module_spec(
if backend is None:
backend = _get_backend_spec_provider(config=config)

if config.experimental_attention_variant == "gated_delta_net":
if is_gated_delta_net_variant(config.experimental_attention_variant):
return get_gated_delta_net_module_spec(config=config, backend=backend)
elif config.experimental_attention_variant == "dsa":
return get_dsa_module_spec_for_backend(config=config, backend=backend)
Expand Down Expand Up @@ -409,10 +438,34 @@ def get_transformer_block_with_experimental_attention_variant_spec(
##########


def normalize_experimental_attention_variant(
experimental_attention_variant: Optional[str],
) -> Optional[str]:
"""Resolve a deprecated attention-variant spelling to its canonical name."""
canonical = _DEPRECATED_ATTENTION_VARIANT_ALIASES.get(experimental_attention_variant)
if canonical is None:
return experimental_attention_variant

warnings.warn(
f"experimental_attention_variant='{experimental_attention_variant}' is deprecated "
f"and will be removed in a future release. Use '{canonical}' instead.",
DeprecationWarning,
stacklevel=2,
)
return canonical


def is_gated_delta_net_variant(experimental_attention_variant: Optional[str]) -> bool:
"""Return whether a name selects a GDN-family attention implementation."""
canonical = _DEPRECATED_ATTENTION_VARIANT_ALIASES.get(
experimental_attention_variant, experimental_attention_variant
)
return canonical in GDN_ATTENTION_VARIANTS


def is_linear_attention_variant(experimental_attention_variant: Optional[str]) -> bool:
"""Check if the experimental attention variant is a linear attention variant."""
linear_attention_variants = ["gated_delta_net"]
return experimental_attention_variant in linear_attention_variants
return is_gated_delta_net_variant(experimental_attention_variant)


def _validate_dsa_index_share_pipeline_split(config: TransformerConfig, local_layer_ids) -> None:
Expand Down
3 changes: 3 additions & 0 deletions megatron/core/models/gpt/gpt_layer_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ def get_gpt_layer_with_inference_submodules(
linear_kv_down_proj=backend.linear(),
linear_kv_up_proj=linear_kv_up_proj,
core_attention=backend.core_attention(),
linear_gate=backend.column_parallel_linear(),
linear_proj=backend.row_parallel_linear(),
q_layernorm=IdentityOp,
kv_layernorm=IdentityOp,
Expand Down Expand Up @@ -314,6 +315,7 @@ def get_gpt_layer_with_transformer_engine_submodules(
linear_kv_down_proj=backend.linear(),
linear_kv_up_proj=linear_kv_up_proj,
core_attention=backend.core_attention(),
linear_gate=backend.column_parallel_linear(),
linear_proj=backend.row_parallel_linear(),
q_layernorm=IdentityOp,
kv_layernorm=IdentityOp,
Expand Down Expand Up @@ -445,6 +447,7 @@ def get_gpt_layer_local_submodules(
linear_kv_down_proj=backend.column_parallel_linear(),
linear_kv_up_proj=backend.column_parallel_linear(),
core_attention=backend.core_attention(),
linear_gate=backend.column_parallel_linear(),
linear_proj=backend.row_parallel_linear(),
q_layernorm=qk_norm if qk_layernorm else IdentityOp,
kv_layernorm=qk_norm if qk_layernorm else IdentityOp,
Expand Down
22 changes: 22 additions & 0 deletions megatron/core/models/hybrid/hybrid_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@ class HybridStackSubmodules:

mamba_layer: Union[ModuleSpec, type] = IdentityOp
gdn_layer: Union[ModuleSpec, type] = IdentityOp
kda_layer: Union[ModuleSpec, type] = IdentityOp
attention_layer: Union[ModuleSpec, type] = IdentityOp
dsa_layer: Union[ModuleSpec, type] = IdentityOp
mla_layer: Union[ModuleSpec, type] = IdentityOp
csa_layer: Union[ModuleSpec, type] = IdentityOp
hca_layer: Union[ModuleSpec, type] = IdentityOp
window_layer: Union[ModuleSpec, type] = IdentityOp
Expand Down Expand Up @@ -654,6 +656,17 @@ def __init__(
pp_layer_offset=pp_layer_offset,
name=(name + f".layers.{i}") if name is not None else None,
)
elif layer_type == LayerSymbols.MLA:
layer = build_module(
submodules.mla_layer,
config=self.config,
layer_number=layer_number,
pg_collection=pg_collection,
is_mtp_layer=is_mtp_layer,
add_layer_offset=False,
pp_layer_offset=pp_layer_offset,
name=(name + f".layers.{i}") if name is not None else None,
)
elif layer_type == LayerSymbols.CSA:
# DSv4 Compressed Sparse Attention (compress_ratio fixed by the spec).
layer = build_module(
Expand Down Expand Up @@ -717,6 +730,15 @@ def __init__(
add_layer_offset=False,
name=(name + f".layers.{i}") if name is not None else None,
)
elif layer_type == LayerSymbols.KDA:
layer = build_module(
submodules.kda_layer,
config=self.config,
layer_number=layer_number,
pg_collection=pg_collection,
add_layer_offset=False,
name=(name + f".layers.{i}") if name is not None else None,
)
else:
raise ValueError("unexpected layer_type")
if self.is_mtp_layer and self.mtp_layer_number is not None:
Expand Down
16 changes: 9 additions & 7 deletions megatron/core/models/hybrid/hybrid_layer_allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,20 @@ class Symbols:

MAMBA = "M"
GDN = 'G'
KDA = 'K'
ATTENTION = "*"
DS_ATTENTION = "D"
MLA = "+"
CSA = "C" # DSv4 Compressed Sparse Attention (compress_ratio=4)
HCA = "H" # DSv4 Heavily Compressed Attention (compress_ratio=128)
WINDOW = "W" # DSv4 sliding-window-only attention (compress_ratio=0; no compressor/indexer)
MLP = "-"
MOE = 'E'
PIPE = '|'
MTP_SEPARATOR = "/"
VALID_LAYERS = {MAMBA, GDN, ATTENTION, DS_ATTENTION, CSA, HCA, WINDOW, MLP, MOE}
VALID_LAYERS = {MAMBA, GDN, KDA, ATTENTION, DS_ATTENTION, MLA, CSA, HCA, WINDOW, MLP, MOE}
# MLA-based attention layers (incompatible with standard '*' attention in one model).
MLA_ATTENTION = {DS_ATTENTION, CSA, HCA, WINDOW}
MLA_ATTENTION = {MLA, DS_ATTENTION, CSA, HCA, WINDOW}

@classmethod
def name_sorted_valid_layer_symbols(cls) -> list[str]:
Expand Down Expand Up @@ -178,10 +180,10 @@ def get_hybrid_layer_counts(pattern: str) -> Dict[str, int]:

Examples:
>>> get_hybrid_layer_counts("M*M*")
{'*': 2, 'C': 0, 'D': 0, 'G': 0, 'H': 0, 'M': 2, '-': 0, 'E': 0, 'W': 0}
{'*': 2, 'C': 0, 'D': 0, 'G': 0, 'H': 0, 'K': 0, 'M': 2, '-': 0, 'E': 0, 'W': 0}

>>> get_hybrid_layer_counts("M-M-|M-M*-/MM/MM")
{'*': 1, 'C': 0, 'D': 0, 'G': 0, 'H': 0, 'M': 8, '-': 4, 'E': 0, 'W': 0}
>>> get_hybrid_layer_counts("M-M-|M-M*-/MM/MM")
{'*': 1, 'C': 0, 'D': 0, 'G': 0, 'H': 0, 'K': 0, 'M': 8, '-': 4, 'E': 0, 'W': 0}
Comment thread
fkuner marked this conversation as resolved.
Resolved
"""
parsed = parse_hybrid_pattern(pattern)
counts = {symbol: 0 for symbol in Symbols.name_sorted_valid_layer_symbols()}
Expand Down Expand Up @@ -297,7 +299,7 @@ def _validate_pattern(pattern: str, pattern_name: str, allow_pipe: bool = False)
f"Valid symbols are: {valid_chars}"
)

# Disallow standard Attention ('*') mixed with any MLA-based attention (D/C/H/W).
# Disallow standard Attention ('*') mixed with any MLA-based attention (+/D/C/H/W).
# MLA variants (DSA / CSA / HCA / Window) may freely coexist with each other.
if Symbols.ATTENTION in pattern and any(s in pattern for s in Symbols.MLA_ATTENTION):
raise ValueError(
Expand Down Expand Up @@ -328,7 +330,7 @@ def validate_segment_layers(segment: str) -> List[str]:
f"one of {Symbols.VALID_LAYERS}"
)

# Disallow standard Attention ('*') mixed with any MLA-based attention (D/C/H/W).
# Disallow standard Attention ('*') mixed with any MLA-based attention (+/D/C/H/W).
if Symbols.ATTENTION in segment and any(s in segment for s in Symbols.MLA_ATTENTION):
raise ValueError(
"Not supported to have both Attention and MLA/DSA/CSA/HCA/Window in one model"
Expand Down
46 changes: 45 additions & 1 deletion megatron/core/models/hybrid/hybrid_layer_specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
get_moe_module_spec,
)
from megatron.core.models.hybrid.hybrid_block import HybridStack, HybridStackSubmodules
from megatron.core.ssm.gated_delta_net import GatedDeltaNet, GatedDeltaNetSubmodules
from megatron.core.ssm.gated_delta_net import (
GatedDeltaNet,
GatedDeltaNetSubmodules,
KimiDeltaAttention,
KimiDeltaAttentionSubmodules,
)
from megatron.core.ssm.mamba_layer import MambaLayer, MambaLayerSubmodules
from megatron.core.ssm.mamba_mixer import MambaMixer, MambaMixerSubmodules
from megatron.core.ssm.mlp_layer import MLPLayer
Expand Down Expand Up @@ -121,6 +126,22 @@
self_attn_bda=get_bias_dropout_add,
),
),
kda_layer=ModuleSpec(
module=TransformerLayer,
submodules=TransformerLayerSubmodules(
input_layernorm=TENorm,
self_attention=ModuleSpec(
module=KimiDeltaAttention,
submodules=KimiDeltaAttentionSubmodules(
in_proj=TEColumnParallelLinear,
beta_proj=TEColumnParallelLinear,
out_norm=TENorm,
out_proj=TERowParallelLinear,
),
),
self_attn_bda=get_bias_dropout_add,
),
),
# Started with spec from gpt_layer_specs.py (with MLP removed)
# Using the TE spec because we had problems getting the non-TE spec
# working
Expand Down Expand Up @@ -174,6 +195,29 @@
self_attn_bda=get_bias_dropout_add,
),
),
mla_layer=ModuleSpec(
module=TransformerLayer,
submodules=TransformerLayerSubmodules(
input_layernorm=TENorm,
self_attention=ModuleSpec(
module=MLASelfAttention,
params={"attn_mask_type": AttnMaskType.causal},
submodules=MLASelfAttentionSubmodules(
linear_q_proj=TEColumnParallelLinear,
linear_q_down_proj=TELinear,
linear_q_up_proj=TEColumnParallelLinear,
linear_kv_down_proj=TELinear,
linear_kv_up_proj=TEColumnParallelLinear,
core_attention=TEDotProductAttention,
linear_gate=TEColumnParallelLinear,
linear_proj=TERowParallelLinear,
q_layernorm=IdentityOp,
kv_layernorm=IdentityOp,
),
),
self_attn_bda=get_bias_dropout_add,
),
),
# Started with spec from gpt_layer_specs.py
# Using the TE spec because we had problems getting the non-TE spec
# working
Expand Down
Loading
Loading