Lesson 79: Scaled Dot-Product Attention

USAAIO Lesson 79, from Phase 3 on transformers and NLP. It starts from the sequence bottleneck in an RNN, then covers Bahdanau alignment scores and self-attention, where Q, K, and V all come from the same sequence. It derives Attention(Q,K,V) = softmax(QK^T/sqrt(d_k))V, proves by a variance argument that dot products grow with d_k, and explains why the scaling factor prevents the softmax from saturating. It was verified with torch 2.7.1+cpu in June 2026. The lesson runs to 28 slides.

Subject: Machine Learning · 54 slides · code lesson

Open the interactive version of this deck · Homework for this lesson

What this lesson covers

The lesson, slide by slide

1. Scaled Dot-Product Attention

Title

USAAIO · Lesson 79 · Phase 3 (Transformers & NLP)

From the RNN bottleneck to Bahdanau alignment scores, self-attention, and the full derivation of Attention(Q,K,V) = softmax(QKᵀ / √d_k) V — with a proof that the scaling factor is not optional.

2. By the end of this lesson you can

Objectives

  1. Explain the RNN fixed-vector bottleneck and how Bahdanau attention resolves it
  2. Derive each component of Attention(Q,K,V) = softmax(QKᵀ / √d_k) V from first principles
  3. Prove that Var(qᵀk) = d_k when q, k ~ N(0,1), and show this causes softmax saturation
  4. Show that when Q=K=V=X (self-attention) the output is a weighted average of value vectors
  5. Contrast attention with a lookup table and connect it to kernel regression

3. What survived from Mock Exam — Phase 2 (Theory + Coding)?

Warm-up

Discussion prompt

Before we open Lesson 79: Scaled Dot-Product Attention: without looking back, what was the main idea of Mock Exam — Phase 2 (Theory + Coding), and what could you do by the end of it that you could not do before?

Hint: One sentence for the idea, one for the skill. If the second one is blank, that is the part to revisit.

Answer:

3-hour combined Phase 2 mock exam — 30 theory questions spanning all Phase 2 topics, plus two timed coding problems: logistic regression from scratch and a CNN in PyTorch, each with a theory sub-question and a verified answer key. The Phase-2 coding and theory milestone.

4. The RNN bottleneck & Bahdanau attention

Section

Part 1 of 3

5. RNN encoder-decoder: the fixed-vector bottleneck

Concept

A standard RNN encoder-decoder (Sutskever 2014) maps an entire source sentence into one fixed-length vector — the final hidden state h_T — from which the decoder must recover everything.

\[ h_t = f(h_{t-1},\, x_t), \quad c = h_T \]

For long sentences the single vector c is asked to hold too much information. Translation quality degrades sharply past ~20 tokens (Bahdanau 2015, Fig. 2). This is the bottleneck.

6. Break it if you can: RNN encoder-decoder: the fixed-vector bottleneck

Counterexample

Discussion prompt

A standard RNN encoder-decoder (Sutskever 2014) maps an entire source sentence into one fixed-length vector — the final hidden state h_T — from which the decoder must recover everything.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

For long sentences the single vector c is asked to hold too much information. Translation quality degrades sharply past ~20 tokens (Bahdanau 2015, Fig. 2). This is the bottleneck.

7. Bahdanau attention (2015): alignment scores

Concept

Bahdanau's fix: let the decoder compute a soft alignment over all encoder states at each decode step, instead of using a single c.

\[ e_{tj} = a(s_{t-1},\, h_j), \quad \alpha_{tj} = \frac{e^{e_{tj}}}{\sum_k e^{e_{tk}}}, \quad c_t = \sum_j \alpha_{tj}\, h_j \]

symbolmeaning
s_{t-1}decoder hidden state at previous step
h_jencoder hidden state at source position j
e_tjalignment score (MLP or dot product)
α_tjattention weight (sums to 1 over j)
c_tcontext vector — weighted sum of encoder states

8. Fill in: meaning for Bahdanau attention (2015): alignment scores

Comparison

Comparison matrix

From Bahdanau attention (2015): alignment scores: refill the meaning column from what you know. The rest of the table is as it appeared.

symbolmeaning
s_{t-1}decoder hidden state at previous step
h_jencoder hidden state at source position j
e_tjalignment score (MLP or dot product)
α_tjattention weight (sums to 1 over j)
c_tcontext vector — weighted sum of encoder states

9. Queries, keys, values, and self-attention

Section

Part 2 of 3

10. Q, K, V: the general frame

Concept

Transformer attention (Vaswani 2017) abstracts Bahdanau into three matrices: a query Q (what am I looking for?), keys K (what does each entry advertise?), and values V (what each entry contributes if selected).

\[ \text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V \]

