Current: Week 7

0%

Week 7

Week 7 - Attention:让当前位置按需读取左侧上下文

Key question当“喜欢”既出现在 [我,喜欢] 又出现在 [猫,喜欢] 时,模型如何从输入向量计算 Q、K、V,给可见位置打分,并产生不同的上下文表示与下一词 Logits?

Learning objectives

  • 用同一个可复现数值例子完整计算 X → Q/K/V → Raw Scores → Scale → Causal Mask → Attention Weights → Weighted Values。
  • 解释 Attention 如何打分、分数如何通过训练学会,以及 Attention Softmax 与 Vocabulary Softmax 的区别。
  • 准确读取 [B,T,C]、[B,T,d_head]、[B,T,T] 和 [B,n_head,T,T] 中每个轴的语义。
  • 实现并审计可复现的单 Head、可复用的 Causal Attention Head 与 Multi-Head Attention。
  • 说明 Attention Output 如何继续影响 Logits、Attention 的边界、T² 成本,以及它为什么需要 Week 8 的 Transformer Block。

110 min estimated reading time

Week progress: 0 of 20 sections (0%)Course progress: 0 of 321 sections (0%)

上一周的 Bigram 只用当前 token 查一行分数。现在保留“我 喜欢”和“猫 喜欢”两条前缀,想让最后的“喜欢”读到不同的左侧信息。Token 编号不变;为算术清楚,本周显式使用更简单的四维输入和固定投影。

Scroll horizontally to view all columns.

Course data table
学习单元本次解决的问题
一:读取什么平均和手工加权的限制;再说明 query 是本次匹配需求、key 用来打分、value 是实际取回内容。
二:逐步手算Q/K/V → 点积 → 缩放 → 每行权重 → 加权值;每个数都追溯到同一组输入。
三:为什么不能偷看对应输入和下一词目标,说明 mask 的方向、负无穷和位置轴。
四:怎样参与学习从末位置输出接词表评分和 CE;观察一个 Wq 参数的梯度,再理解多头。

运行 python week07_attention.py;需看到 A/B 末位置权重不同,并解释两种 Softmax 分别沿“位置”还是“词表”归一化。多头并行读信息,多个 block 串行改造表示,不要混为一谈。

建议阅读、手算、改代码交替进行,每个单元可拆成几次完成。章节编号保留用于旧链接和回查;按页面从上到下的新顺序学习,不需要按旧编号来回跳转。

Week 7 核心目标:让相同 Token 读取不同前缀

这一周保留词语“我、猫、喜欢”及 [B,T,C] 的含义,但为了让 Q/K/V 每项都能手算,改用本章指定的简单浮点 X 和固定投影。它不是把 Week 6 训练到某一步的 embedding 原样搬来。Week 8 会另给 token+position 的演示值;Weeks 10–12 则使用同一套可学习 MiniGPT。

本周只围绕两个 Prompt 展开:Prompt A=[我,喜欢],Prompt B=[猫,喜欢]。两者最后一个 Token 相同,但合理的模型应该能让最后的“喜欢”读取不同的左侧信息。Attention 增加的就是这条动态信息通路。

Scroll horizontally to view all columns.

固定 B=2、T=2、C=4;后续所有矩阵都来自这两个 Prompt。
Batch / PositionTensor SlotToken语义
b=0, t=0x[0,0,:]Prompt A 的第一个 Token
b=0, t=1x[0,1,:]喜欢Prompt A 的第二个 Token
b=1, t=0x[1,0,:]Prompt B 的第一个 Token
b=1, t=1x[1,1,:]喜欢Prompt B 的第二个 Token
Concept sequence
  1. 输入表示 X [B,T,C] = [2,2,4]
  2. 每个 Head 投影出 Q / K / V [2,2,2]
  3. QKᵀ 产生 Raw Scores [B,T,T] = [2,2,2]
  4. 除以 √d_k,应用 Causal Mask
  5. 沿 Key Position Axis 做 Softmax 得 Weights [2,2,2]
  6. Weights @ Values 得 Head Output [2,2,2]
  7. 两个 Heads Concat + W_O 恢复 [2,2,4]
  8. 后续 Transformer 与 LM Head 产生 Logits [2,2,V_vocab]
contextt=jtαt,jvj\operatorname{context}_t=\sum_{j\le t}\alpha_{t,j}v_j

Knowledge check

