garagelm.org
hf
the garage intelligence lab · real models, cheap hardware, honest numbers
home  ·  models  ·  notes  ·  competitions  ·  learn  ·  team
01 forward-pass explorer ● live
03 kv-cache visualizer (tbd)
04 attention-head zoo (tbd)
INPUT
causal · predict next
d=4 · heads=2 · d_ff=8
OUTPUT
{{ predWord }}{{ predPct }}
most likely next token
YOU ARE HERE
× N
skip
skip
{{ n.main }}
{{ n.sub }}
{{ sel.tag }} {{ sel.name }}
{{ sel.formula }}
{{ sel.desc }}
OUTPUT SHAPE
{{ sel.shape }}
PARAMETERS
{{ sel.params }}
LEGEND
learned sublayer (attention / FFN)
add & norm (residual + LayerNorm)
fixed / lookup op
residual skip connection
≈ 188 params in this toy · a real 7B model repeats the block ×32 with d_model≈4096.
PIPELINE · CLICK A STAGE
└ stages 2–5 form one transformer block · real models stack N identical blocks ┘
STAGE {{ curIndex }} {{ curName }} {{ curFormula }}
Each token id indexes a row of the embedding table E. We add a sinusoidal positional encoding so positions are distinguishable. The sum x⁰ is the first state of the residual stream.
E[token]
+
PE(pos)
=
x⁰ residual in [{{ seqDim }}]
PE(pos,2i)=sin(pos / 10000^(2i/d)) · PE(pos,2i+1)=cos(pos / 10000^(2i/d))
Project x⁰ into queries, keys, values; each head scores every query against every key, softmaxes (future masked), and mixes values. Heads concat then mix by W_O.
1 · PROJECTIONS  Q=x⁰W_Q · K=x⁰W_K · V=x⁰W_V
Q
K
V
2 · PER-HEAD
dims {{ headDimText }} · √dₖ = {{ sqrtdk }}
scores = QKᵀ/√dₖ (▨ mask)
softmax weights
·V=
head output
vs
without causal mask · hypothetical
hover a score to trace its Q · K rows above · click a visible cell to open that exact derivation in Math · keys: ←/→ query row · H head
without the mask, every token would also average over FUTURE positions: the model could cheat by reading the answer it is meant to predict
where "{{ qWord }}" (q{{ qpos }}) looks · head {{ headNo }} · arc width ∝ weight · click a token to move the query · ▨ = masked
{{ tk.pct }}
query row:
{{ qExplain }}
3 · CONCAT → W_O
concat [{{ seqDim }}]
·W_O=
attention output
Attention output is added back to the residual stream (skip connection), then LayerNorm re-centers & re-scales each row to zero mean / unit variance.
x⁰
+
attn out
→ LN →
normalized
LayerNorm(x) = γ·(x − μ)/√(σ² + ε) + β · per-row μ,σ · γ=1, β=0, ε=1e-5
Position-wise MLP: project each row up to d_ff=8, apply GELU, project back to 4. Same weights at every position, where most parameters live.
ln1 [{{ seqDim }}]
W₁+b₁→
up-project [{{ seq }}×8]
GELU(·)
W₂+b₂→
down-project [{{ seqDim }}]
FFN(x) = GELU(x·W₁ + b₁)·W₂ + b₂ · GELU(x) ≈ 0.5x(1 + tanh[√(2/π)(x + 0.044715x³)])
Second residual add + LayerNorm gives the block output. In a real model this feeds the next of N identical blocks: the residual stream flows straight through.
ln1
+
ffn out
→ LN →
block output
Take the block output at the final position ("{{ lastWord }}"), project against the vocabulary (tied to Eᵀ) for one logit per token, and softmax into a distribution. The argmax is the prediction.
h = final position
·Eᵀ→
logits [vocab=8]
softmax(logits) · NEXT-TOKEN DISTRIBUTION
{{ bar.word }}
{{ bar.logit }} {{ bar.valText }}
Hand-set toy weights: the distribution shows the mechanism, not linguistic accuracy. Greedy decoding takes the argmax, appends it, and runs the whole pass again.
THE MATH, BY HAND
The same numbers as the Pipeline, expanded to every scalar operation.
Pick a query, key, token and activation below; every derivation recomputes live from your input. These are the exact values the matrices in the other tabs are built from: nothing here is collapsed into a matmul.
HEAD
QUERY i
KEY j (≤ i)
1 One attention score · scalar by scalar
Query "{{ mathQword }}" (q{{ mQi }}) scored against key "{{ mathKword }}" (k{{ mKj }}) in head {{ mHeadi }}: a dot product over the head's 2 dimensions, scaled by √dₖ.
{{ l.t }}
2 Softmax over query {{ mQi }}'s scores
Exponentiate each visible score (max subtracted for numerical stability) and normalize so the row sums to 1. The result is the attention-weight row.
KEY
score s
exp(s−max)
weight
k{{ r.j }}·{{ r.word }} {{ r.s }} {{ r.ez }} {{ r.w }}
{{ softInfo }}
3 Head output = weighted sum of values
Each output dimension is the attention-weighted average of that dimension across all visible value vectors: out[i] = Σⱼ wᵢⱼ · vⱼ.
{{ l.dim }}{{ l.t }}
TOKEN (for 4 & 5)
FFN UNIT (for 5)
4 LayerNorm on token "{{ mathTokWord }}"
Standardize the residual vector: subtract the row mean, divide by the row standard deviation. (γ=1, β=0 here, so no rescale.)
{{ l.t }}
{{ l.dim }}{{ l.t }}
5 GELU on FFN unit {{ mFFi }} of "{{ mathTokWord }}"
The feed-forward non-linearity: a smooth gate that lets small negatives leak through and passes positives almost unchanged.
{{ l.t }}
6 Temperature reshapes the final softmax
The same eight next-token logits z, divided by T before the exponent: pⱼ = e^(zⱼ/T) / Σₖ e^(zₖ/T). Small T stretches the gaps between logits apart (sharper), large T shrinks them (flatter): the ranking never changes, only the confidence. This is the knob on the Generate tab.
tokenzz / {{ mTLabel }}e^(z−max)/Tp at Tp at 1
{{ r.word }}{{ r.z }}{{ r.zt }}{{ r.e }}{{ r.p }}{{ r.p1 }}
{{ tempInfo }}
x⁰
1 Embed + Positional Encodingx⁰ = E[token] + PE(pos)
+ =
read
2 Multi-Head Self-Attentionsoftmax(QKᵀ/√dₖ)·V · causal
head 0 · attention weights
head 1 · attention weights
→ writes to stream
each row (a query) is a probability distribution over ≤ its own position · lower-triangular by the causal mask
⊕ add attn output back → LayerNorm=
read
3 Feed-Forward (position-wise MLP)GELU(x·W₁+b₁)·W₂+b₂ · 4→8→4
GELU→ W₂→
⊕ add ffn output back → LayerNorm = block output=
↻ real models stack N identical blocks · the stream flows straight through, each block reading & writing it
out
4 Unembed + Softmaxfinal position "{{ lastWord }}" → softmax(h·Eᵀ)
{{ bar.word }}
{{ bar.logit }} {{ bar.valText }}
AUTOREGRESSIVE GENERATION
Predict one token, append it, feed the whole thing back in. repeat.
A single forward pass predicts one token. Generation is the outer loop: sample from that distribution, stick the token on the end of the sequence, and run the entire pass again on the longer input. Everything you saw in the other tabs runs once per generated token.
sequence forward pass sample next append
DECODING
TEMP {{ genTempVal }}
{{ genHint }}
SEQUENCE
{{ genCount }} / 14 tokens
{{ c.word }}
prompt generated
Context cap reached (this toy runs to 14 tokens). Reset to start over.
NEXT-TOKEN DISTRIBUTION
{{ distSub }}
{{ b.word }}
{{ b.pct }}
ROLL-OUT TRACE
Press Generate (or Step) to roll out tokens: each row records the token that was sampled and the top candidates it was drawn from.
#
SAMPLED
ln p
Σ ln p
TOP CANDIDATES
{{ s.idx }} {{ s.word }} {{ s.p }} {{ s.lnp }} {{ s.cum }} from: {{ s.cand }}
ln p uses the model's untempered (T=1) probability · the running total is the log-likelihood the model assigns its own output
COMPUTE · WHAT GENERATION COSTS
You've now seen both phases of inference. Prefill: all prompt positions computed in parallel, one pass, like the all-position pass on the Training tab. Decode: this loop, strictly one token at a time, because each token's input includes the last output. This toy recomputes every position on every step; real models keep a KV-cache (the K and V rows you saw in attention, stored per layer) so each new token only computes its own row.
{{ c.k }}
{{ c.v }}
{{ c.sub }}
Decode speed is memory-bound, not FLOP-bound: every single token streams all N weights through the chip once, so bandwidth sets the single-stream ceiling and the arithmetic units mostly idle. Serving stacks many users into one batch to share each weight-read: that's why inference is priced per token.
reference numbers for an 8-B-param model in fp16 on one H100-class GPU (~3.4 TB/s memory) · this toy: ~2 kFLOP per token, no cache · your browser tab is a supercomputer for it
TRAINING PIPELINE
Everything you've seen is inference. Training is the loop that sets the weights.
The forward pass in the other tabs runs a fixed set of weights. Training is the outer loop that produced them: run the forward pass, measure how wrong the prediction was, push every weight a little in the direction that reduces the error, and repeat. Crucially, the operations never change. Attention, LayerNorm, the FFN shapes are all fixed. Training only nudges the ≈188 numbers inside them.
THE TRAINING LOOP
{{ c.n }}
{{ c.t }}
{{ c.d }}
W ← W − η · ∂L/∂W  ·  η = learning rate  ·  only weights move; the architecture is frozen
LIVE · TEACHER FORCING ON YOUR INPUT
computed live · edit the input above
The training signal, on your actual sentence. At each position the model predicts the next token; we compare that to the real next token and score it with cross-entropy loss = −ln p(correct). This is exactly what backprop would minimize.
CONTEXT SO FAR
PREDICTS
ACTUAL
−ln p(correct)
LOSS
{{ r.mark }}
{{ r.ctx }} _
{{ r.pred }} {{ r.predP }}
{{ r.target }}
{{ r.loss }}
AVG LOSS (this input)
{{ avgLoss }}
PERPLEXITY · e^loss
{{ perplexity }}
Random-guess baseline = ln(8) = {{ randomLoss }}. These untrained weights sit near it: training is the process of driving this number down across billions of examples.
THE TRAINING DATA
A dataset is just text: no labels, no answers. Teacher forcing slices every sentence into one example per position: everything so far → the actual next word. Your 5-token sentence is the entire corpus here, so it yields exactly these {{ tdCount }} examples:
{{ p.n }}
{{ c }}
→ target {{ p.target }}
model now: p = {{ p.p }} {{ p.mark }}
real models do exactly this, but over web text, books and code: ~15 trillion tokens ≈ 15 trillion (context → next word) examples, batched millions at a time. same loss, same update rule; only the scale changes.
EXERCISE · TRAIN IT · REAL GRADIENT DESCENT
{{ trainedNote }}
Run W ← W − η·∂L/∂W for real, on all 172 trainable weights, against the loss above. Every tab reads the same weights: watch the prediction chip in the top bar, the ✓ column, and the attention heatmaps reorganize as the loss falls.
η LR {{ lrVal }}
training…
LOSS CURVE · {{ stepCount }} STEPS
┄ ln(8) = 2.08 random guessloss now {{ curTrainLoss }}
GREEDY ROLLOUT · UNTRAINED vs NOW
untrained{{ c.w }}
now{{ c.w }}
6-token greedy continuation of your prompt · highlighted = diverged from untrained
Loss ≈ 0: it memorized your sentence. Now edit one input token in the top bar: the loss jumps right back up. That's memorization, not language: real training drives loss down across billions of different sentences so the model generalizes.
EXERCISE · TOUCH ONE WEIGHT
Training is nothing more than search over these numbers. Drag one and watch the loss respond: the curve is the loss landscape along this single weight's axis. Gradient descent just rolls downhill on it, in 172 dimensions at once.
{{ twVal }}
{{ twName }} swept −1.5 … +1.5 · everything else held fixedloss here {{ curTrainLoss }} · prediction "{{ predWord }}"
THE WEIGHTS, ARCHITECTURALLY · 172 TRAINABLE
{{ wMapDesc }}
EMBED · 32 params
E · token embeddings (8×4 · unembed is Eᵀ, tied)
ATTENTION · 64 params
W_Q (4×4)
W_K (4×4)
W_V (4×4)
W_O (4×4)
FFN · 76 params
W₁ (4×8 up-projection)
b₁ (8)
W₂ (8×4 down-projection)
b₂ (4)
this is the whole model. there is nothing else for training to touch. the 16 LayerNorm params (γ, β) are fixed at 1, 0 in this toy. GPT-scale models are the same picture with bigger rectangles: more dims per matrix, more heads, N stacked blocks.
COMPUTE · WHAT TRAINING COSTS
THE STEP BUTTON ABOVE 172 weights · 345 forward passes per step (2 per weight, finite differences) · ≈0.7 MFLOP · ~7 ms in this tab
Real training never brute-forces the gradient like that button does. Backpropagation reuses the forward pass's intermediate values to get all N gradients for roughly the cost of 2 extra forward passes, so one step costs ~3 forwards' compute regardless of N. At 8 B parameters, finite differences would need 16 billion forward passes per step; backprop needs the equivalent of ~3. That algorithm is the only reason any of the rows below are possible. The totals follow one rule of thumb: FLOPs ≈ 6 · N params · D tokens.
{{ c.k }}
{{ c.v }}
{{ c.sub }}
{{ scaleNote }}
wall-clock = 6·N·D ÷ (GPUs × ~0.4 PFLOP/s sustained per H100-class GPU) · real runs add data pipelines, checkpoint restarts and node failures · architecture is unchanged across every row: same attention, same FFN, same loss as this page
PRODUCTION IS THREE STAGES · THIS TOY HAS NONE
{{ s.k }}
{{ s.t }}
{{ s.tag }}
{{ s.d }}
{{ s.obj }}
TRAINING: THIS TOY vs PRODUCTION-GRADE
The objective is the one thing that stays the same. everything about scale, data and machinery is where production lives.
ASPECT
THIS TOY
PRODUCTION LLM
{{ r.label }}
{{ r.toy }}
{{ r.real }}
GARAGELM.ORG · ABOUT THIS PROJECT
A working transformer forward pass, small enough to read every number.
This is a complete decoder-only transformer, the same architecture behind GPT-class models, shrunk to a size where nothing is hidden. Four dimensions, two heads, one block, an eight-word vocabulary. Type in five tokens and watch text turn into a next-token probability distribution, one matrix multiply at a time. Nothing is pre-baked: every value on screen is computed live in your browser from the input you choose.
HOW TO USE IT
{{ u.n }}
{{ u.t }}
{{ u.d }}
WHAT THE PASS COVERS
Every operation a real forward pass runs, in order:
{{ c }}
THE COMPLEXITY TRUTH
What's real here is the shape of the computation: the operations, the data flow, the causal masking, the way dimensions transform stage to stage. What's not real is everything about scale and training. The numbers are illustrative, the weights are hand-chosen rather than learned, and a single four-dimensional block obviously cannot model language. Read this as a wiring diagram you can run, not a small language model.
DIMENSION
THIS TOY
PRODUCTION LLM (≈ 7B)
{{ r.label }}
{{ r.toy }}
{{ r.real }}
DELIBERATELY LEFT OUT
These live in every real model but would only obscure the mechanism at this scale. Their absence is the edge between this toy and a production system:
{{ o }}
Same architecture, honest about its scale · computed live · no data leaves your browser
TOUR · {{ tourN }} / 6
{{ tourTitle }}
{{ tourDesc }}
home · models · notes · competitions · learn · team
hf ↗ · github ↗
© 2026 garagelm.org
real models · cheap hardware · honest numbers