Skip to content
Merged
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
86 changes: 24 additions & 62 deletions src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from ...activations import ACT2FN
from ...cache_utils import Cache, DynamicCache
from ...generation import GenerationMixin
from ...integrations import use_experts_implementation, use_kernel_forward_from_hub, use_kernel_func_from_hub
from ...integrations import use_experts_implementation, use_kernel_forward_from_hub
from ...masking_utils import create_causal_mask
from ...modeling_flash_attention_utils import FlashAttentionKwargs
from ...modeling_layers import GradientCheckpointingLayer
Expand Down Expand Up @@ -130,22 +130,23 @@ def forward(self, x, position_ids):
return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)


def rotate_half(x):
"""Rotates half the hidden dims of the input."""
x1 = x[..., : x.shape[-1] // 2]
x2 = x[..., x.shape[-1] // 2 :]
return torch.cat((-x2, x1), dim=-1)

def apply_rotary_pos_emb_interleave(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
r"""
Applies interleaved Rotary Position Embedding to the query and key tensors.

@use_kernel_func_from_hub("rotary_pos_emb")
def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
"""Applies Rotary Position Embedding to the query and key tensors.
DeepSeek lays the rotary dimensions out in interleaved pairs `(x0, x1), (x2, x3), ...`, each rotated by a
single frequency. We compute that rotation directly on the even/odd slices instead of de-interleaving with a
`view`/`transpose`/`reshape`; the output is bit-identical to the de-interleaved `rotate_half` formulation while
avoiding the extra contiguous copy.

Args:
q (`torch.Tensor`): The query tensor.
k (`torch.Tensor`): The key tensor.
cos (`torch.Tensor`): The cosine part of the rotary embedding.
sin (`torch.Tensor`): The sine part of the rotary embedding.
position_ids (`torch.Tensor`):
The position indices of the tokens corresponding to the query and key tensors. For example, this can be
used to pass offsetted position ids when working with a KV-cache.
unsqueeze_dim (`int`, *optional*, defaults to 1):
The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
Expand All @@ -156,10 +157,15 @@ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
Returns:
`tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
"""
cos = cos.unsqueeze(unsqueeze_dim)
sin = sin.unsqueeze(unsqueeze_dim)
q_embed = (q * cos) + (rotate_half(q) * sin)
k_embed = (k * cos) + (rotate_half(k) * sin)
# `cos`/`sin` are `cat(freqs, freqs)`; the first half holds the per-pair angle.
cos = cos[..., : cos.shape[-1] // 2].unsqueeze(unsqueeze_dim)
sin = sin[..., : sin.shape[-1] // 2].unsqueeze(unsqueeze_dim)

q1, q2 = q[..., 0::2], q[..., 1::2]
k1, k2 = k[..., 0::2], k[..., 1::2]

q_embed = torch.cat([q1 * cos - q2 * sin, q2 * cos + q1 * sin], dim=-1)
k_embed = torch.cat([k1 * cos - k2 * sin, k2 * cos + k1 * sin], dim=-1)
return q_embed, k_embed


Expand Down Expand Up @@ -207,13 +213,8 @@ def forward(
"""
Selects the top-k tokens per query for DeepSeek Sparse Attention (DSA).

This is the bf16 equivalent of the reference Indexer which uses `rotate_activation` (Hadamard transform)
and `fp8_index` (FP8 quantized scoring kernel). Since the Hadamard transform is orthogonal (dot products
are preserved: Hq·Hk = q·k), and FP8 quantization is a precision optimization, we skip both and compute
scores directly in bf16/fp32.

The scoring logic computes:
index_score[b,s,t] = Σ_h (weight[b,s,h] · softmax_scale · q[b,s,h,:] · k[b,t,:])
Same as [`DeepseekV32Indexer.forward`], but the indexer applies **interleaved** RoPE
rather than the non-interleaved half-split RoPE used by DeepSeek-V3.2.

Args:
hidden_states: Input hidden states `[B, S, hidden_size]`.
Expand All @@ -235,8 +236,8 @@ def forward(
k = self.k_norm(self.wk(hidden_states)).unsqueeze(2) # [B, S, 1, D]
k_rot, k_pass = torch.split(k, [self.qk_rope_head_dim, self.head_dim - self.qk_rope_head_dim], dim=-1)

# The indexer uses NON-interleaved (half-split) RoPE — unlike the main MLA attention
q_rot, k_rot = apply_rotary_pos_emb(q_rot, k_rot, cos, sin, unsqueeze_dim=2)
# GLM-MoE-DSA uses interleaved RoPE in the indexer
q_rot, k_rot = apply_rotary_pos_emb_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2)
q = torch.cat([q_rot, q_pass], dim=-1) # [B, S, H, D]
k = torch.cat([k_rot, k_pass], dim=-1).squeeze(2) # [B, S, D]

Expand Down Expand Up @@ -299,45 +300,6 @@ def eager_attention_forward(
return attn_output, attn_weights


def apply_rotary_pos_emb_interleave(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
r"""
Applies interleaved Rotary Position Embedding to the query and key tensors.

DeepSeek lays the rotary dimensions out in interleaved pairs `(x0, x1), (x2, x3), ...`, each rotated by a
single frequency. We compute that rotation directly on the even/odd slices instead of de-interleaving with a
`view`/`transpose`/`reshape`; the output is bit-identical to the de-interleaved `rotate_half` formulation while
avoiding the extra contiguous copy.

Args:
q (`torch.Tensor`): The query tensor.
k (`torch.Tensor`): The key tensor.
cos (`torch.Tensor`): The cosine part of the rotary embedding.
sin (`torch.Tensor`): The sine part of the rotary embedding.
position_ids (`torch.Tensor`):
The position indices of the tokens corresponding to the query and key tensors. For example, this can be
used to pass offsetted position ids when working with a KV-cache.
unsqueeze_dim (`int`, *optional*, defaults to 1):
The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
Returns:
`tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
"""
# `cos`/`sin` are `cat(freqs, freqs)`; the first half holds the per-pair angle.
cos = cos[..., : cos.shape[-1] // 2].unsqueeze(unsqueeze_dim)
sin = sin[..., : sin.shape[-1] // 2].unsqueeze(unsqueeze_dim)

q1, q2 = q[..., 0::2], q[..., 1::2]
k1, k2 = k[..., 0::2], k[..., 1::2]

q_embed = torch.cat([q1 * cos - q2 * sin, q2 * cos + q1 * sin], dim=-1)
k_embed = torch.cat([k1 * cos - k2 * sin, k2 * cos + k1 * sin], dim=-1)
return q_embed, k_embed


def yarn_get_mscale(scale=1, mscale=1):
if scale <= 1:
return 1.0
Expand Down
63 changes: 62 additions & 1 deletion src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from collections.abc import Callable

import torch
import torch.nn.functional as F
from huggingface_hub.dataclasses import strict

from ...cache_utils import Cache, DynamicCache
Expand Down Expand Up @@ -134,7 +135,67 @@ class GlmMoeDsaRotaryEmbedding(DeepseekV32RotaryEmbedding):


class GlmMoeDsaIndexer(DeepseekV32Indexer):
pass
@torch.no_grad()
def forward(
self,
hidden_states: torch.Tensor,
q_resid: torch.Tensor,
position_embeddings: tuple[torch.Tensor, torch.Tensor],
attention_mask: torch.Tensor | None,
position_ids: torch.Tensor,
past_key_values: Cache | None = None,
) -> torch.Tensor:
"""
Selects the top-k tokens per query for DeepSeek Sparse Attention (DSA).

Same as [`DeepseekV32Indexer.forward`], but the indexer applies **interleaved** RoPE
rather than the non-interleaved half-split RoPE used by DeepSeek-V3.2.

Args:
hidden_states: Input hidden states `[B, S, hidden_size]`.
q_resid: Query residual from `q_a_layernorm(q_a_proj(x))`, shape `[B, S, q_lora_rank]`.
position_embeddings: `(cos, sin)` from RotaryEmbedding.
attention_mask: Causal mask, broadcastable to `[B, S, T]`.
past_key_values: Cache object containing the indexer key cache for this layer.

Returns:
`torch.Tensor`: the `int32` top-k token indices of shape `[B, S, topk]`. The eager / SDPA paths
turn these into an additive sparse mask; the `flash-mla` kernel consumes them directly.
"""
batch_size, seq_len, _ = hidden_states.shape
cos, sin = position_embeddings
q = self.wq_b(q_resid) # [B, S, H*D]
q = q.view(batch_size, seq_len, self.n_heads, self.head_dim) # [B, S, H, D]
q_rot, q_pass = torch.split(q, [self.qk_rope_head_dim, self.head_dim - self.qk_rope_head_dim], dim=-1)

k = self.k_norm(self.wk(hidden_states)).unsqueeze(2) # [B, S, 1, D]
k_rot, k_pass = torch.split(k, [self.qk_rope_head_dim, self.head_dim - self.qk_rope_head_dim], dim=-1)

# GLM-MoE-DSA uses interleaved RoPE in the indexer
q_rot, k_rot = apply_rotary_pos_emb_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2)
q = torch.cat([q_rot, q_pass], dim=-1) # [B, S, H, D]
k = torch.cat([k_rot, k_pass], dim=-1).squeeze(2) # [B, S, D]

if past_key_values is not None:
k = past_key_values.update_indexer(k, self.layer_idx)

scores = torch.matmul(q.float(), k.transpose(-1, -2).float().unsqueeze(1)) * self.softmax_scale
scores = F.relu(scores)

# Weight per head and sum across heads: [B, S, 1, H] @ [B, S, H, T] → [B, S, T]
weights = self.weights_proj(hidden_states.to(self.weights_proj.weight.dtype)).float() * (self.n_heads**-0.5)
index_scores = torch.matmul(weights.unsqueeze(-2), scores).squeeze(-2)

# Causality needs to be taken into account when computing scores so padding tokens don't affect computation
if attention_mask is not None:
index_scores = index_scores + attention_mask
else:
key_positions = torch.arange(index_scores.shape[-1], device=index_scores.device)
causal = key_positions[None, None, :] > position_ids[:, :, None] # [B, S, T]
index_scores = index_scores.masked_fill(causal, float("-inf"))

topk = min(self.index_topk, index_scores.shape[-1])
return index_scores.topk(topk, dim=-1).indices.to(torch.int32) # [B, S, topk]


class GlmMoeDsaAttention(DeepseekV3Attention):
Expand Down
Loading