Transformer 手动计算:从 Attention 到 Encoder-Decoder 完整数据流
📅 创建时间:2026-07-29 🏷️ 标签:#Transformer #Attention #QKV #EncoderDecoder #ManualComputation 📚 前置知识:[[01-neural-network-basics]](知道矩阵乘法和 Softmax 就行)
📋 本章目标
- 用具体数字走完一次完整的 Self-Attention 前向传播
- 理解 Q、K、V 不是公式里的字母——它们是实实在在算出来的矩阵
- 理解 Multi-Head Attention 为什么需要多个头
- 理解 Encoder 和 Decoder 的完整数据流
- 理解训练时(Teacher Forcing)和推理时(Auto-Regressive)的根本区别
- 从此看 Transformer 论文不再"满眼公式、脑中空白"
第0部分:先定下战场——我们用哪个例子、哪些数字
0.1 为什么这篇值得你花时间
大多数 Transformer 教程给你看这张图:
Input → Embedding → Q,K,V → Attention → FFN → Output然后开始讲公式。但你看完之后,Q 还是一个字母,K 还是一个字母。你不知道 Q × K^T 算出来到底是什么东西,Softmax 之后到底变成了一张什么样的表。
这篇不一样。我们选一个最小但完整的例子,每一步都填上具体数字。 你看完之后可以自己在草稿纸上重算一遍。
0.2 我们的例子
输入句子(中文):"我 爱 你"
目标翻译(英文):"I love you"
简化设定:
• 词表大小 V = 6(只有这 6 个词:我, 爱, 你, I, love, you)
• Embedding 维度 d_model = 4(故意很小,手算友好)
• 只用 1 个 Attention 头,d_k = d_model = 4
• 序列长度 n = 3(3 个 token)
实际 Transformer 的 d_model = 512 或 768 或 4096,
但原理完全一样——只是矩阵更大。0.3 我们手动定义所有权重
真实训练中权重是通过反向传播学出来的。这里我们人为指定每一步的权重矩阵,这样你可以跟着算。
# 词表 → ID 映射
vocab = {"我": 0, "爱": 1, "你": 2, "I": 3, "love": 4, "you": 5}
# Token IDs
input_ids = [0, 1, 2] # ["我", "爱", "你"]
target_ids = [3, 4, 5] # ["I", "love", "you"]
# Embedding 矩阵 E (V × d_model = 6 × 4)
# 每一行是一个词的 embedding 向量
E = [
[1.0, 0.0, 1.0, 0.0], # 词0: "我"
[0.0, 1.0, 0.0, 1.0], # 词1: "爱"
[1.0, 1.0, 0.0, 0.0], # 词2: "你"
[0.0, 0.0, 1.0, 1.0], # 词3: "I"
[1.0, 0.0, 0.0, 1.0], # 词4: "love"
[0.0, 1.0, 1.0, 0.0], # 词5: "you"
]第1部分:Embedding——把词 ID 变成向量
┌─────────────────────────────────────────────────────────────┐
│ Embedding 查表(不是计算) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 输入:"我" → ID=0 → 查 E 的第 0 行 → [1.0, 0.0, 1.0, 0.0]│
│ 输入:"爱" → ID=1 → 查 E 的第 1 行 → [0.0, 1.0, 0.0, 1.0]│
│ 输入:"你" → ID=2 → 查 E 的第 2 行 → [1.0, 1.0, 0.0, 0.0]│
│ │
│ 堆叠成矩阵 X (n × d_model = 3 × 4): │
│ │
│ d=0 d=1 d=2 d=3 │
│ "我" [1.0, 0.0, 1.0, 0.0] ← 第0行 │
│ "爱" [0.0, 1.0, 0.0, 1.0] ← 第1行 │
│ "你" [1.0, 1.0, 0.0, 0.0] ← 第2行 │
│ │
│ 关键认知:Embedding 就是查表,没有矩阵乘法。 │
│ E 本身是训练出来的参数。 │
│ │
└─────────────────────────────────────────────────────────────┘输入矩阵 X(3×4):
X = [[1.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 0.0, 1.0],
[1.0, 1.0, 0.0, 0.0]]第2部分:Positional Encoding——让模型知道"顺序"
┌─────────────────────────────────────────────────────────────┐
│ 没有 Positional Encoding 会怎样? │
├─────────────────────────────────────────────────────────────┤
│ │
│ "我爱你" 和 "你爱我" 的 Embedding 只是行顺序不同。 │
│ 但 Self-Attention 对顺序不敏感——它看所有 token 两两交互。 │
│ 如果把三行的顺序打乱后做 Attention,结果只是行被打乱了。 │
│ │
│ → 需要给每个位置注入"这是第几个 token"的信息 │
│ │
│ 原始 Transformer 用 Sinusoidal 编码(固定公式,不训练): │
│ PE(pos, 2i) = sin(pos / 10000^(2i/d_model)) │
│ PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model)) │
│ │
└─────────────────────────────────────────────────────────────┘用公式算出每个位置的 PE 向量(d_model=4,手算近似值):
位置 0 (pos=0): PE₀ = [0.00, 1.00, 0.00, 1.00]
位置 1 (pos=1): PE₁ = [0.84, 0.54, 0.01, 1.00]
位置 2 (pos=2): PE₂ = [0.91, -0.42, 0.02, 1.00]X + PE = 位置感知的输入:
X' = X + PE =
位置0 "我": [1.00, 0.00, 1.00, 0.00] + [0.00, 1.00, 0.00, 1.00] = [1.00, 1.00, 1.00, 1.00]
位置1 "爱": [0.00, 1.00, 0.00, 1.00] + [0.84, 0.54, 0.01, 1.00] = [0.84, 1.54, 0.01, 2.00]
位置2 "你": [1.00, 1.00, 0.00, 0.00] + [0.91,-0.42, 0.02, 1.00] = [1.91, 0.58, 0.02, 1.00]第3部分:Q、K、V 是怎么算出来的——这是核心
3.1 三个权重矩阵
Self-Attention 有三个可训练的权重矩阵。我们手动指定它们:
Wq (d_model × d_k = 4 × 4) Wk (4 × 4) Wv (4 × 4)
[0.5, 0.0, 0.5, 0.0] [0.0, 0.5, 0.0, 0.5] [1.0, 0.0, 0.0, 0.0]
[0.0, 0.5, 0.0, 0.5] [0.5, 0.0, 0.5, 0.0] [0.0, 1.0, 0.0, 0.0]
[0.5, 0.0, 0.5, 0.0] [0.0, 0.5, 0.0, 0.5] [0.0, 0.0, 1.0, 0.0]
[0.0, 0.5, 0.0, 0.5] [0.5, 0.0, 0.5, 0.0] [0.0, 0.0, 0.0, 1.0]3.2 计算 Q = X' × Wq
Q = X' × Wq (3×4) × (4×4) = (3×4)
位置0 "我": [1.00, 1.00, 1.00, 1.00] × Wq = [1.00, 1.00, 1.00, 1.00]
位置1 "爱": [0.84, 1.54, 0.01, 2.00] × Wq = [1.43, 1.77, 0.43, 1.77]
位置2 "你": [1.91, 0.58, 0.02, 1.00] × Wq = [1.47, 0.79, 0.97, 0.79]3.3 计算 K = X' × Wk
K = X' × Wk (3×4) × (4×4) = (3×4)
位置0 "我": K₀ = [1.00, 1.00, 1.00, 1.00]
位置1 "爱": K₁ = [1.43, 0.84, 1.43, 0.84]
位置2 "你": K₂ = [0.79, 1.47, 0.79, 1.47]3.4 计算 V = X' × Wv
V = X' × Wv (3×4) × (4×4) = (3×4)
位置0 "我": V₀ = [1.00, 1.00, 1.00, 1.00]
位置1 "爱": V₁ = [0.84, 1.54, 0.01, 2.00]
位置2 "你": V₂ = [1.91, 0.58, 0.02, 1.00]3.5 Q、K、V 是什么——直觉
┌─────────────────────────────────────────────────────────────┐
│ Q、K、V 的直觉含义 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Q (Query): "我在找什么?" │
│ → 每个 token 的"查询向量"——我想了解其他 token 的什么? │
│ │
│ K (Key): "我是什么?" │
│ → 每个 token 的"键向量"——我有哪些特征可以被别人查询? │
│ │
│ V (Value): "我提供什么信息?" │
│ → 每个 token 的"值向量"——如果别人关注我,我给什么信息? │
│ │
│ 类比:你在图书馆查资料 │
│ • Q = 你手上的查询关键词 │
│ • K = 每本书的索引标签 │
│ • V = 每本书的内容 │
│ │
│ Q·K 的相似度 → 决定你读哪本书 │
│ V → 你从那本书里实际获取的信息 │
│ │
└─────────────────────────────────────────────────────────────┘第4部分:Attention Score——这是整个 Transformer 的灵魂
4.1 计算 Q × K^T
┌─────────────────────────────────────────────────────────────┐
│ Q × K^T:每个 token 对每个 token 的"关注度" │
├─────────────────────────────────────────────────────────────┤
│ │
│ Score = Q × K^T (3×4) × (4×3) = (3×3) │
│ │
│ 结果矩阵的每个元素 Score[i][j] = 第i个token 的 Q 与 │
│ 第j个token 的 K 的点积 │
│ │
└─────────────────────────────────────────────────────────────┘
Q × K^T:
K₀("我") K₁("爱") K₂("你")
Q₀("我") [ 4.00, 3.54, 4.52 ]
Q₁("爱") [ 5.40, 5.78, 6.94 ]
Q₂("你") [ 4.02, 4.56, 5.18 ]4.2 除以 √d_k(缩放)
d_k = 4, √d_k = 2
Scaled Scores = Scores / 2:
K₀("我") K₁("爱") K₂("你")
Q₀("我") [ 2.00, 1.77, 2.26 ]
Q₁("爱") [ 2.70, 2.89, 3.47 ]
Q₂("你") [ 2.01, 2.28, 2.59 ]为什么除以 √d_k? 当 d_k 很大时(真实 Transformer 中 d_k=64),点积的值会很大。大值经过 Softmax 后会变成极端分布(几乎全 0 和一个接近 1 的值),导致梯度消失。除以 √d_k 保持方差稳定。
4.3 Softmax——把分数变成概率
┌─────────────────────────────────────────────────────────────┐
│ Softmax:每一行变成一个概率分布 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Attention Weights = softmax(每行) │
│ │
│ 每一行加起来 = 1.0 │
│ 每个元素 = "第 i 个 token 应该花多少注意力在 第 j 个 token上│
│ │
└─────────────────────────────────────────────────────────────┘手算 Softmax(逐行计算 e^x / Σe^x):
第0行 "我" 的 Attention Weights:
e^2.00=7.39, e^1.77=5.87, e^2.26=9.58
sum = 22.84
→ [0.324, 0.257, 0.419]
第1行 "爱" 的 Attention Weights:
e^2.70=14.88, e^2.89=17.99, e^3.47=32.14
sum = 65.01
→ [0.229, 0.277, 0.494]
第2行 "你" 的 Attention Weights:
e^2.01=7.46, e^2.28=9.78, e^2.59=13.33
sum = 30.57
→ [0.244, 0.320, 0.436]
Attention Weights A (3×3):
"我" "爱" "你"
Q₀("我") [0.324, 0.257, 0.419]
Q₁("爱") [0.229, 0.277, 0.494]
Q₂("你") [0.244, 0.320, 0.436]这张表就是 Self-Attention 的核心输出——每个 token 对每个 token 的"关注度"。
读法:
- 第 0 行:token"我"花了 32.4% 注意力在自己身上,25.7% 在"爱",41.9% 在"你"
- 第 1 行:token"爱"花了 49.4% 注意力在"你"身上(最强关联)
4.4 加权求和:Output = A × V
Output = A × V (3×3) × (3×4) = (3×4)
每个位置的输出 = 所有位置的 V 的加权和,权重来自 Attention Weights
Output₀("我"的新表示):
= 0.324 × V₀ + 0.257 × V₁ + 0.419 × V₂
= 0.324×[1.00,1.00,1.00,1.00] + 0.257×[0.84,1.54,0.01,2.00] + 0.419×[1.91,0.58,0.02,1.00]
= [0.324, 0.324, 0.324, 0.324]
+ [0.216, 0.396, 0.003, 0.514]
+ [0.800, 0.243, 0.008, 0.419]
= [1.340, 0.963, 0.335, 1.257]
Output₁("爱"的新表示):
= [0.229×V₀ + 0.277×V₁ + 0.494×V₂]
= [1.405, 1.043, 0.242, 1.277]
Output₂("你"的新表示):
= [0.244×V₀ + 0.320×V₁ + 0.436×V₂]
= [1.345, 0.990, 0.256, 1.320]4.5 发生了什么——Self-Attention 的本质
┌─────────────────────────────────────────────────────────────┐
│ Self-Attention 的本质 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 输入:3 个独立的 token 向量(只看自己的 embedding) │
│ "我" [1.00, 1.00, 1.00, 1.00] │
│ "爱" [0.84, 1.54, 0.01, 2.00] │
│ "你" [1.91, 0.58, 0.02, 1.00] │
│ │
│ 输出:3 个"融合了上下文"的向量 │
│ "我" [1.340, 0.963, 0.335, 1.257] ← 吸收了"你"的信息 │
│ "爱" [1.405, 1.043, 0.242, 1.277] ← 吸收了所有词的信息 │
│ "你" [1.345, 0.990, 0.256, 1.320] ← 吸收了"我"和"爱"的信息│
│ │
│ 输出向量的每个维度 = 所有输入 token 的 V 的加权和。 │
│ 权重来自 Q 和 K 的相似度("这个 token 和那个 token 多相关")│
│ │
│ "Self" = 同一句话里的 token 互相看对方 │
│ "Attention" = 不是平均看,而是按相关性加权看 │
│ │
└─────────────────────────────────────────────────────────────┘第5部分:Multi-Head Attention——为什么需要多个头
┌─────────────────────────────────────────────────────────────┐
│ 为什么一个头不够? │
├─────────────────────────────────────────────────────────────┤
│ │
│ 一个 Attention 头只能捕获一种"关系模式"。 │
│ │
│ 在句子 "我 爱 你" 中: │
│ │
│ Head 1 可能学到:主谓关系("我" → "爱") │
│ Head 2 可能学到:动宾关系("爱" → "你") │
│ Head 3 可能学到:代词指代("我"和"你"的对照) │
│ Head 4 可能学到:位置邻近性(相邻词的局部模式) │
│ │
│ 多个头 = 从多个角度同时做 Attention │
│ 每个头有自己的 Wq, Wk, Wv │
│ │
│ 操作流程: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 把 d_model 切成 h 份,每份 d_k = d_model / h │ │
│ │ 2. 每个头独立做 Attention │ │
│ │ 3. 把所有头的输出拼起来 │ │
│ │ 4. 再乘一个 Wo 矩阵投影回 d_model 维度 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 原始论文:h=8, d_model=512, d_k=64 │
│ │
└─────────────────────────────────────────────────────────────┘Multi-Head 的数据流(简化为 2 头,d_model=4,d_k=2):
X' (3×4) → 切成两份 (3×2) 分别给两个头
Head 1 (维度 0-1): Head 2 (维度 2-3):
Q1 = X'[0:2] × Wq1 Q2 = X'[2:4] × Wq2
K1 = X'[0:2] × Wk1 K2 = X'[2:4] × Wk2
V1 = X'[0:2] × Wv1 V2 = X'[2:4] × Wv2
→ Attention → Out1 (3×2) → Attention → Out2 (3×2)
Concat: [Out1 | Out2] (3×4)
→ × Wo (4×4) → Multi-Head Output (3×4)第6部分:完整的 Encoder 层
┌─────────────────────────────────────────────────────────────┐
│ 一层 Encoder 的完整数据流 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 输入 X' (3×4) │
│ │ │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Multi-Head Attention│ ← 上面第3-5部分的全部计算 │
│ └──────────┬───────────┘ │
│ │ 输出: AttnOut (3×4) │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Add & Norm │ ← X' + AttnOut(残差连接) │
│ │ │ 然后 LayerNorm │
│ └──────────┬───────────┘ │
│ │ │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Feed-Forward (FFN) │ ← 两层全连接,中间 ReLU │
│ │ │ FFN(x) = W2·ReLU(W1·x+b1)+b2 │
│ └──────────┬───────────┘ │
│ │ │
│ ↓ │
│ ┌──────────────────────┐ │
│ │ Add & Norm │ ← 残差连接 + LayerNorm │
│ └──────────┬───────────┘ │
│ │ │
│ ↓ │
│ 输出 (3×4) —— 和输入形状一样! │
│ │
│ 关键在于:输入和输出形状完全相同 (3×4) │
│ → 可以堆叠多层(原始论文 N=6) │
│ → 每层学到不同层次的抽象 │
│ │
└─────────────────────────────────────────────────────────────┘Encoder 的输入输出:
- 输入:源语言句子("我爱你")的 Embedding
- 输出:源语言句子的"上下文表示"——每个 token 融合了整个句子信息后的向量
- 这个输出会被传给 Decoder 的 Cross-Attention 层
第7部分:Decoder——和 Encoder 有两个关键区别
┌─────────────────────────────────────────────────────────────┐
│ Decoder 的两个关键区别 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 区别1:Masked Self-Attention │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 在生成第 i 个 token 时,只能看到第 0,1,...,i 个 token│ │
│ │ 不能"偷看"后面的 token(因为推理时后面还没生成出来) │ │
│ │ │ │
│ │ 实现方式:在 Softmax 之前,把未来位置的值设为 -∞ │ │
│ │ 这样 Softmax 之后那些位置的概率 = 0 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 区别2:Cross-Attention │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Q 来自 Decoder 自己的上一层输出 │ │
│ │ K, V 来自 Encoder 的最终输出 │ │
│ │ │ │
│ │ 含义:Decoder 在生成每个词时,去 Encoder 的输出里 │ │
│ │ "查询"相关的源语言信息 │ │
│ │ │ │
│ │ 举例:生成 "love" 时,Cross-Attention 可能高度关注 │ │
│ │ Encoder 中 "爱" 的位置 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘7.1 Masked Self-Attention 手动演示
假设 Decoder 正在生成目标句子 "I love you",已经生成了 "I" 和 "love",现在要预测 "you"。
Decoder 输入(已经生成的):["<s>", "I", "love"]
Attention Weights BEFORE masking:
<s> I love
<s> [0.577, 0.232, 0.191]
I [0.368, 0.413, 0.219]
love [0.169, 0.264, 0.567]
AFTER masking(把未来位置设为 -∞ 再 Softmax):
<s> I love
<s> [1.000, 0.000, 0.000] ← <s> 只能看自己
I [0.471, 0.529, 0.000] ← I 可以看 <s> 和自己
love [0.169, 0.264, 0.567] ← love 可以看所有(因为它是最后一个)7.2 Cross-Attention 的 Q、K、V 来源
┌─────────────────────────────────────────────────────────────┐
│ Cross-Attention 的数据流 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Q: 来自 Decoder 的 Masked Self-Attention 的输出 │
│ "我想在这个位置生成什么词?" │
│ │
│ K: 来自 Encoder 的最终输出 │
│ "源语言句子的每个 token 有什么特征?" │
│ │
│ V: 来自 Encoder 的最终输出 │
│ "源语言句子的每个 token 提供什么信息?" │
│ │
│ 流程: │
│ Decoder Output ──→ Q │
│ Encoder Output ──→ K, V │
│ → Q × K^T → Attention Weights → × V → Cross-Attn Output │
│ │
└─────────────────────────────────────────────────────────────┘第8部分:训练 vs 推理——同一个架构,两条完全不同的路径
8.1 这是最关键但最容易被忽略的区别
┌─────────────────────────────────────────────────────────────┐
│ 训练时 vs 推理时 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 训练时(Teacher Forcing): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 输入:完整的目标句子 ["<s>","I","love","you"] │ │
│ │ │ │
│ │ Decoder 一次性看到所有目标 token(通过 Mask 防止作弊)│ │
│ │ 所有位置并行计算 → 一次前向传播输出所有位置的预测 │ │
│ │ │ │
│ │ 输出:每个位置预测下一个 token │ │
│ │ position 0: 预测 "I" (看到 <s>) │ │
│ │ position 1: 预测 "love" (看到 <s>, I) │ │
│ │ position 2: 预测 "you" (看到 <s>, I, love) │ │
│ │ position 3: 预测 <eos> (看到全部) │ │
│ │ │ │
│ │ Loss = 所有位置的预测误差之和 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 推理时(Auto-Regressive): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 输入:只有 "<s>" │ │
│ │ → 预测出 "I" │ │
│ │ → 把 "I" 拼到输入:["<s>", "I"] │ │
│ │ → 预测出 "love" │ │
│ │ → 把 "love" 拼到输入:["<s>", "I", "love"] │ │
│ │ → 预测出 "you" │ │
│ │ → ...直到预测出 <eos> │ │
│ │ │ │
│ │ 每次只生成一个 token,需要 N 次前向传播 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 关键区别: │
│ • 训练:并行,Teacher Forcing(用正确答案作为下一步输入) │
│ • 推理:串行,Auto-Regressive(用自己的预测作为下一步输入) │
│ • 速度:训练快(并行),推理慢(串行,N步) │
│ │
└─────────────────────────────────────────────────────────────┘8.2 完整 Encoder-Decoder 架构图(标注了数据维度)
┌─────────────────────────────────────────────────────────────┐
│ Transformer Encoder-Decoder (训练时) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ENCODER(处理源语言 "我爱你"): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "我""爱""你" → Embedding (3×4) + PE (3×4) │ │
│ │ → [Self-Attn → Add&Norm → FFN → Add&Norm] × 6 │ │
│ │ → Encoder Output (3×4) │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ DECODER(生成目标语言 "I love you"): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ "<s>""I""love""you" → Embedding (4×4) + PE │ │
│ │ → [Masked Self-Attn → Add&Norm │ │
│ │ → Cross-Attn(Q=Decoder, K,V=Encoder) → Add&Norm│ │
│ │ → FFN → Add&Norm] × 6 │ │
│ │ → Linear(4→V) → Softmax → 每个位置预测下一个词 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Cross-Attention 是 Encoder 和 Decoder 唯一的交互点! │
│ │
└─────────────────────────────────────────────────────────────┘核心总结
总结1:Self-Attention 不是你背的公式——它是这三步
- 用
X' × Wq、X' × Wk、X' × Wv算出 Q、K、V - 用
softmax(QK^T / √d_k)算出注意力权重——一张 n×n 的表,每行是一个 token 对其他所有 token 的关注度分布 - 用
Attention_Weights × V算出每个 token 的新表示——融合了上下文信息的向量
总结2:Encoder 和 Decoder 的分工
- Encoder:读源语言句子,输出融合了上下文的表示。只看源语言,不看目标语言。
- Decoder:逐 token 生成目标语言。通过 Cross-Attention 从 Encoder 输出中"查询"相关信息。
- Cross-Attention 是两者唯一的交互点:Q 来自 Decoder,K/V 来自 Encoder。
总结3:训练和推理的根本差异
- 训练:Teacher Forcing——Decoder 输入是完整的目标句子(Mask 防止偷看),并行计算所有位置。
- 推理:Auto-Regressive——每次只生成一个 token,用自己生成的 token 作为下一步输入,串行 N 步。
- Mask 在两种场景都使用——训练时防止模型"作弊"看到未来,推理时自然地只能看到已生成的部分。
总结4:位置编码为什么重要
Self-Attention 对 token 顺序不敏感。没有位置编码,"我爱你"和"你爱我"在 Attention 权重矩阵中只是行的排列不同。位置编码在每个 token 的向量中注入"这是第几个位置"的信息。
章节测试
测试1:Q、K、V 是从哪里算出来的?
A. 直接从 Embedding 矩阵查表得到 B. Q = X × Wq, K = X × Wk, V = X × Wv,其中 Wq/Wk/Wv 是可训练参数 C. Q、K、V 是固定的常数 D. Q = K = V = X(输入本身)
测试2:Self-Attention 中 Softmax 之前为什么要除以 √d_k?
A. 为了把数值映射到 [0, 1] 区间 B. 防止点积值过大导致 Softmax 梯度消失 C. 为了让 Attention Weights 变成对称矩阵 D. 为了减少计算量
测试3:Masked Self-Attention 和普通 Self-Attention 的区别是什么?
A. Masked 版本不计算 V B. Masked 版本在 Softmax 前把未来位置的分数设为 -∞,使模型无法"偷看"后面的 token C. Masked 版本只用在 Encoder 中 D. 两者完全相同
测试4:Cross-Attention 中 Q、K、V 分别来自哪里?
A. Q、K、V 都来自 Encoder B. Q 来自 Decoder,K 和 V 来自 Encoder C. Q、K、V 都来自 Decoder D. Q 来自 Encoder,K 和 V 来自 Decoder
测试5:训练时的 Teacher Forcing 和推理时的 Auto-Regressive 有什么区别?
测试6:为什么 Multi-Head Attention 需要多个头?
参考答案
测试1答案
答案:B。Q、K、V 是通过输入 X 分别乘以三个可训练的权重矩阵 Wq、Wk、Wv 得到的。这三个权重矩阵是 Transformer 在训练中学到的核心参数。
测试2答案
答案:B。当 d_k 很大时(实际中 d_k=64 或更大),Q 和 K 的点积值会很大。大值经过 Softmax 后会产生极端分布(概率几乎全集中在最大值上),导致梯度接近零,训练不动。除以 √d_k 保持方差稳定。
测试3答案
答案:B。Masked Self-Attention 在计算 Attention Weights 时,把"未来"位置(即 j > i 的元素)的分数设为 -∞。这样 Softmax 后这些位置的概率为 0,模型只能看到当前位置及之前的 token。Decoder 的自注意力层使用 Masked 版本。
测试4答案
答案:B。Cross-Attention 中,Q(查询)来自 Decoder 的上一层输出,K(键)和 V(值)来自 Encoder 的最终输出。这允许 Decoder 在生成每个词时从 Encoder 的源语言表示中检索相关信息。
测试5答案
训练时(Teacher Forcing):Decoder 的输入是完整的目标句子(正确答案)。通过 Mask 防止看到未来 token。所有位置并行计算,一次前向传播得到所有位置的预测。速度快,Loss = 所有位置的预测误差之和。
推理时(Auto-Regressive):Decoder 开始时只有起始标记。每次生成一个 token,把它追加到输入序列中,再进行下一次前向传播。需要 N 步串行计算才能生成 N 个 token。速度慢,但这是唯一可行的方式——因为推理时没有"正确答案"可以喂给 Decoder。
核心差异:训练用真实答案(快),推理用自己的预测(慢)。
测试6答案
一个 Attention 头只能学到一种"关系模式"(比如主谓关系)。多个头允许模型同时从多个角度捕捉不同类型的依赖关系——语法结构、语义关联、位置邻近等。每个头有自己的 Wq/Wk/Wv,独立计算 Attention,最后拼接所有头的输出。
相关笔记
- [[07-llm-evolution]] — 从 Word2Vec 到 Transformer 的历史演进
- [[09-decoder-only-llm]] — GPT 为什么只需要 Decoder(不需要 Encoder)
- [[10-training-vs-inference]] — 训练和推理完整对比(FLOPs/KV Cache)
下一步学习
- [ ] 在草稿纸上手算一遍第1-4部分——用文中的数字,自己重新算 Q、K、V、Attention Weights、Output
- [ ] 阅读 09 - GPT 为什么是 Decoder-Only — 理解现代 LLM 为什么抛弃了 Encoder
- [ ] 用 PyTorch 写一个 50 行的 Self-Attention,对比你的手算结果
学习状态:🟡 开始学习