matrixshaperole
Q(T_q, d_k)one row per query token — what it seeks
K(T_k, d_k)one row per key token — what it offers
V(T_k, d_v)one row per value token — what it contributes
output(T_q, d_v)weighted mixture of value rows

11. What each one costs: Q, K, V: the general frame

Trade off

Comparison matrix

From Q, K, V: the general frame: every row here is a choice with a cost. Fill the shape column, then say which row you would actually pick and what you give up for it.

matrixshaperole
Q(T_q, d_k)one row per query token — what it seeks
K(T_k, d_k)one row per key token — what it offers
V(T_k, d_v)one row per value token — what it contributes
output(T_q, d_v)weighted mixture of value rows

12. Self-attention: Q = K = V = X·W

Concept

In self-attention all three matrices are projections of the same input sequence X. Every token simultaneously acts as query, key, and value — it asks and answers its own question about every other token.

\[ Q = XW_Q,\quad K = XW_K,\quad V = XW_V \quad (X\in\mathbb{R}^{T\times d_{\rm model}}) \]

This lets token i attend to tokens j anywhere in the sequence, regardless of distance — the key advantage over RNNs (Lesson 70 context: RNNs lose long-range dependencies through the vanishing gradient).

13. Guess the shape of the answer: Traced attention forward pass (T=3, d_k=4)

Estimation

Predict first

Trace the full computation for a 3-token sequence with d_k=4. Seed 42, Q and K fixed to torch.randn(3,4).

Commit before you compute: what does Traced attention forward pass (T=3, d_k=4) come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: scores[0] = [0.0725, 0.099, 0.325]; after softmax → weights[0] = [0.3017, 0.3098, 0.3884]

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Higher score for token 2 → it receives the largest weight in token 0's output mix.

14. Traced attention forward pass (T=3, d_k=4)

Worked example

Trace the full computation for a 3-token sequence with d_k=4. Seed 42, Q and K fixed to torch.randn(3,4).

import torch, torch.nn.functional as F
import numpy as np
torch.manual_seed(42)
d_k = 4
Q = torch.randn(3, d_k)
K = torch.randn(3, d_k)
V = torch.tensor([[ 0.1, -0.5,  0.8,  0.3],
                   [ 0.6,  0.2, -0.4,  0.9],
                   [-0.3,  0.7,  0.1, -0.2]])
scores = Q @ K.T / (d_k ** 0.5)
weights = F.softmax(scores, dim=-1)
out = weights @ V
print('scores :', np.round(scores.numpy(), 4))
print('weights:', np.round(weights.numpy(), 4))
print('output :', np.round(out.numpy(), 4))

scores[0] = [0.0725, 0.099, 0.325]; after softmax → weights[0] = [0.3017, 0.3098, 0.3884]

Why: Higher score for token 2 → it receives the largest weight in token 0's output mix. The weights sum to 1 (they are a probability distribution over value rows).

token iscore row (3 values)weight row (sums to 1)which token dominates
0[0.0725, 0.099, 0.325][0.302, 0.310, 0.388]token 2 (barely)
1[-1.863, -1.425, -1.439][0.245, 0.380, 0.375]tokens 1 & 2
2[0.154, -0.094, 0.638][0.294, 0.229, 0.477]token 2

15. Work backwards from the answer: Traced attention forward pass (T=3, d_k=4)

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

scores[0] = [0.0725, 0.099, 0.325]; after softmax → weights[0] = [0.3017, 0.3098, 0.3884]

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

Trace the full computation for a 3-token sequence with d_k=4. Seed 42, Q and K fixed to torch.randn(3,4).

16. Guess the shape of the answer: Output is a weighted average of value rows

Estimation

Predict first

Verify the homework claim: for self-attention (Q=K=V=X), output row i equals ∑_j W[i,j] · V[j].

Commit before you compute: what does Output is a weighted average of value rows come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: W[0] = [0.499, 0.039, 0.309, 0.153]; sum = 1.000; manual == out[0]: True

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. The softmax weights are a valid probability distribution over the T=4 value rows.

17. Output is a weighted average of value rows

Worked example

Verify the homework claim: for self-attention (Q=K=V=X), output row i equals ∑_j W[i,j] · V[j].

import torch, torch.nn.functional as F
import numpy as np
torch.manual_seed(7)
X = torch.randn(4, 8)              # 4 tokens, d=8
W = F.softmax(X @ X.T / 8**0.5, dim=-1)  # (4,4)
out = W @ X                        # (4,8)
# manual check for token 0
manual0 = sum(W[0, j].item() * X[j] for j in range(4))
print('W[0]:', np.round(W[0].numpy(), 4))
print('sum W[0]:', W[0].sum().item())
print('match:', torch.allclose(manual0, out[0], atol=1e-5))

W[0] = [0.499, 0.039, 0.309, 0.153]; sum = 1.000; manual == out[0]: True

