Contents The Transformer Era Code Course home

Chapter 17A Transformer You Can Check By Hand

Why do this arithmetic

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.

The prompt.

Four tokens in, one prediction out.

a woodchuck would chuck \;\rightarrow\; ?

The vocabulary is a, chuck, how, much, wood, woodchuck, would, and the full stop. Eight tokens, indexed alphabetically from zero.

A warning about the weights.

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.

From words to vectors

Step 1: the prompt becomes integers

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

Step 2: each integer indexes one row

The embedding table EE is 8×48 \times 4: 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.

Step 3: position, because attention cannot see order

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:

PE(pos,2i)=sin(pos100002i/d),PE(pos,2i+1)=cos(pos100002i/d).(17.1)\begin{equation} PE_{(pos,\,2i)} = \sin\!\left(\frac{pos}{10000^{2i/d}}\right), \qquad PE_{(pos,\,2i+1)} = \cos\!\left(\frac{pos}{10000^{2i/d}}\right). \label{eq:tt-pe} \quad\text{(17.1)} \end{equation}

With d=4d = 4 there are two frequencies: one that turns once per token, and one a hundred times slower.

position d0d_0 sin d1d_1 cos d2d_2 sin d3d_3 cos
0 0.0000 1.0000 0.0000 1.0000
1 0.8415 0.5403 0.0100 1.0000
2 0.9093 0.4161-0.4161 0.0200 0.9998
3 0.1411 0.9900-0.9900 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.

Step 4: the two are added

xp=E[idp]+PEp.(17.2)\begin{equation} x_p = E[\mathrm{id}_p] + PE_p. \label{eq:tt-input} \quad\text{(17.2)} \end{equation}

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 x0x_0 x1x_1 x2x_2 x3x_3
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.

The attention sublayer

Step 5: normalise first

LN(x)=xμσ2+ϵ,μ=1djxj,σ2=1dj(xjμ)2.(17.3)\begin{equation} \mathrm{LN}(x) = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}, \qquad \mu = \frac{1}{d}\sum_j x_j, \qquad \sigma^2 = \frac{1}{d}\sum_j (x_j - \mu)^2. \label{eq:tt-ln} \quad\text{(17.3)} \end{equation}

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 0.750.75 and variance 0.18750.1875, so its first entry becomes (00.75)/0.1875+ϵ=1.7320(0 - 0.75)/\sqrt{0.1875 + \epsilon} = -1.7320 with ϵ=105\epsilon = 10^{-5}. That ϵ\epsilon is not decoration. It is what stops a row of identical values, whose variance is zero, from dividing by nothing.

token n0n_0 n1n_1 n2n_2 n3n_3
a 1.7320-1.7320 0.5773 0.5773 0.5773
woodchuck 1.1856 0.5906-0.5906 1.3145-1.3145 0.7194
would 0.7324 0.1157-0.1157 1.5850-1.5850 0.9683
chuck 0.4463-0.4463 0.6561-0.6561 0.6241-0.6241 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.

Steps 6 and 7: three projections, then one comparison

Each head sees the same normalised rows through three different matrices.

Q=XnWQ,K=XnWK,V=XnWV.(17.4)\begin{equation} Q = X_n W^Q, \qquad K = X_n W^K, \qquad V = X_n W^V. \label{eq:tt-qkv} \quad\text{(17.4)} \end{equation}

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.

S=QKdk,dk=2,dk=1.4142.(17.5)\begin{equation} S = \frac{QK^{\top}}{\sqrt{d_k}}, \qquad d_k = 2, \qquad \sqrt{d_k} = 1.4142. \label{eq:tt-scores} \quad\text{(17.5)} \end{equation}

query \backslash key a woodchuck would chuck
a 0.4714-0.4714 0.7777 0.6943 0.5226
woodchuck 1.0170 0.1291-0.1291 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 dk\sqrt{d_k} looks cosmetic at dk=2d_k = 2. It is not cosmetic at dk=64d_k = 64, and Chapter 16 measured why: the dot product of two vectors of unit variance has standard deviation dk\sqrt{d_k}, and a softmax fed large numbers saturates onto one key before training has begun.

Step 8: the mask, then the softmax

The upper triangle is set to -\infty before the softmax, so e=0e^{-\infty} = 0 and no weight survives.

