Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Inside a transformer block

After this lesson: I can trace one token through the steps of a transformer block.

65 min lesson2 commandsJupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

The part a language model repeats

A transformer block is the unit a language model is built from, and a model is the same block stacked many times. It holds masked attention with several heads, then a small network for each token, with shortcuts and rescaling around both.

Once you know one block, you know the shape of the whole model.

By the end of this lesson

Note

I can trace one token through the steps of a transformer block.

Key words

WordWhat it means
Causal maskstops a token looking at later tokens
Headone attention step with its own query, key and value tables
Feed-forward stepa small network applied to each token on its own
Residual connectionadds a step's input to its output
Normalisationrescales a vector so its numbers stay a steady size
Blockattention and feed-forward with their residuals, repeated to build a model

The cells students fill in are left blank in this preview.

A causal mask hides the future

A language model learns to guess the next token. If a token could look at the tokens after it, it would see the answer.

So a causal mask lets each token look only at itself and the tokens before it. A later token gets a weight of 0. In practice its score is set to minus infinity before softmax, because e to minus infinity is 0.

Worked example: weights with the future masked

TokenScoresMay look atWeights
First1, 2, 0first only1, 0, 0
Second2, 1, 1first and second0.731, 0.269, 0
Third0, 1, 3all three0.042, 0.114, 0.844

The cells students fill in are left blank in this preview.

Mask the future in Python

Code: python
import math

# scores[i][j]: how well token i's query matches token j's key.
scores = [[1, 2, 0], [2, 1, 1], [0, 1, 3]]

for i in range(3):
    powers = []
    for j in range(3):
        if j <= i:
            powers.append(math.exp(scores[i][j]))
        else:
            # A later token: masked, so it gets no weight at all.
            powers.append(0)
    total = sum(powers)
    weights = []
    for p in powers:
        weights.append(round(p / total, 3))
    print("token", i, "weights", weights)
# Change j <= i to True, so nothing is masked, and press Run.
# How does the first token's row change, and why is that cheating?

The same mask with NumPy

Command
docker exec jupyter python3 -c "import numpy as np; S = np.array([[1, 2, 0], [2, 1, 1], [0, 1, 3]], dtype=float); S[np.triu(np.ones((3, 3), dtype=bool), 1)] = -np.inf; W = np.exp(S) / np.exp(S).sum(axis=1, keepdims=True); print(W.round(3).tolist())"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Several heads look for different things

One attention step can only mix in one way. So a block runs several heads side by side, each with its own query, key and value tables.

One head might link a word to the word before it. Another might link a word to the thing it describes. Their outputs are joined end to end, then one more learnt table mixes them back to the usual length.

Worked example: joining two heads

StepFor the token late
Head 1 output7.88, 2.12
Head 2 output1, 3
Joined end to end7.88, 2.12, 1, 3
After the mixing tableback to the usual length

The cells students fill in are left blank in this preview.

Feed-forward, residuals and normalisation

After attention, each token goes on its own through a small feed-forward step: two layers with ReLU between. ReLU turns every number below 0 into 0.

A residual connection adds each step's input to its output, so nothing already known is lost. Normalisation rescales each vector so its numbers average 0 and keep a steady spread. Both keep a deep stack trainable.

Worked example: a residual, then normalising

StepNumbers
Input of late1, 2
Attention output7.88, 2.12
Residual: add them8.88, 4.12
Normalised: average 01, -1

The cells students fill in are left blank in this preview.

Normalise a vector with NumPy

Command
docker exec jupyter python3 -c "import numpy as np; x = np.array([2.0, 4.0, 6.0]); print(x.mean(), ((x - x.mean()) / x.std()).round(2).tolist())"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think the feed-forward step lets tokens talk to each other. It works on each token alone. Tokens share information only in attention, so a fact from another token must arrive there first.

About this lab and its source: JupyterLab data science lab: pandas, Matplotlib and scikit-learn in the browser

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.