Why: The softmax weights are a valid probability distribution over the T=4 value rows. The output is exactly their weighted sum — attention interpolates between value vectors by learned similarity.

jW[0,j]contribution
00.4990dominates — token 0 attends mostly to itself
10.0390barely contributes
20.3093second largest weight
30.1527minor contribution

18. Work backwards from the answer: Output is a weighted average of value rows

Reverse engineer

Discussion prompt

Work backwards. The example finished here:

W[0] = [0.499, 0.039, 0.309, 0.153]; sum = 1.000; manual == out[0]: True

What was it asked to do, and what must it have been given? Reconstruct the problem from its answer.

Hint: Every quantity in the result had to enter somewhere. Account for each one.

Answer:

Verify the homework claim: for self-attention (Q=K=V=X), output row i equals ∑_j W[i,j] · V[j].

19. Something is wrong here: confusing cross-attention and self-attention

Anomaly

Predict first

A student writes this, and it looks reasonable:

In encoder self-attention, Q comes from the encoder and K, V come from the decoder — that's why it's called 'self' (the encoder queries itself).

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Backwards. In self-attention Q, K, V all come from the SAME sequence (encoder attends to encoder, decoder attends to decoder).

Self-attention: Q, K, V are all projections of the same X. Cross-attention (in the transformer decoder): Q from decoder states, K and V from encoder outputs.

Why: Backwards. In self-attention Q, K, V all come from the SAME sequence (encoder attends to encoder, decoder attends to decoder). Cross-attention is different: Q from decoder, K/V from encoder.

20. Trap: confusing cross-attention and self-attention

Trap

The trap

In encoder self-attention, Q comes from the encoder and K, V come from the decoder — that's why it's called 'self' (the encoder queries itself).

Use Q from encoder, K and V from decoder

Why: Backwards. In self-attention Q, K, V all come from the SAME sequence (encoder attends to encoder, decoder attends to decoder). Cross-attention is different: Q from decoder, K/V from encoder.

The fix

Self-attention: Q, K, V are all projections of the same X. Cross-attention (in the transformer decoder): Q from decoder states, K and V from encoder outputs.

Self: Q=XW_Q, K=XW_K, V=XW_V (one sequence). Cross: Q=X_dec·W_Q, K=X_enc·W_K, V=X_enc·W_V

Why: The word 'self' means Q, K, V all derive from the same source sequence. Bahdanau attention is actually cross-attention: decoder queries encoder states.

21. Break it on purpose: confusing cross-attention and self-attention

Break the constraint

Discussion prompt

The rule this trap just fixed:

The word 'self' means Q, K, V all derive from the same source sequence. Bahdanau attention is actually cross-attention: decoder queries encoder states.

Now break it on purpose. Build a case that violates it and follow the consequences until something visibly fails. Where does the failure first show up — and would you have noticed it if you had not been looking?

Hint: The dangerous rules are the ones whose violation still produces an answer. If yours fails loudly, try to find one that fails quietly.

Answer:

Backwards. In self-attention Q, K, V all come from the SAME sequence (encoder attends to encoder, decoder attends to decoder). Cross-attention is different: Q from decoder, K/V from encoder.

22. The √d_k scaling: variance proof

Section

Part 3 of 3

23. Why divide by √d_k?

Concept

If q and k are independent N(0,1) vectors of dimension d_k, their dot product qᵀk = ∑ q_i k_i has zero mean — but its variance grows linearly with d_k.

\[ \text{Var}(q^\top k) = \sum_{i=1}^{d_k} \text{Var}(q_i k_i) = \sum_{i=1}^{d_k} 1 = d_k \]

Large variance → large scores → softmax pushes all weight onto one entry (saturation) → gradient through softmax ≈ 0. Dividing by √d_k rescales the dot product to unit variance.

24. By analogy: Why divide by √d_k?

Analogy

Discussion prompt

Explain Why divide by √d_k? by analogy to something with no Machine Learning in it at all — a queue, a recipe, a map, a bank balance, whatever fits. Then say where your analogy breaks.

Hint: An analogy that never breaks is not an analogy, it is the same idea wearing a hat. Find the seam — that is the part that is actually new.

Answer:

If q and k are independent N(0,1) vectors of dimension d_k, their dot product qᵀk = ∑ q_i k_i has zero mean — but its variance grows linearly with d_k.

25. Guess the shape of the answer: Proving saturation numerically

Estimation

Predict first

Measure the entropy of the softmax distribution as d_k grows, with and without √d_k scaling. High entropy = spread attention; low entropy ≈ one-hot = saturated.

Commit before you compute: what does Proving saturation numerically come out to? A rough magnitude and the right form is enough — the point is to have something concrete to be wrong about.

Correct: d=256: raw_std=22.2 (H=0.000, completely saturated), scaled_std=1.39 (H=1.065, still spread)

