Index Ai Academy
Phase 5 · Transformers · Attention

Attention: the intuition

Each token decides which other tokens matter to it.

About 30 minutes · A free account opens the whole lesson

Create a free account Try part of it here

Already have an account? Sign in to open this lesson

Why it matters

"Attention is all you need" is the title of the 2017 paper that introduced the transformer. Attention is the part that lets a language model link words across a text. It connects "it" to the right noun. It ties a pronoun to a name three paragraphs back. It carries an instruction at the top of a prompt down to the answer at the bottom. This lesson builds the idea with a tiny example you can check by hand. Then you'll watch it work on real words. The full formulas come over the next few lessons.

What you'll be able to do

  • Explain why a word's meaning depends on other words, possibly far away
  • Describe attention as a weighted average where each token chooses its own weights
  • Compute content-based attention weights from similarity and a softmax
  • Say what attention fixes compared with fixed windows and recurrent networks

What it covers

  1. Meaning comes from context
  2. Attention in three steps
  3. Worked example: "dry" next to "jacket"
  4. Same word, new sentence
  5. Seeing it
  6. What real attention adds

Meaning comes from context

Read these two sentences.

  • "The river bank flooded."
  • "The bank approved the loan."

The word "bank" is the same in both. Its meaning isn't. You can tell which bank is meant from the other words around it. Here the clue sits right next to it. In real text, the clue can be anywhere: one word away, or three paragraphs back.

A group chat works the same way. Someone writes "Sounds good, see you at 7." To know what they agreed to, you scroll up to the message they're answering. It might be the one just before. It might be from an hour ago.

A language model has the same job. To understand a token, it must pull in facts from whichever other tokens matter. Those can be near or far.

Two earlier tries

The last lesson's model looked through a fixed window. It saw the last few characters, with a separate set of weights for each slot. Anything outside the window was invisible.

Recurrent networks, from Phase 4, read one token at a time. They squeezed everything so far into one vector, a single list of numbers. Over a long text, early details faded.

Attention takes a different route. Each token looks at all the tokens directly. It decides how much each one matters to it, based on what the tokens contain. Then it blends their information in.

Blending advice from friends

Say you ask two friends whether a new restaurant is any good. One is a chef. The other mostly eats takeout. The chef gives it 9 out of 10. The other friend gives it 5. You trust the chef more on food. So you let the chef's view count for 75% of your opinion, and the other friend's for 25%.

0.75 × 9  +  0.25 × 5  =  6.75 + 1.25  =  8

That's a weighted average. Each value counts in proportion to its weight, and the weights add up to 1. Attention does this for every token. The "friends" are the tokens in the text. The weights say how relevant each one is, and each token works them out from what the tokens contain.

Quick check
You trust two friends equally, and they rate a restaurant 6 and 10. What's the weighted average?
print(0.5 * 6 + 0.5 * 10)
  1. 6.0
  2. 16.0
  3. 10.0
  4. 8.0
Show the answer

8.0

0.5 × 6 + 0.5 × 10 is 3 + 5, which prints as 8.0. Equal weights of 0.5 give the plain middle. Attention does the same, with weights it works out for each token.

Attention in three steps

Pick one token. Call its position ii: it's the token doing the looking. Any token it looks at sits at a position called jj. Token ii looks at every position, itself included.

  1. Score. Give every token jj a score for how relevant it is to token ii. In this lesson the score is the dot product of their two vectors, from Phase 2. Vectors that point in similar directions score high.
  2. Normalize. Run the scores through a softmax, also from Phase 2. It turns them into weights that are all positive and add up to 1.
  3. Mix. Token ii's new vector is the weighted average of all the tokens' vectors.

Here are steps 2 and 3 as formulas. First, the symbols, one at a time.

  • xjx_j is the vector of token jj: its embedding, a list of numbers.
  • sijs_{ij} is the score token ii gives token jj in step 1.
  • wijw_{ij} is the weight token ii gives token jj after the softmax.
  • newi\text{new}_i is token ii's new vector, after attention.
  • ee is the number 2.718..., the base of the exponential that softmax uses.
  • ∑\sum (the Greek letter sigma) means "add these up over every position".
  • The letter under the sigma names the position that changes as you add. Here it's kk or jj.
wij=esij∑kesiknewi=∑jwij xjw_{ij} = \frac{e^{s_{ij}}}{\sum_k e^{s_{ik}}} \qquad \text{new}_i = \sum_j w_{ij}\, x_j

In plain words: on the left, take token ii's score for token jj and raise ee to that power. That always gives a positive number. Then divide by the same thing added up over all tokens. That makes the weights add up to 1. On the right, multiply each token's vector by its weight, and add them all up. That's token ii's new vector.

The weights aren't learned parameters fixed in advance. They're recomputed for every input. So the same token attends to different things in different sentences. The worked example below runs all three steps on three words, with numbers small enough to check by hand.

