22

The complete transformer

What is inside an LLM, block by block?

12 min

The complete transformer

With voice. You can pause and jump to any step.

Lesson complete

Subtitles

You already know the pieces: tokens, embeddings, attention, and next-word prediction. Today we put them together into one machine: the transformer, the one inside GPT.

Voice

No voice: the lesson advances on its own with subtitles.

The voice for this lesson is being recorded: for now it advances on its own with subtitles.

0:00 / 0:00

What you will achieve

  • Follow the full path of a sentence inside a transformer: tokens, numbers, embeddings, blocks, scores, softmax, and the next word.
  • Explain what each piece of a block does (attention, network, residual connection, and layer normalization) and why positional encoding and the causal mask are needed.
  • Calculate how many parameters each part of a small transformer has and recognize where almost all the size of a real model like GPT-3 lives.

Key ideas

  1. A transformer always follows the same path: tokens, numbers, embeddings plus position, a stack of blocks, and, from the last token’s list, one score per word that softmax turns into probabilities.
  2. Each block has two parts: attention, the only path by which a token receives information from the others, and a network that processes each token separately.
  3. No part replaces a token’s list: it adds its contribution (residual connection). Before each part, layer normalization subtracts the list’s average and divides it by its standard deviation.
  4. Attention alone does not know order, so a positional encoding is added. The causal mask blocks looking at the future; without it, the model learns to copy and is useless for writing.
  5. Parameters are counted table by table. In a large model almost all of them are in the blocks’ attention and network: in GPT-3, the embedding is less than one percent.

What many people believe

Common belief A transformer reads the text from left to right, word by word, like a person.

What is true All the tokens go in at once and each block processes them in parallel. Order comes from the positional encoding, and it is the causal mask that stops each token from looking ahead. What does go word by word is the writing: one full pass for each new token.

Check yourself

Four questions. No grade, no account: they just show whether the idea stuck.

ConceptWhat is the causal mask for in a transformer that writes text?
ConceptIn a block, which part processes each token separately, without looking at the others?
CalculationIn a block with lists of ten numbers, attention has four ten-by-ten tables (queries, keys, values, and output). Not counting biases, how many parameters do those tables add up to?
ApplicationA team builds a model with unmasked attention to classify reviews and, by mistake, leaves out the positional encoding. What problem should they expect?

Under the hoodFor those who want the math and the code. You do not need it to follow the course.

The full pass, equation by equation

For a sequence of TT tokens with numbers t1,…,tTt_1,\dots,t_T, an embedding table EE (one row of dd numbers per vocabulary word), and LL blocks, in GPT-2’s “pre-normalization” variant, which is the one our toy uses (d=8d=8, L=2L=2):

xi←E[ti]+PEix_i \leftarrow E[t_i] + \mathrm{PE}_i

and, in each block, with LN\mathrm{LN} the layer normalization:

x←x+Attention(LN1(x))x←x+MLP(LN2(x))x \leftarrow x + \mathrm{Attention}\big(\mathrm{LN}_1(x)\big) \qquad x \leftarrow x + \mathrm{MLP}\big(\mathrm{LN}_2(x)\big)

Those two sums are the residual connections: each part adds its contribution to xx. At the exit of the last block, the last token’s list gives the scores (logits) and softmax the probabilities:

z=LNf(xT) E⊤p=softmax(z)z = \mathrm{LN}_f(x_T)\,E^\top \qquad p = \mathrm{softmax}(z)

“Comparing with every vocabulary word” is exactly this: a dot product with each row of EE, the same table as at the input. Reusing it saves V⋅dV\cdot d parameters; the 2017 paper already did it (section 3.4), and the technique is studied in Press and Wolf (2017). In training all TT positions are used at once: each one predicts the next token.

Masked attention

With QQ, KK, and VV as in lesson 21 and the mask Mij=0M_{ij}=0 if j≤ij\le i and −∞-\infty if j>ij>i:

Attention(Q,K,V)=softmax ⁣(QK⊤dh+M)V\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\!\Big(\frac{QK^\top}{\sqrt{d_h}}+M\Big)V

Since e−∞=0e^{-\infty}=0, the future gets zero weight. That is how the original paper describes the decoder: every softmax input that corresponds to an “illegal” connection is set to −∞-\infty to preserve the auto-regressive property (section 3.2.3). Our toy has two heads of dh=4d_h=4 numbers per block; the table in the scene is one head of the second block.

The experiment. We trained two identical models (same seed, same data, 4,000 steps) and removed the mask from one. On new sentences, the masked model has a loss of 0.527 nats per word, almost the grammar’s entropy (0.526, the lowest possible). The unmasked model drives its training loss down to 0.059 by copying, but used the way it is used when writing (masked, with no future) it scores 3.088, worse than spreading evenly over twelve words: ln⁡12=2.485\ln 12 = 2.485.

Each block’s network

MLP(x)=W2 GELU(W1x+b1)+b2\mathrm{MLP}(x)=W_2\,\mathrm{GELU}(W_1x+b_1)+b_2, with W1W_1 of d×4dd\times 4d and W2W_2 of 4d×d4d\times d, the same for every position in the block (“applied to each position separately and identically,” section 3.3 of the paper). The original paper used ReLU; GPT-1 and GPT-2 use GELU (Hendrycks and Gimpel, 2016), a smooth ReLU: GELU(x)=x Φ(x)\mathrm{GELU}(x)=x\,\Phi(x), with Φ\Phi the normal cumulative distribution.

Layer normalization

Over the dd numbers of a list (Ba, Kiros, and Hinton, 2016):

LN(x)=γ⊙x−μσ2+ε+β,μ=1d∑jxj,σ2=1d∑j(xj−μ)2\mathrm{LN}(x)=\gamma\odot\frac{x-\mu}{\sqrt{\sigma^2+\varepsilon}}+\beta,\qquad \mu=\tfrac1d\sum_j x_j,\quad \sigma^2=\tfrac1d\sum_j (x_j-\mu)^2

γ\gamma and β\beta are the “two adjustments that are also learned” (gain and bias, 2d2d parameters per normalization). The 2017 paper normalized after adding, LN(x+Sublayer(x))\mathrm{LN}(x+\mathrm{Sublayer}(x)); GPT-2 moved it to the input of each part and added one more after the last block. Xiong and colleagues (2020) show that with pre-normalization the gradients are well behaved from the start, so the learning-rate warm-up stage can be dropped. In the scene, the list for “eats” entering the second block has an average of 1.10 and a standard deviation of 0.78.

Positional encoding

The 2017 paper’s (section 3.5), with pospos the place and ii the index of the pair of numbers:

PEpos,2i=sin⁡ ⁣(pos/100002i/d)PEpos,2i+1=cos⁡ ⁣(pos/100002i/d)\mathrm{PE}_{pos,2i}=\sin\!\big(pos/10000^{2i/d}\big)\qquad \mathrm{PE}_{pos,2i+1}=\cos\!\big(pos/10000^{2i/d}\big)

The wavelengths run from 2π2\pi to 10000⋅2π10000\cdot2\pi. The authors also tried learned positions and got nearly identical results; GPT-1 learns its own. “Attention alone does not know order” is exact for unmasked attention: if the tokens are permuted, their outputs are permuted the same way. With a causal mask it is subtler: Haviv and colleagues (2022) show that a causal language model without positional encoding learns an implicit notion of position, thanks to the mask itself. Even so, almost every model uses one. In the scene, the “no position” calculation uses a real head of our model on the embeddings alone, without the mask.

Counting parameters

With VV words, dd numbers per list, LL blocks, ctxctx learned positions, and a d→4d→dd\to4d\to d network:

Part Parameters Toy (V=12V{=}12, d=8d{=}8, L=2L{=}2)
Embeddings (and learned positions) Vd+ctx dV d + ctx\,d 96 (wave positions: 0)
Attention L (4d2+4d)L\,(4d^2+4d) 576
Network L (8d2+5d)L\,(8d^2+5d) 1,104
Normalizations (4L+2) d(4L+2)\,d 80
Total ≈12 L d2\approx 12\,L\,d^2 when dd is large 1,856

The approximation N≈12 L d2N\approx 12\,L\,d^2 is Kaplan and colleagues’ (2020, equation 2.1), not counting embeddings. With GPT-2’s vocabulary (50,257) and ctx=2048ctx=2048, the table gives 125,226,240 for 12 blocks with d=768d=768 and 174,604,259,328 for 96 blocks with d=12,288d=12{,}288; the GPT-3 paper (table 2.1) lists 125 million for “GPT-3 Small” and 175.0 billion for GPT-3. The split by parts in the scene is our calculation with this table. For the smallest GPT-2 (context 1,024) it gives 124,439,808, the same count as the Hugging Face Transformers library (checked in train_tiny.py); the GPT-2 paper said 117 million.

In code

import numpy as np

def softmax(z):
    e = np.exp(z - z.max(-1, keepdims=True))
    return e / e.sum(-1, keepdims=True)

def causal_attention(Q, K, V):
    T, d = Q.shape
    scores = Q @ K.T / np.sqrt(d)
    scores[np.triu_indices(T, k=1)] = -np.inf      # causal mask: no peeking at the future
    w = softmax(scores)
    return w, w @ V

def layer_norm(x, g, b, eps=1e-5):
    return g * (x - x.mean(-1, keepdims=True)) / np.sqrt(x.var(-1, keepdims=True) + eps) + b

def n_params(d, L, V, ctx, ff=None):
    ff = ff or 4 * d
    return V*d + ctx*d + L*(4*d*d + 4*d) + L*(2*d*ff + ff + d) + (4*L + 2)*d

rng = np.random.default_rng(0)
Q = K = V = rng.normal(size=(3, 4))
w, out = causal_attention(Q, K, V)
print(np.round(w, 2))
x = rng.normal(2.0, 3.0, size=8)
y = layer_norm(x, np.ones(8), np.zeros(8))
print(round(y.mean(), 3), round(y.std(), 3))
print(n_params(8, 2, 12, 0), n_params(12288, 96, 50257, 2048))
# [[1.   0.   0.  ]
#  [0.25 0.75 0.  ]
#  [0.17 0.13 0.7 ]]
# 0.0 1.0
# 1856 174604259328

The lesson’s model is complete in model.ts and is trained with train_tiny.py (PyTorch, on CPU, under a minute): both live next to this lesson, and verify-forward.mts checks that the TypeScript pass matches PyTorch (maximum difference 7·10⁻⁶).

What comes next

A model with this architecture starts with random parameters. Lesson 23 shows what it learns when trained to predict the next word on internet-scale text, and lesson 24 what that costs. How it is served (the KV cache, which avoids recomputing the earlier tokens) arrives in lesson 26.

Decision lens

What this idea means if your job is to decide, not to code.

  • Almost all general-purpose language models share this skeleton. Saying “we use a transformer” tells you very little: ask about size (number of blocks and length of the lists), how much text it can read at once, and the data it was trained on.
  • Size grows with the square of the list length and with the number of blocks: doubling the length quadruples each block’s parameters. Compare both numbers, not just the total.
  • Every token the model writes requires a pass through all the blocks. Long answers cost more time and money, and a deeper model takes longer per token.
  • There are models that write (decoder-only, like GPT) and models that only read (encoder-only, like BERT). To classify or search documents you do not always need one that writes: it can be cheaper and easier to evaluate.

Terms in this lesson

Full transcript
CommentsThis lesson’s public conversation: questions, facts to check, ideas.

Comments live in GitHub Discussions, in the course’s public repository; writing one requires a GitHub account. They load from giscus.app when you open this section. The course reviewers read them, and if a change is accepted, it is applied in both languages. Open the thread on GitHub

Sources