Why: A prediction you can defend turns the computation into a check rather than a leap of faith — and an answer that contradicts it is caught on the spot. Without scaling, by d_k=256 the softmax has entirely collapsed to a one-hot — gradient is zero and the model can't learn.

26. Proving saturation numerically

Worked example

Measure the entropy of the softmax distribution as d_k grows, with and without √d_k scaling. High entropy = spread attention; low entropy ≈ one-hot = saturated.

import torch, torch.nn.functional as F
results = []
for d in [4, 16, 64, 256]:
    torch.manual_seed(0)
    q = torch.randn(1, d); k = torch.randn(5, d)
    raw = (q @ k.T)[0]
    scaled = raw / d**0.5
    def entropy(x): p=F.softmax(x,dim=-1); return -(p*torch.log(p+1e-9)).sum().item()
    results.append((d, raw.std().item(), scaled.std().item(),
                     entropy(raw), entropy(scaled)))
for d, rs, ss, hr, hs in results:
    print(f'd={d:3d}: raw_std={rs:.3f}, scaled_std={ss:.3f}, H_raw={hr:.3f}, H_scaled={hs:.3f}')

d=256: raw_std=22.2 (H=0.000, completely saturated), scaled_std=1.39 (H=1.065, still spread)

Why: Without scaling, by d_k=256 the softmax has entirely collapsed to a one-hot — gradient is zero and the model can't learn. Scaling keeps std near 1 and entropy high regardless of dimension.

d_kraw score stdscaled stdH(unscaled)H(scaled)
41.2400.6201.2401.491
163.4360.8590.6261.439
647.3210.9150.4041.367
25622.1891.3870.0001.065

27. Watch it run: Proving saturation numerically

Pattern

Step through it

Step through Proving saturation numerically one row at a time. What is driving the change, and what would the row after the last one be?

  1. Step 1: d_k is 4
  2. Step 2: d_k is 16
  3. Step 3: d_k is 64
  4. Step 4: d_k is 256

28. Variance per component: the formal proof

Concept

Let q_i, k_i ~ N(0,1) i.i.d. Then q_i k_i has E[q_i k_i] = 0 and Var(q_i k_i) = E[q_i² k_i²] − 0 = 1·1 = 1 (independence + unit normal second moment).

\[ q^\top k = \sum_{i=1}^{d_k} q_i k_i \quad\Rightarrow\quad \text{Var}(q^\top k) = \sum_{i=1}^{d_k} 1 = d_k \]

\[ \frac{q^\top k}{\sqrt{d_k}} \quad\Rightarrow\quad \text{Var} = \frac{d_k}{d_k} = 1 \]

29. Where does each piece belong: Lesson 79: Scaled Dot-Product Attention

Sorting

Sort into buckets

These are the pieces of Lesson 79: Scaled Dot-Product Attention, out of order. Put each one back under the part of the lesson it belongs to.

The RNN bottleneck & Bahdanau attention
RNN encoder-decoder: the fixed-vector bottleneck; Bahdanau attention (2015): alignment scores
Queries, keys, values, and self-attention
Q, K, V: the general frame; Self-attention: Q = K = V = X·W; Traced attention forward pass (T=3, d_k=4)
The √d_k scaling: variance proof
Why divide by √d_k?; Proving saturation numerically; Variance per component: the formal proof
s1
The RNN bottleneck & Bahdanau attention is where Lesson 79: Scaled Dot-Product Attention puts RNN encoder-decoder: the fixed-vector bottleneck, Bahdanau attention (2015): alignment scores. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s2
Queries, keys, values, and self-attention is where Lesson 79: Scaled Dot-Product Attention puts Q, K, V: the general frame, Self-attention: Q = K = V = X·W, Traced attention forward pass (T=3, d_k=4). Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.
s3
The √d_k scaling: variance proof is where Lesson 79: Scaled Dot-Product Attention puts Why divide by √d_k?, Proving saturation numerically, Variance per component: the formal proof. Knowing which part of the lesson a problem belongs to is most of knowing which method to reach for.

30. Something is wrong here: √d_k scaling is optional or cosmetic

Anomaly

Predict first

A student writes this, and it looks reasonable:

The 1/√d_k factor is just a normalization convention — it doesn't change which token gets the highest weight, so it doesn't really affect training.

It is wrong. Say what breaks — and say it before you turn the page.

Correct: Softmax is NOT argmax. Rescaling changes the sharpness of the distribution: large scores produce near-one-hot outputs.

Scaling controls the softmax temperature. Without it, at d_k=256 the entropy collapses to 0.000 — the gradient of softmax w.r.t. logits is p_i(1-p_i), near zero when p_i ≈ 1.