为什么两个最后都是“喜欢”的 Prompt 在 Bigram 中相同,在 Attention 中却可能不同?

1. 从输入 X 到最简单的 Context Aggregation

Attention 不直接接收 Token ID。Token ID 只是用于查表的整数;进入 Attention 的 X 是 Embedding 或上一层输出的向量。本课使用简化表示,使每一步矩阵乘法都能手算。真实模型中的向量通常是训练得到的稠密小数。

XA=[10000100],XB=[00100100]X_A=\begin{bmatrix}1&0&0&0\\0&1&0&0\end{bmatrix},\qquad X_B=\begin{bmatrix}0&0&1&0\\0&1&0&0\end{bmatrix}
XR[B,T,C]=R[2,2,4]X\in\mathbb{R}^{[B,T,C]}=\mathbb{R}^{[2,2,4]}

一个简单基线是对当前位置允许读取的前缀做均匀平均。最终位置 t=1 的固定读取比例是 [0.5,0.5];它能让 A 与 B 因输入不同而产生不同平均值,但不能根据当前 Query 动态改变读取比例。

xˉA,1=[1,0,0,0]+[0,1,0,0]2=[0.5,0.5,0,0]\bar{x}_{A,1}=\frac{[1,0,0,0]+[0,1,0,0]}{2}=[0.5,0.5,0,0]
xˉB,1=[0,0,1,0]+[0,1,0,0]2=[0,0.5,0.5,0]\bar{x}_{B,1}=\frac{[0,0,1,0]+[0,1,0,0]}{2}=[0,0.5,0.5,0]

Scroll horizontally to view all columns.

Attention 的优势不是“第一次能汇总”,而是每个 Query 都能计算自己的读取比例。
机制最终位置对 j=0 / j=1 的读取能否按 Query 动态选择
Causal Uniform Mean[0.5, 0.5]不能,比例固定
Prompt A 的教学 Attention Head[0.599, 0.401]可以
Prompt B 的教学 Attention Head[0.426, 0.574]可以

Knowledge check

Uniform Mean 与 Attention 的核心区别是什么?

2. 用搜索系统理解 Query、Key、Value

Scroll horizontally to view all columns.

Course data table
Attention 必须回答的问题名称在最终“喜欢”的例子中
当前位置正在寻找什么?Query (Q)“喜欢”形成当前读取请求
每个位置凭什么被找到?Key (K)“我”或“猫”提供可匹配线索
找到位置后带回什么?Value (V)对应位置的内容参与新表示

可以把 Query 想成搜索请求、Key 想成索引线索、Value 想成记录正文。这个比喻只解释职责;模型中没有真正的字符串搜索,也没有人为指定“某一维等于主语”。

Q=XWQ,K=XWK,V=XWVQ=XW_Q,\qquad K=XW_K,\qquad V=XW_V
X:[2,2,4],WQ,WK,WV:[4,2]Q,K,V:[2,2,2]X:[2,2,4],\qquad W_Q,W_K,W_V:[4,2]\quad\Longrightarrow\quad Q,K,V:[2,2,2]

Knowledge check

Attention Weights 形成以后,Q、K、V 中哪一种会被加权相加?

3. 为什么需要三份表示

一个位置可以用 Key 宣传“怎样找到我”,用 Value 保存“找到我后应带走什么”,并在自己的读取轮次用 Query 表达“我现在需要什么”。三者来自同一个 x_t,但使用不同参数。

WQ,WK,WVR4×2,qt,kt,vtR2W_Q,W_K,W_V\in\mathbb{R}^{4\times2},\qquad q_t,k_t,v_t\in\mathbb{R}^{2}
xtR1×4,xtWR1×2x_t\in\mathbb{R}^{1\times4},\qquad x_tW\in\mathbb{R}^{1\times2}

Knowledge check

为什么 Key 不需要保存 Value 中的全部信息?

4. 从 XW 得到 Q、K、V,再计算 Raw Scores

下面的参数专门为教学设计,目的是让所有数字可以手算。真实模型通常从随机参数开始,通过训练得到稠密小数。这里的 W 仍是全局共享矩阵,并不是为每个 Token 单独写一套规则。

