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

Free lesson

How attention works in a transformer

After this lesson: I can work out the output of attention for one token by hand.

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

Which earlier words matter for this one?

Attention is the step in a transformer where each token looks at the other tokens and takes in what it needs from them. It is why a language model can link "it" to the right noun many words back.

In "the bus is late", the word late is about the bus. Attention is how the model works that out with numbers.

By the end of this lesson

Note

I can work out the output of attention for one token by hand.

Key words

WordWhat it meansExample
Querya vector saying what a token is looking for
Keya vector saying what a token offers
Valuea vector holding what a token passes on
Scorehow well a query matches a key2
Softmaxturns scores into shares that add up to 1
Weightthe share of a value that goes into the output0.665

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

Every token has a query, key and value

In linear algebra, a table of numbers can multiply a vector to give a new vector. Attention multiplies each input vector by three learnt tables, which gives three new vectors.

The query says what this token is looking for. The key says what it offers to others. The value is what it passes on if chosen. Below, we start from the three vectors already made.

Worked example: scoring the query of late

TokenKeyQuery of late times keyScore
bus2, 11x2 + 0x12
is0, 21x0 + 0x20
late1, 11x1 + 0x11

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

Softmax turns scores into weights

Scores can be any size, even below 0. Softmax turns them into weights that are all positive and add up to 1.

First take e to the power of each score, the exp button on a calculator. That makes every number positive, and a big score much bigger. Then divide each one by the total. Real models also divide every score by a fixed number first. We skip that here.

Worked example: softmax of 2, 0 and 1

TokenScoree to the scoreWeight: divide by 11.11
bus27.390.665
is01.000.090
late12.720.245
Total11.111.000

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

Scores and weights with NumPy

Command
docker exec jupyter python3 -c "import numpy as np; K = np.array([[2, 1], [0, 2], [1, 1]]); s = K @ np.array([1, 0]); w = np.exp(s) / np.exp(s).sum(); print(s.tolist(), w.round(3).tolist())"

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

The output is a weighted mix of values

Multiply each token's value by its weight, then add the results. That sum is the new vector for late.

Late took two thirds of its new vector from bus, so late now carries some of what bus means. Every token does the same with its own query at the same time.

Worked example: mixing the values

TokenWeightValueWeight times value
bus0.66510, 06.65, 0
is0.0900, 100, 0.90
late0.2455, 51.22, 1.22
Outputabout 7.88, 2.12

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

Attention for one token in Python

Code: python
import math

keys = [[2, 1], [0, 2], [1, 1]]       # bus, is, late
values = [[10, 0], [0, 10], [5, 5]]
query = [1, 0]                        # the query of late

scores = []
for key in keys:
    scores.append(query[0] * key[0] + query[1] * key[1])

powers = []
for s in scores:
    powers.append(math.exp(s))
total = sum(powers)
weights = []
for p in powers:
    weights.append(p / total)

mix = [0, 0]
for i in range(len(values)):
    for j in range(2):
        mix[j] = mix[j] + weights[i] * values[i][j]

print("scores:", scores)
print("weights:", [round(w, 3) for w in weights])
print("output:", [round(m, 2) for m in mix])
# Change the query to [0, 1] and press Run. Which token gets the most weight now?

A common mistake

Warning

Many people think attention picks one token and ignores the rest. It takes a share of every token's value. A weight is near 0 only when that key matches the query badly, and the weights always add up to 1.

Attention for all three tokens at once

Command
docker exec jupyter python3 -c "import numpy as np; Q = np.array([[0, 1], [1, 1], [1, 0]]); K = np.array([[2, 1], [0, 2], [1, 1]]); V = np.array([[10, 0], [0, 10], [5, 5]]); S = Q @ K.T; W = np.exp(S) / np.exp(S).sum(axis=1, keepdims=True); print(W.round(3).tolist()); print((W @ V).round(2).tolist())"

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

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.