How a transformer works

The architecture underneath every current language model, built one piece at a time. Assumes linear algebra; assumes no machine learning.

TL;DR
  1. A transformer is one function: text in, a probability for every possible next chunk of text out. Everything else is that function called repeatedly.
  2. Text is cut into tokens (word-ish chunks). Each token is just an integer — a row number, carrying no meaning of its own.
  3. That row number indexes a big table of learned numbers, giving each token a vector. Words used similarly end up with similar vectors, so meaning becomes something you can do arithmetic on.
  4. A token's vector starts out context-free: bank gets the identical vector in "river bank" and "savings bank". The rest of the model exists to fix that.
  5. Attention is the fix. Each token asks a question, every other token advertises what it offers, and the token pulls in a weighted blend of whatever matched.
  6. Attention plus a per-token transformation makes one layer. Stack about a hundred, read the final vector, convert to probabilities, pick a token, append it, run the whole thing again.

Every term below that isn't standard mathematics gets defined where it first appears, and no mechanism shows up before the problem it exists to solve. There's a glossary at the end.

What the function does

Before any internals, be precise about input and output, because it's narrower than it looks from outside.

You hand the model a sequence of text. It returns one probability distribution over its entire vocabulary — a number for each of roughly 100,000 possible chunks of text, giving the likelihood that each one comes next. That is the whole output. A single list of probabilities.

input:   "The probe reached "

output:  Jupiter   0.31
         Saturn    0.22
         Earth     0.14
         the       0.09
         ...  100,000 entries in total, summing to 1.0

Something outside the model then picks one entry, appends it to the text, and calls the function again on the longer string. Predict, append, repeat. That loop is where sentences come from. The model itself never produces a sentence — it produces one distribution, with no notion that it is about to be called again.

Step 1 · text becomes integers

The model can only do arithmetic, so text has to become numbers. First it's cut into pieces called tokens.

Tokens are not characters and not quite words. They're chunks chosen by a compression procedure run over a large body of text before training starts: begin with individual characters, repeatedly find the most frequent adjacent pair and merge it into a new unit, stop after about 100,000 units. Common words survive whole; rare ones get split. A leading space is usually glued to the front, which is why ·the and the are two distinct entries.

The output is a fixed list — the vocabulary — and each token is identified by its position in that list. Nothing more than that.

An approximation — the real merge table is learned rather than the heuristic used here — but the granularity is right. Note the consequence: the model never sees the letters inside a token, which is why counting the r's in "strawberry" is genuinely hard for it.
The ID number carries no meaning whatsoever. If The is token 84049, that 84049 is a row number — exactly like an index into a Fortran array. Token 84050 is not "one greater" in any meaningful sense; it's just whatever landed in the next slot. Shuffle the entire vocabulary, retrain, and nothing changes. The integer is therefore useless on its own, which is what the next step is for.

Step 2 · integers become vectors

The model holds a large matrix called the embedding table: one row per vocabulary entry, each row a few thousand numbers wide. GPT-2 small used 50,257 × 768. A frontier model is closer to 100,000 × 5,000 — tens of millions of numbers, every one of them learned during training.

Turning a token ID into a vector is then the simplest operation in the entire model: read that row. Token 84049 means "fetch row 84049 of the table". It's a lookup, not a computation. The vector you get back is the token's embedding.

Why go to the trouble? Two reasons:

The embedding table begins as random noise. Everything meaningful in it is a residue of training.

Step 3 · adding position

There's a problem with what we have, and it's clearest if you look one step ahead: the mixing operation coming in step 5 treats its inputs as an unordered set. Give it "Jupiter orbits the probe" or "The probe orbits Jupiter" and, with embeddings alone, it computes identical results. Word order would be invisible to it.

So position has to be pushed into the vectors themselves. The original scheme — still the easiest to look at — builds a positional encoding vector for each slot out of sines and cosines at geometrically spaced frequencies, and simply adds it:

vector at position p  =  embedding[token_id]  +  position_vector[p]

Adding sounds destructive — surely the two signals scramble together? In a space of several thousand dimensions there's room for both to coexist and for later operations to pull them apart again. Different frequencies occupy different dimensions, so "which token" and "which slot" don't compete for the same coordinates.

click a token:
the embedding table — 6 of ~100,000 rows, 10 of ~5,000 columns
what actually enters layer 1
Table values here are illustrative random numbers — real learned ones aren't interpretable by eye anyway. The position row, though, is really computed: it's the sinusoidal encoding from the 2017 paper, sin(p/100002i/d) on even dimensions and cos on odd. Clicking a different token changes the top row; its slot changes the middle row; the two vary independently. Most current models use a variant called rotary encoding that rotates vectors inside the attention step rather than adding at the input, but the purpose is the same.