WQ=[0.50.5110.50.500],WK=[0.8200.220.2200.1200],WV=[10011000]W_Q=\begin{bmatrix}0.5&0.5\\1&1\\-0.5&0.5\\0&0\end{bmatrix},\quad W_K=\begin{bmatrix}0.8\sqrt2&0\\0.2\sqrt2&0.2\sqrt2\\0&0.1\sqrt2\\0&0\end{bmatrix},\quad W_V=\begin{bmatrix}1&0\\0&1\\-1&0\\0&0\end{bmatrix}

例如 x_喜欢=[0,1,0,0],所以它乘任一 W 时会取出对应矩阵的第二行:q_喜欢=[1,1],k_喜欢=[0.2√2,0.2√2]≈[0.283,0.283],v_喜欢=[0,1]。

QA=[0.50.511],KA=[1.13100.2830.283],VA=[1001]Q_A=\begin{bmatrix}0.5&0.5\\1&1\end{bmatrix},\quad K_A=\begin{bmatrix}1.131&0\\0.283&0.283\end{bmatrix},\quad V_A=\begin{bmatrix}1&0\\0&1\end{bmatrix}
QB=[0.50.511],KB=[00.1410.2830.283],VB=[1001]Q_B=\begin{bmatrix}-0.5&0.5\\1&1\end{bmatrix},\quad K_B=\begin{bmatrix}0&0.141\\0.283&0.283\end{bmatrix},\quad V_B=\begin{bmatrix}-1&0\\0&1\end{bmatrix}

最终“喜欢”的 Query 是 [1,1]。它与“我”的 Key 做 Dot Product:1×1.131+1×0=1.131;与“喜欢”自己的 Key 计算:1×0.283+1×0.283=0.566。

在 Prompt B 中,它与“猫”的 Key 计算:1×0+1×0.141=0.141;与“喜欢”自己的 Key 仍得到 0.566。

SA=QAKA=[0.5660.2831.1310.566],SB=QBKB=[0.07100.1410.566]S_A=Q_AK_A^\top=\begin{bmatrix}0.566&0.283\\1.131&0.566\end{bmatrix},\qquad S_B=Q_BK_B^\top=\begin{bmatrix}0.071&0\\0.141&0.566\end{bmatrix}

Knowledge check

Prompt A 中最终“喜欢”对“我”的 Raw Score 1.131 是怎样产生的?

5. 从 Scores 到 Attention Weights

先将最终 Query Row 除以 √d_k。本例 d_k=2,所以 Prompt A 从 [1.131,0.566] 得到 [0.8,0.4],Prompt B 从 [0.141,0.566] 得到 [0.1,0.4]。最终位置可以读取两列,因此这一行没有未来位置需要屏蔽。

αt,j=exp(s^t,j)r=0T1exp(s^t,r)\alpha_{t,j}=\frac{\exp(\hat{s}_{t,j})}{\sum_{r=0}^{T-1}\exp(\hat{s}_{t,r})}
text
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.

数学函数相同,但输入、Axis 与含义不同。Attention Weight 不是下一词概率。
Softmax竞争的 Axis回答的问题
Attention SoftmaxT 个 Key Positions当前 Query 应读取哪些位置?
Vocabulary SoftmaxV 个 Vocabulary Tokens下一个 Token 应该是哪一个?
P=softmax(S^,dim=1),S^,P:[B,T,T]=[2,2,2]P=\operatorname{softmax}(\widehat{S},\mathrm{dim}=-1),\qquad \widehat{S},P:[B,T,T]=[2,2,2]

Knowledge check

为什么 Attention Softmax 必须让每个 Query Row 分别加总为 1?

6. 用 Weights 加权汇总 Values

ot=j=0T1αt,jvjo_t=\sum_{j=0}^{T-1}\alpha_{t,j}v_j
text
Prompt A, final 喜欢
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 喜欢
weights = [0.426, 0.574]
Values  = [[-1,0], [0,1]]

o_B = 0.426 * [-1,0] + 0.574 * [0,1]
    = [-0.426, 0.574]

两条 Prompt 的最后 Token 都是“喜欢”,但 Head Output 已经不同。差异一部分来自 Key 不同造成的 Weight 不同,另一部分来自“我”和“猫”的 Value 本身不同。

P:[B,T,T] @ V:[B,T,dhead]O:[B,T,dhead]P:[B,T,T]\ @\ V:[B,T,d_{\mathrm{head}}]\longrightarrow O:[B,T,d_{\mathrm{head}}]

Knowledge check

Prompt A 与 Prompt B 的最终 Head Output 为什么不同?

7. 一条完整的 Scaled Dot-Product Attention 计算链

