Why this matters
At hidden size 512, a transform costs only 4,608 butterfly pairs. Learned parameters are three 512-value diagonals instead of two large dense matrices.
Needle 2 is a Simple Attention Network specialized for tool calling. It is small by current language-model standards—about 45 million parameters—but it is not a reduced Llama. Its memory and residual structure are deliberately different.
This tutorial follows the public deployment archive used by needle.js:
| Property | Value |
|---|---|
| Vocabulary | 8,192 |
| Hidden dimension | 512 |
| Layers | 27 |
| Query heads | 8 × 64 |
| Key/value heads | 4 × 64 |
| mHC lanes | 4 |
| Engram orders | 2 and 3 |
| Engram sites | layers 2 and 15 |
| KV window | 256 tokens |
| Maximum sequence | 2,048 tokens |
token id │ ▼ tied CQ embedding × √512 ────────┐ │ │ ├── hashed 2/3-gram tables ───┤ (at layers 2 and 15) │ │ ▼ │ broadcast to four mHC lanes │ │ │ ├─ 27 × [lane pre-mix │ │ + gated GQA │ │ + Hadamard MLP │ │ + Sinkhorn routing] ◄┘ │ mean lanes → final RMS norm → tied embeddingᵀ → 8,192 logits√512.Needle stores a learned offset rather than a conventional RMSNorm scale:
function zcrms(x: Float32Array, offset: Float32Array) { let squareSum = 0; for (const value of x) squareSum += value * value; const inverseRms = 1 / Math.sqrt(squareSum / x.length + 1e-6);
return Float32Array.from( x, (value, index) => (1 + (offset[index] ?? 0)) * value * inverseRms, );}Initializing the stored offset to zero makes the effective scale one. The same operation normalizes layer input, queries, keys, post-attention output, the MLP input, and final hidden state.
Eight query heads share four key/value heads, so each KV head serves two query heads. For one token at logical position p:
q = Wq · norm(x) shape (8, 64)k = Wk · norm(x) shape (4, 64)v = Wv · norm(x) shape (4, 64)q = ZCRMS(q); k = ZCRMS(k)q, k = RoPE(q, k, p)RoPE splits each 64-vector into two 32-value halves. It rotates x[d] with x[d + 32]; it does not use the interleaved even/odd layout found in some models.
For query head h, choose KV head floor(h / 2):
score(t) = dot(q[h], K[kvHead, t]) / √64weight = softmax(score)headOut = Σ weight(t) · V[kvHead, t]A learned projection gates every output channel before the final attention projection:
gated = headOut × sigmoid(Wgate · attentionInput)attn = Wo · gatedx = skip + sigmoid(attentionGate) × ZCRMS(attn)The cache is int8 in the deployment engine. Each key and value vector gets its own absolute-maximum scale, so a cached element reconstructs as int8 × scale.
There is no large pair of learned feed-forward matrices. Instead Needle uses the normalized Walsh–Hadamard matrix H, three learned diagonals, and SiLU:
z = H · (d1 × norm(x))z = SiLU(d2 × z)z = H · zmlp(x) = d3 × zH is never stored. A fast Walsh–Hadamard transform computes it in n log₂n additions and subtractions:
function fwht(values: Float32Array) { for (let stride = 1; stride < values.length; stride *= 2) { for (let base = 0; base < values.length; base += stride * 2) { for (let j = 0; j < stride; j++) { const a = values[base + j]!; const b = values[base + j + stride]!; values[base + j] = a + b; values[base + j + stride] = a - b; } } }}Multiply by 1 / √n after each transform to match the orthonormal matrix.
Why this matters
At hidden size 512, a transform costs only 4,608 butterfly pairs. Learned parameters are three 512-value diagonals instead of two large dense matrices.
Quantization bonus
Cactus Quants also rotates weight groups with a Hadamard matrix. Because the transform is symmetric, an engine can rotate each activation group once and dot directly against packed codebook indices.
The model carries four residual lanes x[0…3], each 512 values. At the start of a layer, flatten all 2,048 values and RMS-normalize them:
n = RMSUnit(flatten(x))hpre = sigmoid(a_pre × (Φpre · n) + b_pre + laneOffset)u = Σ hpre[lane] × x[lane]u is the 512-vector sent through the attention/MLP block. One lane per layer receives a +4 pre-offset; the other lanes receive −4, rotating the favored path across layers.
The block returns an update y = block(u) − u. Two mechanisms build the next lanes:
hpost = 2 × sigmoid(a_post × (Φpost · n) + b_post + postOffset)logits = a_res × reshape(Φres · n, 4, 4) + b_resR = Sinkhorn(logits)newX[i] = Σ R[i,j] × oldX[j] + hpost[i] × yTwenty alternating log-space row and column normalizations turn R into an approximately doubly stochastic routing matrix. Log-space normalization matters: a linear-space implementation can underflow and produce NaNs.
Attention remembers exact previous hidden states. Engrams add a different signal: learned rows selected by token n-grams.
For every order and head, Needle starts from a deterministic seed and folds recent token IDs with 32-bit FNV-style multiplication:
hash = seed;for (let back = 0; back < order; back++) { hash = Math.imul(hash ^ history[position - back], 0x01000193) >>> 0;}hash ^= hash >>> 15;slot = hash % 8192;Two orders × two heads produce four table rows of 128 values. Concatenate them into 512 values, then calculate an engram key and value with learned projections. The value also passes through a four-tap causal, per-channel convolution at offsets 0, 3, 6, 9.
At an engram layer:
α = sigmoid(dot(RMSUnit(u), RMSUnit(engramKey)) / √512)u = u + α × engramValueThe tables give the tiny model a large, cheap associative memory without extending the attention window.
The archive declares a 256-token recent window. For long conversations, old KV rows are overwritten in a ring. Tool schemas and early system context would disappear, so the runtime can pin an initial prefix as an attention sink:
visible keys = pinned prefix ∪ latest 256 logical positionsThis keeps memory bounded while preserving the instructions that define which calls are legal.
After layer 27, average lanes, apply final ZCRMS, and reuse the embedding matrix:
logits = embedding · finalHiddenOptional probe heads pool every token/layer cell with learned query vectors. needle.js evaluates confidence pooling online with a numerically stable streaming softmax, so it does not retain all hidden cells. The final action confidence is combined with constrained-token probability.