Why: Softmax is NOT argmax. Rescaling changes the sharpness of the distribution: large scores produce near-one-hot outputs. That makes gradients nearly zero and breaks backprop through the attention weights.

31. Trap: √d_k scaling is optional or cosmetic

Trap

The trap

The 1/√d_k factor is just a normalization convention — it doesn't change which token gets the highest weight, so it doesn't really affect training.

Omit scaling, argue argmax is invariant to positive rescaling

Why: Softmax is NOT argmax. Rescaling changes the sharpness of the distribution: large scores produce near-one-hot outputs. That makes gradients nearly zero and breaks backprop through the attention weights.

The fix

Scaling controls the softmax temperature. Without it, at d_k=256 the entropy collapses to 0.000 — the gradient of softmax w.r.t. logits is p_i(1-p_i), near zero when p_i ≈ 1.

Run the entropy table above: d_k=256, no scaling → H=0.000; with scaling → H=1.065

Why: Gradient of softmax ∂α_j/∂e_j = α_j(1-α_j). If α_j→1, gradient→0; if α_j≈1/T, gradient is maximized. The scaling keeps the regime trainable.

32. Which of these survive contact with Lesson 79: Scaled Dot-Product Attention?

Two truths and a lie

Sort into buckets

Some of these hold up and some are the exact mistakes this lesson is built to prevent. Sort them.

Holds up
Bahdanau's fix: let the decoder compute a soft alignment over all encoder states at each decode step, instead of using a single c.; If q and k are independent N(0,1) vectors of dimension d_k, their dot product qᵀk = ∑ q_i k_i has zero mean — but its variance grows linearly with d_k.; Let q_i, k_i ~ N(0,1) i.i.d. Then q_i k_i has E[q_i k_i] = 0 and Var(q_i k_i) = E[q_i² k_i²] − 0 = 1·1 = 1 (independence + unit normal second moment).
Breaks
In encoder self-attention, Q comes from the encoder and K, V come from the decoder — that's why it's called 'self' (the encoder queries itself).; The 1/√d_k factor is just a normalization convention — it doesn't change which token gets the highest weight, so it doesn't really affect training.
sound
These are stated as this lesson states them — each one survives the edge cases Lesson 79: Scaled Dot-Product Attention puts it through.
flawed
Each of these is lifted from a trap in this deck: reasonable-sounding, and wrong in a way that only shows up once you rely on it.

33. Attention as soft lookup — and kernel regression

Concept

A hard lookup table maps a key exactly to one value: output = V[k == query]. Attention is the soft version: it computes similarity to every key and returns a weighted mixture of all values.

This is precisely Nadaraya-Watson kernel regression: f(x) = ∑_i K(x, x_i) · y_i / ∑_i K(x, x_i) where the kernel K = exp(qᵀk/√d_k) after softmax normalization.

viewquerykeysvaluesweighting
hard lookupexact keytable keystable valuesone-hot
soft attentionQ rowK rows (all)V rows (all)softmax similarities
kernel regressionxx_i (all)y_i (all)normalized kernel K(x,x_i)

34. Fill in: keys for Attention as soft lookup — and kernel…

Comparison

Comparison matrix

From Attention as soft lookup — and kernel regression: refill the keys column from what you know. The rest of the table is as it appeared.

viewquerykeysvaluesweighting
hard lookupexact keytable keystable valuesone-hot
soft attentionQ rowK rows (all)V rows (all)softmax similarities
kernel regressionxx_i (all)y_i (all)normalized kernel K(x,x_i)

35. Rebuild the recipe: Scaled dot-product attention: the full recipe

Ranking

Put in order

These are the steps of Scaled dot-product attention: the full recipe, scrambled. Put them back in order before the next slide shows you.

  1. Project: form Q=XW_Q, K=XW_K, V=XW_V (or cross-attention: Q from decoder, K/V from encoder)
  2. Score: S = QKᵀ — each query's dot product with every key; shape (T_q × T_k)
  3. Scale: S / √d_k — ensures Var(scores) ≈ 1, prevents softmax saturation
  4. Normalize: W = softmax(S/√d_k, dim=-1) — each row is a probability distribution over keys
  5. Aggregate: Output = WV — weighted average of value rows; shape (T_q × d_v)

Why: This is the order the recipe itself gives. Recalling the sequence without the slide in front of you is the difference between recognising the method and being able to run it — most of what goes wrong in practice is a step done out of turn.

36. Scaled dot-product attention: the full recipe

Pattern

  1. Project: form Q=XW_Q, K=XW_K, V=XW_V (or cross-attention: Q from decoder, K/V from encoder)
  2. Score: S = QKᵀ — each query's dot product with every key; shape (T_q × T_k)
  3. Scale: S / √d_k — ensures Var(scores) ≈ 1, prevents softmax saturation
  4. Normalize: W = softmax(S/√d_k, dim=-1) — each row is a probability distribution over keys
  5. Aggregate: Output = WV — weighted average of value rows; shape (T_q × d_v)