Attention(Q,K,V)=softmax ⁣(mask ⁣(QKdk))V\operatorname{Attention}(Q,K,V)=\operatorname{softmax}\!\left(\operatorname{mask}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)\right)V
Concept sequence
  1. X @ W_Q/W_K/W_V → Q、K、V
  2. Q @ Kᵀ → Raw Scores S
  3. S / √d_k → Scaled Scores
  4. Future Columns → −∞ → Masked Scores
  5. Row Softmax → Attention Weights P
  6. P @ V → Head Output O
Q:[B,T,dk] @ K:[B,dk,T]S:[B,T,T],P:[B,T,T] @ V:[B,T,dv]O:[B,T,dv]Q:[B,T,d_k]\ @\ K^\top:[B,d_k,T]\rightarrow S:[B,T,T],\qquad P:[B,T,T]\ @\ V:[B,T,d_v]\rightarrow O:[B,T,d_v]

训练时,O 继续影响 Transformer 的后续表示、Language Model Head、Logits 与 Cross Entropy Loss。梯度再从 Loss 反向经过 O、P、Softmax 和 Scores,更新 W_Q、W_K 与 W_V。模型不是先拥有正确 Attention 表,而是在降低最终预测 Loss 的过程中逐步学会怎样打分和传递内容。

Scroll horizontally to view all columns.

Course data table
从输出 O=PV 回传经过哪些中间量最后影响
内容这条路O → VW_V 以及输入 X
读取比例这条路O → P → Softmax → S → Q、KW_Q、W_K 以及输入 X

两条路在同一个输入 X 处要汇总梯度,和 Week 4 的共享变量一样。V 不参与 QKᵀ 的 score 计算,因此它的直接梯度不需要绕过 S。先能指出分支,再看完整矩阵求导。

Knowledge check

模型中“什么位置应得高分”是谁决定的?

8. 为什么除以 √d_k,而不是 d_k

Dot Product 是 d_k 个乘积之和。若每个分量大致零均值、单位方差且不过度相关,那么这些乘积之和的方差约随 d_k 增长,标准差则约随 √d_k 增长。除以 √d_k 可以把典型 Score Scale 拉回较稳定的范围。

qk=i=1dkqiki,Var(qk)dk,Std(qk)dkq\cdot k=\sum_{i=1}^{d_k}q_i k_i,\qquad \operatorname{Var}(q\cdot k)\approx d_k,\qquad \operatorname{Std}(q\cdot k)\approx\sqrt{d_k}
text
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]

如果同一行分数差距过大,Softmax 容易过早接近 One-Hot,使部分注意力权重对分数的局部变化不敏感。所有分数同加一个大常数不改变 Softmax;也不能把这里的局部斜率简单等同于所有最终参数的梯度。缩放不会改变同一行的排序,只调整 Softmax 接收到的数值尺度。

S~=S/dk,S,S~:[B,T,T]\widetilde{S}=S/\sqrt{d_k},\qquad S,\widetilde{S}:[B,T,T]

Knowledge check

除以 √d_k 后,什么保持不变,什么变得更稳定?

9. Self-Attention 的 Shape 与每个 Axis

Scroll horizontally to view all columns.

Shape 数字相同不表示 Axis 含义相同。
TensorShape三个 Axis 的含义
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
Q:[B,T,dk] @ K.transpose(2,1):[B,dk,T]S:[B,T,T]Q:[B,T,d_k]\ @\ K.\operatorname{transpose}(-2,-1):[B,d_k,T]\rightarrow S:[B,T,T]
P:[B,T,T] @ V:[B,T,dv]O:[B,T,dv]P:[B,T,T]\ @\ V:[B,T,d_v]\rightarrow O:[B,T,d_v]

每张 [T,T]=[2,2] 表中,行 t 是发起读取的 Query Position,列 j 是被比较的 Key Position,也是稍后对应的 Value Position。它们都不是 Feature Axis。

Scroll horizontally to view all columns.

Course data table
索引含义
weights[0,1,0]Prompt A 最终“喜欢”分给“我”的 Weight
weights[1,1,0]Prompt B 最终“喜欢”分给“猫”的 Weight
output[1,1,:]Prompt B 最终“喜欢”的 Head Output Vector

Scroll horizontally to view all columns.

