Lesson 2 of 5
Inside a transformer
A transformer cuts text into tokens, turns each token into a vector, sends the vectors through a stack of identical blocks of attention and small networks, and turns the last vector into a probability for every possible next token. This lesson follows one sentence through every step, with the shapes and the numbers.
Follow a sentence from characters to token ids to the matrix that a transformer reads.
Explain why a transformer needs position information and how it is supplied.
Compute multi-head attention with its shapes, and say why a layer has several heads.
Write one pre-norm transformer block, count its parameters, and implement it in numpy.
Trace how the last position's logits become the next-token distribution, and how generation reuses cached keys and values.

Type "Transformers read tokens." into a chat model. Before any attention can happen, those 25 characters have to become numbers. After the last layer, numbers have to become a probability for every token the model knows. In between sits a stack of identical blocks, and in each block, the attention you built in the previous lesson is the one step where tokens exchange information.
This lesson assembles everything around attention, in the order the data flows: text to tokens, tokens to vectors with positions, several attention heads at once, the block that wraps them, the stack and its output, and finally the loop that generates text. Four step-by-step diagrams draw the path with real numbers, and at the end you write a whole block in numpy.
From text to tokens
A network computes with numbers, so the first job is to cut text into pieces and give each piece an integer. The choice of pieces is a trade. Single characters, as in your models of module 7 and module 8, need only a small vocabulary but make sequences long: every letter is a step. Whole words make sequences short but need an enormous vocabulary, and still cannot handle a word never seen before. Subword tokens sit in between.
Many current tokenizers learn their pieces from data with byte-pair encoding. In the byte-level form many models use, it starts from the 256 possible byte values as the first pieces. Then it counts every pair of neighboring pieces in a large sample of text, merges the most frequent pair into a new piece, and repeats, until the vocabulary reaches its target size, often tens of thousands to a few hundred thousand pieces. Common words end up as single tokens, rare words split into several, and any string at all can still be written as bytes. In ordinary English prose a token averages roughly four characters, depending on the tokenizer.
Each token's id then selects one row of an embedding table , with one row of learned numbers per id. This is the lookup from module 7, turned on its side. There, a matrix times the one-hot column picked out column . Here tokens are rows, so the one-hot row times picks out row of , written E[ids] in numpy for all the ids at once. Stacking the rows in the order the tokens appear gives the matrix of shape , one row per token.
The diagram follows "Transformers read tokens." through those steps with a real tokenizer, cl100k_base (the tokenizer of GPT-3.5 and GPT-4). The strip at the top shows where each step is on the way from text to numbers, and the lime parts are what the step adds.
To recap: text becomes pieces, pieces become ids, ids select rows of , and the rows stack into . One thing is still missing from : where each token sits.
Where each token sits: positions
Why does a transformer need to be told the positions? Because attention, on its own, cannot tell "dog bites man" from "man bites dog". Here is the argument in three moves, for self-attention without the causal mask.
- Each score depends only on the two token vectors involved, not on where they sit in the sentence.
- Swap two tokens in the input. Their rows in , and swap, so every score is the same number as before, moved to a new place in .
- So each token computes exactly the same weighted average as before, and the output rows come out swapped the same way as the input rows. Nothing in the output records which word came first.
Run the check: the same attention on three token vectors, then on the same vectors with the first and last swapped, then again after adding a position vector to each row.
Without positions, the output for "bites" is identical in both sentences. With a position vector added to each row, the words move while the positions stay, so the two sentences give different results. (The causal mask reveals a little order on its own, since each position sees a different number of tokens, but most transformers are still given positions explicitly.)
There are three common ways to do it:
- Learned position vectors. A second table with one learned row per position, added to the token rows: . GPT-2 learns 1,024 of them, so it can read at most 1,024 tokens.
- Fixed sinusoidal vectors. The original 2017 transformer added fixed patterns of sines and cosines at many different frequencies, so every position gets a distinct vector without any training.
- Rotary embeddings. Many recent models add nothing to . Instead they rotate each pair of coordinates in every query and key by an angle proportional to the token's position. Rotating both vectors changes their dot product only through the difference of the two angles, so position enters each score only as how far apart the two tokens are.
The next question checks the argument above.
Self-attention with no causal mask and no position information reads "dog bites man", then "man bites dog": the same three token vectors, with the first and last swapped. How do the two outputs compare?
Choose one answer, then check.
Several heads at once: multi-head attention
One attention pattern per layer is limiting. Each row of the attention weights is one set of weights that adds up to 1, so each token gathers one weighted average. But a token may need the previous word for grammar and a name twenty tokens back for meaning, two different searches. Multi-head attention runs several attentions side by side, so each can search for something different.
With heads and numbers per token:
- Project, as before: , and , each , from three matrices.
- Split the columns into blocks of width . Head 1 uses the first columns of , and , head 2 the next , and so on. Every head still reads all numbers of every token: each of its columns of has entries, one per input number. Only the outputs are split.
- Attend in each head separately, with its own scores divided by , the causal mask and a row softmax. Head gives an output of shape .
- Concatenate: place the head outputs side by side, , of shape again.
- Project the result with one more learned matrix of shape , which mixes what the heads found.
The cost is about the same as one head of full width: the projections are the same size, and score matrices of width take multiply-adds, the same as one of width .
The diagram runs two heads on "the cat sat" with , so each head has width 2. Head 1's columns are blue and head 2's are coral throughout.
The two patterns in the diagram were set by hand to be easy to see. In trained models, some heads do track something recognizable, such as the previous token; many do not have a tidy description.
The transformer block
Attention moves information between tokens, and that is all it does. A block wraps it with three more pieces: a small network that works on each token separately, and two devices that keep a deep stack trainable, residual connections and layer normalization. In the arrangement most current language models use, called pre-norm, one block makes four moves.
- Normalize each token's vector with layer normalization: subtract the vector's own mean, divide by its own standard deviation, then multiply each entry by a learned gain and add a learned bias. You wrote the first part as
normalize_rowsin How this lab works. It keeps the numbers at a steady scale however deep the stack. (Many recent models use a variant, RMSNorm, that skips subtracting the mean.) - Attend: multi-head causal self-attention on the normalized vectors, then add the result back to the block's input. This addition is a residual connection.
- Normalize again.
- Transform each token on its own with a two-layer MLP, , with a nonlinearity in between (ReLU in the original paper, GELU in GPT-2 and many later models, gated variants in many current ones), and add the result back again.
In symbols, with for layer normalization and each token a row:
Attention is the only step where tokens exchange information. Layer norm uses each row's own mean and spread, the MLP applies the same weights to each row separately, and each residual addition adds row of a sublayer's output to row of its input. The original 2017 transformer normalized after each addition instead of before each sublayer; normalizing first turned out to train more stably in deep stacks, and most recent models do that.
Layer norm on one row. Take the row , which is the vector for "the" in the diagram below, and use a gain of 1 and a bias of 0.
- Mean: .
- Subtract it: .
- Square those: , whose average is . That is the variance.
- Standard deviation: .
- Divide: .
The result has mean 0 and standard deviation 1, whatever the scale of the row that came in.
Counting the parameters. , , and are each (all heads together), and the MLP has two matrices, and . That is numbers, plus small bias and normalization vectors. Two thirds of every block is the MLP.
The diagram follows "the cat sat" through a tiny trained transformer: 2 blocks, , 2 heads, an MLP of width 16 and a vocabulary of 9 words, trained on two sentences. The left side is the path, with the shape written beside every arrow; the lime stage is the one the step computes, and the panel on the right holds its numbers (rounded to 1 decimal, probabilities to 3). The text under the figure, and the walkthrough below, give the key rows to 2 decimals. The steps continue past one block to the stack and the output, which the next section explains.
To recap: a block normalizes, attends and adds, then normalizes, transforms each token and adds. The shape in equals the shape out, , which is what lets blocks stack.
The identity path
Why add the input back at all? Look at a residual connection as a function: , where is a whole sublayer, its layer norm included. Its Jacobian, the matrix of partial derivatives from module 5, is
because the derivative of with respect to itself is the identity matrix , and the derivative of is its Jacobian . Backpropagation through a stack multiplies one such matrix per sublayer. For two of them the product expands to
Go slower: Expanding the product
Multiply out the brackets, each term of the first by each term of the second, keeping the order of the factors, since matrix products do not commute: Multiplying by the identity changes nothing, so , and : With sublayers the same expansion gives , plus every single , plus every product of two, and so on up to the product of all .
The first term is the identity: there is always a path along which the gradient arrives unchanged, however deep the stack, and the other terms are added to it rather than multiplied into it. Compare a stack without residuals, whose Jacobian is the bare product : that is the long product which shrinks or grows geometrically, as in backpropagation through time. This is the idea you met in the LSTM's cell state: along its direct path , as Why gradients survive derived, and a forget gate near 1 lets gradients cross many steps. The LSTM adds through time; the residual connections add through depth.
The residual stream. Each sublayer reads a normalized copy of the stream and adds its result back; nothing ever replaces the stream, so information and gradients can travel the whole depth on it.
Researchers call the running sum that flows through the stack the residual stream: every sublayer reads from it and writes an addition to it.
The next two exercises check the block's parts and shapes. First, which part does what.
In the diagram's tiny model, the prediction at the position of "sat" changes from "on" to "by" when "cat" is replaced by "dog". Which part of a transformer block carries information from the token "cat" to the position of "sat"?
Choose one answer, then check.
Now trace every shape through one block of a real size, and count its parameters.
The base model of the original 2017 transformer used , heads and an MLP of width 2,048. Take one pre-norm block of that size reading tokens.
- Write the shape (rows × columns) of each of these: ; ; for all heads together; one head's queries ; one head's score matrix; one head's output ; the heads side by side; the attention output after ; ; the MLP's hidden layer; the block output .
- Count the numbers in the block's six weight matrices (, , , , , ), ignoring biases and layer-norm parameters. Compare with .
- How many numbers are in the weight matrices of a stack of 6 such blocks?
- After the last block and the output layer, with a vocabulary of , what shape are the logits?
Work it on real paper: writing each step is the point. Then check your final answer here and compare your working with the walk-through.
One entry per box, top to bottom. 0.25, -2, 3/4 and sqrt(2) all work. Enter moves to the next empty box and checks once all are filled.
Then build the block. The exercise gives you the reference attention from the previous lesson and the diagram's tiny model, so your block can reproduce step 6 of the diagram exactly.
Build one pre-norm transformer block in numpy, rows as tokens. The setup gives you attention(Q, K, V, mask) and causal_mask(T), reference versions of what you wrote in the attention lesson, and TINY_X and TINY_BLOCK, the input and the weights of block 1 in the lesson's diagram "Inside a transformer".
layer_norm(X, gain, bias, eps=1e-5): normalize every row to mean 0 and standard deviation 1, dividing the variance by (asnp.vardoes) and addingepsinside the square root, then multiply bygainand addbias, both of shape .multi_head_attention(X, Wq, Wk, Wv, Wo, n_heads): causal multi-head self-attention. Head uses columns to of , and , with . Return the output and the weights .mlp(X, W1, b1, W2, b2): , row by row.transformer_block(X, p, n_heads): , then , with the weights in the dictionaryp(keysln1_g,ln1_b,Wq,Wk,Wv,Wo,ln2_g,ln2_b,W1,b1,W2,b2).
The tests check each piece, reproduce step 6 of the diagram for "the cat sat", check that the block returns unchanged when both sublayers output zero (the identity path), and check that later tokens never change earlier outputs.
raise NotImplementedError, then press Run tests. Each check says what it expects.Stack, then predict
One block lets every token gather information from earlier tokens once. Stacking blocks lets later blocks build on what earlier ones gathered: a token can collect something from its neighbor in one block and pass it on in the next. Because a block's output has the shape of its input, , blocks stack without any glue. The diagram's tiny model has blocks; GPT-2's smallest model has 12 with .
After the last block come two more steps, which are steps 8 and 9 of the diagram above.
- A final layer norm, then a linear layer of shape , where is the vocabulary size. It turns every row into logits, one score per token in the vocabulary: . Some models reuse the embedding table as this layer (GPT-2 does), scoring each token by the dot product of its embedding with the final vector.
- A softmax turns a row of logits into probabilities that add up to 1, the softmax of module 6. Row is the model's prediction of the token after position .
In training, every row is used: the loss is the average cross-entropy of the true next token over all positions, the objective of module 7, and the causal mask makes that honest. To generate, only the last row matters. In the diagram, the last row after "the cat sat" gives "on" 0.840. Given "the dog sat" instead, the same model gives "by" 0.890, as in its training sentence "the dog sat by the door .": attention carried the second word's identity to the last position.
Where the parameters live. Add up a whole model: blocks of , plus the embedding table (), plus the output layer (, unless it is shared), plus learned positions if the model has them. Take , 32 blocks and a vocabulary of 32,000:
- One block: .
- All 32 blocks: , about 6.4 billion.
- Embedding table and output layer: .
- Total: about 6.7 billion, the size sold as "7B".
About 96% of it sits in the blocks, and two thirds of each block is its MLP. Layers and parameter counts counted a published 7B design entry by entry and found 6,738,415,616. The difference, about 34 million, is its gated MLP (three matrices, a little more than ) and its normalization weights.
Small models look different, as the next exercise shows.
GPT-2's smallest model has , 12 blocks, a vocabulary of 50,257 tokens and 1,024 learned position vectors, and its output layer reuses the embedding table (there is no separate output matrix). Estimate its parameter count as per block, plus the embedding table and the position table. Give the answer in millions, to one decimal place.
Type a number: 0.25, -2, 3/4 and sqrt(2) all work. Enter checks.
Generating text, and the KV cache
A trained model gives a probability for every possible next token. To write text it has to choose one, append it, and ask again. Generating text introduced greedy choice, sampling and temperature for your character models, with the diagram below; a transformer generates the same way. As a reminder, the diagram runs the loop with a tiny character model continuing the prompt "th", with made-up logits and temperature .
The KV cache. Taken literally, the loop runs the whole transformer again on the whole text for every new token. Most of that work would repeat itself, because of one fact about the causal mask:
- In the first block, the key and value of token come from token 's own row of only (its embedding plus its position).
- The block's output row depends only on rows to of its input: the causal mask stops attention from reading later rows, and layer norm, the MLP and the residual additions work row by row.
- So the input to the second block, row , depends only on tokens to , and the same holds in every later block.
- So appending a new token changes nothing in the rows of earlier tokens, in any layer. Their keys and values stay exactly the same.
An inference server therefore computes each token's keys and values once, in every layer, and keeps them in memory: the KV cache. For each new token it computes only that token's own query, key and value, compares the query with the cached keys, and averages the cached values: one row of work per layer instead of the whole matrix.
The cache has a size. Per token it holds one key vector and one value vector per layer, each of numbers counting all heads together: numbers. For and 32 layers that is numbers per token. At 2 bytes per number, a context of 32,768 tokens needs bytes, about 17.2 GB, more than the 12.9 GB taken by the 6.4 billion block weights at the same 2 bytes each. The cache grows linearly with the context, where a recurrent network carries a state of fixed size.
The next exercise sizes a cache for a smaller model.
A smaller model has 24 layers and (all heads together), keeps a key and a value for every head, and stores its cache in 16-bit numbers (2 bytes each). How large is its KV cache for a context of 8,192 tokens? Answer in gigabytes ( GB bytes), to 2 decimal places.
Type a number: 0.25, -2, 3/4 and sqrt(2) all work. Enter checks.
Finally, put the whole path into words.
Explain to a colleague, step by step, how a transformer language model turns the text "Transformers read tokens." into a probability for the next token. Give the shapes where they help, and say which part does what.
Saved in this browser as you type.