Skip to content
270 changes: 181 additions & 89 deletions megatron/core/fusions/fused_mla_yarn_rope_apply.py

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions megatron/core/models/common/embeddings/rope_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ def _apply_rotary_pos_emb_bshd(
rotary_interleaved: bool = False,
mla_rotary_interleaved: bool = False,
mscale: float = 1.0,
inverse: bool = False,
mla_output_remove_interleaving: bool = False,
multi_latent_attention: Optional[bool] = None,
) -> Tensor:
"""Apply rotary positional embedding to input tensor T.
Expand All @@ -118,6 +120,13 @@ def _apply_rotary_pos_emb_bshd(
)
mla_rotary_interleaved = multi_latent_attention

# Some callers may pass freqs with an extra singleton axis, e.g.
# t: [s, b, d] and freqs: [s, 1, 1, d]. In that case, broadcasting would
# accidentally expand to [s, s, b, d]. Squeeze the extra singleton axis to
# keep freqs rank aligned with t.
if freqs.dim() == t.dim() + 1 and freqs.size(-2) == 1:
freqs = freqs.squeeze(-2)

rot_dim = freqs.shape[-1]

# ideally t_pass is empty so rotary pos embedding is applied to all tensor t
Expand All @@ -132,8 +141,18 @@ def _apply_rotary_pos_emb_bshd(
# second part is sine component, need to change signs with _rotate_half method
cos_ = (torch.cos(freqs) * mscale).to(t.dtype)
sin_ = (torch.sin(freqs) * mscale).to(t.dtype)
if inverse:
sin_ = -sin_

t = (t * cos_) + (_rotate_half(t, rotary_interleaved) * sin_)

# Fallback to original permutation
Comment thread
yaox12 marked this conversation as resolved.
# DSv4 applies rope on V and O, so we need to uninterleave the tensor.
# The existing MLA code is safe because the dot product is permutation-invariant.
if mla_rotary_interleaved and mla_output_remove_interleaving:
x1, x2 = torch.chunk(t, 2, dim=-1)
t = torch.stack((x1, x2), dim=-1).flatten(start_dim=-2)

return torch.cat((t, t_pass), dim=-1)


Expand Down Expand Up @@ -193,6 +212,8 @@ def _apply_rotary_pos_emb_thd(
rotary_interleaved: bool = False,
mla_rotary_interleaved: bool = False,
mscale: float = 1.0,
inverse: bool = False,
mla_output_remove_interleaving: bool = False,
cp_group: torch.distributed.ProcessGroup = None,
multi_latent_attention: Optional[bool] = None,
) -> Tensor:
Expand Down Expand Up @@ -246,6 +267,8 @@ def _apply_rotary_pos_emb_thd(
rotary_interleaved=rotary_interleaved,
mla_rotary_interleaved=mla_rotary_interleaved,
mscale=mscale,
inverse=inverse,
mla_output_remove_interleaving=mla_output_remove_interleaving,
).squeeze(1)
else:
# CASE 2: Traditional mapping without offsets
Expand All @@ -262,6 +285,8 @@ def _apply_rotary_pos_emb_thd(
rotary_interleaved=rotary_interleaved,
mla_rotary_interleaved=mla_rotary_interleaved,
mscale=mscale,
inverse=inverse,
mla_output_remove_interleaving=mla_output_remove_interleaving,
).squeeze(1)


Expand All @@ -273,6 +298,8 @@ def apply_rotary_pos_emb(
mscale: float = 1.0,
cp_group: torch.distributed.ProcessGroup = None,
mla_rotary_interleaved: bool = False,
inverse: bool = False,
mla_output_remove_interleaving: bool = False,
):
"""
Reroute to the appropriate apply_rotary_pos_emb function depending on
Expand Down Expand Up @@ -307,6 +334,12 @@ def apply_rotary_pos_emb(
"Using unfused implementation."
)
use_unfused = True
if inverse:
warnings.warn(
"inverse RoPE is not supported by TE's fused RoPE. "
"Using unfused implementation."
)
use_unfused = True
if not use_unfused:
assert fused_apply_rotary_pos_emb is not None, "apply_rope_fusion is not available."
return fused_apply_rotary_pos_emb(t, freqs, interleaved=config.rotary_interleaved)
Expand All @@ -328,6 +361,8 @@ def apply_rotary_pos_emb(
rotary_interleaved=config.rotary_interleaved,
mla_rotary_interleaved=mla_rotary_interleaved,
mscale=mscale,
inverse=inverse,
mla_output_remove_interleaving=mla_output_remove_interleaving,
)
else:
return _apply_rotary_pos_emb_thd(
Expand All @@ -338,6 +373,8 @@ def apply_rotary_pos_emb(
mla_rotary_interleaved=mla_rotary_interleaved,
mscale=mscale,
cp_group=cp_group,
inverse=inverse,
mla_output_remove_interleaving=mla_output_remove_interleaving,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
from megatron.core.models.backends import BackendSpecProvider
from megatron.core.ssm.gated_delta_net import GatedDeltaNet, GatedDeltaNetSubmodules
from megatron.core.transformer.enums import AttnMaskType, LayerType
from megatron.core.transformer.experimental_attention_variant.csa import (
CompressedSparseAttention,
CompressedSparseAttentionSubmodules,
Compressor,
CompressorSubmodules,
CSAIndexer,
CSAIndexerSubmodules,
)
from megatron.core.transformer.experimental_attention_variant.deepseek_v4_hybrid_attention import (
DSv4HybridSelfAttention,
DSv4HybridSelfAttentionSubmodules,
)
from megatron.core.transformer.experimental_attention_variant.dsa import (
DSAIndexer,
DSAIndexerSubmodules,
Expand Down Expand Up @@ -128,6 +140,63 @@ def get_dsa_module_spec_for_backend(
return attention


def get_dsv4_hybrid_module_spec_for_backend(
config: TransformerConfig, backend: BackendSpecProvider = None
) -> ModuleSpec:
"""Helper function to get module spec for DSv4 Hybrid Sparse Attention."""
assert config.multi_latent_attention, "Currently only MLA supports sparse attention."
assert config.qk_l2_norm is False, "qk_l2_norm is not supported with MLA."

# Adjust for RMS norm.
rms_norm = config.normalization == "RMSNorm"
# DSA indexer requires normalized q as input, so here we cannot fuse qk layernorm
# with linear projection and have to use unfused qk layernorm.
qk_norm = (
backend.layer_norm(rms_norm=rms_norm, for_qk=True) if config.qk_layernorm else IdentityOp
)

compressor_spec = ModuleSpec(
module=Compressor,
submodules=CompressorSubmodules(
linear_wkv=backend.linear(),
linear_wgate=backend.linear(),
norm=backend.layer_norm(rms_norm=True, for_qk=False),
),
)

indexer_spec = ModuleSpec(
module=CSAIndexer,
submodules=CSAIndexerSubmodules(
linear_wq_b=backend.linear(),
linear_weights_proj=backend.linear(),
compressor=compressor_spec,
),
)

core_attention = ModuleSpec(
module=CompressedSparseAttention,
submodules=CompressedSparseAttentionSubmodules(
compressor=compressor_spec, indexer=indexer_spec
),
)

attention = ModuleSpec(
module=DSv4HybridSelfAttention,
params={"attn_mask_type": AttnMaskType.causal},
submodules=DSv4HybridSelfAttentionSubmodules(
linear_q_down_proj=backend.linear(),
linear_q_up_proj=backend.column_parallel_linear(),
linear_kv_proj=backend.column_parallel_linear(),
core_attention=core_attention,
linear_proj=backend.row_parallel_linear(),
q_layernorm=qk_norm,
kv_layernorm=qk_norm,
),
metainfo={"fuse_input_layernorm": False},
)
return attention


def get_experimental_attention_variant_module_spec(
config: TransformerConfig, backend: BackendSpecProvider = None
) -> ModuleSpec:
Expand All @@ -140,6 +209,8 @@ def get_experimental_attention_variant_module_spec(
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)
elif config.experimental_attention_variant == "dsv4_hybrid":
return get_dsv4_hybrid_module_spec_for_backend(config=config, backend=backend)
else:
raise ValueError(
f"Invalid experimental attention variant: {config.experimental_attention_variant}"
Expand Down
4 changes: 2 additions & 2 deletions megatron/core/models/gpt/gpt_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ def __init__(
cp_group=self.pg_collection.cp,
)

elif self.position_embedding_type == 'yarn':
elif self.position_embedding_type == 'yarn' and not self.config.multi_latent_attention:
self.rotary_pos_emb = YarnRotaryEmbedding(
kv_channels=self.config.kv_channels,
rotary_percent=rotary_percent,
Expand Down Expand Up @@ -381,7 +381,7 @@ def _preprocess(
and packed_seq_params.qkv_format == 'thd',
cp_group=packed_seq_params.cp_group if packed_seq_params is not None else None,
)
elif self.position_embedding_type == 'yarn':
elif self.position_embedding_type == 'yarn' and not self.config.multi_latent_attention:
if self.training or not self.config.flash_decode:
rotary_seq_len = self.rotary_pos_emb.get_rotary_seq_len(
inference_context, self.decoder, decoder_input, self.config, packed_seq_params
Expand Down
6 changes: 6 additions & 0 deletions megatron/core/transformer/attention.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,12 +258,14 @@ def __init__(
cp_comm_type: str | None = None,
pg_collection: ProcessGroupCollection | None = None,
pp_layer_offset: Optional[int] = None,
is_mtp_layer: bool = False,
):
super().__init__(config=config)

self.config = config
self.layer_number = layer_number
self._pp_layer_offset = pp_layer_offset
self.is_mtp_layer = is_mtp_layer

self.attn_mask_type = attn_mask_type
self.attention_type = attention_type
Expand Down Expand Up @@ -1357,6 +1359,7 @@ def __init__(
cp_comm_type: str | None = None,
pg_collection: ProcessGroupCollection | None = None,
pp_layer_offset: Optional[int] = None,
is_mtp_layer: bool = False,
):
super().__init__(
config=config,
Expand All @@ -1367,6 +1370,7 @@ def __init__(
cp_comm_type=cp_comm_type,
pg_collection=pg_collection,
pp_layer_offset=pp_layer_offset,
is_mtp_layer=is_mtp_layer,
)

self.linear_qkv_out_dim = self.query_projection_size + 2 * self.kv_projection_size
Expand Down Expand Up @@ -1766,6 +1770,7 @@ def __init__(
attn_mask_type: AttnMaskType = AttnMaskType.padding,
cp_comm_type: str | None = None,
pg_collection: ProcessGroupCollection | None = None,
is_mtp_layer: bool = False,
):
super().__init__(
config=config,
Expand All @@ -1775,6 +1780,7 @@ def __init__(
attention_type="cross",
cp_comm_type=cp_comm_type,
pg_collection=pg_collection,
is_mtp_layer=is_mtp_layer,
)

if self.config.num_query_groups != self.config.num_attention_heads:
Expand Down
Loading
Loading