Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
44 changes: 18 additions & 26 deletions fla/layers/gated_deltanet.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang

from __future__ import annotations

Expand All @@ -8,36 +8,29 @@

import torch
import torch.nn as nn
from einops import rearrange, repeat
from einops import rearrange
from torch.nn import functional as F

from fla.layers.utils import get_layer_cache, get_unpad_data, index_first_axis, pad_input, update_layer_cache
from fla.modules import FusedRMSNormGated, RMSNorm, ShortConvolution
from fla.ops.gated_delta_rule import chunk_gated_delta_rule, fused_recurrent_gated_delta_rule
from fla.ops.gated_delta_rule.gate import fused_gdn_gate

if TYPE_CHECKING:
from transformers.processing_utils import Unpack

from fla.models.utils import Cache


@torch.compile
def elu_p1(x):
return (F.elu(x, 1., False) + 1.).to(x)


@torch.compile
def sum_norm(x):
return (x / x.sum(-1, keepdim=True)).to(x)


class GatedDeltaNet(nn.Module):
"""
The layer implementaion for [Gated Delta Networks: Improving Mamba2 with Delta Rule](https://arxiv.org/abs/2412.06464). # noqa
Gated Delta Networks (GDN) layer implementation.

Reference: `Gated Delta Networks: Improving Mamba2 with Delta Rule <https://arxiv.org/abs/2412.06464>`_

Similar to Mamba2, each layer contains around 6*hidden_size*hidden_size parameters.

Parameter alloation when use_gate=True:
Parameter allocation when use_gate=True:
- 0.75 * hidden_size * hidden_size for the q_proj and k_proj each
- 1.5 * hidden_size * hidden_size for the v_proj, g_proj and o_proj each
- Others are ignorably small.
Expand All @@ -54,27 +47,27 @@ class GatedDeltaNet(nn.Module):
hidden_size (int, Optional):
The hidden size of the input. Default: 2048.
expand_v (float, Optional):
The expansion ratio for the value dim. Default: 2.0.
The expansion ratio for the value dimension. Default: 2.0.
head_dim (int, Optional):
The dimension of each head. Default: 256.
num_heads (int, Optional):
The number of heads. Default: 4.
The number of heads. Default: 6.
num_v_heads (int, Optional):
The number of heads for the value projection, equal to `num_heads` if `None`.
GVA is applied if `num_v_heads` > `num_heads`. Default: `None`.
GVA (Grouped Value Attention) is applied if `num_v_heads` > `num_heads`,
where `num_v_heads` must be divisible by `num_heads`. Default: `None`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject num_v_heads < num_heads up front.

The new native GVA path only supports HV >= H, but the module currently only checks divisibility when num_v_heads > num_heads. Smaller values still get through and later hit invalid HV // H math inside the kernels.

🔧 Suggested fix
-        if self.num_v_heads > self.num_heads and self.num_v_heads % self.num_heads != 0:
+        if self.num_v_heads < self.num_heads or self.num_v_heads % self.num_heads != 0:
             raise ValueError(
-                f"num_v_heads={self.num_v_heads} must be divisible by num_heads={self.num_heads}.",
+                f"num_v_heads={self.num_v_heads} must be >= num_heads={self.num_heads} "
+                f"and divisible by it.",
             )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@fla/layers/gated_deltanet.py` around lines 55 - 58, Add an input validation
in the module's initializer (where num_v_heads and num_heads are processed) to
immediately reject cases where num_v_heads is not None and num_v_heads <
num_heads by raising a ValueError; keep the existing divisibility check for
num_v_heads > num_heads (i.e., ensure num_v_heads % num_heads == 0) but precede
it with this new check so the native GVA path never receives HV < H and avoids
invalid HV // H math inside the kernels.

mode (str, Optional):
Which Gated DeltaNet kernel to use.
Currently available: `chunk` and `fused_recurrent`.
Default: `chunk`.
use_beta (bool, Optional):
Whether to use beta. Default: `True`.
use_gate (bool, Optional):
Whether to use output gate. Default: `True`.
use_short_conv (bool, Optional):
Whether to use short convolutions. Default: `True`.
allow_neg_eigval (bool, Optional):
Allow negative eigenvalues. Default: `False`. If set to `True`, the beta will be multiplied by 2.
See reference: [Unlocking State-Tracking in Linear RNNs Through Negative Eigenvalues](https://arxiv.org/abs/2411.12537)
See reference:
`Unlocking State-Tracking in Linear RNNs Through Negative Eigenvalues <https://arxiv.org/abs/2411.12537>`_
conv_size (int, Optional):
The kernel size of the short convolution, only used when `use_short_conv` is `True`. Default: 4.
conv_bias (bool, Optional):
Expand Down Expand Up @@ -258,29 +251,28 @@ def forward(
q, k = map(lambda x: rearrange(x, '... (h d) -> ... h d', d=self.head_k_dim), (q, k))
v = rearrange(v, '... (h d) -> ... h d', d=self.head_v_dim)

if self.num_v_heads > self.num_heads:
q, k = map(lambda x: repeat(x, '... h d -> ... (h g) d', g=self.num_v_heads // self.num_heads), (q, k))

beta = self.b_proj(hidden_states).sigmoid()
if self.allow_neg_eigval:
beta = beta * 2.

g = -self.A_log.float().exp() * F.softplus(self.a_proj(hidden_states).float() + self.dt_bias)

recurrent_state = last_state['recurrent_state'] if last_state is not None else None
if mode == 'chunk':
o, recurrent_state = chunk_gated_delta_rule(
q=q,
k=k,
v=v,
g=g,
g=self.a_proj(hidden_states),
beta=beta,
initial_state=recurrent_state,
output_final_state=use_cache,
cu_seqlens=cu_seqlens,
use_qk_l2norm_in_kernel=True,
use_gate_in_kernel=True,
A_log=self.A_log,
dt_bias=self.dt_bias,
)
elif mode == 'fused_recurrent':
g = fused_gdn_gate(g=self.a_proj(hidden_states), A_log=self.A_log, dt_bias=self.dt_bias)
o, recurrent_state = fused_recurrent_gated_delta_rule(
q=q,
k=k,
Expand Down
Loading
Loading