Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← 人工智能 / Artificial Intelligence

大语言模型 / Large Language Models

1. LLM 前置知识学习路线 / A Prerequisite Learning Path for Large Language Models

2. 神经网络基础 - 从零理解 AI 的"计算单元" / Neural Network Fundamentals from Artificial Neurons

3. Token 与上下文窗口 - LLM 的计费与记忆单位 / Tokens and Context Windows as the Units of LLM Cost and Memory

4. 解码策略 - 控制 LLM 输出的艺术 / Decoding Strategies for Controlling LLM Output

5. 消息角色 - 构建 Agent 对话的基础 / Message Roles as the Foundation of Agent Conversations

6. 流式输出 - 实时交互的体验优化 / Streaming Output for Responsive Interaction

7. Prompt 工程基础 - 与 LLM 高效对话的技巧 / Prompt Engineering Fundamentals for Effective LLM Interaction

8. LLM 进化史 - 从词向量到 Transformer / The Evolution of LLMs from Word Embeddings to Transformers

9. Transformer 手动计算:从 Attention 到 Encoder-Decoder 完整数据流

10. Transformer 核心原理 - 现代 LLM 的基石 / Transformer Fundamentals Behind Modern LLMs

11. Decoder-Only LLM 深度解析:为什么扔掉 Encoder,以及 KV Cache 如何工作

12. 训练 vs 推理:同一个 Transformer,两条完全不同的执行路径

13. Transformer 训练阶段计算详解 - 手算每一行矩阵 / Transformer Training Computation Matrix by Matrix

14. Transformer 推理阶段详解 — 模型如何"思考"并生成回答 / Transformer Inference and Autoregressive Generation

15. 训练基础扫盲 - 理解 Fine-tune 在做什么 / A Training Primer for Understanding Fine-Tuning

16. LLM 预训练全景:数据管道、Scaling Laws 与训练稳定性

17. 训练基础设施 - 从单卡到千卡集群 / Training Infrastructure from One GPU to Thousand-GPU Clusters

18. Post-Training Pipeline - 从 Base Model 到可用助手 / The Post-Training Pipeline from Base Model to Assistant

19. SFT 深度解析:从 Base Model 到指令跟随——后训练第一步 / SFT Deep Dive: Teaching Base Models to Follow Instructions

20. RLHF 深度解析:从 Reward Model 到 PPO 的完整对齐流程

21. DPO 与对齐方法:从 RLHF 复杂度到直接偏好优化

22. 研究视角:DL/RL 理论到 LLM 训练的完整映射

本页目录

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
1

然后开始讲公式。但你看完之后,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,
但原理完全一样——只是矩阵更大。
1
2
3
4
5
6
7
8
9
10
11

0.3 我们手动定义所有权重 ​

真实训练中权重是通过反向传播学出来的。这里我们人为指定每一步的权重矩阵,这样你可以跟着算。

python
# 词表 → 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

第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 本身是训练出来的参数。                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

输入矩阵 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]]
1
2
3

第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))             │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

用公式算出每个位置的 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]
1
2
3

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]
1
2
3
4
5

第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]
1
2
3
4
5

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]
1
2
3
4
5

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]
1
2
3
4
5

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]
1
2
3
4
5

3.5 Q、K、V 是什么——直觉 ​

┌─────────────────────────────────────────────────────────────┐
│                  Q、K、V 的直觉含义                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Q (Query):  "我在找什么?"                                │
│  → 每个 token 的"查询向量"——我想了解其他 token 的什么?     │
│                                                             │
│  K (Key):    "我是什么?"                                  │
│  → 每个 token 的"键向量"——我有哪些特征可以被别人查询?      │
│                                                             │
│  V (Value):  "我提供什么信息?"                            │
│  → 每个 token 的"值向量"——如果别人关注我,我给什么信息?    │
│                                                             │
│  类比:你在图书馆查资料                                     │
│  • Q = 你手上的查询关键词                                   │
│  • K = 每本书的索引标签                                     │
│  • V = 每本书的内容                                         │
│                                                             │
│  Q·K 的相似度 → 决定你读哪本书                              │
│  V → 你从那本书里实际获取的信息                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

第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  ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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  ]
1
2
3
4
5
6
7
8

为什么除以 √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上│
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10

手算 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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这张表就是 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]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

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" = 不是平均看,而是按相关性加权看                │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

第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                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

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)
1
2
3
4
5
6
7
8
9
10

第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)                               │
│  → 每层学到不同层次的抽象                                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

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 中 "爱" 的位置                               │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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 可以看所有(因为它是最后一个)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

第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步)                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

核心总结 ​

总结1:Self-Attention 不是你背的公式——它是这三步 ​

  1. 用 X' × Wq、X' × Wk、X' × Wv 算出 Q、K、V
  2. 用 softmax(QK^T / √d_k) 算出注意力权重——一张 n×n 的表,每行是一个 token 对其他所有 token 的关注度分布
  3. 用 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,对比你的手算结果

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇8. LLM 进化史 - 从词向量到 Transformer / The Evolution of LLMs from Word Embeddings to Transformers
下一篇10. Transformer 核心原理 - 现代 LLM 的基石 / Transformer Fundamentals Behind Modern LLMs

持续记录,持续成长

Copyright © Tidenflow