Every token now has one vector encoding what it is and where it is. That's the input to the model proper.

Step 4 · the problem to be solved

Here's the limitation of everything so far, and it motivates the entire rest of the architecture.

An embedding is context-free. The row for ·bank is one fixed set of numbers. It is the same vector in "the river bank was steep" as in "the bank approved the loan". Those are different meanings, and any useful model has to end up representing them differently.

The same goes for anything involving reference. In "the probe reached it in 2016", the vector for ·it starts as the generic ·it row, carrying nothing about what it points to.

So each token's vector needs to be updated using information from the other tokens present. Which raises exactly two questions:

  1. Which other tokens should it draw from? For ·bank, the answer had better be ·river or ·loan, not ·the.
  2. What should it take from them? Having decided ·river is relevant, what exactly gets copied across?

The mechanism that answers both is called attention. That is all the word means: a procedure letting each token gather information from the others, with learned control over which ones and what.

Step 5 · how attention answers them

Start somewhere familiar. In a dictionary or hash table, a lookup works like this:

d = {"river": 0.9, "loan": 0.4, "the": 0.1}
d["river"]   →   0.9

There are three roles in play. You supply a search term. It gets compared against the stored labels. Exactly one matches, and you get back the contents filed under it. Matching is all-or-nothing: one hit, everything else contributes zero.

Attention keeps the three roles and abandons the all-or-nothing part. Every token produces all three:

Then, instead of finding the single matching key: compare the query against every key, get a match score for each, convert those scores into weights summing to 1, and return the weighted average of all the values. A key that matched well dominates; one that matched poorly contributes nearly nothing; nothing is excluded outright.

That's what makes it a soft lookup — retrieval by blending rather than selection. It also matters that it's a blend: a smooth weighted sum has a useful derivative everywhere, so the whole thing can be trained by gradient descent. A hard lookup, being a branch, does not.

Where the three vectors come from

The layer holds three learned matrices, WQ, WK and WV. Each token's input vector gets multiplied by each of them:

query_i = W_Q · x_i     key_i = W_K · x_i     value_i = W_V · x_i

The same three matrices apply to every token — they're properties of the layer, not of any particular word. Three separate matrices are needed because the three roles really are different things. What ·bank wants to ask ("is there a water word or a money word near me?") is not what ·river wants to advertise ("I am a water word"), and neither is the content ·river passes along. A single matrix couldn't produce all three.

Scoring a match

The match score between a query and a key is their dot product — large and positive when they point the same way, near zero when orthogonal, negative when opposed. Training's job is to shape WQ and WK so that queries and keys which ought to match come out aligned.

Two adjustments before scores become weights:

Not looking ahead

One constraint remains. During training the model predicts the next token at every position of a passage at once — that's what makes training tractable. But a position predicting its own successor must not be allowed to see it. So before softmax, every score pointing at a later position is set to −∞, which softmax maps to exactly zero weight. This is the causal mask, and a model built with it is autoregressive: each position sees only itself and what came before.

Here is the whole operation with numbers you can step through:

token doing the looking:
1 · match scores
2 · divide by √d
3 · mask the future
4 · softmax to weights
5 · blend the values
A toy model: 4 tokens, 4-dimensional vectors, one set of Q/K/V matrices. The vectors are hand-picked rather than trained, but every number derived from them is computed live — including the mask, which you can watch engage by selecting an earlier token. Step 5's output is the update added to that token's vector. A real layer does exactly this with ~5,000-dimensional vectors over thousands of tokens.

Several attentions at once

One set of WQ/WK/WV can express only one notion of relevance. Real layers run dozens of independent copies side by side, each with its own three matrices. Each copy is a head. They operate in parallel on the same input, and their outputs are concatenated and mixed by one further learned matrix.

The point is specialisation: one head can track grammatical subjects while another tracks the immediately preceding token and a third tracks topic.

Four illustrative heads over one sentence. Each square is a query-key pair: the row is the token doing the looking, the column is the token looked at, darker means more weight. The blank upper-right triangle is the causal mask — those positions lie in the future. These particular patterns are hand-drawn, but heads answering to these descriptions really are found in trained models by interpretability researchers. Most heads are far less legible than this.

Step 6 · the other half of a layer

