Back to blog
ResearchModels

Can We Trade MLPs for Engrams?

Needle keeps its facts in hashed n-gram tables instead of feed-forward layers. 70.8M of its 121M parameters live there, and a token reads 30 rows of them and multiplies none. This post is the lookup, the gate, where the sites sit, and what the tables buy.

HN

Henry Ndubuaku

||12 min read

A transformer holds two kinds of knowledge. Attention reads what is in the context. The weights hold what is not: that a thermostat takes a temperature, that living room is a place and warm white is a colour. In a standard transformer those facts live in the feed-forward layers, which act as key-value memories, and every token multiplies through all of them. Needle replaced its feed-forward layers with the Hadamard MLP, so the facts need another home. The engram is that home: hashed n-gram tables that a token reads by index.

Two kinds of parameter

A parameter in a matrix is read by every token. A parameter in a table is read by the tokens that need it.

For a matrix WRm×nW \in \mathbb{R}^{m \times n} and a token xx, the product WxWx costs mnmn multiply-adds and moves mnmn weights from memory, every token, whatever the token is. For a table TRS×cT \in \mathbb{R}^{S \times c} read at row ii, the fetch T[i]T[i] costs cc numbers moved and no multiply-adds, and it costs the same for any SS:

cost(Wx)=mn per token,cost(T[i])=c per token, for any S.\mathrm{cost}(Wx) = mn \text{ per token}, \qquad \mathrm{cost}(T[i]) = c \text{ per token, for any } S.

Capacity in a matrix costs every token. Capacity in a table costs only the tokens that read it. So the design is to keep the matrices small and let the tables be large. The engram is the table: a conditional memory of hashed n-gram rows, in Needle's case sized for a phone.

two ways to hold a parameter, token 1left: a matrix, every cell for every token · right: a table, six rows by indexW · x: m × n multiply-adds, m × n weights movedT[i]: no multiply-adds, six rows movedfor any number of rowsa matrix is read in full by every token · a table is read six rows at a time
Two kinds of parameter. Each token sweeps through the whole matrix on the left. On the right the token picks six rows and reads only those; the table can be a thousand times taller and the token would still read six rows.

The lookup

A table needs a row index, and the index has to come from the tokens with no learned computation in the way. The engram hashes the last few tokens. For a position nn with tokens tn,tn1,t_n, t_{n-1}, \dots and an order oo, the row is

io(n)=mix(tn,tn1,,tno+1)modS.i_o(n) = \mathrm{mix}(t_n, t_{n-1}, \dots, t_{n-o+1}) \bmod S.

mix\mathrm{mix} is the FNV hash, a few integer operations per token. Start from a seed aa, then for each token in the window:

a(atj)16777619(mod232),then aa(a15).a \leftarrow (a \oplus t_j) \cdot 16777619 \pmod{2^{32}}, \qquad \text{then } a \leftarrow a \oplus (a \gg 15).

Needle uses orders 2 and 3: one row indexed by the last two tokens, one by the last three. Each order has three heads with different seeds, so a site has six tables, each with S=18,432S = 18{,}432 rows of 128 numbers. The six rows a token fetches are concatenated into one vector

e=[T1[i1], T2[i2], , T6[i6]]R768,e = [\,T_1[i_1],\ T_2[i_2],\ \dots,\ T_6[i_6]\,] \in \mathbb{R}^{768},

which is exactly the model width. A row that would reach past the start of the sequence is zeroed.

at token 4: hash the last 2 and the last 3 tokensacc = seed; for each token: acc = (acc xor token) · prime; row = acc mod slotssetthelivingroomlightstowarmwhiteorder 2 · row 8order 3 · row 014 of the 18,432 rows of each table are drawneach row is 128 numbers3 heads per order, 6 tables6 rows per token: 768 numbersno arithmetic to find them
The lookup. The last two tokens and the last three tokens are each hashed to a row of a table. Different heads use different seeds, so a collision in one table is not a collision in another. The rows are fetched by index; nothing is multiplied to find them.

The vocabulary has 8,192 tokens, so there are 67 million possible bigrams and a table has 18,432 rows. Most rows are shared by many n-grams. Three things make that work:

  • the rows are learned, so a row becomes whatever the n-grams that land on it need, and the common n-grams win;
  • the three heads of an order have different seeds, so two n-grams that collide in one head almost never collide in the other two, and the concatenated ee still tells them apart;
  • the gate below decides, per token, how much of the fetched row to believe.