\[ \text{Attention}(Q,K,V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V \]

37. Where does it stop working: Scaled dot-product attention: the full recipe

Edge cases

Discussion prompt

Scaled dot-product attention: the full recipe works on the cases you have just seen. Push it to the edge: what is the most degenerate input it still handles — empty, zero, one item, everything equal — and what is the first case where it stops being true? Name the case, not just "it breaks".

Hint: Try the smallest legal input, then the largest, then the one where two things collide. Methods are specified at their edges; the middle takes care of itself.

Answer:

  1. Project: form Q=XW_Q, K=XW_K, V=XW_V (or cross-attention: Q from decoder, K/V from encoder)
  2. Score: S = QKᵀ — each query's dot product with every key; shape (T_q × T_k)
  3. Scale: S / √d_k — ensures Var(scores) ≈ 1, prevents softmax saturation
  4. Normalize: W = softmax(S/√d_k, dim=-1) — each row is a probability distribution over keys
  5. Aggregate: Output = WV — weighted average of value rows; shape (T_q × d_v)

38. Rule out three: Check yourself — the scaling factor

Elimination

Eliminate the wrong options

If q and k are independent N(0,1) vectors of dimension d_k, what is Var(qᵀk)?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. d_k
  • B. √d_k
  • C. 1
  • D. d_k²

Survives elimination: A

Why: Var(qᵀk) = Var(∑ q_i k_i) = ∑ Var(q_i k_i) = ∑ 1 = d_k. Each component q_i k_i has Var=1 (independence + E[z²]=1 for z~N(0,1)), and they sum. Dividing by √d_k gives a rescaled dot product with variance d_k/d_k = 1.

39. Check yourself — the scaling factor

Check

Work out the variance claim before clicking.

Check your understanding

If q and k are independent N(0,1) vectors of dimension d_k, what is Var(qᵀk)?

  • A. d_k (correct)
  • B. √d_k
  • C. 1
  • D. d_k²

Answer: A

Why: Var(qᵀk) = Var(∑ q_i k_i) = ∑ Var(q_i k_i) = ∑ 1 = d_k. Each component q_i k_i has Var=1 (independence + E[z²]=1 for z~N(0,1)), and they sum. Dividing by √d_k gives a rescaled dot product with variance d_k/d_k = 1.

Why B tempts people
√d_k is the std, not the variance — confusing std with variance. Var = (std)² = d_k.
Why C tempts people
Var=1 is what you get AFTER dividing by √d_k — that's the goal of the scaling, not the pre-scaled quantity.
Why D tempts people
d_k² would require Var(q_i k_i) = d_k, but each independent component contributes exactly 1 by the product rule for independent zero-mean unit-variance variables.

40. Answer it before you see the options: Check yourself — self-attention output

Prediction

Predict first

In self-attention with Q=K=V=X (no learned projections), the i-th output vector is:

Answer it in your own words, now, with nothing to choose from. The options are on the next slide — and picking the right one off a list is an easier skill than producing it.

Correct: a weighted average of the rows of X, with weights softmax(X[i]·Xᵀ / √d_k)

Why: Output[i] = ∑_j W[i,j] · X[j] where W[i,j] = softmax_j(X[i]·X[j] / √d_k). Verified in the deck: W[0]=[0.499,0.039,0.309,0.153], and the manual weighted sum equals out[0] exactly.

41. Check yourself — self-attention output

Check

Think through what the matrix multiply WV actually computes.

Check your understanding

In self-attention with Q=K=V=X (no learned projections), the i-th output vector is:

  • A. a weighted average of the rows of X, with weights softmax(X[i]·Xᵀ / √d_k) (correct)
  • B. the row X[i] unchanged (self-attention is an identity)
  • C. the row of X with the highest dot product with X[i]
  • D. the mean of all rows of X, equally weighted

Answer: A

Why: Output[i] = ∑_j W[i,j] · X[j] where W[i,j] = softmax_j(X[i]·X[j] / √d_k). Verified in the deck: W[0]=[0.499,0.039,0.309,0.153], and the manual weighted sum equals out[0] exactly.

Why B tempts people
Attention is not an identity — even with Q=K=V=X, the softmax weights redistribute information from neighboring tokens into each output position.
Why C tempts people
Hard-max would select a single row; softmax returns a soft mixture of ALL rows weighted by similarity. The highest-score token dominates but does not receive weight 1.
Why D tempts people
Equal-weight averaging corresponds to uniform attention weights — that only happens if all dot products are equal. In general the weights are non-uniform.

42. Rule out three: Check yourself — attention vs lookup table

Elimination

Eliminate the wrong options

How does scaled dot-product attention differ from a hard lookup table?

3 of these 4 are wrong. Strike them one at a time, and say what rules each one out before you strike the next. The survivor is the answer.

  • A. Attention retrieves a soft mixture of all values weighted by query-key similarity; a hard lookup retrieves exactly one value by exact key match
  • B. A hard lookup is differentiable; attention is not
  • C. Attention uses the argmax of scores, making it equivalent to a lookup
  • D. They are equivalent when d_k is large

Survives elimination: A

Why: A lookup table is a one-hot selection (only the matching key's value is returned). Attention is differentiable and returns ∑_j softmax(score_j) · V[j] — a continuous interpolation. This is exactly Nadaraya-Watson kernel regression with an exponential kernel.

43. Check yourself — attention vs lookup table

Check

Recall the kernel regression analogy.

Check your understanding

How does scaled dot-product attention differ from a hard lookup table?

  • A. Attention retrieves a soft mixture of all values weighted by query-key similarity; a hard lookup retrieves exactly one value by exact key match (correct)
  • B. A hard lookup is differentiable; attention is not
  • C. Attention uses the argmax of scores, making it equivalent to a lookup
  • D. They are equivalent when d_k is large

Answer: A

Why: A lookup table is a one-hot selection (only the matching key's value is returned). Attention is differentiable and returns ∑_j softmax(score_j) · V[j] — a continuous interpolation. This is exactly Nadaraya-Watson kernel regression with an exponential kernel.

Why B tempts people
Reversed. A hard lookup (argmax) is non-differentiable; attention with softmax is smooth and fully differentiable — that's the whole point for gradient-based training.
Why C tempts people
Argmax is the zero-temperature limit of softmax (temperature → 0). Standard attention uses finite-temperature softmax, producing non-zero weights for all keys, not just the top one.
Why D tempts people
Large d_k without scaling causes softmax saturation (near-argmax). The 1/√d_k fix keeps the distribution spread. Large d_k does not make attention equivalent to lookup.

44. Your turn: implement attention

Section

Project

45. Project: scaled dot-product attention from scratch

Concept

Implement attention(Q, K, V, d_k) from scratch in PyTorch, verify the output matches torch.nn.functional.scaled_dot_product_attention, and demonstrate softmax saturation with and without scaling.

#milestonekey tool
1Implement attention formula; verify row sums = 1Q@K.T, F.softmax, @V
2Prove Var(q^T k) = d_k numerically for d in [4,16,64]torch.randn, .var()
3Show entropy collapse without scaling; full programF.softmax entropy

Build rules: type every line, print shapes at each step, verify weight rows sum to 1.0 using assert abs(W.sum(-1) - 1).max() < 1e-5.

46. Break it if you can: Project: scaled dot-product attention from scratch

Counterexample

Discussion prompt

Implement attention(Q, K, V, d_k) from scratch in PyTorch, verify the output matches torch.nn.functional.scaled_dot_product_attention, and demonstrate softmax saturation with and without scaling.

That is stated as though it always holds. Do one of two things: produce a case where it fails, or say precisely what rules such a case out. "It just does" is not on the menu.

Hint: Hunt at the extremes first — zero, one, negative, empty, equal. If every extreme survives, the reason they survive is the proof.

Answer:

Build rules: type every line, print shapes at each step, verify weight rows sum to 1.0 using assert abs(W.sum(-1) - 1).max() < 1e-5.

47. Milestone 1 — implement and verify attention

Worked example

Your turn: write attention(Q, K, V, d_k) that returns the output matrix. Predict what weights.sum(-1) should be.

Hint: scores = Q @ K.T / d_k**0.5; weights = F.softmax(scores, dim=-1); output = weights @ V. Assert row sums = 1.

import torch, torch.nn.functional as F
import numpy as np
torch.manual_seed(42)
d_k = 4
Q = torch.randn(3, d_k)
K = torch.randn(3, d_k)
V = torch.tensor([[ 0.1,-0.5, 0.8, 0.3],
                   [ 0.6, 0.2,-0.4, 0.9],
                   [-0.3, 0.7, 0.1,-0.2]])
def attention(Q, K, V, d_k):
    scores = Q @ K.T / d_k**0.5
    weights = F.softmax(scores, dim=-1)
    return weights @ V, weights
out, W = attention(Q, K, V, d_k)
print('weight row sums:', np.round(W.sum(-1).numpy(), 6))
print('output:', np.round(out.numpy(), 4))
tokenW row sumsoutput[0]
01.000000[0.069, 0.259, 0.086, 0.434]
11.000000[0.227, 0.254, -0.009, 0.746]
21.000000[0.053, 0.226, 0.183, 0.290]

48. Milestone 2 — prove Var(qᵀk) = d_k

Worked example

Your turn: for each d_k in [4, 16, 64], sample 10,000 (q, k) pairs from N(0,1) and compute (q·k).var(). Predict what value you'll see.

Hint: q=torch.randn(10000, d); k=torch.randn(10000, d); dots=(q*k).sum(1). Print dots.var().item() — it should equal d_k.

import torch
for d in [4, 16, 64]:
    q = torch.randn(10000, d)
    k = torch.randn(10000, d)
    dots = (q * k).sum(1)
    print(f'd_k={d:2d}: var(q^T k)={dots.var().item():.2f}  '
          f'(expected {d}),  std={dots.std().item():.3f}')
d_kVar(q·k) empiricalexpectedstd
43.9341.983
1616.10164.013
6464.67648.042

49. What each one costs: Milestone 2 — prove Var(qᵀk) = d_k

Trade off

Comparison matrix

From Milestone 2 — prove Var(qᵀk) = d_k: every row here is a choice with a cost. Fill the Var(q·k) empirical column, then say which row you would actually pick and what you give up for it.

d_kVar(q·k) empiricalexpectedstd
43.9341.983
1616.10164.013
6464.67648.042

50. Milestone 3 — full program with saturation demo

Worked example

Your turn: combine the attention function and the saturation demo. Print the entropy for d_k ∈ {4,16,64,256} both with and without scaling.

Hint: entropy = -(p * torch.log(p + 1e-9)).sum(). At d_k=256, unscaled entropy should drop to near 0.

import torch, torch.nn.functional as F

def entropy(logits):
    p = F.softmax(logits, dim=-1)
    return -(p * torch.log(p + 1e-9)).sum().item()

for d in [4, 16, 64, 256]:
    torch.manual_seed(0)
    q = torch.randn(1, d); k = torch.randn(5, d)
    raw = (q @ k.T)[0]
    print(f'd_k={d:3d}:  H(unscaled)={entropy(raw):.3f}  '
          f'H(scaled)={entropy(raw / d**0.5):.3f}')
print('\nAttention formula verified above.')
d_kH(unscaled)H(scaled)verdict
41.2401.491both spread
160.6261.439unscaled begins to sharpen
640.4041.367unscaled nearly peaked
2560.0001.065unscaled saturated; scaled healthy

51. Fill in: H(unscaled) for Milestone 3 — full program with saturation…

Comparison

Comparison matrix

From Milestone 3 — full program with saturation demo: refill the H(unscaled) column from what you know. The rest of the table is as it appeared.

d_kH(unscaled)H(scaled)verdict
41.2401.491both spread
160.6261.439unscaled begins to sharpen
640.4041.367unscaled nearly peaked
2560.0001.065unscaled saturated; scaled healthy

52. Show it off

Concept

Out loud, slides closed: (1) derive Attention(Q,K,V) from Bahdanau alignment to the matrix formula; (2) prove Var(qᵀk)=d_k and state why that forces the √d_k divisor; (3) explain why attention is a soft lookup table and how it relates to Nadaraya-Watson regression.

Stretch (homework): implement multi-head attention by splitting d_model into h heads of size d_k=d_model/h each, running attention in parallel, and concatenating; show that the output dimension is unchanged. Next: Lesson 80 — multi-head attention, positional encodings, and the full transformer block.

53. Connect it up: Lesson 79: Scaled Dot-Product Attention

Connect it up

Draw it

One page, no notation unless you need it: draw how these connect — The RNN bottleneck & Bahdanau attention · Queries, keys, values, and self-attention · The √d_k scaling: variance proof · Your turn: implement attention. Put an arrow wherever one of them is what makes another possible, and label the arrow with why.

54. What you can do now

Recap

conceptthe one thing to remember
RNN bottleneckwhole sequence compressed into one h_T; Bahdanau attends to all encoder states instead
Q / K / VQ=what I seek, K=what I advertise, V=what I contribute
√d_k scalingVar(q·k)=d_k; dividing restores unit variance and prevents softmax collapse
self-attentionQ=K=V=XW; output i = ∑_j softmax(sim)[i,j] · V[j]
soft lookupattention = differentiable Nadaraya-Watson regression; hard lookup is the 0-temp limit

Sources

  1. USAAIO Year-Long Master Lesson Plan, Lesson 79 — Scaled Dot-Product Attention — Barron · USAAIO Round 2 Preparation, 2026
  2. Bahdanau et al. 2015, 'Neural Machine Translation by Jointly Learning to Align and Translate' — arXiv:1409.0473
  3. Vaswani et al. 2017, 'Attention Is All You Need' — arXiv:1706.03762
  4. Attention math (scores, softmax saturation, self-attention weighted avg) verified with torch 2.7.1+cpu and numpy 2.2.6, June 2026 — Real execution, verified

Want this taught 1-on-1? Alexander tutors Machine Learning — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108