linear attention and its gated descendants
August 7, 2026
a brief history of linear attention, starting from softmax attention we'll walk through the progression into modern variants of linear attention like GDN and KDA.
softmax attention
Consider ordinary casual softmax attention. For a sequence of length and head dimension , ignoring the usual scaling factor, we compute
where , and is the causal mask.
The expensive part is the token-token interaction. Computing produces an matrix, and multiplying the attention weights by has the same sequence-quadratic cost:
So attention takes arithmetic (FA avoids materializing quadratic attention matrix).
The problem materalizes slightly differently under autoregressive inference. With a KV cache, keys and values aren't recomputed but every new query still has to compare against the keys already in the cache and use the resulting weights to read their values. At step , that's work, and the cache itself grows as .
There's a simple algebraic observation that makes linear attention appealing. Suppose for a moment that we didn't have softmax or causal masking. We could reassociate
The left-hand side constructs an matrix. On the right, we first construct a matrix. If is much larger than (typically in the range of 4-8k) this is favorable.
Of course, we can't just move the parentheses in softmax attention. Both softmax and the causal mask act on pairwise token-token scores. The trick of linear attention is to change the attention computation so that this reassociation becomes possible, while handling causality through an accumulated state rather than a full attention matrix. This isn't algebraically equivalent, we're changing the operation in favor of speed/memory.
linear attention as a recurrent memory
The reassociation trick is promising, but we need to remember to deal with causality. Dropping softmax is one thing. We still can't let a token attend to positions that haven't occurred yet.
So rather than looking at the entire sequence, let's write out the output for a single token at position . I'll use column vectors for one attention head. If we remove softmax but keep causal attention, the simplest version becomes
This is familiar. We compare the current query with each key seen so far, use the dot product to weight its value , and sum the results. The difference from softmax attention is that these weights are no longer exponentiated and normalized. For now, I'll stick to this unnormalized dot-product form; practical linear-attention formulations can introduce feature maps and normalization, but those aren't needed to understand the trick.
As written, though, we haven't gained anything. Every new query still seems to require going back over all previous keys and values. So where does the reassociation help?
Notice that is the same in every term of the sum. We can regroup the expression as
This is the causal version of the idea from before, now written for column vectors. Instead of building a single from all the tokens, including future ones, we build the equivalent key-value summary only from the prefix up to position . Focus now on the matrix inside the parantheses. All the past keys and values have been combined into one matrix. Let's give it a name:
Now the output is simply . If we already had the matrix from the previous token, computing is especially easy: we just add the contribution from the new token.
That's where the recurrent update comes from. It's not an additional operation we're inventing; it's just a way of maintaining the causal prefix sum without reconstructing it from scratch for every query. Rewriting the operation like this is known as the recurrent form of linear attention.
For example, after two tokens we have . When the third token arrives, we add and read the result with . There's no need to retrieve the first two keys and values individually again.
I find it useful to think of as a key-value associative memory. Each token writes an outer product into it; a query reads from the resulting matrix. The state has entries, and both the rank-1 write and the matrix-vector read cost , regardless of how long the prefix is.
This is also why people describe linear attention as a linear RNN. It's recurrent because the next state is computed from the previous state, and linear refers to that dependence on the state. It doesn't mean the whole model is linear in its input tokens.
Of course, compressing an arbitrarily long history into a fixed-size matrix has consequences for what the model can remember. We'll return to that when we get to the gated variants. First, let's see what this recurrence buys us computationally.
inference under the recurrent form
Quite a lot, at least asymptotically. With softmax attention, each new token reads an increasingly long KV cache. With recurrent linear attention, each new token performs just two operations:
The work per generated token is now , independent of how many tokens came before it. Across generated tokens, that's rather than .
The same shift happens for the memory:
These are per-head comparisons, leaving aside common implementation details. The important distinction is that the recurrent state doesn't grow with context length. Of course, isn't automatically cheaper than for every , and real inference speed depends on the kernels and the rest of the model. But importantly we've managed to remove the context-length-dependent scan altogether.
Okay, victory? Decoding looks great, memory is constant in sequence length and even the total arithmetic is linear. Unfortunately while decoding is a straightforward win, training/prefill is not.
training
Suppose we implement the recurrence exactly as written, one token at a time:
Each step consists of a rank-1 outer product and a matrix-vector multiplication. The arithmetic cost is still , which looks attractive next to softmax attention's . But GPUs despise doing a long sequence of tiny operations. There's two clear issues here:
- Dependency chain length is , so parallelism is poor in the naive recurrent implementation.
- The operations are many small rank-1 outer products () and matrix-vector products (), which are inefficient compared to dense matmuls.
While algorithmically fewer in FLOPs we're making poor use of existing hardware.
chunkwise parallel linear attention
The problem isn't really the amount of computation we're doing, but how we're organizing it. We have a sequence of small, dependent operations, whereas GPUs want large matmuls with high arithmetic intensity. Ideally, we'd like to keep the favorable scaling of linear attention while restructuring the computation into something the hardware can actually make good use of.
Fortunately, there's something useful about our recurrence that we haven't taken advantage of yet. Recall that the state is simply a running sum of outer products:
Each token contributes an outer product, and we accumulate these contributions into the state. But since we're just adding matrices together, there's no fundamental reason we have to do this one token at a time. We could group several tokens together and compute their combined contribution in one matrix multiplication.
This is the idea behind chunkwise parallel linear attention.
Instead of processing the entire sequence recurrently, we split it into chunks of tokens. We then maintain the recurrent state between chunks, while using parallel matrix multiplications within each chunk. This gives us a middle ground between the two approaches we've considered: we avoid the long chain of tiny operations from naive recurrence, without falling back to an attention matrix.
More precisely, we reduce the sequential dependency chain from token-level steps to roughly chunk-level steps, while replacing many of the small operations with dense matmuls that can take advantage of GPU tensor cores. For vanilla linear attention, the additive state also admits parallel prefix scans, so chunking isn't the only way to parallelize the computation. Its appeal is that it gives us a particularly hardware-friendly way of doing so.
Let's work through this process in more detail shall we. There are roughly chunks. We'll call the state at the start of chunk , and use for its token representations, now stored as rows.
step 1: update the state between chunks
Rather than applying outer-product updates individually, we can accumulate all of them in one matmul:
This is exactly the same update as before, just grouped together, because
The cost for one chunk is . With chunks, the total cost of updating the boundary states is . So we haven't changed the arithmetic order at all. We've just replaced many small outer products with much larger, more GPU-friendly matmuls.
step 2: compute the outputs inside a chunk
Now consider a query in the current chunk. It needs to attend to two sets of tokens: everything in previous chunks, and the earlier tokens (including itself) in the current chunk.
Those two parts can be computed separately. All previous chunks have already been summarized into . Their contribution is simply
which costs per chunk. For tokens within the current chunk, we can use ordinary causal token-token interactions, but the attention matrix is only rather than :
Here is a binary causal mask, and denotes elementwise multiplication. This part costs per chunk.
Putting both contributions together,
I think this equation captures the whole algorithm nicely. The first term reads the compressed memory of all earlier chunks. The second handles the token-level causal interactions that haven't yet been folded into that memory.
Summing the costs over all chunks gives
Compare this with for full attention. If is kept much smaller than , we retain the favorable sequence-length scaling while exposing dense matmuls inside each block.
The limiting cases help us understand the trade-off: gets us back to the token-wise recurrent form, gets us the full parallel, quadratic causal form. Intermediate chunk sizes, often around 64, 128, or 256 in practical implementations, trade a shorter cross-chunk dependency chain against more work within each chunk.
Notice that chunking doesn't eliminate recurrence. The state still moves from chunk to chunk. We've reduced the naive sequential chain from token steps to roughly chunk steps, and made the work at each step much more suitable for GPU hardware.
mental model
Softmax attention:
all tokens x all tokens
good GPU shape, but quadratic in sequence length
Recurrent linear attention:
token -> token -> token -> token
linear in sequence length, great for decoding, poor GPU utilization for naive training
Chunkwise linear attention:
chunk -> chunk -> chunk
inside each chunk: parallel dense matmuls
between chunks: compact recurrent state
hardware performance
Songlin comments on the speedup achieved after implementing both the recurrent and chunkwise version in Triton:
chunkwise parallel approach consistently outperforms the recurrent baseline...this performance advantage grows more pronounced under two key conditions: as sequences get longer and as head dimensions increase.
Why? Songlin points to two issues (1) existing recurrent implementations process token-by-token, relying instead on both batch and head dimension to saturate GPU cores:
While this strategy worked well with moderate sequence lengths and larger batch sizes, it faces challenges in modern training scenarios. Today's models increasingly work with longer sequences or larger model parameters, often necessitating smaller batch sizes for memory efficiency. This shift was notably highlighted in the FlashAttention2 paper, which identified sequence-level parallelism as crucial for training.
when batch size x head dim is small, there isn't enough parallel work to keep modern GPUs fully utilized.
(2):
Tensor cores are designed around matmuls offering up to >16x speedup compared to other operations with equivalent FLOP counts. Recurrent implementations, despite requiring fewer total FLOPs, struggle to effectively leverage these hardware accelerators...Our chunkwise implementation, in contrast, restructures the computation to maximize use of tensor cores, achieving better real-world performance despite higher theoretical FLOP counts. This performance analysis illustrates a crucial principle in modern hardware-efficient deep learning: raw FLOP counts don't always translate directly to wall-clock time. The ability to leverage specialized hardware accelerators and maintain high GPU utilization often matters more than theoretical operation counts.
the catch: a fixed-size memory
We've addressed the computational side. But there's a more fundamental limitation of vanilla linear attention, one which the associative-memory perspective makes quite clear. Recall that our state is just
If we try to retrieve the value associated with some key , we get
Let's assume the keys are normalized. If they were also perfectly orthogonal, then whenever , while . We'd retrieve almost perfectly. But real keys aren't orthogonal, and there's only so much room in a -dimensional key space. As we add more associations, the other terms don't disappear:
leading to a memory-overload problem. Multiple keys overlap, so retrieving one association also picks up contributions from others. Naturally, compressing an arbitrarily long history into a fixed-size state necessarily limits what we can preserve. So we want to build the memory with care. So what does vanilla linear attention do, well its update function is append-only...
Every new token adds another association, even if its key is very similar to something we've already stored. There's no notion of removing stale information or correcting an existing value. The memory simply keeps accumulating writes.
first, give the memory a way to forget
Probably the simplest idea is to decay the old state before adding a new association. Language modeling has a strong recency bias anyway, so it's reasonable to expect that more recent information will often matter more.
We can start with a fixed decay factor:
Expanding the recurrence shows what this does:
Older associations are exponentially downweighted. A similar decay mechanic was used by MiniMax last year in their Lightning Attention. The obvious limitation is that the same forgetting rate applies regardless of what we're processing. Sometimes old information should be discarded quickly; sometimes we'd like to retain it for a long time.
So let's make the decay data dependent:
Now can be predicted from the current token representation, much like we predict , , and . Instead of having one fixed timescale, the model can decide how much of its previous state to retain at each step. This broader style of gated state decay appears in architectures such as Mamba-2 and mLSTM.
We can easily take this one step further. Why should every part of the state be forgotten at the same rate? In a matrix-valued memory, we could apply more fine-grained decay:
where is a gate with the same shape as . Gated Linear Attention (GLA) explores this direction, using structured gates rather than an arbitrary dense matrix of unrelated decay factors.
One must be careful however. That structural constraint matters. A completely unrestricted elementwise gate would make it difficult to retain the efficient chunkwise computation we just spent so much time developing. We want more expressive memory updates, but we still need to be able to train them efficiently.
We're definately improving on the original design, we're now able to decay parts of the state in a data-dependent way. But we're still just adding to whatever remains of the memory. What if a key has already exists in memory and we'd like to change its value rather than pile another value on top?
DeltaNet: from adding memories to editing them
Suppose we're about to write a new key-value association . Before writing, why not ask what our memory already returns for that key?
We want the memory to return , but right now it returns . Rather than adding the full value again, we could write only the difference:
This is the delta rule. The term in parentheses is the retrieval error, and controls how strongly we correct it. Compare it with vanilla linear attention:
The difference is small algebraically but coneptually very important. Vanilla linear attention writes without checking what's already there. DeltaNet uses the current memory to determine what needs to change.
We can see the overwrite behavior more explicitly. Assuming , reading back the same key after the update gives
So preserves the old retrieved value, replaces it with the new target along that key direction, and values in between interpolate between the two.
Equivalently, we can expand the update into an erase-and-write form:
The first term adjusts what the old memory says along ; the second writes the new association. Obviously the state size hasn't changed, we're just giving the model a much more useful way to manage its capacity. This doesn't magically eliminate all interference between correlated keys. Editing one direction in a finite-dimensional memory can still affect others. But it is a meaningful step beyond an append-only sum of outer products.
Gated DeltaNet
At this point we have two useful mechanisms. Gated linear attention can forget old state, while DeltaNet can correct an existing association. They address slightly different problems, so it's natural to combine them.
Start by decaying the previous memory with a data-dependent scalar gate :
Then perform the delta-rule update against that decayed memory:
Substituting gives
This is Gated DeltaNet. The roles of the two gates are different: decides how much of the old memory survives, while decides how strongly we update the association at the current key.
One detail worth noticing is the order of operations. The prediction used by the delta rule is now
not . We're correcting what the memory returns after forgetting, rather than what it returned before.
It's always nice when things make sense intuitively. This combination means we can make room by decaying old information, and we can use that room more carefully by writing prediction errors rather than unconditional additions. I feel like the next step should almost feel routine at this point, we've combined the two mechanisms together, but now someone realizes that the forgetting factor could be even more fine-grained. Right now we've got one scalar gate per attention head, why stop there?
Kimi Delta Attention
Kimi Delta Attention (KDA), used in Kimi Linear and Kimi K3, takes Gated DeltaNet and makes the decay more fine-grained. The succession is very simple, GDN uses one forget gate per head. KDA uses one forget gate per channel within a head.
It's worth making the multi-head setup explicit here. Each head gets its own projected queries, keys, and values, and maintains its own recurrent memory. With key/query dimension and value dimension , we've been using the convention
so the output is . With heads, the total recurrent-state size per layer is , not a single matrix. (KDA is also commonly written with the state transposed; I'm keeping our original convention to avoid switching orientations halfway through.)
In Gated DeltaNet, is a scalar, so every entry of a head's memory gets multiplied by the same number. In KDA, it becomes a vector over key channels:
We can express the corresponding decay as a diagonal matrix. First, decay the old memory:
The multiplication is on the right because, with our convention, the key dimension indexes the columns of . Each key channel can therefore retain or forget information at its own rate. Then apply the same delta-rule correction as before:
Expanding both steps gives the full recurrence:
The two gates still do what they did in Gated DeltaNet. controls the correction at the current key. But can now preserve some key channels while aggressively decaying others, rather than treating the whole head as one unit.