Skip to content

Commit 6096273

Browse files
authored
Skip the query/key head repeat for grouped value heads in gated DeltaNet (#49141)
* Skip the query/key head repeat for grouped value heads in gated DeltaNet FLA's chunked and fused recurrent gated delta rule kernels accept fewer query/key heads than value heads since 0.5.0, and the hub kernel does too. The torch fallbacks now repeat query/key themselves, so the modeling code only repeats when an older FLA is installed. This removes a query/key copy and its backward reduction in every linear-attention layer of Qwen3-Next, Qwen3.5, Qwen3.5-MoE, Qwen4-Exp and OLMo-Hybrid. * Pass g and beta by keyword in test_gdn_chunked_matches_recurrent FLA's fused recurrent kernel takes gk and gv between g and beta, so the positional call fed beta into gk whenever FLA was installed. FLA's chunked kernel also runs its dot products in TF32, so the tolerance follows the backend. * Name the FLA version check fla_needs_equal_qk_heads * Always pass grouped query/key heads to the gated delta rule kernels Drop the FLA version check and the layer-level repeat; the torch fallbacks repeat query/key themselves when there are fewer of them. * Add a helper for the gated delta rule test inputs * Move the gated delta rule input helper into the model tester
1 parent 70152d9 commit 6096273

9 files changed

Lines changed: 130 additions & 51 deletions

File tree

‎src/transformers/models/olmo_hybrid/modeling_olmo_hybrid.py‎

Lines changed: 12 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/transformers/models/olmo_hybrid/modular_olmo_hybrid.py‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -398,11 +398,6 @@ def forward(
398398
k = k.view(batch_size, seq_len, -1, self.head_k_dim)
399399
v = v.view(batch_size, seq_len, -1, self.head_v_dim)
400400

401-
if self.num_v_heads > self.num_k_heads:
402-
expand_ratio = self.num_v_heads // self.num_k_heads
403-
q = q.repeat_interleave(expand_ratio, dim=2)
404-
k = k.repeat_interleave(expand_ratio, dim=2)
405-
406401
beta = self.b_proj(hidden_states).sigmoid()
407402
if self.allow_neg_eigval:
408403
beta = beta * 2.0

‎src/transformers/models/qwen3_5/modeling_qwen3_5.py‎

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/transformers/models/qwen3_5/modular_qwen3_5.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,6 @@ def forward(
297297
beta = b.sigmoid()
298298
# If the model is loaded in fp16, without the .float() here, A might be -inf
299299
g = -self.A_log.float().exp() * F.softplus(a.float() + self.dt_bias)
300-
if self.num_v_heads // self.num_k_heads > 1:
301-
query = query.repeat_interleave(self.num_v_heads // self.num_k_heads, dim=2)
302-
key = key.repeat_interleave(self.num_v_heads // self.num_k_heads, dim=2)
303300

304301
if recurrent_state is not None and seq_len == 1:
305302
core_attn_out, last_recurrent_state = torch_recurrent_gated_delta_rule(

‎src/transformers/models/qwen3_5_moe/modeling_qwen3_5_moe.py‎

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/transformers/models/qwen3_next/modeling_qwen3_next.py‎

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/transformers/models/qwen3_next/modular_qwen3_next.py‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -240,12 +240,17 @@ def torch_chunk_gated_delta_rule(
240240
- Either None or the new recurrent state tensor of shape [batch_size, num_v_heads, k_head_dim, v_head_dim]
241241
"""
242242
initial_dtype = query.dtype
243-
batch_size, sequence_length, _, k_head_dim = key.shape
243+
batch_size, sequence_length, num_k_heads, k_head_dim = key.shape
244244
num_v_heads, v_head_dim = value.shape[-2:]
245245
recurrent_state_shape = (batch_size, num_v_heads, k_head_dim, v_head_dim)
246246
padded_output_shape = (batch_size, num_v_heads, -1, v_head_dim) # -1 is the padded sequence length
247247
decay = g # rename for clarity: argument name must stay "g" to match flash_linear_attention's API
248248

249+
# Grouped value attention: each query/key head serves num_v_heads // num_k_heads consecutive value heads
250+
if num_k_heads != num_v_heads:
251+
query = query.repeat_interleave(num_v_heads // num_k_heads, dim=2)
252+
key = key.repeat_interleave(num_v_heads // num_k_heads, dim=2)
253+
249254
# Make sure all tensors are fp32 and reshape them to [batch_size, num_*_heads, seqlen, ...]
250255
query, key, value, beta, decay = [
251256
x.transpose(1, 2).to(torch.float32, memory_format=torch.contiguous_format)
@@ -362,10 +367,15 @@ def torch_recurrent_gated_delta_rule(
362367
Same args and return value as torch_chunk_gated_delta_rule, except for `chunk_size` because the sequence dim is not
363368
chunked."""
364369
initial_dtype = query.dtype
365-
batch_size, sequence_length, _, k_head_dim = key.shape
370+
batch_size, sequence_length, num_k_heads, k_head_dim = key.shape
366371
num_v_heads, v_head_dim = value.shape[-2:]
367372
decay = g # rename for clarity: argument name must stay "g" to match flash_linear_attention's API
368373

374+
# Grouped value attention: each query/key head serves num_v_heads // num_k_heads consecutive value heads
375+
if num_k_heads != num_v_heads:
376+
query = query.repeat_interleave(num_v_heads // num_k_heads, dim=2)
377+
key = key.repeat_interleave(num_v_heads // num_k_heads, dim=2)
378+
369379
# Make sure all tensors are fp32 and reshape them to [batch_size, num_*_heads, seqlen, ...]
370380
query, key, value, beta, decay = [
371381
x.transpose(1, 2).to(torch.float32, memory_format=torch.contiguous_format)
@@ -558,9 +568,6 @@ def forward(
558568
beta = b.sigmoid()
559569
# If the model is loaded in fp16, without the .float() here, A might be -inf
560570
g = -self.A_log.float().exp() * F.softplus(a.float() + self.dt_bias)
561-
if self.num_v_heads // self.num_k_heads > 1:
562-
query = query.repeat_interleave(self.num_v_heads // self.num_k_heads, dim=2)
563-
key = key.repeat_interleave(self.num_v_heads // self.num_k_heads, dim=2)
564571

565572
if recurrent_state is not None and seq_len == 1:
566573
core_attn_out, last_recurrent_state = torch_recurrent_gated_delta_rule(

‎src/transformers/models/qwen4_exp/modeling_qwen4_exp.py‎

Lines changed: 12 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)