This is the hashing trick applied to n-grams, close to hash embeddings with several hash functions per key, and the table read is the gather that product-key memories also rely on, without the learned key search.

The gate

The fetched vector ee is projected to a key and a value, two ordinary 768×768768 \times 768 matrices:

k=Wke,v=Wve.k = W_k\, e, \qquad v = W_v\, e.

The value is smoothed over earlier positions with four learned taps at stride 3, so the value at a position also carries the rows fetched three, six and nine tokens back:

vnj=03τjvn3j.v_n \leftarrow \sum_{j=0}^{3} \tau_j \odot v_{n-3j}.

Then the gate compares the key with the residual stream xx of the block the site belongs to, and adds the value in proportion:

α=σ ⁣(x^k^d),xx+αv,x^=xrms(x), k^=krms(k).\alpha = \sigma\!\left(\frac{\hat{x} \cdot \hat{k}}{\sqrt{d}}\right), \qquad x \leftarrow x + \alpha\, v, \qquad \hat{x} = \frac{x}{\mathrm{rms}(x)},\ \hat{k} = \frac{k}{\mathrm{rms}(k)}.

If the key points the way the stream already points, α\alpha is near one and the value is written in. If it does not, α\alpha is small and the row is mostly ignored. A hash collision is a key that does not agree with the stream, and a gate that stays closed. The model never has to know which rows are shared; it learns keys that only open for the right context.

the gate: how much of the memory to addα = sigmoid( x̂ · k̂ / √d ) · x̂ the stream, k̂ the memory key, each divided by its rmsx̂ the streamk̂ the keyagreement 0.00 · gate α = 0.50x ← x + α · va row that matches what the streamalready says gets written in;a hash collision that does notmatch is mostly ignored
The gate. Each fetched row is projected to a key and a value. The key is compared with the residual stream; the closer they point, the more of the value is added. This is what makes hashing safe: a wrong row, fetched because two n-grams share a slot, is a key that does not agree, and a gate that stays closed.

Where the sites sit

Needle 3 has five sites, at layers 3, 7, 11, 15 and 19 of its 20 blocks. Every site has its own six tables and its own WkW_k, WvW_v and taps. The hash indices depend only on the tokens, so they are the same at all five sites; what differs between sites is the rows, and what the stream looks like when the key is compared with it. A site fires at the start of its block, before that block's attention, so the attention and the mixer of the block work on a stream that already contains the fetched facts.

The sites belong to blocks, and the intelligence ladder keeps them with their blocks. needle build --layers 8 keeps the sites whose layers survive and drops the other tables from the file, so a shallower model is also a smaller file.

The cost

Per site, with d=768d = 768:

per siteparametersmultiply-adds per tokennumbers moved per token
six tables, 6×18,432×1286 \times 18{,}432 \times 12814.16M0768
WkW_k and WvW_v1.18M1.18M1.18M
taps and gate3K4K3K

Over five sites the engram is 76.7M parameters, 70.8M of them in tables, for 5.9M multiply-adds per token. The dense feed-forward layers it replaces, twenty of them at d=768d = 768, would be 94M parameters for 94M multiply-adds per token. The tables hold 92% of the engram's parameters and cost nothing to multiply; a token reads six rows of the 110,592 in a site, 768 numbers out of 14 million.

Results

Four questions, each answered with one run per setting on the SYNTH mixture, validation cross-entropy in nats per token, general text and tool calls scored separately.

Does it help. The same model trained twice for 4,000 steps, once with two memory sites of 8,192 rows and once without, sizes at 16 bits:

file sizeaddedvalidation CEloss change
without the engram71 MB2.204
with the engram90 MB+27%2.162−1.9%

1.9% of the loss for 27% more file, almost all of it table rows that no token multiplies.

How many rows. An 18-layer, d=1024d = 1024 model with sites at layers 2 and 10, trained for 3,000 steps, tables of three sizes:

rows per tablefile sizeaddedtext CEchangetool-call CEchange
8,192185 MB2.0220.615
18,432227 MB+23%1.989−1.6%0.601−2.3%
28,672269 MB+45%1.977−2.2%0.595−3.3%

