Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
# 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 @@ -392,7 +392,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 not InferenceMode.is_active() 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 @@ -291,6 +291,7 @@ def __init__(
pg_collection: ProcessGroupCollection | None = None,
pp_layer_offset: Optional[int] = None,
name: str | None = None,
is_mtp_layer: bool = False,
):
"""
Args:
Expand All @@ -301,6 +302,7 @@ def __init__(
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 @@ -1386,6 +1388,7 @@ def __init__(
pg_collection: ProcessGroupCollection | None = None,
pp_layer_offset: Optional[int] = None,
name: str | None = None,
is_mtp_layer: bool = False,
):
"""
Args:
Expand All @@ -1401,6 +1404,7 @@ def __init__(
pg_collection=pg_collection,
pp_layer_offset=pp_layer_offset,
name=name,
is_mtp_layer=is_mtp_layer,
)

self.linear_qkv_out_dim = self.query_projection_size + 2 * self.kv_projection_size
Expand Down Expand Up @@ -1802,6 +1806,7 @@ def __init__(
cp_comm_type: str | None = None,
pg_collection: ProcessGroupCollection | None = None,
name: str | None = None,
is_mtp_layer: bool = False,
):
"""
Args:
Expand All @@ -1816,6 +1821,7 @@ def __init__(
cp_comm_type=cp_comm_type,
pg_collection=pg_collection,
name=name,
is_mtp_layer=is_mtp_layer,
)

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