Course data table
额外形状检查,另取 B=1,T=3,C=4,H=2预期 shape
输入[1,3,4]
分 head 后 Q/K/V[1,2,3,2]
scores:每个 query 对每个 key[1,2,3,3]
拼回通道[1,3,4]

这只是检验轴的独立例子,不改变前面的两词手算。数字不全相同时,误把 head 轴当时间轴更容易暴露。

Knowledge check

weights[1,1,0] 表示什么?

10. 为什么 GPT 需要 Causal Mask

例如输入 [我,喜欢]、目标 [喜欢,猫]。位置 t=0 应只根据“我”预测“喜欢”;若未使用 Mask,它可以直接读取右侧已经出现的“喜欢”,相当于训练时看见答案。生成时未来 Token 尚不存在,因此这种能力无法使用。

Mt,j={1,jt0,j>tM_{t,j}=\begin{cases}1,&j\le t\\0,&j>t\end{cases}
MT=2=[1011],MT=4=[1000110011101111]M_{T=2}=\begin{bmatrix}1&0\\1&1\end{bmatrix},\qquad M_{T=4}=\begin{bmatrix}1&0&0&0\\1&1&0&0\\1&1&1&0\\1&1&1&1\end{bmatrix}

位置 t=3 可以读取 0、1、2、3,不是只能读取紧邻的前一个 Token。Mask 只划定信息边界,不决定允许位置之间具体读取多少。

M:[T,T] broadcast over Batch and Heads Scores:[B,nhead,T,T]M:[T,T]\ \xrightarrow{\text{broadcast over Batch and Heads}}\ \mathrm{Scores}:[B,n_{\mathrm{head}},T,T]

Knowledge check

为什么不使用 Mask 会造成训练与生成不一致?

11. 为什么 Mask 在 Softmax 前使用 −∞

Scroll horizontally to view all columns.

0 是合法 Score,不等于“移除候选”;−∞ 的指数才是 0。
t=0 的 Scaled Row结果
原始 [0.2,0.9]第二列是 Future
错误:Future 改为 0Softmax([0.2,0]) = [0.550,0.450]
正确:Future 改为 −∞Softmax([0.2,−∞]) = [1,0]
S^t,j={S~t,j,Mt,j=1,Mt,j=0,e=0\widehat{S}_{t,j}=\begin{cases}\widetilde{S}_{t,j},&M_{t,j}=1\\-\infty,&M_{t,j}=0\end{cases},\qquad e^{-\infty}=0
S^A=[0.40.80.4],S^B=[0.050.10.4]\widehat{S}_A=\begin{bmatrix}0.4&-\infty\\0.8&0.4\end{bmatrix},\qquad \widehat{S}_B=\begin{bmatrix}0.05&-\infty\\0.1&0.4\end{bmatrix}
PA=[100.5990.401],PB=[100.4260.574]P_A=\begin{bmatrix}1&0\\0.599&0.401\end{bmatrix},\qquad P_B=\begin{bmatrix}1&0\\0.426&0.574\end{bmatrix}

Knowledge check

为什么 Forbidden Score 不能简单改成 0?

12. 可复现全部教学数字的 PyTorch 代码

python
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],  # 我
        [0.0, 1.0, 0.0, 0.0],  # 喜欢
    ],
    [
        [0.0, 0.0, 1.0, 0.0],  # 猫
        [0.0, 1.0, 0.0, 0.0],  # 喜欢
    ],
])

# 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)

text
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]

这段代码使用 x @ w_q,所以 W 按公式写成 [C,d_head]。nn.Linear(C,d_head) 内部存储的 weight 是 [d_head,C],运行时框架使用其转置;两种写法的数学含义相同。

Knowledge check

为什么这段代码不使用随机初始化的 nn.Linear?

13. 可复用的 Causal Attention Head

python
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.

Course data table
代码阶段公式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_fillFuture → −∞[B,T,T] → [B,T,T]
softmax(dim=-1)Row Softmax[B,T,T] → [B,T,T]
weights @ vO=PV[B,T,T] @ [B,T,d_head] → [B,T,d_head]

register_buffer 让 Mask 随模型移动到 CPU 或 GPU,但不会成为 Trainable Parameter。[:T,:T] 让同一个最大 Context Mask 适配当前 Runtime Sequence Length。

Knowledge check

这个 AttentionHead 为什么不能直接与输入 x 做 Residual Addition?

14. 为什么叫 Self-Attention

