A KV cache retains records for historical tokens. A recurrent model instead updates a fixed-shaped state after every token. Decode no longer has to scan every historical record, but exact item-by-item access is lost. Gated DeltaNet studies how to write, revise, and forget within that limited state.
Treat the state as a small associative memory
Let $S\in\mathbb R^{d_v\times d_k}$, with key $k$ and query $q$ as $d_k$-dimensional columns and value $v$ as a $d_v$-dimensional column. Reading means computing $Sq$.
An additive write uses $S_t=S_{t-1}+v_tk_t^\top$. Repeated keys accumulate rather than replace their old values. In a scalar example, writing 2 and then 6 returns 8, not the latest value 6.
The delta rule reads the current value and writes the error:
$$ S_t=S_{t-1}+\beta_t(v_t-S_{t-1}k_t)k_t^\top. $$
For $\|k_t\|_2=1$, reading along that key afterward gives:
$$ S_tk_t=(1-\beta_t)S_{t-1}k_t+\beta_tv_t. $$
This interpolates between old and new values. At $\beta=1$, the value along that direction is replaced. Orthogonal directions receive no delta correction. Similar, nonorthogonal keys can interfere; the state is not an exact dictionary.
Why add forgetting?
Correcting only the current key cannot quickly clear other stale information. Gated DeltaNet first decays the state with $\alpha_t$, then corrects the prediction made by that decayed state:
$$ \bar S_t=\alpha_tS_{t-1},\qquad S_t=\bar S_t+\beta_t(v_t-\bar S_tk_t)k_t^\top. $$
Equivalently:
$$ S_t=\alpha_tS_{t-1}(I-\beta_tk_tk_t^\top)+\beta_tv_tk_t^\top,\qquad o_t=S_tq_t. $$
Do not omit $\alpha$ inside the error. Once the old state has decayed, the correction must target the post-decay prediction.
There is also an optimization interpretation: starting from $\bar S_t$, take one gradient step of size $\beta_t$ on the local squared loss $\frac12\|Sk_t-v_t\|^2$. This updates a fast state within a forward pass, not the model's parameters through an extra optimizer step. Outer-loop training still learns projections and gates.
Work through a two-slot state
Take $d_v=1$ and $d_k=2$, so $S=[2,10]$ is a single row; let $k=[1,0]^\top$, new value $v=6$, and gates $\alpha=0.5,\beta=0.25$.
| Step | Result |
|---|---|
| Decay the complete state | $[1,5]$ |
| Read with the key | $1$ |
| Prediction error | $6-1=5$ |
| Write along the key | $0.25\times5[1,0]=[1.25,0]$ |
| New state | $[2.25,5]$ |
The second slot receives no delta correction but still decays. Using the pre-decay value 2 in the error incorrectly produces [2,5].
import math
def gated_delta_step(state, key, value, decay, write):
if not key or len(state) != len(value) or not state:
raise ValueError("Invalid state, key, or value shape")
if any(len(row) != len(key) for row in state):
raise ValueError("State rows must match key dimension")
scalars = [decay, write, *key, *value, *(entry for row in state for entry in row)]
if any(not math.isfinite(entry) for entry in scalars):
raise ValueError("Expected finite inputs")
if not 0 <= decay <= 1 or not 0 <= write <= 1:
raise ValueError("This example uses gates in [0, 1]")
if not math.isclose(sum(entry * entry for entry in key), 1.0, abs_tol=1e-9):
raise ValueError("This example assumes a unit key")
decayed = [[decay * entry for entry in row] for row in state]
prediction = [sum(entry * key_entry for entry, key_entry in zip(row, key)) for row in decayed]
return [[entry + write * (target - old) * key_entry
for entry, key_entry in zip(row, key)]
for row, target, old in zip(decayed, value, prediction)]
state = gated_delta_step([[2.0, 10.0]], [1.0, 0.0], [6.0], 0.5, 0.25)
assert state == [[2.25, 5.0]]
assert gated_delta_step([[2.0, 10.0]], [1.0, 0.0], [6.0], 1.0, 1.0) == [[6.0, 10.0]]
This reproduces only the recurrence; it omits input projections, short convolutions, normalization, output gating, and the GPU kernel. Endpoints 0 and 1 illustrate limiting behavior; inspect each model's actual parameterization. Some extensions permit a larger writing range, where this interpolation interpretation no longer applies directly.
Decode is recurrent; training does not need a per-token Python loop
Each step is $S_t=S_{t-1}A_t+B_t$. Combining two steps gives:
$$ S_{t+1}=S_{t-1}A_tA_{t+1}+B_tA_{t+1}+B_{t+1}. $$
Composition is ordered; the matrices cannot be freely swapped. The paper uses chunking and structured transformations to turn within-chunk work into hardware-friendly matrix operations. It neither removes every dependency nor treats the slow loop above as a high-throughput trainer.
Check agreement among single-step recurrence, chunked prefill, and save-state/resume execution. Reset between independent samples, but retain state when continuing the same request. Incorrect boundaries leak across samples or erase the prefix.
What does fixed state actually save?
A hypothetical layer with 16 heads, each retaining a $128\times128$ FP32 state, uses 1 MiB regardless of prefix length. Another hypothetical attention layer with 16 KV heads, dimension 128, storing BF16 K and V for 8,192 positions uses 64 MiB.
This is not an equal-capability performance comparison. It illustrates different storage growth. Recurrent state still occupies memory; training stores or recomputes activations; full-attention layers in a hybrid still retain length-dependent KV caches.
| Desired property | Tradeoff |
|---|---|
| Fixed-shaped decode state | Compressed history, without guaranteed exact token recall |
| Targeted delta correction | Correlated-key interference and limited state capacity |
| Global decay | Fast clearing can also erase useful information |
| Hybrid attention | Some direct historical access, with its storage and compute costs |
Checked 2026-10-08 against Gated Delta Networks and the authors' code. Full training and GPU speed have not been reproduced. Continue with Qwen to see how recurrence combines with attention and FFN/MoE layers.