Reframing softmax attention to see the bottleneck#
For a query at position , causal softmax attention computes the output as a weighted sum over all previous tokens:
the output at is a blend of every past value , weighted by how well each token's key matches the current query , where the match scores are normalized across all past tokens so the weights sum to 1.
The two costs (the reason linear attention exists)#
Compute. To produce all outputs for a sequence of length , you compute for every pair. That's dot products. Double the sequence, quadruple the work.
Memory. To compute you need every past and . Nothing lets you throw any away, because the next query might match an old key strongly. So at decode you cache all past keys and values (the KV cache), and it grows linearly with sequence length.
Why you can't compress the cache: the exponential. Because sits between and , the weight for token can't be computed until is known. You cannot pre-summarize the past into a small fixed state, because the past has to wait for the future query before it knows how much to contribute.
The question that sets up everything: what if we removed the and replaced it with something that lets us reassociate the math, so the entire past collapses into a single fixed-size state each new token updates in constant work?
Linear attention, folding the past into a fixed state#
Why the exponential blocks compression#
is non-separable. There's no way to split it into a piece depending only on times a piece depending only on . Query and key are fused inside one nonlinear function, so no useful precomputation on keys alone is possible.
What becomes possible without it#
Drop the exponential, let the weight be the plain dot product . Now query and key are only multiplied, which is separable, so the sum can be rearranged:
Since is a scalar, move it around freely:
Substitute back and pull out of the sum (it doesn't depend on ):
Define the state:
This is the compression. is a single matrix of fixed size . It does not grow with . It's a running sum:
Each new token costs constant work: form the outer product , add it to the running state, read out with . No scanning, no growing cache. Compute drops from to linear, memory becomes a constant state.
Caveat: real linear attention keeps a small nonlinearity via a feature map , so the weight is . Still separable, so the same state trick applies with in place of . What matters is separability, not zero nonlinearity.
The outer product is the storage primitive#
Everything about the memory rests on one operation: the outer product . This is what writes a single key-value pair into the matrix. Build up its behavior in three steps.
Step 1: an outer product turns a pair into a matrix. is a column vector times a row vector, which produces a matrix (not a number). Each entry:
Every component of is paired with every component of . With and :
Step 2: the key decides where the value is filed. All the content above landed in column 1, because has its weight in position 1. Change to and the same value files into column 2:
Think of filing a document. The key is the address (picks which column/direction the value is written into); the value is the contents (what gets stored there). One outer product is one filled slot, holding the binding "."
Step 3: reading a slot back with a query. Multiply the slot by a query vector . It simplifies:
is a single scalar measuring how aligned the query is with the stored key. It acts as a volume knob on the value: query along , get at full strength; query perpendicular to (), get nothing; partial alignment gives a scaled-down . Check with the slot from Step 1, queried by :
Putting many slots together. A real memory holds many pairs. Add their outer products into one matrix:
Addition preserves the shape, so stays fixed-size no matter how many pairs you store. Each pair was filed into its own key-direction, and they coexist in one matrix. Read the whole memory at once by querying with a key :
Every stored value comes back, each weighted by how much its key aligns with your query . Values whose keys point toward dominate; the rest fade out. That is a soft lookup, and it's the entire reason the matrix behaves like an associative memory.
Worked storage example. Store and :
Different keys wrote into different columns, so values never collided. Reading with : . Reading with : . Clean, because the keys are orthogonal.
Interference appears when keys are not orthogonal. If , then querying gives : the wrong value leaks in. Non-orthogonal keys write into overlapping columns, so reads mix.
Where are the savings? The keys are not stored#
The key is consumed at write time and discarded. In , once 's contribution is folded in, ceases to exist as a separate object. Retrieval never matches against stored keys, it just multiplies by the query, . The key-matching was already baked into the geometry of at write time.
| KV cache (softmax) | Linear attention | |
|---|---|---|
| Stores | every , size | one matrix , size |
| Grows with ? | yes | no |
| Recovery | exact, fully separable | lossy, superimposed |
The keys aren't stored twice, they're stored zero times. They were spent to build and discarded. The price is that summed outer products can't be pulled apart again: lossy compression.
is attention, in the opposite association order#
Substitute into the read and push inside:
That last form is exactly standard (softmax-free) attention. Same sum, same scores, same weighted blend. The only difference is when you group:
- Score-first (softmax) can't pre-sum, because scores need the future query. Holds the full list. Memory and work grow with .
- Sum-first (linear) pre-sums the past into before the query arrives. Holds one fixed matrix. Constant memory and work. The softmax's is exactly what forbids the regrouping. Linear attention insists on a regroupable form and accepts the blur as the cost.
DeltaNet, giving the memory an eraser#
Flaw in plain linear attention: it only ever adds. It never removes. Two problems:
- Interference. Non-orthogonal keys leak into each other on read.
- No overwrite. The same key with two values stores a smeared superposition , with no way to replace the old binding.
DeltaNet's fix: before writing, check what memory already returns for this key, and write only the correction.
The gap is the delta. Write only that correction, scaled by write strength :
Expand and group the terms:
The erase operator strips out existing content along the direction before the fresh value goes in. acts like a learning rate: 0 leaves memory untouched, 1 fully wipes and replaces along .
Note: vs #
- is a scalar (squared norm). Never zero for a nonzero vector, so the erase always subtracts something.
- is a matrix (outer product), the thing inside .
Worked collision example#
Tokens (note , a deliberate collision), throughout:
Plain linear attention (add only):
Column 1 became : the values smeared together.
DeltaNet (erase before write). At token 3, . Apply to first:
Column 1 (token 1's stale value) is zeroed; column 2 (token 2) untouched. Then write :
Reading with gives cleanly. The stale is gone, not smeared.
Honest limit: has fixed capacity . It can't hold unlimited separable bindings. Past capacity, something gives: overwrite or interference. This is the fundamental price of refusing to let memory grow, and it's exactly why Kimi Linear is a hybrid (3 KDA layers : 1 full-attention layer): the full-attention layer keeps an exact view as a safety net.
Gated DeltaNet, a fading knob#
Flaw in DeltaNet: the erase only touches the single direction . Every other direction sits frozen forever. No general fading, so stale content clogs the fixed-capacity matrix.
Fix: a decay gate that shrinks the whole state before each update:
is a scalar computed per token. It uniformly scales down everything stored, before the targeted erase and write.
- : nothing fades, back to plain DeltaNet.
- : entire past wiped, restart from this token.
- : gentle decay, ~2% lost per step, soft half-life.
Because is per token, the model learns when to hold and when to flush (like an LSTM forget gate, applied to a matrix-valued memory).
Decay chain. A value written at step and read at step has been multiplied by . That product gives each memory a controllable lifespan.
Position for free. How much a value has faded signals how long ago it was written. So the gates encode recency and ordering, which is why these architectures can drop explicit positional encodings like RoPE.
Flaw that motivates KDA: is a single scalar. Every feature dimension decays at the same rate. But different features live on different timescales (document-wide topic vs. local detail). One shared knob is forced into a compromise.
Concept 5: Kimi Delta Attention, per-channel forgetting#
The one change: replace the scalar with a diagonal matrix , where is a vector with one decay value per channel:
holds on its diagonal. Each key-channel of the memory now fades at its own rate. The erase and write are unchanged; only the forgetting got finer.
Numerical contrast#
State .
Gated DeltaNet, scalar (both channels lose 30%, no choice):
KDA, per-channel (each column its own rate):
Channel 1 keeps 90%, channel 2 is halved. One update, two forgetting speeds.
Why it matters#
Long-range channels (overall subject) want and persist. Short-range channels (local detail) want small and flush fast. A single scalar can't serve both. Per-channel decay lets each feature choose, making better use of the fixed-size memory, which is KDA's headline for long-context recall.
Richer position. With per-channel decay, each channel carries its own decay timeline, so the memory holds a multi-rate sense of position. This is why KDA needs no explicit RoPE at all, and the positional signal is more expressive than a scalar could give.
The full arc#
| Stage | Update | What it adds |
|---|---|---|
| Softmax attention | exact recall, but growing cache | |
| Linear attention | fixed matrix via associativity (lossy) | |
| DeltaNet | erase, so writes overwrite not smear | |
| Gated DeltaNet | scalar decay, stale memory fades | |
| KDA | per-channel decay |
Reference#
Kimi Linear: An Expressive, Efficient Attention Architecture (arXiv:2510.26692)