Where the first site sits. The same model with the second site fixed at layer 10 and the first moved:

first site at layertext CEtool-call CE
12.0370.623
22.022, 2.0260.615, 0.619
32.0300.619
42.0360.621
52.0340.622

Layer 2 was run twice; the two runs differ by 0.004, 0.2% of the loss, which is the noise floor for this series. Layer 1 is the only clearly bad position, 0.7% above layer 2; from layer 2 on, the differences are within that noise.

rows per table1.962.002.042.0228,1921.98918,4321.97728,672slots per table · sites at layers 2 and 10where the first site sits1.962.002.042.0371, 102.0262, 102.0303, 102.0364, 102.0345, 10site layers · 8,192 slots
Two knobs, 18 layers, 3,000 steps each. Left: more rows per table keeps paying, 2.2% of the loss from 8,192 to 28,672 rows. Right: which layer holds the first site moves the loss by 0.7% at most, and two identical runs at layers 2 and 10 differ by 0.2%. Layer 1 is the one place it should not be.

Sites against rows. At 20 layers, two layouts of the tables:

sites × rowsfile sizeaddedtext CEchangetool-call CEchange
5 × 28,672472 MB1.9760.746
10 × 18,432576 MB+22%1.966−0.5%0.675−9.6%

More sites with fewer rows each beat fewer sites with more rows, on tool calls by a wide margin, for a file 22% larger. Needle 3 ships five sites of 18,432 rows, where the tables at 2 bits are already two thirds of a 29 MB file; doubling the sites would add about 19 MB.

What the bytes buy, against the other knobs in the same 18-layer series. From 8,192 to 28,672 rows adds 45% to the file for 2.2% less loss. Widening the value heads from 64 to 128 adds 46% for 6.5% less loss, nearly three times as much for the same bytes. But the value bytes are multiplied by every token, and the table bytes are not. Table rows are the cheapest bytes to run, not the cheapest bytes to store, and Needle spends its byte budget on both.

On the device

In the shipped needle3.cact the five table tensors are 2-bit: 110,592 rows of 128 each, 17.7 MB of the 29 MB file. A token reads 30 rows, 3,840 numbers, 960 bytes at 2 bits, 32 bytes per row. Each row is decoded on fetch through a 4-entry codebook and the six rows are concatenated straight into ee. The hash is a few integer operations per table. WkW_k and WvW_v are 2-bit as well, and the engine runs both projections from one quantised copy of ee. The projected value is kept in an int8 cache per site, so the taps read the values three, six and nine tokens back instead of recomputing them.

The dense feed-forward layer, had it stayed, would have been 94M weights moved per token. The engram moves 5.9M through its projections and 3,840 through its tables. The rest of the tables stay where they are until a token asks for them.

What it gives up

  • A row is indexed by the surface form of an n-gram. set the lights and turn on the lights are different rows; a paraphrase is a different key, and the model has to learn the fact twice or let attention carry it.
  • Rows collide. The gate and the multiple heads make collisions cheap, not free; a rare n-gram that shares a row with a common one mostly reads the common one's content.
  • The tables are two thirds of the file. On a device where flash is the limit and the model is never sliced, that is the cost of capacity that costs no arithmetic.
  • The projections are ordinary matrices. 8% of the engram's parameters still cost 5.9M multiply-adds per token.
  • The tables hold what the training data put in them. The attention-only study found that what a small feed-forward layer contributes is mostly parameter count, and the engram is the same bet made in a cheaper place; whether that holds for knowledge-heavy text at larger scale is the open question it left.

References

  1. Cheng et al., Conditional Memory via Scalable Lookup: A New Axis of Sparsity for Large Language Models, 2026.
  2. Geva et al., Transformer Feed-Forward Layers Are Key-Value Memories, 2020.
  3. Weinberger et al., Feature Hashing for Large Scale Multitask Learning, 2009.
  4. Svenstrup et al., Hash Embeddings for Efficient Word Representations, 2017.
  5. Lample et al., Large Memory Layers with Product Keys, 2019.
  6. Eastlake, Hansen, Fowler, Noll and Vo, The FNV Non-Cryptographic Hash Algorithm, IETF Internet-Draft.
  7. Ndubuaku et al., A Controlled Study of Attention-Only Transformers, 2026.