Chapter 16 took the transformer apart and explained each piece, and 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, which comes to 172 parameters against GPT-3’s 175 billion, and every equation is the same. That last sentence is the reason for the chapter, because scale changes what a model can do without changing what a model is. If you can follow four numbers through sixteen steps then 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.
The prompt is four tokens in and one prediction out.
a woodchuck would chuck ?
The vocabulary is a, chuck, how, much, wood, woodchuck, would and the full stop, which is eight tokens indexed alphabetically from zero.
One warning about the weights before we start. They are chosen rather than trained, because 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, so the tokenizer of Chapter 5 converts the string to integers, and here every word already sits in the vocabulary, which makes the mapping a lookup.
| position | token | id |
|---|---|---|
| 0 | a | 0 |
| 1 | woodchuck | 5 |
| 2 | would | 6 |
| 3 | chuck | 1 |
The embedding table is , with one row per token and four numbers per row, and the lookup is not a multiplication because the row is the answer.
To make the arithmetic readable the four dimensions have been given meanings, namely thing, action, quantifier and 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 while chuck is an action that is half about wood, and that overlap is what makes the example worth running.
Self-attention takes a set of vectors, so permuting the input permutes the output with it and changes nothing else. A woodchuck and woodchuck a would therefore be identical, which is a fatal flaw.
The fix is to make position part of the vector, and 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, while the slow pair barely moves across four tokens and would be doing the work if the context were a thousand long.
Added rather than concatenated, which is worth a pause. The model gets four dimensions in total, for meaning and position together, and it 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, and 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 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, and it is the reason a stack of ninety-six blocks can be trained at all, since the residual path from the input to the loss passes through no normalisation and gradients therefore 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, one per projection.
Head 1 was written to ask what thing am I acting on, so its query reads the action dimension while its key reads the thing dimension, and head 2 asks what else here is about wood. The comparison is then a scaled dot product, and the scores below are head 1’s.
| 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 and is nothing of the kind at , for the reason Chapter 16 measured: 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 that and no weight survives there.
| 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 are worth reading here. The first row of each head is and it has no choice, because the first token can attend to nothing but itself, so the softmax is over a single value and whatever the model knows about a at this point it knew before attention ran.
Every row sums to one, which is the softmax doing its job and the reason 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, which 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, and the heads are then concatenated back to width four and mixed by .
Then comes 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, which was near for chuck before attention and is after, because the wood dimension has been amplified by looking at woodchuck and its wood-ish neighbours. Information has moved between positions, which is the one thing attention is for.
Four dimensions in, eight hidden and four out, with every position going through this independently, using the same weights and with 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, 22 are zero, which is per cent, and they are zero because the ReLU cut them off. Note that the pattern is uneven: row a keeps four of its eight units alive while the other three rows keep only two each. This sparsity is not an accident of a toy, because 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, while the feed-forward layer is the only part that transforms a position on its own, and two thirds of the parameters in a real transformer sit in this second job.
Equations (17.8) and (17.10) together are the block, and a large model is this pair ninety-six times over with wider matrices.
The block produced four vectors and only the last one is used, because only the last one has seen the whole prompt, while 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, and the transformer uses one it already has in , the embedding table read the other way.
This is weight tying, and the argument for it is exactly the distributional hypothesis, because if a row of says what a token means on the way in then 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, and it is worth being clear about why, since 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, and the dot product in Equation (17.11) is doing nothing more than measuring that agreement.
Note that how and much tie exactly at , because they have identical embedding rows and 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, and cross-entropy asks one question, which is what probability you gave the right answer.
Now the derivative, which is the reason softmax and cross-entropy are always paired, because for a softmax followed by cross-entropy the gradient with respect to the logits collapses to something you can write down without any chain rule at all.
where is the one-hot target, so it is the predicted probability minus what it should have been, and 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, so the negative one raises the right answer while 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 |
Only three numbers changed the parameter count: the width, the number of blocks and the vocabulary. The head count moved too, and it is free, for the reason Chapter 16 gave. No equation in this chapter was added to, removed or altered, so 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.
Then build the same block as
torch.nn.TransformerEncoderLayer(d_model=4, nhead=2, dim_feedforward=8)and sum its parameters. It comes to , the same total as Step 16, and not one of its parts matches. The library spends nothing on an embedding and spends the that buys it here on attention biases and two layer norms instead. Itemise both columns until you can say where every parameter went, because a total that agrees for the wrong reasons is the easiest kind of number to trust by mistake.
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 SourceFrom Natural Language Processing: From Corpus Statistics to Grounded LLMs, by Ramaseshan Ramachandran.