From 5cb7944575d2a4e141711a5b59c531e7708739ed Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Tue, 23 Jun 2026 04:15:04 -0700 Subject: [PATCH 1/4] [glm-mode-dsa] Indexer uses interleaved rope --- src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py index 34d7bf139c36..4986b2368b9e 100644 --- a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py @@ -236,7 +236,7 @@ def forward( 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) + 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] From 3f73ed947dd45c1ee3c42bd4690cbd2c40287090 Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Thu, 25 Jun 2026 10:06:17 -0700 Subject: [PATCH 2/4] Apply to modular, read from config --- .../configuration_deepseek_v32.py | 4 + .../deepseek_v32/modeling_deepseek_v32.py | 82 +++++++++---------- .../deepseek_v32/modular_deepseek_v32.py | 8 +- .../glm_moe_dsa/configuration_glm_moe_dsa.py | 3 + .../glm_moe_dsa/modeling_glm_moe_dsa.py | 82 +++++++++---------- .../models/glm_moe_dsa/modular_glm_moe_dsa.py | 3 + 6 files changed, 98 insertions(+), 84 deletions(-) diff --git a/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py b/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py index 5ef27459d13e..14fe6f26a4ed 100644 --- a/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py @@ -38,6 +38,9 @@ class DeepseekV32Config(PreTrainedConfig, RotaryEmbeddingConfigMixin): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 64): Number of heads for the indexer projections (DSA). + indexer_rope_interleave (`bool`, *optional*, defaults to `False`): + Whether the DSA indexer applies interleaved RoPE (GLM-style) instead of the non-interleaved + RoPE used by DeepSeek-V3.2. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. @@ -115,6 +118,7 @@ class DeepseekV32Config(PreTrainedConfig, RotaryEmbeddingConfigMixin): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 64 + indexer_rope_interleave: bool = False mlp_bias: bool = False num_experts: int = 256 head_dim: int = 64 diff --git a/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py b/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py index 46f6f6c74762..54e678865b1f 100644 --- a/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py @@ -163,6 +163,45 @@ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1): return q_embed, k_embed +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 + + class DeepseekV32Indexer(nn.Module): """ DeepSeek Sparse Attention (DSA) indexer for selecting top-k tokens. @@ -235,8 +274,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) + rope_fn = apply_rotary_pos_emb_interleave if self.config.indexer_rope_interleave else apply_rotary_pos_emb + q_rot, k_rot = rope_fn(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] @@ -299,45 +338,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 diff --git a/src/transformers/models/deepseek_v32/modular_deepseek_v32.py b/src/transformers/models/deepseek_v32/modular_deepseek_v32.py index 77be4aa9c943..61d6e129048f 100644 --- a/src/transformers/models/deepseek_v32/modular_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/modular_deepseek_v32.py @@ -67,6 +67,9 @@ class DeepseekV32Config(Glm4MoeLiteConfig, RotaryEmbeddingConfigMixin): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 64): Number of heads for the indexer projections (DSA). + indexer_rope_interleave (`bool`, *optional*, defaults to `False`): + Whether the DSA indexer applies interleaved RoPE (GLM-style) instead of the non-interleaved + RoPE used by DeepSeek-V3.2. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. @@ -136,6 +139,7 @@ class DeepseekV32Config(Glm4MoeLiteConfig, RotaryEmbeddingConfigMixin): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 64 + indexer_rope_interleave: bool = False mlp_bias: bool = False num_experts: int = 256 head_dim: int = 64 @@ -239,8 +243,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) + rope_fn = apply_rotary_pos_emb_interleave if self.config.indexer_rope_interleave else apply_rotary_pos_emb + q_rot, k_rot = rope_fn(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] diff --git a/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py index 5ecf328bd170..d5615f2daa81 100644 --- a/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py @@ -39,6 +39,8 @@ class GlmMoeDsaConfig(PreTrainedConfig, RotaryEmbeddingConfigMixin): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 32): Number of heads for the indexer projections (DSA). + indexer_rope_interleave (`bool`, *optional*, defaults to `True`): + Whether the indexer applies interleaved RoPE. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. indexer_types (`list[str]`, *optional*): @@ -122,6 +124,7 @@ class GlmMoeDsaConfig(PreTrainedConfig, RotaryEmbeddingConfigMixin): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 32 + indexer_rope_interleave: bool = True mlp_bias: bool = False num_experts: int = 256 head_dim: int = 64 diff --git a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py index 4986b2368b9e..ca470a71a9a8 100644 --- a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py @@ -163,6 +163,45 @@ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1): return q_embed, k_embed +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 + + class GlmMoeDsaIndexer(nn.Module): """ DeepSeek Sparse Attention (DSA) indexer for selecting top-k tokens. @@ -235,8 +274,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_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + rope_fn = apply_rotary_pos_emb_interleave if self.config.indexer_rope_interleave else apply_rotary_pos_emb + q_rot, k_rot = rope_fn(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] @@ -299,45 +338,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 diff --git a/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py index a4e91beb4633..e9be5d7547d1 100644 --- a/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py @@ -58,6 +58,8 @@ class GlmMoeDsaConfig(DeepseekV32Config): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 32): Number of heads for the indexer projections (DSA). + indexer_rope_interleave (`bool`, *optional*, defaults to `True`): + Whether the indexer applies interleaved RoPE. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. indexer_types (`list[str]`, *optional*): @@ -105,6 +107,7 @@ class GlmMoeDsaConfig(DeepseekV32Config): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 32 + indexer_rope_interleave: bool = True # `"full"` runs the indexer, `"shared"` reuses the previous full layer's index mask. indexer_types: list[str] | None = None From 99ae73e3703b8a5df8bbab7a5b9ba10b34cc6513 Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Fri, 26 Jun 2026 03:47:43 -0700 Subject: [PATCH 3/4] Remove from config --- .../configuration_deepseek_v32.py | 4 - .../deepseek_v32/modeling_deepseek_v32.py | 85 ++++++++++--------- .../deepseek_v32/modular_deepseek_v32.py | 11 ++- .../glm_moe_dsa/configuration_glm_moe_dsa.py | 3 - .../glm_moe_dsa/modeling_glm_moe_dsa.py | 42 ++------- .../models/glm_moe_dsa/modular_glm_moe_dsa.py | 7 +- 6 files changed, 58 insertions(+), 94 deletions(-) diff --git a/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py b/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py index 14fe6f26a4ed..5ef27459d13e 100644 --- a/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/configuration_deepseek_v32.py @@ -38,9 +38,6 @@ class DeepseekV32Config(PreTrainedConfig, RotaryEmbeddingConfigMixin): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 64): Number of heads for the indexer projections (DSA). - indexer_rope_interleave (`bool`, *optional*, defaults to `False`): - Whether the DSA indexer applies interleaved RoPE (GLM-style) instead of the non-interleaved - RoPE used by DeepSeek-V3.2. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. @@ -118,7 +115,6 @@ class DeepseekV32Config(PreTrainedConfig, RotaryEmbeddingConfigMixin): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 64 - indexer_rope_interleave: bool = False mlp_bias: bool = False num_experts: int = 256 head_dim: int = 64 diff --git a/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py b/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py index 54e678865b1f..f70967ebbd86 100644 --- a/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py @@ -163,45 +163,6 @@ def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1): return q_embed, k_embed -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 - - class DeepseekV32Indexer(nn.Module): """ DeepSeek Sparse Attention (DSA) indexer for selecting top-k tokens. @@ -233,6 +194,10 @@ def __init__(self, config: "DeepseekV32Config", layer_idx: int): self.weights_proj = nn.Linear(self.hidden_size, self.n_heads, bias=False) self.softmax_scale = self.head_dim**-0.5 + def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): + # The indexer uses NON-interleaved (half-split) RoPE — unlike the main MLA attention. + return apply_rotary_pos_emb(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + @torch.no_grad() def forward( self, @@ -274,8 +239,7 @@ 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) - rope_fn = apply_rotary_pos_emb_interleave if self.config.indexer_rope_interleave else apply_rotary_pos_emb - q_rot, k_rot = rope_fn(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + q_rot, k_rot = self.apply_indexer_rotary_pos_emb(q_rot, k_rot, cos, sin) 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] @@ -338,6 +302,45 @@ 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 diff --git a/src/transformers/models/deepseek_v32/modular_deepseek_v32.py b/src/transformers/models/deepseek_v32/modular_deepseek_v32.py index 61d6e129048f..0b9dc73b7174 100644 --- a/src/transformers/models/deepseek_v32/modular_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/modular_deepseek_v32.py @@ -67,9 +67,6 @@ class DeepseekV32Config(Glm4MoeLiteConfig, RotaryEmbeddingConfigMixin): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 64): Number of heads for the indexer projections (DSA). - indexer_rope_interleave (`bool`, *optional*, defaults to `False`): - Whether the DSA indexer applies interleaved RoPE (GLM-style) instead of the non-interleaved - RoPE used by DeepSeek-V3.2. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. @@ -139,7 +136,6 @@ class DeepseekV32Config(Glm4MoeLiteConfig, RotaryEmbeddingConfigMixin): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 64 - indexer_rope_interleave: bool = False mlp_bias: bool = False num_experts: int = 256 head_dim: int = 64 @@ -202,6 +198,10 @@ def __init__(self, config: "DeepseekV32Config", layer_idx: int): self.weights_proj = nn.Linear(self.hidden_size, self.n_heads, bias=False) self.softmax_scale = self.head_dim**-0.5 + def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): + # The indexer uses NON-interleaved (half-split) RoPE — unlike the main MLA attention. + return apply_rotary_pos_emb(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + @torch.no_grad() def forward( self, @@ -243,8 +243,7 @@ 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) - rope_fn = apply_rotary_pos_emb_interleave if self.config.indexer_rope_interleave else apply_rotary_pos_emb - q_rot, k_rot = rope_fn(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + q_rot, k_rot = self.apply_indexer_rotary_pos_emb(q_rot, k_rot, cos, sin) 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] diff --git a/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py index d5615f2daa81..5ecf328bd170 100644 --- a/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/configuration_glm_moe_dsa.py @@ -39,8 +39,6 @@ class GlmMoeDsaConfig(PreTrainedConfig, RotaryEmbeddingConfigMixin): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 32): Number of heads for the indexer projections (DSA). - indexer_rope_interleave (`bool`, *optional*, defaults to `True`): - Whether the indexer applies interleaved RoPE. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. indexer_types (`list[str]`, *optional*): @@ -124,7 +122,6 @@ class GlmMoeDsaConfig(PreTrainedConfig, RotaryEmbeddingConfigMixin): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 32 - indexer_rope_interleave: bool = True mlp_bias: bool = False num_experts: int = 256 head_dim: int = 64 diff --git a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py index ca470a71a9a8..8dccf0b9584e 100644 --- a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py @@ -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 @@ -130,39 +130,6 @@ 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) - - -@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. - - 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. - 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 = 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) - return q_embed, k_embed - - 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. @@ -233,6 +200,10 @@ def __init__(self, config: "GlmMoeDsaConfig", layer_idx: int): self.weights_proj = nn.Linear(self.hidden_size, self.n_heads, bias=False) self.softmax_scale = self.head_dim**-0.5 + def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): + # GLM-MoE-DSA uses interleaved RoPE in the indexer. + return apply_rotary_pos_emb_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + @torch.no_grad() def forward( self, @@ -274,8 +245,7 @@ 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) - rope_fn = apply_rotary_pos_emb_interleave if self.config.indexer_rope_interleave else apply_rotary_pos_emb - q_rot, k_rot = rope_fn(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + q_rot, k_rot = self.apply_indexer_rotary_pos_emb(q_rot, k_rot, cos, sin) 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] diff --git a/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py index e9be5d7547d1..3dc74404d829 100644 --- a/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py @@ -58,8 +58,6 @@ class GlmMoeDsaConfig(DeepseekV32Config): Head dimension for the indexer projections (DSA). index_n_heads (`int`, *optional*, defaults to 32): Number of heads for the indexer projections (DSA). - indexer_rope_interleave (`bool`, *optional*, defaults to `True`): - Whether the indexer applies interleaved RoPE. first_k_dense_replace (`int`, *optional*, defaults to 3): Number of leading layers that use a dense MLP; the rest use the MoE block. indexer_types (`list[str]`, *optional*): @@ -107,7 +105,6 @@ class GlmMoeDsaConfig(DeepseekV32Config): index_topk: int = 2048 index_head_dim: int = 128 index_n_heads: int = 32 - indexer_rope_interleave: bool = True # `"full"` runs the indexer, `"shared"` reuses the previous full layer's index mask. indexer_types: list[str] | None = None @@ -137,7 +134,9 @@ class GlmMoeDsaRotaryEmbedding(DeepseekV32RotaryEmbedding): class GlmMoeDsaIndexer(DeepseekV32Indexer): - pass + def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): + # GLM-MoE-DSA uses interleaved RoPE in the indexer. + return apply_rotary_pos_emb_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2) class GlmMoeDsaAttention(DeepseekV3Attention): From ad786f6552bb9e224bcdc7dd064c58cc8ec2f68a Mon Sep 17 00:00:00 2001 From: Pedro Cuenca Date: Tue, 30 Jun 2026 06:09:40 -0700 Subject: [PATCH 4/4] Copy and modify forward implementation --- .../deepseek_v32/modeling_deepseek_v32.py | 7 +- .../deepseek_v32/modular_deepseek_v32.py | 7 +- .../glm_moe_dsa/modeling_glm_moe_dsa.py | 16 ++--- .../models/glm_moe_dsa/modular_glm_moe_dsa.py | 65 ++++++++++++++++++- 4 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py b/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py index f70967ebbd86..46f6f6c74762 100644 --- a/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/modeling_deepseek_v32.py @@ -194,10 +194,6 @@ def __init__(self, config: "DeepseekV32Config", layer_idx: int): self.weights_proj = nn.Linear(self.hidden_size, self.n_heads, bias=False) self.softmax_scale = self.head_dim**-0.5 - def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): - # The indexer uses NON-interleaved (half-split) RoPE — unlike the main MLA attention. - return apply_rotary_pos_emb(q_rot, k_rot, cos, sin, unsqueeze_dim=2) - @torch.no_grad() def forward( self, @@ -239,7 +235,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) - q_rot, k_rot = self.apply_indexer_rotary_pos_emb(q_rot, k_rot, cos, sin) + # 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) 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] diff --git a/src/transformers/models/deepseek_v32/modular_deepseek_v32.py b/src/transformers/models/deepseek_v32/modular_deepseek_v32.py index 0b9dc73b7174..77be4aa9c943 100644 --- a/src/transformers/models/deepseek_v32/modular_deepseek_v32.py +++ b/src/transformers/models/deepseek_v32/modular_deepseek_v32.py @@ -198,10 +198,6 @@ def __init__(self, config: "DeepseekV32Config", layer_idx: int): self.weights_proj = nn.Linear(self.hidden_size, self.n_heads, bias=False) self.softmax_scale = self.head_dim**-0.5 - def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): - # The indexer uses NON-interleaved (half-split) RoPE — unlike the main MLA attention. - return apply_rotary_pos_emb(q_rot, k_rot, cos, sin, unsqueeze_dim=2) - @torch.no_grad() def forward( self, @@ -243,7 +239,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) - q_rot, k_rot = self.apply_indexer_rotary_pos_emb(q_rot, k_rot, cos, sin) + # 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) 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] diff --git a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py index 8dccf0b9584e..1997312fdd02 100644 --- a/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modeling_glm_moe_dsa.py @@ -200,10 +200,6 @@ def __init__(self, config: "GlmMoeDsaConfig", layer_idx: int): self.weights_proj = nn.Linear(self.hidden_size, self.n_heads, bias=False) self.softmax_scale = self.head_dim**-0.5 - def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): - # GLM-MoE-DSA uses interleaved RoPE in the indexer. - return apply_rotary_pos_emb_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2) - @torch.no_grad() def forward( self, @@ -217,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]`. @@ -245,7 +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) - q_rot, k_rot = self.apply_indexer_rotary_pos_emb(q_rot, k_rot, cos, sin) + # 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] diff --git a/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py b/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py index 3dc74404d829..95013a9e763d 100644 --- a/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py +++ b/src/transformers/models/glm_moe_dsa/modular_glm_moe_dsa.py @@ -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 @@ -134,9 +135,67 @@ class GlmMoeDsaRotaryEmbedding(DeepseekV32RotaryEmbedding): class GlmMoeDsaIndexer(DeepseekV32Indexer): - def apply_indexer_rotary_pos_emb(self, q_rot, k_rot, cos, sin): - # GLM-MoE-DSA uses interleaved RoPE in the indexer. - return apply_rotary_pos_emb_interleave(q_rot, k_rot, cos, sin, unsqueeze_dim=2) + @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):