Attention moves information between positions. The second half of each layer does processing within a position: a small two-layer neural network applied to each token's vector separately and identically. It's called the feed-forward network, or MLP, and mechanically it's two matrix multiplications with a nonlinear function applied elementwise between them:

out = W_2 · nonlinear(W_1 · x)

The intermediate result is typically 4× wider than the vector itself, which is why this block holds most of the model's numbers despite being simpler than attention.

The nonlinearity is not decoration, and the reason is worth stating plainly: a stack of linear maps collapses into a single linear map. Without something nonlinear between them, a 100-layer model would be algebraically identical to one matrix and could represent nothing that one matrix couldn't. The nonlinearity is the only thing that makes depth mean anything.

Interpretability work suggests much of what we'd loosely call stored knowledge lives in these blocks, as patterns of which intermediate units fire. Attention decides what to look at; the feed-forward network decides what to make of it.

Step 7 · stacking

Both halves write their results back by addition rather than replacement:

x = x + attention(x)
x = x + feedforward(x)

The running vector being added into is the residual stream — "residual" because each block computes a correction to what's already there rather than a replacement value. It runs unbroken from embedding to output, and every block both reads from it and writes into it.

residual stream the token's vector, unchanged unless written to attention gathers across tokens + feed-forward processes each token alone + one layer · repeat 40 to 120 times
Adding rather than replacing buys two things. Gradients flow straight back down the stream during training instead of being attenuated by a hundred successive multiplications — the reason very deep networks became trainable at all. And a block with nothing useful to contribute at some position can write approximately zero and leave the stream alone, which is why you can sometimes delete a middle layer and the model still mostly works.

One piece of housekeeping appears at each block: layer normalisation, which rescales a vector to zero mean and unit variance before use. It doesn't change what's represented, it just keeps magnitudes in a range where the arithmetic stays well-conditioned. Numerical hygiene.

Rough intuition for what depth buys, with the caveat that it's a simplification: early layers resolve tokens into words and local grammar, middle layers build meaning and relationships, late layers narrow toward a specific prediction.

Step 8 · back to words

At the top of the stack, take the vector at the last position — the one that has been accumulating context about what should come next — and multiply it by a final matrix with one row per vocabulary entry. That yields one raw score per possible token. These unnormalised scores are called logits, and softmax turns them into the probability distribution promised at the start.

Something then has to choose one. That choice is not part of the model:

Real softmax over a fixed set of illustrative logits, recomputed as you drag. Greyed rows have been cut by top-p and cannot be sampled at all. Pull temperature toward zero and watch everything collapse onto the leader.

The chosen token is appended and the entire stack runs again from the beginning. The model has no plan, no draft, and no sense that a sentence is in progress. Longer structure is an emergent consequence of repeatedly making a good local choice.

The cost consequence. Producing token n means attending over all n−1 tokens before it. Naively, a 1,000-token answer would reprocess the whole prompt a thousand times. The fix is to keep each token's key and value vectors in memory once computed — the KV cache. That is a different thing from the prompt caching on your bill, and confusing the two is the usual source of trouble. Both are in the third document.

What is not in there

Glossary

tokenA chunk of text, roughly a word or word-fragment. The model's atomic unit.
vocabularyThe fixed list of every possible token, around 100,000 entries.
embeddingThe vector fetched from the embedding table for a given token ID.
positional encodingA vector added to the embedding so word order is visible to the model.
attentionThe operation letting each token pull a weighted blend of information from the others.
query / key / valueThree vectors derived from each token: what it seeks, what it advertises, what it hands over.
softmaxExponentiate a list of numbers, divide by their sum. Gives positive weights totalling 1.
causal maskForcing attention weights on later positions to zero, so nothing sees the future.
autoregressiveGenerating one token at a time, each conditioned only on what precedes it.
headOne independent set of Q/K/V matrices. A layer runs many in parallel.
feed-forward / MLPThe per-token half of a layer: two matrix multiplies with a nonlinearity between.
residual streamThe running vector each block adds its output into, from input through to output.
layer normalisationRescaling a vector to keep magnitudes numerically well-behaved.
logitsRaw unnormalised scores over the vocabulary, before softmax.
temperatureA divisor on the logits controlling how sharp or flat the output distribution is.
top-pTruncating candidates to the smallest set whose probabilities sum to p.
parameters / weightsAll the learned numbers together: the embedding table, every W matrix, everything.
KV cacheKeys and values kept in memory during generation so they aren't recomputed per token.

then go play with a real one