Try it now

Pick "bank": it leans on "river", which is how it knows this isn't about money. These scores are illustrative, but the percentages are a real softmax.

Quick check
What decides how much token i attends to token j?
  1. Whether j comes right before i
  2. A fixed weight learned for the distance between them
  3. A score computed from the content of both tokens, normalized with a softmax
  4. The order of the tokens alphabetically
Show the answer

A score computed from the content of both tokens, normalized with a softmax

Attention weights are recomputed for every input from the tokens themselves.

Worked example

Worked example: "dry" next to "jacket"

Take the phrase "the dry jacket". Give each word a vector of two numbers. The first number means "rain gear". The second means "food".

  • "the" is [0, 0]: no topic at all.
  • "dry" is [1, 1]: it could be rain gear (a dry jacket) or food (dry toast).
  • "jacket" is [2, 0]: pure rain gear.

Follow "dry" through the three steps by hand.

  1. Score with the dot product. "the": 1 × 0 + 1 × 0 = 0. "dry": 1 × 1 + 1 × 1 = 2. "jacket": 1 × 2 + 1 × 0 = 2.
  2. Normalize with softmax. e0e^0 is 1 and e2e^2 is about 7.39. The total is about 15.78. So the weights are about 0.06, 0.47 and 0.47.
  3. Mix. 0.06 × [0, 0] + 0.47 × [1, 1] + 0.47 × [2, 0] comes to about [1.4, 0.47].

The code does the same thing with plain loops.

import math

the, dry, jacket = [0, 0], [1, 1], [2, 0]
words = [the, dry, jacket]

# Step 1, score: how relevant is each word to "dry"?
scores = [dry[0] * w[0] + dry[1] * w[1] for w in words]
print("scores: ", scores)

# Step 2, normalize: softmax turns the scores into weights
exps = [math.exp(s) for s in scores]
weights = [e / sum(exps) for e in exps]
print("weights:", [round(w, 2) for w in weights])

# Step 3, mix: the weighted average of the word vectors
new_dry = [sum(weights[j] * words[j][k] for j in range(3)) for k in range(2)]
print("new dry:", [round(v, 2) for v in new_dry])
It runs right here in your browser, with nothing to install.

"dry" started at [1, 1], an even split between rain gear and food. After attention it's about [1.4, 0.47], so it now leans toward rain gear. It took most of its new meaning from "jacket". "the" got the lowest score and the smallest weight, and its vector is all zeros, so it added nothing.

Same word, new sentence

Now swap "jacket" for "toast", which is [0, 2]: pure food. This time the code uses NumPy and does every word at once.

X holds one row per word. X @ X.T is matrix multiplication, from Phase 2. It works out the dot product of every pair of words in one go. Row ii, column jj of the result is the score sijs_{ij}. The next line is the softmax for each row. W @ X then does the mixing step for every word.

import numpy as np

X = np.array([[0.0, 0.0],    # the
              [1.0, 1.0],    # dry
              [0.0, 2.0]])   # toast
scores = X @ X.T                                  # every word scores every word
W = np.exp(scores) / np.exp(scores).sum(axis=1, keepdims=True)
new = W @ X                                       # every word's mixed vector
print("weights for dry:", W[1].round(2))
print("new dry:", new[1].round(2))

The weights for "dry" come out the same as before: 0.06, 0.47, 0.47. But the vectors it blends are different. So "dry" ends up at [0.47, 1.4], leaning toward food. Same word, same starting vector, a different meaning in a different sentence. That's attention's whole point.

Quick check
Predict the output. Three tokens all get the same score.
import numpy as np
scores = np.array([1.0, 1.0, 1.0])
w = np.exp(scores) / np.exp(scores).sum()
print(np.round(w, 2))
  1. [0.33 0.33 0.33]
  2. [1. 1. 1.]
  3. [0.5 0.25 0.25]
  4. [2.72 2.72 2.72]
Show the answer

[0.33 0.33 0.33]

Softmax only cares about the gaps between scores. Equal scores mean no gaps, so each token gets an equal share of 1 / 3, which rounds to 0.33, and the three shares add up to 1.

Seeing it

Now try real words. The code below embeds each word of a sentence with the practice embedding model. Its 24 dimensions are topics, much like "rain gear" and "food" above. Then every word attends to every word.

Two details in the code are new.

  • * 4 sharpens the scores. Multiplying every score by 4 widens the gaps between them. So softmax hands more weight to the top few words and less to the rest.
  • plt.imshow draws a heatmap. It's a grid of colored squares. Each square shows one weight. Brighter means more attention.
import numpy as np
import requests
import matplotlib.pyplot as plt

