Step 3 · Attention, derived slowly
The one idea the whole field rests on. Three vectors per token, one dot product, one softmax. Click the diagram to walk through it.
In 60 seconds
Step 3 · Attention, derived slowly
The one idea the whole field rests on. Three vectors per token, one dot product, one softmax. Click the diagram to walk through it.
| Vector | Name | Plain meaning |
|---|---|---|
q | Query | What I am looking for |
k | Key | What I am, advertised to others |
v | Value | What I will contribute if you attend to me |
Tap any box in the diagram
Produced by multiplying this token's current vector by a learned matrix W_q. Think of it as the question this position is asking of the rest of the sequence. In "the animal did not cross the street because it was tired", the query at "it" is roughly "which noun am I referring to?"
import torch, torch.nn as nn, torch.nn.functional as F
class CausalSelfAttention(nn.Module):
def __init__(self, d_model, n_head, dropout=0.0):
super().__init__()
assert d_model % n_head == 0
self.n_head = n_head
self.d_head = d_model // n_head
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
self.proj = nn.Linear(d_model, d_model, bias=False)
self.drop = nn.Dropout(dropout)
def forward(self, x):
B, T, C = x.shape
q, k, v = self.qkv(x).split(C, dim=2)
# (B, T, C) -> (B, n_head, T, d_head)
q = q.view(B, T, self.n_head, self.d_head).transpose(1, 2)
k = k.view(B, T, self.n_head, self.d_head).transpose(1, 2)
v = v.view(B, T, self.n_head, self.d_head).transpose(1, 2)
# fused, memory-efficient, and applies the causal mask for us
y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
y = y.transpose(1, 2).contiguous().view(B, T, C)
return self.drop(self.proj(y))Why multiple heads
The thing worth remembering for Track B
Watch and read more
Lab
Attention implemented twice, verified identical.
The problem
F.scaled_dot_product_attention to within floating-point tolerance. Then measure memory and time at sequence lengths 128, 512, 2048 and plot.def attention_by_hand(q, k, v):
d = q.size(-1)
scores = (q @ k.transpose(-2, -1)) / math.sqrt(d)
mask = torch.triu(torch.ones_like(scores, dtype=torch.bool), diagonal=1)
scores = scores.masked_fill(mask, float("-inf"))
return torch.softmax(scores, dim=-1) @ v
assert torch.allclose(attention_by_hand(q,k,v),
F.scaled_dot_product_attention(q,k,v,is_causal=True), atol=1e-4)You are done when
Hard questions
Try to answer before you reveal. If you can answer these, you understood the lesson.
Q1Remove the 1/sqrt(d) scaling. Predict the failure precisely, then verify.Reveal
Questions people ask
Why divide by the square root of the head dimension?
Dot products of high-dimensional random vectors grow with the square root of the dimension. Without scaling, the softmax saturates — one weight goes to 1, the rest to 0 — and gradients vanish. It is a small detail that makes training possible.
Is attention the same as memory?
It is more like a lookup over the current context. Nothing persists between forward passes. Everything the model "remembers" is either in its weights or in the tokens currently in front of it.
Why is the value matrix separate from the key?
So relevance and content can differ. A token can be very findable for a certain query while contributing something quite different once found. Tying them together measurably reduces capability.
Do I need to implement attention myself?
Write it once by hand to understand it. Then use F.scaled_dot_product_attention in production — it dispatches to FlashAttention kernels and is dramatically faster and more memory-efficient than a naive implementation.
Lesson test
5 questions. Get 3 right (60%) to pass and complete this lesson.
Sign in with your phone number to take the test and save your progress