Self 的含义是 Q、K、V 都来自同一条输入 X。它不表示 Token 只能看自己;GPT 的 Causal Self-Attention 允许每个位置看自己和全部可见左侧。

Q=XWQ,K=XWK,V=XWVQ=XW_Q,\qquad K=XW_K,\qquad V=XW_V

Scroll horizontally to view all columns.

Course data table
类型Query 来源Key / Value 来源
Self-Attention序列 X同一序列 X
Cross-Attention目标序列或当前状态另一条 Source Sequence
S:[B,T,T][B,T,B,T]S:[B,T,T]\neq[B,T,B,T]

Prompt A 的 Scores Slice 只包含“我、喜欢”;Prompt B 的 Slice 只包含“猫、喜欢”。把两条 Prompt 放入同一 Batch 只是并行计算,不是把它们接成一段文本。

Knowledge check

Prompt A 的“喜欢”可以读取 Prompt B 的“猫”吗?

15. Multi-Head Attention 与 Output Projection

每个 Head 都有自己的 W_Q、W_K、W_V,可以学习不同的匹配空间和 Payload。不同 Head 可能利用不同线索,但不保证它们自动变成可命名的语法专家。

C=4,nhead=2,dhead=C/nhead=2C=4,\qquad n_{\mathrm{head}}=2,\qquad d_{\mathrm{head}}=C/n_{\mathrm{head}}=2
Concept sequence
  1. X [B,T,C]=[2,2,4]
  2. 并行:Head 1(X) 与 Head 2(X),各 [2,2,2]
  3. 沿特征轴拼接为 [2,2,4]
  4. 乘 W_O:[4,4],输出 [2,2,4]
MultiHead(X)=Concat(O(1),O(2))WO\operatorname{MultiHead}(X)=\operatorname{Concat}(O^{(1)},O^{(2)})W_O
python
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)

Output Projection 不只是为了 Shape。Concat 后不同 Head 的 Features 仍只是并排放置;W_O 允许模型学习如何跨 Head 重新组合信息,并为后续 Residual Addition 提供 [B,T,C] 接口。

Head 2 接收的是同一个 X,不是 Head 1 的输出。可以把两个结果想成同一行的两份两维记录,先并排拼成四维,再由输出投影学习怎样组合。堆叠两个 Transformer blocks 才是前一块输出进入后一块的顺序计算。

Knowledge check

为什么 Multi-Head Concat 后还需要 W_O?

16. Attention Output 怎样影响最终 Logits

Concept sequence
  1. X [B,T,C]
  2. Multi-Head Attention [B,T,C]
  3. Transformer 后续 Residual / FFN / Layers [B,T,C]
  4. Language Model Head W_vocab:[C,V_vocab]
  5. Logits [B,T,V_vocab]
  6. 需要解释概率时再对 Vocabulary Axis 做 Softmax
H:[B,T,C] @ Wvocab:[C,Vvocab]Logits:[B,T,Vvocab]H:[B,T,C]\ @\ W_{\mathrm{vocab}}:[C,V_{\mathrm{vocab}}]\rightarrow\mathrm{Logits}:[B,T,V_{\mathrm{vocab}}]

本课的 Prompt A 与 Prompt B 已在最终位置产生不同的 Head Output。经过 Multi-Head、W_O 与后续 Transformer 处理后,它们可以形成不同的 H,因此 Language Model Head 可以产生不同 Logits。Attention 并不直接输出哪个 Token。

Scroll horizontally to view all columns.

Course data table
对象典型 Shape意义
Attention Weights[B,n_head,T,T]每个 Query 读取哪些 Key/Value Positions
Contextual Features H[B,T,C]每个位置经过上下文处理后的表示
Vocabulary Logits[B,T,V_vocab]每个候选 Token 的原始分数
Vocabulary Probabilities[B,T,V_vocab]Logits 沿 Vocabulary Axis Softmax 后的分布

Knowledge check

Attention Weights 与 Vocabulary Probabilities 分别回答什么问题?

17. Attention 的计算成本

每个 Head 的 Score Matrix 有 T² 个 Elements。所有 Batch Examples 和 Heads 合计拥有 B×n_head×T² 个 Score Elements;Weights 还需要同样数量的 Elements,训练还会保存额外 Activations 与 Gradients。

Scroll horizontally to view all columns.

