Week 7
Week 7 — Attention: read the relevant preceding context
Key questionWhen “like” appears in both [you,like] and [we,like], how does the model calculate Q/K/V, score visible positions, and produce different contextual representations and potentially different next-token logits?
Learning objectives
- Calculate X → Q/K/V → raw scores → scaling → causal mask → attention weights → weighted values using one reproducible example.
- Explain how attention scores are calculated and learned, and distinguish attention Softmax from vocabulary Softmax.
- Read the axis meanings in [B,T,C], [B,T,d_head], [B,T,T], and [B,n_head,T,T].
- Implement and inspect the fixed numerical head, a reusable causal head, and multi-head attention.
- Explain the path from attention output to logits, the mechanism's limits and T² cost, and why Week 8 adds the rest of the Transformer block.
110 min estimated reading time
Last week's Bigram used only the current token to look up one row of scores. Now compare the prefixes “you like” and “we like”: we want the final “like” to read different earlier information. Keep the same token IDs. To make the arithmetic manageable, this chapter explicitly uses simple four-feature input vectors and fixed projection matrices.
Scroll horizontally to view all columns.
| Study unit | The question we solve |
|---|---|
| 1: What should be read? | Start with the limitations of averaging and hand-picked weights. Then introduce a query as the current matching request, keys for scoring, and values as the content actually combined. |
| 2: Calculate each stage | Q/K/V → dot products → scaling → row-wise weights → weighted values. Trace every number back to the same inputs. |
| 3: Why future information is forbidden | Match input positions to next-token targets; explain mask direction, negative infinity, and the position axis. |
| 4: How attention learns | Connect the last position's output to vocabulary scores and cross-entropy; inspect the gradient of one Wq parameter, then introduce multiple heads. |
Run python week07_attention.py from the example directory. Observe different last-position weights for A and B, and explain why attention Softmax normalizes over positions while output Softmax normalizes over vocabulary candidates. Heads read in parallel; successive blocks transform representations in sequence.
Alternate reading, hand calculation, and code changes. Each unit can take several sessions. Existing section numbers are retained for links and reference; follow the displayed top-to-bottom learning order rather than jumping around to follow the old numbers.
Week 7 learning goal: let the same token read different prefixes
We keep Week 6's adapted words “you,” “we,” and “like,” their IDs, and the meanings of [B,T,C]. To calculate each Q/K/V entry by hand, however, we use this chapter's simple floating-point X and fixed projections—not embeddings copied from a particular Week 6 training step. Week 8 supplies a separate token-plus-position illustration; Weeks 10–12 use one shared trainable Mini GPT implementation.
Use two prompts throughout: A=[you,like] and B=[we,like]. Their final token is identical, but a contextual model should be able to let that final “like” read different earlier information. Attention provides this input-dependent information path.
Scroll horizontally to view all columns.
| Batch / Position | Tensor Slot | Token | Meaning |
|---|---|---|---|
| b=0, t=0 | x[0,0,:] | you | First token of Prompt A |
| b=0, t=1 | x[0,1,:] | like | Second token of Prompt A |
| b=1, t=0 | x[1,0,:] | we | First token of Prompt B |
| b=1, t=1 | x[1,1,:] | like | Second token of Prompt B |
- Input representations X [B,T,C] = [2,2,4]
- Each head projects Q / K / V with shape [2,2,2]
- QKᵀ produces raw scores [B,T,T] = [2,2,2]
- Divide by √d_k and apply the causal mask
- Softmax over key positions produces weights [2,2,2]
- Weights @ Values produces head output [2,2,2]
- Concatenate two heads and apply W_O to recover [2,2,4]
- Later Transformer computations and the LM head produce logits [2,2,V_vocab]
Knowledge check
Why can two prompts ending in “like” behave identically in a Bigram but differently with attention?
1. From input X to simple context aggregation
Attention does not directly receive token IDs in this calculation. IDs are integer lookup indices; X contains embedding vectors or a previous layer's outputs. Our simplified values make each matrix multiplication manageable by hand. Actual model representations are usually learned dense floating-point vectors.
A simple baseline uniformly averages the visible prefix. At the final position t=1, the fixed weights are [0.5,0.5]. Different inputs can give different averages for A and B, but these reading weights do not change with the current query.
Scroll horizontally to view all columns.
| Mechanism | Final-position weights for j=0 / j=1 | Can weights depend on the query? |
|---|---|---|
| Causal Uniform Mean | [0.5, 0.5] | No; the weights are fixed |
| Prompt A's teaching attention head | [0.599, 0.401] | Yes |
| Prompt B's teaching attention head | [0.426, 0.574] | Yes |
Knowledge check
What is the key difference between uniform averaging and attention?
2. Understand queries, keys, and values through a search analogy
Scroll horizontally to view all columns.
| The question attention must answer | Name | In the final “like” example |
|---|---|---|
| What is the current position looking for? | Query (Q) | “like” produces the current query |
| What makes each position matchable? | Key (K) | “you” or “we” provides key features to compare |
| What content is retrieved from a matched position? | Value (V) | That position's value contributes to the new representation |
Think of a query as a search request, a key as searchable index features, and a value as record content. This analogy explains the roles only. The model is not searching literal strings, and no person assigns a coordinate the meaning “subject.”
Knowledge check
Once the attention weights are calculated, which of Q, K, and V is combined with those weights?
3. Why use three representations?
A position's key supplies features for matching, its value supplies content to combine, and its query determines its own matching request. All three come from the same x_t but use different parameters.
Knowledge check
Why need a key not contain all the information in the value?
4. Calculate Q, K, and V from XW, then calculate raw scores
These parameters are deliberately chosen for readable arithmetic. Real models normally begin with initialized parameters and learn their values through training. Each W here is still shared across positions and prompts; we do not write a separate projection rule for every token.
For example, x_like=[0,1,0,0], so multiplication by each W selects its second row: q_like=[1,1], k_like=[0.2√2,0.2√2]≈[0.283,0.283], and v_like=[0,1].
The final “like” query is [1,1]. Its dot product with the “you” key is approximately 1×1.131+1×0=1.131. With its own “like” key, it is approximately 1×0.283+1×0.283=0.566.
For Prompt B, its dot product with the “we” key is approximately 1×0+1×0.141=0.141. The score for the “like” key remains approximately 0.566.
Knowledge check
Where does the approximately 1.131 raw score from Prompt A's final “like” to “you” come from?
5. From scores to attention weights
Divide scores by √d_k. Here d_k=2. Using the unrounded scores, Prompt A's final row becomes [0.8,0.4] and Prompt B's becomes [0.1,0.4]. The displayed raw scores [1.131,0.566] and [0.141,0.566] are rounded. The final position can read both columns, so this row has no future position to mask.
Prompt A, final query row
scaled scores = [0.8, 0.4]
exp values = [2.226, 1.492]
sum = 3.718
weights = [0.599, 0.401]
Prompt B, final query row
scaled scores = [0.1, 0.4]
exp values = [1.105, 1.492]
sum = 2.597
weights = [0.426, 0.574]Scroll horizontally to view all columns.
| Softmax | Axis being compared | Question answered |
|---|---|---|
| Attention Softmax | T key positions | Which positions should this query read, and with what weights? |
| Vocabulary Softmax | V vocabulary tokens | Which token should come next? |
Knowledge check
Why should each query row's attention weights sum to 1 before dropout?
6. Combine values using the weights
Prompt A, final like
weights = [0.599, 0.401]
Values = [[1,0], [0,1]]
o_A = 0.599 * [1,0] + 0.401 * [0,1]
= [0.599, 0.401]
Prompt B, final like
weights = [0.426, 0.574]
Values = [[-1,0], [0,1]]
o_B = 0.426 * [-1,0] + 0.574 * [0,1]
= [-0.426, 0.574]Both prompts end in “like,” yet their head outputs differ. The first-position keys produce different weights, and the “you” and “we” values also differ. Both differences affect the result.
Knowledge check
Why do the final head outputs for A and B differ?
7. The complete scaled dot-product attention sequence
- X @ W_Q/W_K/W_V → Q、K、V
- Q @ Kᵀ → Raw Scores S
- S / √d_k → Scaled Scores
- Future Columns → −∞ → Masked Scores
- Row Softmax → Attention Weights P
- P @ V → Head Output O
During training, O affects later representations, the language-model head, logits, and cross-entropy. Gradients flow back from loss through O, with one path through P, Softmax, and scores to W_Q/W_K, and another through values to W_V. The model does not begin with a correct attention table; parameter updates can improve its matching and information flow while reducing prediction loss.
Scroll horizontally to view all columns.
| Backward from output O=PV | Intermediate values on the path | Parameters or inputs affected |
|---|---|---|
| Content path | O → V | W_V and input X |
| Reading-weight path | O → P → Softmax → S → Q、K | W_Q, W_K, and input X |
Both paths contribute to the gradient of the shared input X, just as branches did in Week 4. V does not participate in the QKᵀ score calculation, so its direct gradient path from O does not pass through S. Identify the branches before tackling full matrix derivatives.
Knowledge check
What determines which positions receive higher scores?
8. Why divide by √d_k rather than d_k?
A dot product sums d_k products. Under a simplified model of independent zero-mean, unit-variance query/key components, each product has variance 1 and the sum has variance d_k. Its standard deviation is √d_k. Dividing by √d_k gives a typical score scale that does not grow with head width under these assumptions.
Prompt A, final query
raw scores = [1.131, 0.566]
sqrt(d_k) = sqrt(2) ≈ 1.414
scaled scores = [0.800, 0.400]
Prompt B, final query
raw scores = [0.141, 0.566]
scaled scores = [0.100, 0.400]Large differences within a score row can make Softmax nearly one-hot, making some attention weights locally insensitive to score changes. Adding the same large constant to every score does not change Softmax. These local sensitivities also do not by themselves determine every final parameter gradient. Positive scaling preserves the row's ordering while changing its spread.
Knowledge check
What stays unchanged after dividing by √d_k, and what is moderated?
9. Self-attention shapes: what each axis means
Scroll horizontally to view all columns.
| Tensor | Shape | Meaning of the three axes |
|---|---|---|
| X | [B,T,C] = [2,2,4] | Batch, Token Position, Model Feature |
| Q / K / V | [B,T,d_head] = [2,2,2] | Batch, Token Position, Head Feature |
| Scores / Weights | [B,T,T] = [2,2,2] | Batch, Query Position, Key/Value Position |
| Head Output | [B,T,d_head] = [2,2,2] | Batch, Query Position, Head Feature |
In each [T,T]=[2,2] matrix, row t is the query position requesting information. Column j is the key position being compared and the corresponding value position. Neither axis is a feature axis.
Scroll horizontally to view all columns.
| Index | Meaning |
|---|---|
| weights[0,1,0] | Weight from Prompt A's final “like” to “you” |
| weights[1,1,0] | Weight from Prompt B's final “like” to “we” |
| output[1,1,:] | Prompt B's final “like” head-output vector |
Scroll horizontally to view all columns.
| Separate shape check: B=1, T=3, C=4, H=2 | Expected shape |
|---|---|
| Input | [1,3,4] |
| Q/K/V after splitting into heads | [1,2,3,2] |
| Scores: every query against every key | [1,2,3,3] |
| Merge head features back together | [1,3,4] |
This separate axis-check example does not replace the earlier two-token arithmetic. Unequal axis sizes make mistakes such as exchanging head and time axes easier to detect.
Knowledge check
What does weights[1,1,0] mean?
10. Why GPT needs a causal mask
For a separate next-token illustration, use input [you,like] and target [like,AI]. Position t=0 must predict “like” using only “you.” Without a mask, it could read “like” directly at input position 1—its own answer. During generation, that future token has not yet been produced, so the shortcut is unavailable.
Position t=3 can read positions 0,1,2,3—not only its immediate predecessor. The mask defines visibility; it does not determine the relative reading weights among visible positions.
Knowledge check
Why does omitting the mask create a training/generation mismatch?
11. Why use −∞ before Softmax?
Scroll horizontally to view all columns.
| Scaled row at t=0 | Result |
|---|---|
| Original [0.2,0.9] | The second column is in the future |
| Incorrect: replace the future score with 0 | Softmax([0.2,0]) = [0.550,0.450] |
| Correct: replace it with −∞ | Softmax([0.2,−∞]) = [1,0] |
Knowledge check
Why not just replace a forbidden score with 0?
12. PyTorch code reproducing the teaching calculation
import math
import torch
torch.set_printoptions(precision=4, sci_mode=False)
sqrt_2 = math.sqrt(2)
# [B,T,C] = [2,2,4]
x = torch.tensor([
[
[1.0, 0.0, 0.0, 0.0], # you
[0.0, 1.0, 0.0, 0.0], # like
],
[
[0.0, 0.0, 1.0, 0.0], # we
[0.0, 1.0, 0.0, 0.0], # like
],
])
# Formula convention: [C,d_head] = [4,2]
w_q = torch.tensor([
[0.5, 0.5],
[1.0, 1.0],
[-0.5, 0.5],
[0.0, 0.0],
])
w_k = torch.tensor([
[0.8 * sqrt_2, 0.0],
[0.2 * sqrt_2, 0.2 * sqrt_2],
[0.0, 0.1 * sqrt_2],
[0.0, 0.0],
])
w_v = torch.tensor([
[1.0, 0.0],
[0.0, 1.0],
[-1.0, 0.0],
[0.0, 0.0],
])
q = x @ w_q
k = x @ w_k
v = x @ w_v
raw_scores = q @ k.transpose(-2, -1)
scaled_scores = raw_scores / math.sqrt(q.size(-1))
T = x.size(1)
causal_mask = torch.tril(torch.ones(T, T, dtype=torch.bool))
masked_scores = scaled_scores.masked_fill(~causal_mask, float("-inf"))
weights = torch.softmax(masked_scores, dim=-1)
output = weights @ v
print("Q:", q)
print("K:", k)
print("V:", v)
print("Raw scores:", raw_scores)
print("Scaled and masked scores:", masked_scores)
print("Attention weights:", weights)
print("Head output:", output)Expected final rows
Prompt A weights: [0.5987, 0.4013]
Prompt B weights: [0.4256, 0.5744]
Prompt A output: [ 0.5987, 0.4013]
Prompt B output: [-0.4256, 0.5744]This code uses x @ w_q, so W follows the mathematical [C,d_head] convention. nn.Linear(C,d_head) stores its weight as [d_head,C] and uses the corresponding transpose in the computation. The calculations are equivalent.
Knowledge check
Why does this example avoid a randomly initialized nn.Linear?
13. A reusable causal attention head
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
class AttentionHead(nn.Module):
def __init__(self, embed_dim: int, head_size: int, context_length: int):
super().__init__()
self.query = nn.Linear(embed_dim, head_size, bias=False)
self.key = nn.Linear(embed_dim, head_size, bias=False)
self.value = nn.Linear(embed_dim, head_size, bias=False)
self.register_buffer(
"causal_mask",
torch.tril(
torch.ones(
context_length,
context_length,
dtype=torch.bool,
)
),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
# x: [B,T,C]
_, T, _ = x.shape
if T > self.causal_mask.size(0):
raise ValueError("Sequence length exceeds context_length")
q = self.query(x) # [B,T,d_head]
k = self.key(x) # [B,T,d_head]
v = self.value(x) # [B,T,d_head]
scores = q @ k.transpose(-2, -1) # [B,T,T]
scores = scores / math.sqrt(k.size(-1))
mask = self.causal_mask[:T, :T]
scores = scores.masked_fill(~mask, float("-inf"))
weights = F.softmax(scores, dim=-1)
return weights @ v # [B,T,d_head]Scroll horizontally to view all columns.
| Code stage | Formula | Shape |
|---|---|---|
| query/key/value(x) | Q=XW_Q, K=XW_K, V=XW_V | [B,T,C] → [B,T,d_head] |
| q @ k.transpose(-2,-1) | S=QKᵀ | [B,T,d_head] @ [B,d_head,T] → [B,T,T] |
| scores / sqrt(k.size(-1)) | S/√d_k | [B,T,T] → [B,T,T] |
| masked_fill | Future → −∞ | [B,T,T] → [B,T,T] |
| softmax(dim=-1) | Row Softmax | [B,T,T] → [B,T,T] |
| weights @ v | O=PV | [B,T,T] @ [B,T,d_head] → [B,T,d_head] |
register_buffer registers the mask so it moves with the model between devices without becoming a trainable parameter. [:T,:T] selects the relevant part of the maximum-length mask for the current sequence.
Knowledge check
Why can this AttentionHead not be added directly to input x in our residual path?
14. Why is it called self-attention?
“Self” means queries, keys, and values come from the same input sequence representations X. It does not restrict a token to itself. GPT's causal self-attention permits the current position and all earlier positions.
Scroll horizontally to view all columns.
| Type | Query source | Key/value source |
|---|---|---|
| Self-Attention | Sequence X | The same sequence X |
| Cross-Attention | Target-sequence representation or current state | Another source sequence |
Prompt A's score slice contains only “you” and “like”; B's contains only “we” and “like.” Batching computes them together without concatenating them into one text.
Knowledge check
Can Prompt A's “like” read Prompt B's “we”?
15. Multi-head attention and its output projection
Each head has its own W_Q, W_K, and W_V, allowing different matching features and value representations. Heads may use different signals, but they are not guaranteed to become named grammatical specialists.
- X [B,T,C]=[2,2,4]
- In parallel: Head 1(X) and Head 2(X), each [2,2,2]
- Concatenate along features to obtain [2,2,4]
- Multiply by W_O:[4,4] to produce [2,2,4]
class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim: int, num_heads: int, context_length: int):
super().__init__()
if embed_dim % num_heads != 0:
raise ValueError("embed_dim must be divisible by num_heads")
head_size = embed_dim // num_heads
self.heads = nn.ModuleList([
AttentionHead(embed_dim, head_size, context_length)
for _ in range(num_heads)
])
self.output_projection = nn.Linear(
embed_dim,
embed_dim,
bias=False,
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
head_outputs = [head(x) for head in self.heads]
concatenated = torch.cat(head_outputs, dim=-1)
return self.output_projection(concatenated)The output projection does more than satisfy a shape requirement. After concatenation, features are merely side by side. W_O learns combinations across heads and supplies the [B,T,C] interface for the residual addition.
Head 2 receives the same X, not Head 1's output. Imagine two two-feature records at each position: place them together, then learn how to combine them. Successive Transformer blocks, in contrast, pass one block's output into the next.
Knowledge check
Why use W_O after concatenating the heads?
16. How attention output affects final logits
- X [B,T,C]
- Multi-Head Attention [B,T,C]
- Later Transformer residual / FFN / layer computations [B,T,C]
- Language Model Head W_vocab:[C,V_vocab]
- Logits [B,T,V_vocab]
- Apply Softmax over vocabulary candidates when probabilities are needed
A and B already have different final-position head outputs. Multi-head mixing, W_O, and later Transformer computations can produce different H values, allowing the language-model head to produce different logits. Attention itself does not directly select a token.
Scroll horizontally to view all columns.
| Object | Typical shape | Meaning |
|---|---|---|
| Attention Weights | [B,n_head,T,T] | Which key/value positions each query reads |
| Contextual Features H | [B,T,C] | A contextual representation at each position |
| Vocabulary Logits | [B,T,V_vocab] | A raw score for each vocabulary candidate |
| Vocabulary Probabilities | [B,T,V_vocab] | The distribution from Softmax over the logits' vocabulary axis |
Knowledge check
What do attention weights and vocabulary probabilities answer?
17. The computational cost of attention
Each head's score matrix has T² entries. Across the batch and all heads there are B×n_head×T² score entries. In this explicit implementation, weights need the same number of entries, and training requires additional intermediate values and gradients.
Scroll horizontally to view all columns.
| Fix B=2 and n_head=2 | Score entries across the batch and heads | Relative to T=2 |
|---|---|---|
| T=2 | 2 × 2 × 2² = 16 | 1× |
| T=1000 | 2 × 2 × 1000² = 4,000,000 | 250,000× |
The causal mask forbids future reads, but this ordinary dense implementation still constructs a full T×T matrix. Optimized implementations can change materialization, memory use, and execution details; the underlying query/key axes remain important.
Knowledge check
With batch size, head count, and width fixed, what happens to score-entry count if T doubles?
18. Week 7 debugging checklist and ten essential ideas
Scroll horizontally to view all columns.
| Debugging stage | What to check |
|---|---|
| 1. X | Input vectors have shape [B,T,C]; these are not token IDs |
| 2. Q/K/V | Q/K/V have shape [B,T,d_head] and come from their respective projections |
| 3. Scores | K.transpose(-2,-1) gives scores of shape [B,T,T] |
| 4. Scale | Scores are divided by √d_k |
| 5. Mask | Future-position scores are −∞ before Softmax |
| 6. Softmax | Softmax uses dim=-1; each query row sums to 1 before dropout |
| 7. Retrieval | Output uses Weights @ V, not @ K |
| 8. Multi-Head | Heads concatenate along features and W_O returns width C |
| 9. Batch | Different batch examples do not read one another |
| 10. Logits | The LM head receives contextual features, not attention weights |
- Attention allows the same current token to read information from different prefixes.
- X contains vector representations, not token IDs.
- The query supplies the current position's matching features.
- The key supplies learned features used for matching.
- The value supplies the content combined after matching.
- Each QKᵀ entry is one raw query–key dot-product score.
- Use scaled, causally masked scores before row-wise Softmax; do not treat a post-Softmax zeroing operation as the same normalized calculation.
- The causal mask forbids future positions while allowing history and the current position.
- Head concatenation and W_O produce an output with model width C.
- Attention outputs contextual features; the LM head produces vocabulary logits.
Knowledge check
If scores look correct but a future position has positive weight, which two steps should you inspect first?
19. Week 7 → Week 8: attention is not a complete Transformer block
Week 8 continues with the interface [B,T,C]=[2,2,4]. Adding token and position embeddings preserves [2,2,4]. Multi-head attention must also return [2,2,4] after concatenation and W_O so its result can be added to the residual stream.
- Token Embedding + Position Embedding [2,2,4]
- Pre-Norm Multi-Head Attention [2,2,4]
- x + Attention(LN₁(x)) [2,2,4]
- Per-Position FFN [2,2,4]
- x₁ + FFN(LN₂(x₁)) [2,2,4]
- Language Model Head → Logits [2,2,V_vocab]
Scroll horizontally to view all columns.
| Component | Main responsibility |
|---|---|
| Position Embedding | Supply explicit token-position information |
| Causal Multi-Head Attention | Mix information across allowed token positions |
| FFN | Apply the same nonlinear transformation separately at every position |
| Residual Connection | Provide an identity path that can help train deeper networks |
| Layer Normalization | Control feature scales within each position |
Knowledge check
Which Week 8 component mixes positions, and which processes each position separately?