words = "the waterproof jacket kept me dry on a wet muddy trail run".split()
data = requests.post("https://api.gradient.test/v1/embeddings", json={"input": words}).json()["data"]
X = np.array([d["embedding"] for d in data])          # 12 words × 24 dimensions

scores = X @ X.T * 4                                  # similarity of every pair (× 4 to sharpen)
W = np.exp(scores - scores.max(axis=1, keepdims=True))
W /= W.sum(axis=1, keepdims=True)                     # softmax: each row sums to 1

for w in ["jacket", "trail"]:
    i = words.index(w)
    top = np.argsort(-W[i])[:4]
    print(f"{w!r} attends to: " + ", ".join(f"{words[j]} {W[i, j]:.2f}" for j in top))

new = W @ X                                           # each word's context-mixed vector
print("\nshape before and after attention:", X.shape, new.shape)

plt.imshow(W, cmap="viridis")
plt.xticks(range(len(words)), words, rotation=60, fontsize=8)
plt.yticks(range(len(words)), words, fontsize=8)
plt.xlabel("attends to")
plt.ylabel("word")

Each row of the heatmap is one word's attention. The rain-gear words (waterproof, jacket, dry, wet) attend to each other. "Trail" and "run" attend to each other. The filler words, which this practice model can't tell apart, spread their attention among themselves.

The shape line shows that attention doesn't change the size of anything. There are 12 words with 24 numbers each, before and after. What changes is what's in those numbers. The vector for "dry" now carries information from "waterproof", "jacket" and "wet". It means "dry" in the context of rain gear.

Go deeper Why the code subtracts the biggest score

Softmax uses ee raised to each score. For a large score, that number can get too big for the computer to store, and the result breaks. So the code subtracts each row's biggest score from every score in that row first.

This doesn't change the weights, because softmax only cares about the gaps between scores. The scores [2, 0] and [0, -2] have the same gap. Both give the weights [0.88, 0.12].

Quick check
In the heatmap, "jacket" and "wet" give each other a lot of weight. Why?
  1. The model was trained on this exact sentence
  2. They sit next to each other in the sentence
  3. Attention always favors nouns
  4. Their embeddings point in similar directions, both about rain gear, so their dot product scores high
Show the answer

Their embeddings point in similar directions, both about rain gear, so their dot product scores high

In this toy, the score is plain similarity. "jacket" and "wet" aren't neighbors, but they share a topic, so they score high and get large weights.

What real attention adds

This toy uses raw embeddings and plain similarity. So it can only link words about the same topic. Real transformer attention, coming up over the next lessons, adds four things.

  • It learns what to look for. It uses three learned projections, called queries, keys and values. A projection is a learned matrix that turns each vector into a new one. With them, "it" can look for a noun, a verb can look for its subject, and a closing bracket can look for the opening one. Those links aren't simple similarity.
  • It has several heads. Each head is a separate attention pattern, and they run side by side. Each one is free to learn a different kind of link.
  • It can't look ahead in a language model. The model's job is to predict the next token, so it must not peek at it. A causal mask hides every later token.
  • It knows word order. Attention on its own can't tell which token came first. Positional information, added to the embeddings, tells it.

Why it won

  • Any distance, one step. Token 1 and token 1,000 are linked directly. In a recurrent network, information passes through a long chain of hidden states and fades along the way. So do the gradients that train the model.
  • All at once. Every position is computed at the same time. That suits GPUs, which are built for parallel work. A recurrent network has to finish each step before it starts the next.
  • Driven by content. What gets blended depends on the input. So one mechanism handles countless kinds of links.

The price

Every token scores every other token. With 10 tokens, that's 10 × 10 = 100 scores. With 1,000 tokens, it's 1,000 × 1,000, a million scores. Double the text and the work goes up four times. In other words, the work grows with the square of the sequence length. That's the problem the long-context lesson tackles.

For marketers: say a model follows an instruction from the top of a long brief while it writes the last paragraph. Attention is what carries that instruction across the gap.

Quick check
A prompt grows from 1,000 tokens to 2,000 tokens. Roughly how much more scoring work does attention do?
  1. The same amount
  2. About 4 times as much
  3. About 1,000 times as much
  4. About 2 times as much
Show the answer

About 4 times as much

Every token scores every token. 2,000 × 2,000 is 4 million scores, against 1 million before: four times the work.

Practice what you just read

  • 3 code examples you can edit and run
  • a practice exercise that checks your code
  • a 5-question quiz
  • 4 review cards for spaced repetition

Create a free account

Try a quiz question

After attention, what is token i's new vector?

  1. A weighted average of all tokens' vectors, with weights chosen by relevance to i
  2. The vector of whichever token is most similar to i, copied over
  3. The average of all the tokens' vectors, with each one weighted equally
  4. Token i's vector unchanged, with a separate list of weights stored beside it

Answer it at the end of the lesson to see if you're right, and why.

Before this lesson