A=softmax(S+M),Mij={0jij>i(17.6)\begin{equation} A = \mathrm{softmax}\!\left(S + M\right), \qquad M_{ij} = \begin{cases} 0 & j \le i \\ -\infty & j > i \end{cases} \label{eq:tt-mask} \quad\text{(17.6)} \end{equation}

query \backslash 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 1.00001.0000, 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 0.86180.8618 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.

Steps 9 and 10: combine, project, and add back

Each head returns a weighted average of its values. The heads are concatenated back to width four and mixed by WOW^O.

MHA(X)=[head1;head2]WO,headh=AhVh.(17.7)\begin{equation} \mathrm{MHA}(X) = \left[\mathrm{head}_1 ; \mathrm{head}_2\right] W^O, \qquad \mathrm{head}_h = A_h V_h. \label{eq:tt-mha} \quad\text{(17.7)} \end{equation}

Then the residual connection, which is one addition and the most important addition in the architecture.

x=x+MHA(LN(x)).(17.8)\begin{equation} x' = x + \mathrm{MHA}(\mathrm{LN}(x)). \label{eq:tt-residual} \quad\text{(17.8)} \end{equation}

token after the residual
a 1.7320-1.7320 1.0000 1.0000 0.7320-0.7320
woodchuck 0.8132 0.5403 0.0100 2.7299
would 0.9365 0.5839 0.0200 3.0060
chuck 0.3492-0.3492 0.0100 0.0300 3.2513

Read the last column. It was near 1.51.5 for chuck before attention and is 3.253.25 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.

The feed-forward sublayer

Step 11: widen, rectify, narrow

FFN(z)=max(0,zW1+b1)W2+b2.(17.9)\begin{equation} \mathrm{FFN}(z) = \max(0,\, z W_1 + b_1)\, W_2 + b_2. \label{eq:tt-ffn} \quad\text{(17.9)} \end{equation}

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 u0u_0 u1u_1 u2u_2 u3u_3 u4u_4 u5u_5 u6u_6 u7u_7
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.

Step 12: the second residual

y=x+FFN(LN(x)).(17.10)\begin{equation} y = x' + \mathrm{FFN}(\mathrm{LN}(x')). \label{eq:tt-block} \quad\text{(17.10)} \end{equation}

Equations (17.8) and (17.10) together are the block. A large model is this pair, ninety-six times, with wider matrices.

From vectors back to words

Step 13: only the last position matters

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

z=(0.6805,0.5283,0.5198,1.7285).z = (-0.6805,\; -0.5283,\; -0.5198,\; 1.7285).

Step 14: the tied unembedding

To turn four numbers into a distribution over eight tokens, multiply by a 4×84 \times 8 matrix. The transformer uses one it already has: EE^{\top}, the embedding table read the other way.

=zE,p=softmax().(17.11)\begin{equation} \ell = z E^{\top}, \qquad p = \mathrm{softmax}(\ell). \label{eq:tt-logits} \quad\text{(17.11)} \end{equation}

This is weight tying, and the argument for it is exactly the distributional hypothesis. If a row of EE 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 V×dV \times d 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.2599-0.2599 0.0840 8.4%
much 0.2599-0.2599 0.0840 8.4%
a 0.5198-0.5198 0.0648 6.5%
would 0.5283-0.5283 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 EE 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 0.2599-0.2599. 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.

Learning, in one step

Step 15: the loss and its gradient

The target is wood. Cross-entropy asks one question: what probability did you give the right answer?

L=logpt=log0.3107=1.1690 nats.(17.12)\begin{equation} L = -\log p_{t} = -\log 0.3107 = 1.1690 \text{ nats}. \label{eq:tt-loss} \quad\text{(17.12)} \end{equation}

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.

L=py,(17.13)\begin{equation} \frac{\partial L}{\partial \ell} = p - y, \label{eq:tt-grad} \quad\text{(17.13)} \end{equation}

where yy is the one-hot target. Predicted probability minus what it should have been. That is the whole derivative.

token pp target L/\partial L / \partial \ell pp 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.6893\mathbf{-0.6893} 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 0.50.5 takes the loss from 1.16901.1690 to 0.91190.9119, an improvement of 0.25700.2570.

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 EE itself, by the chain rule of Chapter 8, and repeats it for every token of every document in the corpus.

What scale changes

Step 16: counting this model

part shape parameters
embedding EE, tied 8×48 \times 4 32
attention, per head 3×4×23 \times 4 \times 2 24
attention, two heads 48
output projection WOW^O 4×44 \times 4 16
feed-forward W1,b1W_1, b_1 4×8+84 \times 8 + 8 40
feed-forward W2,b2W_2, b_2 8×4+48 \times 4 + 4 36
total 172

The same equations, four sizes

model dmodeld_{\text{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.py produces every table in this chapter. Run it with no arguments for all sixteen steps in order, or take one stage at a time: --embed for the lookup and the sinusoids, --attention for both heads with their masks, --ffn for the sparsity of the hidden layer, --logits for the tied unembedding, --loss for the gradient step, and --params for 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.

Run it in ColabNotebookSource

Code for this chapter

Each one reproduces the tables above. Open it in Colab, change an input and watch which numbers move.