Chapter 16 took the transformer apart and explained each piece. This chapter puts one back together and runs it, with numbers small enough to check on paper.
The model here has four dimensions, two heads, one block and a vocabulary of eight. It has 172 parameters. GPT-3 has 175 billion. Every equation is the same.
That last sentence is the reason for the chapter. Scale changes what a model can do. It does not change what a model is. If you can follow four numbers through sixteen steps, you have followed the architecture, and the only thing left to learn about the large ones is what emerges when the same arithmetic is run at a size no one can trace.
Four tokens in, one prediction out.
a woodchuck would chuck ?
The vocabulary is a, chuck, how, much, wood, woodchuck, would, and the full stop. Eight tokens, indexed alphabetically from zero.
They are chosen, not trained. Every matrix in this chapter was written by hand so that its job is visible, and the chapter says what each one is for. A trained model reaches its weights by the process of Chapter 8, and they are never this tidy. What is genuinely the same is the arithmetic they go through, which is the subject here.
Nothing in a model consumes text. The tokenizer of Chapter 5 converts the string to integers, and here every word already sits in the vocabulary, so the mapping is a lookup.
| position | token | id |
|---|---|---|
| 0 | a | 0 |
| 1 | woodchuck | 5 |
| 2 | would | 6 |
| 3 | chuck | 1 |
The embedding table is : one row per token, four numbers per row. The lookup is not a multiplication. The row is the answer.
To make the arithmetic readable, the four dimensions have been given meanings: thing, action, quantifier, wood. A trained table has no such labelled columns, which is exactly the difficulty of Chapter 9.
| token | thing | action | quantifier | wood |
|---|---|---|---|---|
| a | 0.0 | 0.0 | 1.0 | 0.0 |
| woodchuck | 1.0 | 0.0 | 0.0 | 0.5 |
| would | 0.0 | 1.0 | 0.0 | 0.0 |
| chuck | 0.0 | 1.0 | 0.0 | 0.5 |
Woodchuck is a thing that is half about wood. Chuck is an action that is half about wood. That overlap is what makes the example worth running.
Self-attention takes a set of vectors. Permute the input and the output permutes with it, unchanged. So a woodchuck and woodchuck a would be identical, which is not a subtlety, it is a fatal flaw.
The fix is to make position part of the vector. Chapter 16 gave the sinusoids:
With there are two frequencies: one that turns once per token, and one a hundred times slower.
| position | sin | cos | sin | cos |
|---|---|---|---|---|
| 0 | 0.0000 | 1.0000 | 0.0000 | 1.0000 |
| 1 | 0.8415 | 0.5403 | 0.0100 | 1.0000 |
| 2 | 0.9093 | 0.0200 | 0.9998 | |
| 3 | 0.1411 | 0.0300 | 0.9996 |
The fast pair separates neighbours. The slow pair barely moves across four tokens, and would be doing the work if the context were a thousand long.
Added, not concatenated, which is worth a pause. The model is not given four dimensions for meaning and four for position. It is given four in total, and has to share them.
| token | ||||
|---|---|---|---|---|
| a | 0.0000 | 1.0000 | 1.0000 | 1.0000 |
| woodchuck | 1.8415 | 0.5403 | 0.0100 | 1.5000 |
| would | 0.9093 | 0.5839 | 0.0200 | 0.9998 |
| chuck | 0.1411 | 0.0100 | 0.0300 | 1.4996 |
This is the block’s input. Everything that follows is a function of these sixteen numbers.
Each row is normalised on its own, across its four dimensions, with no reference to the other tokens or to the batch. Row a has mean and variance , so its first entry becomes with . That is not decoration. It is what stops a row of identical values, whose variance is zero, from dividing by nothing.
| token | ||||
|---|---|---|---|---|
| a | 0.5773 | 0.5773 | 0.5773 | |
| woodchuck | 1.1856 | 0.7194 | ||
| would | 0.7324 | 0.9683 | ||
| chuck | 1.7265 |
Normalising before the sublayer rather than after it is the pre-norm arrangement. It is the reason a stack of ninety-six blocks can be trained at all: the residual path from the input to the loss passes through no normalisation, so gradients reach the early layers undiminished. This is the gradient highway of Chapter 13, built again in a different architecture.
Each head sees the same normalised rows through three different matrices.
Head 1 was written to ask what thing am I acting on: its query reads the action dimension, its key reads the thing dimension. Head 2 asks what else here is about wood.
The comparison is a scaled dot product.
| query key | a | woodchuck | would | chuck |
|---|---|---|---|---|
| a | 0.7777 | 0.6943 | 0.5226 | |
| woodchuck | 1.0170 | 0.1867 | 1.0647 | |
| would | 0.5369 | 0.3956 | 0.6030 | 1.2186 |
| chuck | 1.5083 | 0.3283 | 0.8423 | 2.3148 |
The division by looks cosmetic at . It is not cosmetic at , and Chapter 16 measured why: the dot product of two vectors of unit variance has standard deviation , and a softmax fed large numbers saturates onto one key before training has begun.
The upper triangle is set to before the softmax, so and no weight survives.
| query key | a | woodchuck | would | chuck |
|---|---|---|---|---|
| head 1 | ||||
| a | 1.0000 | |||
| woodchuck | 0.7588 | 0.2412 | ||
| would | 0.3405 | 0.2957 | 0.3638 | |
| chuck | 0.2462 | 0.0757 | 0.1265 | 0.5516 |
| head 2 | ||||
| a | 1.0000 | |||
| woodchuck | 0.1382 | 0.8618 | ||
| would | 0.0401 | 0.3684 | 0.5915 | |
| chuck | 0.0674 | 0.1847 | 0.2821 | 0.4658 |
Three things to read here.
The first row of each head is , and it has no choice. The first token can attend to nothing but itself, so the softmax is over a single value. Whatever the model knows about a at this point, it knew before attention ran.
Every row sums to one. That is the softmax doing its job, and it is why attention is called a weighted average rather than a sum.
The two heads disagree, which is the argument for having more than one. Looking from woodchuck, head 1 sends three quarters of its weight back to a, the determiner that introduced it. Head 2 keeps on woodchuck itself, because it is asking about wood and woodchuck is the wood-bearing token so far. One mechanism, two questions, in parallel.
The honest reading of the last row is that both heads keep most of their weight on the current token. That is what untrained attention does. Specialisation is something training produces, and the heads that get published as clean pictures of syntax are heads that were trained into that shape.
Each head returns a weighted average of its values. The heads are concatenated back to width four and mixed by .
Then the residual connection, which is one addition and the most important addition in the architecture.
| token | after the residual | ||||
|---|---|---|---|---|---|
| a | 1.0000 | 1.0000 | |||
| woodchuck | 0.8132 | 0.5403 | 0.0100 | 2.7299 | |
| would | 0.9365 | 0.5839 | 0.0200 | 3.0060 | |
| chuck | 0.0100 | 0.0300 | 3.2513 | ||
Read the last column. It was near for chuck before attention and is after. The wood dimension has been amplified by looking at woodchuck and wood-ish neighbours. Information has moved between positions, which is the one thing attention is for.
Four dimensions in, eight hidden, four out. Every position goes through this independently, with the same weights and no reference to any other position.
| token | ||||||||
|---|---|---|---|---|---|---|---|---|
| a | 0.000 | 0.953 | 0.953 | 0.000 | 0.000 | 0.453 | 0.453 | 0.000 |
| woodchuck | 0.000 | 0.000 | 0.000 | 1.662 | 0.000 | 0.000 | 0.000 | 1.162 |
| would | 0.000 | 0.000 | 0.000 | 1.658 | 0.000 | 0.000 | 0.000 | 1.158 |
| chuck | 0.000 | 0.000 | 0.000 | 1.723 | 0.000 | 0.000 | 0.000 | 1.223 |
Count the zeros. Of the 32 hidden values, 24 are zero, and they are zero because the ReLU cut them off. This sparsity is not an accident of a toy: the feed-forward layers of real models are sparsely active in exactly this way, and that observation is what makes mixture-of-experts routing possible, a thread Chapter 24 picks up.
Note also that the division of labour is now complete. Attention is the only part of the block that moves information sideways. The feed-forward layer is the only part that transforms a position on its own. Two thirds of the parameters in a real transformer sit in this second job.
Equations (17.8) and (17.10) together are the block. A large model is this pair, ninety-six times, with wider matrices.
The block produced four vectors. Only the last one is used, because only the last one has seen the whole prompt. The other three were needed to build it.
After a final layer norm, the last position is
To turn four numbers into a distribution over eight tokens, multiply by a matrix. The transformer uses one it already has: , the embedding table read the other way.
This is weight tying, and the argument for it is exactly the distributional hypothesis. If a row of says what a token means on the way in, the same row can score how well the output matches that token on the way out. It also saves parameters, which is 32 here and 38 million in GPT-2.
| token | logit | probability | |
|---|---|---|---|
| wood | 1.0480 | 0.3107 | 31.1% |
| chuck | 0.3360 | 0.1524 | 15.2% |
| woodchuck | 0.1838 | 0.1309 | 13.1% |
| . | 0.0000 | 0.1089 | 10.9% |
| how | 0.0840 | 8.4% | |
| much | 0.0840 | 8.4% | |
| a | 0.0648 | 6.5% | |
| would | 0.0642 | 6.4% |
The model’s answer is wood, at 31 per cent.
It is worth being clear about why, because it is not because the model knows the tongue twister. The last position accumulated a large value in the wood dimension, by attending to woodchuck and chuck. The row of with the largest wood component is wood. The dot product in Equation (17.11) is doing nothing more than measuring that agreement.
Note that how and much tie exactly, at . They have identical embedding rows, so nothing anywhere in the model can ever separate them. Two tokens with the same vector are the same token, whatever the spelling.
The target is wood. Cross-entropy asks one question: what probability did you give the right answer?
Now the derivative, which is the reason softmax and cross-entropy are always paired. For the softmax followed by cross-entropy the gradient with respect to the logits collapses to something a student can write down without any chain rule at all.
where is the one-hot target. Predicted probability minus what it should have been. That is the whole derivative.
| token | target | after one step | ||
|---|---|---|---|---|
| a | 0.0648 | 0 | 0.0648 | 0.0575 |
| chuck | 0.1524 | 0 | 0.1524 | 0.1294 |
| how | 0.0840 | 0 | 0.0840 | 0.0738 |
| much | 0.0840 | 0 | 0.0840 | 0.0738 |
| wood | 0.3107 | 1 | 0.4017 | |
| woodchuck | 0.1309 | 0 | 0.1309 | 0.1123 |
| would | 0.0642 | 0 | 0.0642 | 0.0570 |
| . | 0.1089 | 0 | 0.1089 | 0.0945 |
One gradient is negative and seven are positive. The negative one raises the right answer, the positive ones lower every wrong one, and their magnitudes are exactly the probabilities that were wrongly assigned. A step of size takes the loss from to , an improvement of .
That is one step, on one example, on the logits alone. Training pushes this same vector backwards through Equation (17.11), then (17.10), (17.9), (17.8), (17.7), (17.6), (17.5), (17.4), (17.3) and into itself, by the chain rule of Chapter 8, and repeats it for every token of every document in the corpus.
| part | shape | parameters |
|---|---|---|
| embedding , tied | 32 | |
| attention, per head | 24 | |
| attention, two heads | 48 | |
| output projection | 16 | |
| feed-forward | 40 | |
| feed-forward | 36 | |
| total | 172 |
| model | layers | heads | parameters | |
|---|---|---|---|---|
| this chapter | 4 | 1 | 2 | 172 |
| GPT-2 small | 768 | 12 | 12 | 123,578,112 |
| GPT-2 medium | 1024 | 24 | 16 | 353,575,936 |
| GPT-3 | 12288 | 96 | 96 | 174,569,631,744 |
Three numbers changed: the width, the number of blocks, and the vocabulary. No equation in this chapter was added to, removed, or altered. A billion-parameter model is Equations (17.2) through (17.11), run wider and more often.
What does change is what the weights end up containing, and that is the subject of the rest of the book. Chapter 18 asks what happens when this block is trained on a corpus instead of written by hand, and what changes when the causal mask is removed.
Try it yourself.
code/worked_examples/tiny_transformer.pyproduces every table in this chapter. Run it with no arguments for all sixteen steps in order, or take one stage at a time:--embedfor the lookup and the sinusoids,--attentionfor both heads with their masks,--ffnfor the sparsity of the hidden layer,--logitsfor the tied unembedding,--lossfor the gradient step, and--paramsfor the count.Change one weight and run it again. The fastest way to believe the architecture is to break it: set the mask aside and watch the model see its own answer, or zero the positional encoding and watch a woodchuck and woodchuck a become the same prompt.
Each one reproduces the tables above. Open it in Colab, change an input and watch which numbers move.
tiny_transformer.pyA transformer, small enough to check
One prompt through every layer of a four-dimensional model, ending in a prediction, a loss and one step of learning.
pip install numpy Run it in Colab Notebook Source