Encode token positions
An embedding says which token it is, but not where it stands. Without more help, the model cannot tell in which order the earlier words came: "dog bites man" and "bites dog man" would give the same prediction for the next word. So a second list of numbers is added to each embedding, one that depends only on the position. The classic recipe uses sine and cosine waves. Think of a row of clock hands turning at different speeds: read together, they give every position its own pattern.
Learn it
Words to know
- positional encoding
- Numbers added to a token's embedding to say where in the sequence the token stands.
- embedding
- A list of numbers that stands for a token. The model learns these numbers during training.
- token
- One unit of text as the model sees it: a character, a piece of a word or a whole word.
How it works
- Each position gets
widthnumbers. They come in pairs: columns 0 and 1 are the first pair, columns 2 and 3 the second, and so on. - Pair number
i, counted from 0, has its own speed. Its angle at positionposispos / 10000^(2i / width). The first pair turns fastest; later pairs turn more and more slowly. - The even column of a pair holds the sine of that angle, and the odd column holds the cosine.
- For column
j, the pair number isj // 2.
Worked example
Input length = 3 width = 4 Output [[0, 1, 0, 1], [0.841471, 0.540302, 0.00999983, 0.99995], [0.909297, -0.416147, 0.0199987, 0.9998]]
| Position | Fast pair: sin, cos | Slow pair: sin, cos |
|---|---|---|
| 0 | 0, 1 | 0, 1 |
| 1 | 0.8415, 0.5403 | 0.0100, 0.99995 |
| 2 | 0.9093, -0.4161 | 0.0200, 0.9998 |
- Fast pair, sine
- Slow pair, sine
The fast pair changes a lot from one position to the next. The slow pair barely moves, so it can tell apart positions that are far from each other.
Why it matters
The tiny GPT in the workshop learns its position numbers instead, with a second embedding table. Either way they are added to the token embeddings before the first attention layer.
Your turn
Build the table of sine and cosine position numbers for a sequence.
length- the number of positions; 0 or more
width- how many numbers each position gets; positive and even
- Returns
- A list of
lengthrows, each withwidthnumbers. In rowpos, column2iissin(pos / 10000^(2i / width))and column2i + 1is the cosine of the same angle.
Example 1
Input length = 3 width = 4 Output [[0, 1, 0, 1], [0.841471, 0.540302, 0.00999983, 0.99995], [0.909297, -0.416147, 0.0199987, 0.9998]]
Three positions, two pairs each. The worked example shows where each number comes from.
Example 2
Input length = 1 width = 4 Output [[0, 1, 0, 1]]
At position 0 every angle is 0, so every sine is 0 and every cosine is 1.
Example 3
Input length = 0 width = 2 Output []
No positions, so no rows.
Example 4
Input length = 2 width = 2 Output [[0, 1], [0.841471, 0.540302]]
With a width of 2 there is one pair. Its angle at position 1 is 1, giving sin(1) and cos(1).
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Where to start
Two loops: over positions, and inside that over the columns of one row.
Hint 2: The key step
Columns
jandj + 1of a pair share one angle. Work out the pair number asj // 2, then the angle, then choose sine or cosine by whetherjis even. In Python a power is written**, not^.Hint 3: The outline
For each
posand each columnj: setangle = pos / 10000 ** (2 * (j // 2) / width). Appendmath.sin(angle)ifj % 2 == 0, otherwisemath.cos(angle).
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Step by step
- Columns j and j + 1 of a pair share one angle.
j // 2is 0 for columns 0 and 1, and 1 for columns 2 and 3. A larger pair number divides the position by more, so the angle grows more slowly. - Sine in the even column, cosine in the odd one. Sine and cosine of one angle together pin down where the clock hand points. Either one alone would repeat too soon.
The same numbered steps are the comments in the solution.
Python
import math
def solve(length, width):
table = []
for pos in range(length):
row = []
for j in range(width):
# 1. Columns j and j + 1 of a pair share one angle
pair = j // 2
angle = pos / 10000 ** (2 * pair / width)
# 2. Sine in the even column, cosine in the odd one
if j % 2 == 0:
row.append(math.sin(angle))
else:
row.append(math.cos(angle))
table.append(row)
return tableWatch out
- Use the pair number
j // 2in the power, notj. Withj, the sine and cosine of a pair would get different angles. **binds tighter than/in Python, sopos / 10000 ** palready meanspos / (10000 ** p). Brackets the other way round change the result.
Check yourself
Why does the model need position numbers at all?
An embedding says which token it is, not where it stands. Without position numbers the model cannot tell in which order the earlier tokens came.
Why are there fast and slow pairs?
Fast pairs tell neighbouring positions apart. Slow pairs tell distant positions apart. Together they give each position its own pattern.