Course data table
固定 B=2、n_head=2全部 Heads 的 Score Elements相对 T=2
T=22 × 2 × 2² = 161 倍
T=10002 × 2 × 1000² = 4,000,000250,000 倍
Score Elements=BnheadT2\mathrm{Score\ Elements}=B\,n_{\mathrm{head}}\,T^2
Pairwise Attention Cost=O(BnheadT2dhead)=O(BT2C)\mathrm{Pairwise\ Attention\ Cost}=O(B\,n_{\mathrm{head}}\,T^2d_{\mathrm{head}})=O(BT^2C)

Causal Mask 禁止读取未来位置,但普通 Dense 实现仍可能先构造完整 T×T Matrix。不同优化实现可以减少实际 Memory 或运算,但不会改变本课需要理解的基础 Shape。

Knowledge check

当 B、Heads 和 Width 固定时,T 翻倍后 Score Elements 增长几倍?

18. Week 7 调试清单与必须掌握的 10 件事

Scroll horizontally to view all columns.

Course data table
调试顺序应该检查什么
1. X确认是 [B,T,C] 向量,不是 Token ID
2. Q/K/V确认 [B,T,d_head],数值来自相应 Projection
3. Scores确认使用 K.transpose(-2,-1),得到 [B,T,T]
4. Scale确认除以 √d_k
5. Mask确认 Future Columns 在 Softmax 前为 −∞
6. Softmax确认 dim=-1,每个 Query Row 加总为 1
7. Retrieval确认 Weights @ V,而不是 @ K
8. Multi-Head确认在 Feature Axis Concat,并经 W_O 恢复 C
9. Batch确认不同 Batch Examples 没有相互读取
10. Logits确认 LM Head 接收 Contextual Features,而不是 Attention Weights
  1. Attention 解决相同当前 Token 无法读取不同前缀的问题。
  2. X 是 Token 的向量表示,不是 Token ID。
  3. Query 表达当前位置需要寻找什么。
  4. Key 提供用于匹配的 Learned Clue。
  5. Value 提供匹配后真正带回的 Payload。
  6. QKᵀ 的每个元素都是一个 Query-Key Dot Product Raw Score。
  7. Scale、Mask、Row Softmax 的顺序不能交换或省略。
  8. Causal Mask 禁止 Future,但允许全部历史和当前位置。
  9. Multi-Head Concat 与 W_O 将多个 Head 恢复到 Model Width C。
  10. Attention Output 是 Contextual Features;LM Head 才产生 Vocabulary Logits。
XQ,K,VQK/dkMaskSoftmaxPVMultiHeadHLogitsX\rightarrow Q,K,V\rightarrow QK^\top\rightarrow /\sqrt{d_k}\rightarrow\mathrm{Mask}\rightarrow\mathrm{Softmax}\rightarrow PV\rightarrow\mathrm{MultiHead}\rightarrow H\rightarrow\mathrm{Logits}

Knowledge check

如果 Scores 正确,但 Future Position 仍有正 Weight,最应该先检查哪两步?

19. Week 7 → Week 8:Attention 还不是完整 Transformer Block

Week 8 将从同一个 [B,T,C]=[2,2,4] 接口继续。Token Embedding 与 Position Embedding 相加后仍为 [2,2,4];Multi-Head Attention 经过 Concat 与 W_O 后也必须返回 [2,2,4],才能与 Residual Path 相加。

Concept sequence
  1. Token Embedding + Position Embedding [2,2,4]
  2. Pre-Norm Multi-Head Attention [2,2,4]
  3. x + Attention(LN₁(x)) [2,2,4]
  4. Per-Position FFN [2,2,4]
  5. x₁ + FFN(LN₂(x₁)) [2,2,4]
  6. Language Model Head → Logits [2,2,V_vocab]
x1=x+Attention(LN1(x)),x2=x1+FFN(LN2(x1))x_1=x+\operatorname{Attention}(\operatorname{LN}_1(x)),\qquad x_2=x_1+\operatorname{FFN}(\operatorname{LN}_2(x_1))

Scroll horizontally to view all columns.

Course data table
组件主要职责
Position Embedding提供 Token 顺序与位置信息
Causal Multi-Head Attention跨允许的 Token Positions 混合信息
FFN在每个 Position 独立执行相同非线性变换
Residual Connection保留原路径并改善深层训练
Layer Normalization帮助稳定各层输入尺度

Knowledge check

Week 8 中哪个组件跨位置混合,哪个组件逐位置独立处理?