第二阶段:理解原理 —— 详解
读完这篇,你会搞懂 LLM 底层到底在做什么:文字怎么变成数字、Transformer 怎么"理解"上下文、以及模型怎么一个字一个字地生成回复。 预计阅读时间:60-75 分钟。
目录
- 概览:一个 token 的旅程
- 第一站:Tokenization(分词)
- 第二站:Embedding(嵌入)
- 第三站:Self-Attention(自注意力)
- 第四站:Multi-Head Attention(多头注意力)
- 第五站:完整的 Transformer 层
- 第六站:从层到模型
- 第七站:自回归生成
- 本阶段小结
概览:一个 token 的旅程
在深入细节之前,先用一张全景图建立坐标系——一个词元从输入到输出,到底经历了什么:

上图:原始 Transformer 架构(Vaswani et al., 2017,"Attention Is All You Need")。左半边是编码器(Encoder),右半边是解码器(Decoder)。现代 LLM(GPT 系)只用了解码器部分(去掉 Encoder-Decoder Attention 那条线),但核心组件完全一样。
一条文本进来,大致走这几站:

下面逐站拆解。
第一站:Tokenization(分词)
一句话定义:把原始文本切成模型认识的"最小单元"(词元 / token)。
BPE 算法动手走一遍
现代 LLM 普遍使用 BPE(Byte Pair Encoding,字节对编码)。它的核心思路很朴素:把经常一起出现的字符"粘"成一个词元。咱们用一个具体例子走一遍。
准备阶段:假设训练语料只有一句话(实际是万亿级文本),我们要从头训练一个词表大小为 12 的分词器。
语料: "low low low low low lower lower newest newest newest widest widest widest"第一步:每个词拆成字符,末尾加上结束符 </w>:
l o w </w> (×5)
l o w e r </w> (×2)
n e w e s t </w> (×3)
w i d e s t </w> (×3)初始词表:{l, o, w, e, r, n, s, t, i, d, </w>} — 共 11 个,目标是扩展到 12。
第二步:统计所有相邻字符对的频率:
| 字符对 | 出现次数 | 出现在哪些词里 |
|---|---|---|
(e, s) | 6 | newest(×3) + widest(×3) |
(s, t) | 6 | newest(×3) + widest(×3) |
(l, o) | 7 | low(×5) + lower(×2) |
(o, w) | 7 | low(×5) + lower(×2) |
(w, </w>) | 5 | low(×5) |
(w, e) | 5 | lower(×2) + newest(×3) |
(e, w) | 3 | newest(×3) |
| ... | ... | ... |
第三步:合并频率最高的对。(l, o) 7 次和 (o, w) 7 次并列最高,选 (l, o) → 新 token lo。词表变成 12 个,达到目标,过程结束。
如果我们让过程继续(实际词表通常做到 5 万-10 万),后续的合并顺序大概是:
合并 1: (l, o) → lo — 频率最高
合并 2: (lo, w) → low — lo 和 w 又成了最高频的邻居
合并 3: (e, s) → es — 同时也是 (s, t),选哪个取决于实现
合并 4: (es, t) → est — es 和 t 组合,est 作为一个整体出现频率高
合并 5: (e, w) → ew
合并 6: (n, ew) → new
...推荐边看边操作:Cornell BPE 交互演示 —— 可以自己输入语料,一步步看合并过程。
最终效果:常见词的完整拼写(low, new, est)成为独立 token,罕见词的组成部分也被保留。新来一个词 lowest(训练语料里没有),可以拆成 low + est,两个子词模型都认识。
词表大小的权衡
| 词表大小 | 优点 | 缺点 |
|---|---|---|
| 太小 (如 1 万) | 每个 token 含义明确 | 一个词被拆很碎,序列变长,推理变慢 |
| 太大 (如 10 万+) | 常见词不被拆,序列短 | 词表大 → 嵌入矩阵大 → 显存占用大 |
| 中等 (3-5 万) | 目前主流选择,平衡效率和覆盖 | — |
各代模型的词表大小变化:GPT-2 为 50,257,GPT-3/4 约 10 万,DeepSeek-V3 约 12.8 万;到了 2026 年,Llama 4 约 20.2 万,DeepSeek V4 约 12.9 万。词表越大,嵌入矩阵越大(后面会讲),所以不能无限扩。
分词器带来的"怪癖"
这里把第一阶段提到过的几个经典问题收个尾:
- "strawberry 里几个 r?" →
straw+berry,模型看不到单个字母r - "9.11 和 9.9 谁大?" → 数字被切成
9+.+11,模型要做推理而非直接比较 - 中文比英文"贵" → 同等语义,中文通常需要更多 token(一个汉字 ≈ 1-2 token,而一个英文单词 ≈ 1-2 token 但承载的语义少)
第二站:Embedding(嵌入)
一句话定义:给每个词元分配一个高维向量,让语义相近的词元在向量空间里也挨得近。
从 ID 到向量
分词器输出的是一串整数 ID(比如 [15496, 2159, 734, 3186]),计算机需要把这些整数变成它真正能计算的东西——向量。
Embedding 层本质是一张巨大的查找表(lookup table):
词元 ID → Embedding 向量(d 维)
─────────────────────────────────
0 → [0.001, -0.023, 0.047, ..., 0.031]
1 → [0.012, 0.034, -0.002, ..., -0.019]
2 → [-0.008, 0.011, 0.029, ..., 0.044]
... → ...
49999 → [0.003, -0.015, 0.022, ..., -0.037]这个表的大小是 词表大小 × 向量维度,里面的数字全部是训练学出来的。
这些数字是怎么来的? 训练刚开始时,嵌入表里的数字全是随机的——"苹果"和"香蕉"在向量空间里毫无关系。
训练过程大致是:喂给模型一句话(比如"我今天吃了一个"),让它猜下一个词。如果它猜"苹果"的概率只有 3%,但正确答案就是"苹果",就产生一个误差信号。这个误差由一个**损失函数(loss function)**算出来——它把"模型猜的概率分布"和"正确答案"之间的差距量化成一个数字:猜得越离谱,loss 越大。后面第七站讲温度采样时会见到的交叉熵,就是 LLM 最常用的损失函数。
有了 loss 之后,反向传播(backpropagation)从输出端往回走,逐层计算"每个参数对最终误差贡献了多少"——这个贡献量就是梯度(gradient)。某参数梯度大,说明调它能显著减小误差;梯度接近零,说明调它没啥用。最后根据梯度把所有参数往减小误差的方向微调一小步。
整个循环就是三步:前向传播(算预测 + 算 loss)→ 反向传播(算梯度)→ 参数更新(调权重)。
经过万亿级别的训练文本反复迭代后,嵌入表里的数字被逐渐"驯化"——经常出现在相似上下文里的词在向量空间中自动靠近。第三阶段(训练过程)会详细展开这个完整的学习机制。
以 GPT-3(2020 年)为例:d = 12,288,词表 50,257,光这个嵌入表就有 50,257 × 12,288 ≈ 6.17 亿个参数。到了 2026 年,DeepSeek V4 Flash(d = 4,096,词表 129,280)嵌入表 ≈ 5.3 亿参数;Llama 4 Scout 更夸张——词表 202,048,d = 5,120,嵌入表 ≈ 10.3 亿参数。
为什么必须是向量?
向量解决了一个根本问题:让计算机能"比较"两个词有多像。 "苹果"和"香蕉"这两个字符串,机器没法判断谁更接近谁。但转换成了两个向量之后,只需要算一个余弦相似度:
余弦相似度("苹果", "香蕉") ≈ 0.85 ← 都是水果,挨得近
余弦相似度("苹果", "iPhone") ≈ 0.72 ← 苹果公司,也算相关
余弦相似度("苹果", "拖拉机") ≈ 0.12 ← 没啥关系,离得远有了向量,搜索、类比、聚类全部变成了数学运算:
搜索: 用户问题向量 · 文档向量 → 找余弦相似度最高的
类比: "国王" - "男人" + "女人" ≈ "女王"
聚类: 把所有向量按距离分组 → 同组即同主题余弦相似度与欧氏距离
其中最关键的两个数学工具,就是余弦相似度和欧氏距离。它们回答了同一个问题——"两个向量有多像?"——但尺子不同。
余弦相似度:只看方向,不看长度
公式:
其中:
- (向量点积)
- (向量长度 / L2 范数)
直觉:把两个向量看成从原点出发的两支箭。余弦相似度量的是它们夹角的余弦值——箭头指向同一个方向 → cos ≈ 1,箭头垂直 → cos ≈ 0,箭头反向 → cos ≈ -1。
关键特性:向量的长度不影响结果。比如 [1, 1] 和 [100, 100] 方向完全一样,余弦相似度 = 1.0。
用一个二维例子直观感受:
A = [3, 4] B = [30, 40]
|A| = 5 |B| = 50
A·B = 3×30 + 4×40 = 250
cos = 250/(5×50) = 1.0 ← 方向完全一致
A = [1, 0] B = [0, 1]
cos = 0 ← 正交,毫无关系
A = [1, 2] B = [-1, -2]
cos = -1 ← 完全相反为什么在文本嵌入中更常用余弦相似度? 因为同一句话用不同方式表达,向量长度可能不同(短句向量短,长句向量长),但语义方向应该相近。"今天天气真好"和"今天的天气非常不错"——句子长度不同导致向量的 L2 范数不同,但语义方向应当接近。余弦相似度天然忽略了长度差异,只看方向。
欧氏距离:看两点之间的直线距离
公式:
直觉:就是 n 维空间里的"直尺距离"——中学学过的两点间距离公式,推广到高维。d 越小,两个点越近。
同样的二维例子:
A = [1, 1] B = [4, 5]
d = √[(1-4)² + (1-5)²] = √[9 + 16] = 5
A = [1, 1] C = [1.5, 1.2]
d = √[(1-1.5)² + (1-1.2)²] = √[0.25 + 0.04] ≈ 0.54 ← 更近什么时候用欧氏距离? 当你既关心方向也关心大小的时候。比如聚类时,如果你希望"长度相近的向量也分到一组",欧氏距离就比余弦相似度更合适。但在语义搜索中,通常希望"长文档"和"短摘要"也能匹配上,所以余弦相似度是更主流的选择。
两者的联系与选择
| 特性 | 余弦相似度 | 欧氏距离 |
|---|---|---|
| 衡量什么 | 方向是否一致 | 位置是否接近 |
| 受向量长度影响 | 否 | 是 |
| 取值范围 | [-1, 1](实际嵌入通常 ≥ 0) | [0, +∞) |
| 典型场景 | 语义搜索、文本相似度 | 聚类(K-Means)、图像匹配 |
| 计算复杂度 | O(d) | O(d) |
一个关键细节:如果所有向量都预先做了 L2 归一化(把长度统一缩放到 1),那么余弦相似度和欧氏距离是等价的——两者之间存在严格的单调关系。很多向量数据库(如 Chroma、Milvus)内部会先把向量归一化,然后用欧氏距离近似余弦相似度来加速检索。
动手算一遍
假设我们的嵌入维度只有 3 维(实际是 4096 维),有三个词:
"苹果" = [0.8, 0.2, 0.1]
"香蕉" = [0.7, 0.3, 0.1]
"飞机" = [0.1, 0.8, 0.8]余弦相似度("苹果" vs "香蕉"):
点积 = 0.8×0.7 + 0.2×0.3 + 0.1×0.1 = 0.56 + 0.06 + 0.01 = 0.63
|苹果| = √(0.64 + 0.04 + 0.01) = √0.69 ≈ 0.831
|香蕉| = √(0.49 + 0.09 + 0.01) = √0.59 ≈ 0.768
cos = 0.63 / (0.831 × 0.768) ≈ 0.99 ← 几乎完美对齐(都是水果!)余弦相似度("苹果" vs "飞机"):
点积 = 0.8×0.1 + 0.2×0.8 + 0.1×0.8 = 0.08 + 0.16 + 0.08 = 0.32
cos = 0.32 / (0.831 × 1.136) ≈ 0.34 ← 方向不太一致欧氏距离("苹果" vs "香蕉"):
d = √[(0.8-0.7)² + (0.2-0.3)² + (0.1-0.1)²]
= √[0.01 + 0.01 + 0]
= √0.02 ≈ 0.14 ← 很近欧氏距离("苹果" vs "飞机"):
d = √[(0.8-0.1)² + (0.2-0.8)² + (0.1-0.8)²]
= √[0.49 + 0.36 + 0.49]
= √1.34 ≈ 1.16 ← 远得多两种度量给出一致的结论——"苹果"和"香蕉"近,"苹果"和"飞机"远。这就是为什么嵌入向量在 AI 中如此重要。
这就是为什么即使嵌入表动辄数亿参数、显存占用巨大,也必须走"文字→向量"这一步——纯文字做不了这些运算。
为什么是这个维度数(而不是 50 维或 50000 维)?
先看历史上这个数字是怎么演化的:
| 模型 (历史) | 嵌入维度 d | 总参数量 | 年份 |
|---|---|---|---|
| GPT-2 Small | 768 | 1.24 亿 | 2019 |
| GPT-2 XL | 1,600 | 15 亿 | 2019 |
| GPT-3 | 12,288 | 1,750 亿 | 2020 |
| LLaMA-7B | 4,096 | 70 亿 | 2023 |
| LLaMA-70B | 8,192 | 700 亿 | 2023 |
| DeepSeek-V3 | 7,168 | 6,710 亿(MoE) | 2024 |
再到 2026 年的选择:
| 模型 (2026) | 嵌入维度 d | 总参数量 | 备注 |
|---|---|---|---|
| DeepSeek V4 Flash | 4,096 | 2,840 亿 (MoE, ~130 亿激活) | 轻量旗舰,性价比路线 |
| Llama 4 Scout | 5,120 | 1,090 亿 (MoE, 170 亿激活) | 10M 上下文,开源标杆 |
| Llama 4 Scout (Instruct) | 6,144 | 1,090 亿 (MoE, 170 亿激活) | 多模态变体,维度更大 |
| DeepSeek V4 Pro | 7,168 | 1.6 万亿 (MoE, ~490 亿激活) | 正式旗舰 |
| Kimi K3 | 未公开 | 2.8 万亿 (MoE, ~500 亿激活) | 估计在 4096-7168 范围 |
| Qwen 3.8-Max | 未公开 | 2.4 万亿 (MoE) | 官方未放出技术报告 |
可以看到一个有趣的趋势:维度并没有随着模型变大而无限膨胀。GPT-3(2020)就用了 12,288 维,但后来的模型反而"收敛"到了 4,096-7,168 这个范围。原因也很直接——MoE 架构让模型用"更多专家"而非"更宽的维度"来增加容量,这样对 GPU 显存更友好。
选这个范围内的值,有三重考量:
① 表达能力 — 太小装不下。每个维度可以理解为模型学出来的一个"语义特征探测器"。比如某个维度可能对"动物程度"敏感(狗=0.8, 猫=0.7, 石头=0.05),另一个维度对"正式程度"敏感("阁下"=0.9, "您"=0.6, "你"=0.2, "喂"=-0.5)。维度太少,微妙差别就丢了:
50 维: 能区分"大/小"、"快/慢",但分不开"高兴"和"欣喜"
300 维: 经典 Word2Vec 维度,能做国王-男人+女人≈女王这种类比
4096 维: 足以编码讽刺语气、敬语层级、专业术语归属这类极微妙的信息② 计算成本 — 太大算不动。Self-Attention 的计算量 = , 是序列长度, 是每层向量维度。 翻倍,算力直接翻倍。更关键的是显存——Attention 中间矩阵是 级别:
d=768 (GPT-2 Small, 2019): Attention 矩阵 ≈ 4MB
d=4096 (LLaMA-7B, 2023): Attention 矩阵 ≈ 64MB
d=12288 (GPT-3, 2020): Attention 矩阵 ≈ 576MB
── 2026 年 ──
d=4096 (DeepSeek V4 Flash): Attention 矩阵 ≈ 64MB
d=5120 (Llama 4 Scout): Attention 矩阵 ≈ 100MB
d=7168 (DeepSeek V4 Pro): Attention 矩阵 ≈ 196MB以上按 n=1024 估算。实际上下文窗口已达 1M token,Attention 矩阵会膨胀到 GB 级别——这也是现代模型大量采用 GQA/MQA(减少 KV 头数)和稀疏注意力(如 DeepSeek V4 的 CSA/HCA)的原因。
③ GPU 硬件偏好 — 2 的幂或 128 的倍数对齐。GPU 内存访问按 128 字节对齐,矩阵乘法在维度是 2 的幂或 128 的倍数时效率最高:
768 = 64 × 12 ← GPT-2 Small(2019),64 的倍数
1024 = 2^10 ← 经典 2 的幂
4096 = 2^12 ← LLaMA-7B(2023)& DeepSeek V4 Flash,正好 2 的幂
5120 = 128 × 40 ← Llama 4 Scout(2026),128 的倍数
7168 = 128 × 56 ← DeepSeek V4 Pro(2026),128 的倍数所以 4096 本质上是"够表达 + 算得动 + GPU 喜欢"三个约束下的折中点。模型越大,d 就可以(也需要)越大,因为更大的模型有更多参数来"填满"这些维度,不会浪费。
向量空间里的语义几何
嵌入的妙处在于:语义相近的词,向量也相近。看一张实际的词嵌入 3D 可视化:

上图:200 个单词的 Word2Vec 嵌入投影到 3D 空间(PCA 降维)。颜色代表语义聚类——动物(紫色)抱团、食物(黄色)扎堆、交通工具(青色)聚在一起。这就是向量空间里的"语义地图"。
更神奇的是,向量之间的方向编码了语义关系。最经典的例子:
"国王" 的向量 - "男人" 的向量 + "女人" 的向量 ≈ "女王" 的向量或者说:从"国王"到"女王"的方向,和从"男人"到"女人"的方向几乎平行。这暗示向量空间不仅记住了"什么词像什么词",还学会了"性别"这个抽象概念的方向。
当然,这是静态嵌入(Word2Vec、GloVe 时代的技术)。静态嵌入的问题是:同一个词在所有上下文里只有一套向量。"苹果"不管是在讲水果还是讲手机,都是同一个向量。
现代 LLM 用的是上下文嵌入(contextual embedding)——同一个词元经过 Self-Attention 之后,会根据它周围是什么词,动态调整自己的向量表示。比如:
"我吃了一个苹果" → "苹果" 的最终向量靠近 "水果"、"食物"
"我买了一个苹果手机" → "苹果" 的最终向量靠近 "科技"、"电子产品"输入的嵌入向量是一样的(都是"苹果"对应的那行),但经过 Attention 层的"上下文搅拌"后,输出的表示天差地别。
Positional Encoding(位置编码)
Transformer 有一个先天缺陷:Self-Attention 计算时,所有位置一视同仁,它不知道谁先谁后。对于模型来说,"我爱你"和"你爱我"的词袋(bag of words)完全一样。
解决方案:在词嵌入上叠加位置信号。原始论文用的是正弦/余弦函数:

上图:位置编码的可视化。每一行是一个位置,每一列是一个维度。不同维度以不同的频率振荡——低频维度区分大范围位置,高频维度区分相邻位置。图片来源:dvgodoy / CC BY
公式
对于位置 和维度 ( 从 到 ),原始 Transformer 定义:
其中:
- = 词元在序列中的位置(0, 1, 2, ...)
- = 嵌入向量的总维度
- = 维度对的索引()
- = 具体的偶数维度下标
每个位置的编码是一个固定的向量(不是学出来的),直接加到词嵌入上:
最终输入 = 词嵌入向量 + 位置编码向量加完之后,位置 0 的"我"和位置 2 的"我"就有了不同的向量表示,模型就能感知词序了。
为什么是 sin 和 cos?动手算一个
用一个缩小的例子来直观感受。假设 d = 8(实际是 4096),位置 pos = 3:
k=0: 2k=0, 2k+1=1
波长 = 10000^(0/8) = 10000^0 = 1
PE(3, 0) = sin(3/1) = sin(3) = 0.141
PE(3, 1) = cos(3/1) = cos(3) = -0.990
k=1: 2k=2, 2k+1=3
波长 = 10000^(2/8) = 10000^(0.25) = 10
PE(3, 2) = sin(3/10) = 0.296
PE(3, 3) = cos(3/10) = 0.955
k=2: 2k=4, 2k+1=5
波长 = 10000^(4/8) = 10000^(0.5) = 100
PE(3, 4) = sin(3/100) = 0.030
PE(3, 5) = cos(3/100) = 1.000
k=3: 2k=6, 2k+1=7
波长 = 10000^(6/8) = 10000^(0.75) ≈ 1000
PE(3, 6) = sin(3/1000) ≈ 0.003
PE(3, 7) = cos(3/1000) ≈ 1.000
位置 3 的编码向量: [0.141, -0.990, 0.296, 0.955, 0.030, 1.000, 0.003, 1.000]观察规律:
pos=0: [0, 1, 0, 1, 0, 1, 0, 1] ← sin(0)=0, cos(0)=1
pos=1: [0.842, 0.540, 0.100, 0.995, 0.010, 1.000, 0.001, 1.000]
pos=2: [0.909, -0.416, 0.199, 0.980, 0.020, 1.000, 0.002, 1.000]
pos=3: [0.141, -0.990, 0.296, 0.955, 0.030, 1.000, 0.003, 1.000]每个维度对是一个不同频率的振荡器。k=0 是最高频(波长 = 1,每个位置翻转一次),k=3 是最低频(波长 ≈ 1000,变化极慢)。低频维度几乎不变,相当于给句子远距离的"章节编号";高频维度快速变化,给相邻词元精确的"相邻指纹"。
核心特性:线性相对位置
选择 sin 和 cos 不是一个随意的决定——它们有一个精妙的数学性质:
位置 pos + offset 的编码,可以由位置 pos 的编码通过一个固定的线性变换得到。
用公式表达(对于每个固定的 ,令 ):
推导基础是三角恒等式 ,把 看作 , 看作 。
这意味着什么? "位置 5"和"位置 8"之间的关系(Δ = 3)可以直接从它们的编码中通过矩阵乘法算出来——不需要额外的学习。注意力机制在计算 Q·K 时,会自然地利用这个性质,让模型感知两个词元之间的相对距离而不仅是它们的绝对位置。这就是为什么即使训练时只见过长度 1024 的序列,模型也能在一定程度上泛化到更长的文本。
现代 LLM 大多已改用 RoPE(旋转位置编码,Rotary Position Embedding)。它直接在 Attention 的 Q、K 计算时注入位置信息,效果更好,尤其在长上下文场景下。原理是把向量在二维子空间里旋转一个与位置成正比的角度——位置差越大的两个词,Q 和 K 被旋转的角度差也越大。这个细节在进阶阶段再展开。
第三站:Self-Attention(自注意力)
这是 Transformer 的心脏。 理解了这一步,整个架构的大门就敞开了。
直观理解:图书馆检索的类比
想象你去图书馆找资料写一篇关于"苹果公司供应链"的报告。你不会把书架上所有书都翻一遍,而是:
- 脑子里有个查询意图("我需要找供应链管理方面的内容")
- 扫过每本书的标题和目录,判断相关性
- 对高度相关的书,仔细阅读并摘录内容
- 综合所有摘录,写进你的报告
Self-Attention 做的是同一件事,只不过是在向量空间里:
| 图书馆类比 | Self-Attention 中的对应 |
|---|---|
| 你的查询意图 | Q(Query,查询向量) — "我在找什么?" |
| 每本书的标题/目录 | K(Key,键向量) — "我这本书讲什么?" |
| 每本书的实际内容 | V(Value,值向量) — "我能提供什么信息?" |
| 你根据相关性决定读多少 | Attention 权重 — Q·K 的点积,越大越相关 |
| 综合摘录写报告 | 加权求和 — 所有 V 按权重加起来 |
Q、K、V 三步走
下面用代码的思维(而非数学公式)走一遍完整流程。假设输入是 3 个词元,每个的嵌入向量是 4 维:
# 输入:3 个词元,每个 4 维
X = [
[1.0, 0.0, 1.0, 0.0], # 词元 0: "我"
[0.0, 2.0, 0.0, 2.0], # 词元 1: "爱"
[1.0, 1.0, 1.0, 1.0], # 词元 2: "你"
]第一步:生成 Q、K、V。每个词元的嵌入向量分别乘以三套不同的权重矩阵:
"我"的向量 [1, 0, 1, 0] × W_Q → Q₀ = [1, 0, 2]
"我"的向量 [1, 0, 1, 0] × W_K → K₀ = [0, 1, 1]
"我"的向量 [1, 0, 1, 0] × W_V → V₀ = [1, 2, 3]每个词元都这样算一遍,得到三套向量:

上图:每个词元通过乘以 W^Q、W^K、W^V 产生自己的 Q、K、V 向量。注意——同一个 W^Q 对所有词元复用,W^K 和 W^V 同理。图片来源:Jay Alammar
权重矩阵到底长什么样?
上面代码中的简化版本(4 维 → 3 维),W^Q、W^K、W^V 的形状各是 4 × 3。但在真实模型中,它们大得多。以 2026 年的 DeepSeek V4 Flash 为例:
嵌入维度 d_model = 4096
每个头的维度 d_k = 64(MLA 压缩后的等效维度)
W^Q: [4096, 64] ← 4096 行,64 列,共 262,144 个参数
W^K: [4096, 64] ← 同样大小
W^V: [4096, 64] ← 同样大小
三个矩阵合计: 3 × 4096 × 64 = 786,432 个参数(仅一个注意力头!)64 个头 × 786,432 = 约 5000 万个参数,仅 Attention 层的 QKV 投影就这么多。
DeepSeek V4 实际用了 MLA(Multi-head Latent Attention),先压缩到 1024 维的潜空间再投影到各头,实际参数比这个少。这里用简化数字帮助建立直觉。
这些数字是怎么来的?
训练开始时:W^Q、W^K、W^V 里的数字全是随机初始化的(通常用 Xavier 或 Kaiming 初始化,让初始输出不会太大也不会太小)。此时模型"瞎猜"——Q 和 K 的点积毫无意义。
训练过程中:模型读海量文本,每看到一个句子就去预测下一个 token。预测错了,反向传播会根据误差去微调 W^Q、W^K、W^V 里的每一个数字。经过几万亿 token 的训练后,这些矩阵逐渐"学会":
- W^Q 学会把输入投影成"我想找什么"的信号——比如"动词后面通常跟名词",那"吃"这个位置的 Q 向量就会被训练成倾向于找食物类名词的 K
- W^K 学会把输入投影成"我是什么类型"的标签——"苹果"的 K 向量既带一点"水果"标签也带一点"科技公司"标签
- W^V 学会把输入投影成"我能提供什么信息"——当"苹果"被 Attention 选中时,它把自己在上下文中的实际语义贡献出去
为什么需要三套矩阵,一套不够吗?
关键在于 Q 和 K 的职责不同。用一个类比来理解:
你是侦探,要调查"谁和谁是同伙":
Q(Query / 查询):你手里拿的嫌疑人照片 → "我要找这样的人"
K(Key / 键): 每个人档案里的特征描述 → "我长这样"
V(Value / 值): 每个人的详细档案 → "抓到我的话,你能得到这些信息"Attention 的流程就是:拿着照片(Q)去和每份档案的特征描述(K)比对,算出匹配度,然后按匹配度去调取详细档案(V)。
如果 Q 和 K 共享同一套权重,就相当于你用"照片"去比"照片"——只能判断两个人长相是否相似。这限制很大——有时候你想找的不是"长得像的人",而是"身高超过 180cm 的人"(Q 里可以编码身高需求,K 里编码实际身高)。Q 和 K 分开,让模型可以学到"用属性 A 去匹配属性 B",表达能力大幅提升。
Q/K 分离: "我想找【动作的承受者】" vs "我是【名词,可以当宾语】" Q/K 共享: "我想找【名词】" vs "我是【名词】" ← 浪费一套矩阵的表达空间
第二步:用 Q 和 K 算相关性。词元 0("我")想知道应该关注谁,就用 Q₀ 去和所有人的 K 做点积:
Q₀ · K₀ = 1×0 + 0×1 + 2×1 = 2 → "我"和自己:相关性 2
Q₀ · K₁ = 1×1 + 0×2 + 2×1 = 3 → "我"和"爱":相关性 3
Q₀ · K₂ = 1×1 + 0×0 + 2×1 = 3 → "我"和"你":相关性 3值越大,"我"越应该关注那个词元。
第三步:Softmax 归一化。把得分转成一组"权重",总和为 100%。
什么是 Softmax?
Softmax 是一个把任意实数变成概率分布的函数——无论输入是正数、负数、零、还是很大很小的数,输出一定满足:
- 每个输出值在 0 到 1 之间
- 所有输出值加起来等于 1
公式:
其中 (自然常数)。
手算一遍
回到刚才的例子,输入是三个得分 [2, 3, 3]:
第一步:对每个得分取 e 的幂
e² = 2.718² = 7.389
e³ = 2.718³ = 20.086
e³ = 2.718³ = 20.086
第二步:求和
7.389 + 20.086 + 20.086 = 47.561
第三步:每个 e^x 除以总和
Softmax(2) = 7.389 / 47.561 = 0.155 → 15.5%
Softmax(3) = 20.086 / 47.561 = 0.422 → 42.2%
Softmax(3) = 20.086 / 47.561 = 0.422 → 42.2%所以 Softmax([2, 3, 3]) ≈ [0.155, 0.422, 0.422],总和 = 1.0。
注意:这里手算结果和前面注释里的
[0.06, 0.47, 0.47]不同,因为前面用了简化数字。重点是理解计算过程。
为什么要用 e^x?不能直接拿原始得分做归一化吗?
不能。试试直接归一化 [2, 3, 3]:
直接归一化: 2 / (2+3+3) = 0.25 3 / 8 = 0.375 3 / 8 = 0.375
结果: [0.25, 0.375, 0.375]看起来还行?换个例子就看出来问题了——假设有负分:
得分: [1, -2, 1]
直接归一化: 1/(1-2+1) = 1/0 ← 除以零,炸了!
即使分母不为零,负权重在语义上也无意义
Softmax: e^1 = 2.718, e^(-2) = 0.135, e^1 = 2.718
求和 = 5.572
结果: [0.488, 0.024, 0.488] ← 负分被压到接近 0,不会炸e^x 的三个关键作用:
① 消除负数。 永远大于 0,即使 是很大的负数(,极小但不为零)。这保证了 Softmax 输出永远非负。
② 放大差异。指数函数对差值非常敏感:
原始分差不大: [1, 2] → 直接归一化: [0.33, 0.67]
→ Softmax: [0.27, 0.73] ← 差距被拉大了
原始分差很大: [1, 5] → 直接归一化: [0.17, 0.83]
→ Softmax: [0.018, 0.982] ← 差距被剧烈放大这个"赢家通吃"效应正好是 Attention 需要的——"最相关的那几个 token",而不是"每个 token 都分一点关注"。
③ 可导。反向传播时,,导数就是它自己,梯度计算非常方便。这是工程上的重要考量。
完整的直观感受
用几个对比来感受 Softmax 的"性格":
输入 → Softmax 输出 直觉
[1, 1, 1] → [0.333, 0.333, 0.333] 平均分配,三个都一样
[1, 2, 3] → [0.090, 0.245, 0.665] 最大值拿走 2/3
[1, 3, 10] → [0.000, 0.001, 0.999] 近似独热,最大值几乎包揽全部
[1, -100, 1] → [0.500, 0.000, 0.500] 极低分直接被忽略把 Softmax 用回 Attention 的语境中:
"我"的 Q 与各位置的 K 做点积 → [2, 3, 3]
↓ Softmax
关注权重: "我"关注自己 15.5%,关注"爱" 42.2%,关注"你" 42.2%"我"这个字重点看"爱"和"你"——完全符合汉语中"我"的语义高度依赖谓语和宾语的直觉。
第四步:用权重去提取信息。把各位置的 V 按权重加起来:
Output₀ = 0.155 × V₀ + 0.422 × V₁ + 0.422 × V₂
= 0.155 × [1, 2, 3] + 0.422 × [2, 8, 0] + 0.422 × [2, 6, 3]
= [0.155, 0.310, 0.465] + [0.844, 3.376, 0.000] + [0.844, 2.532, 1.266]
= [1.843, 6.218, 1.731]这就是"我"经过这次 Self-Attention 后得到的新表示。它不再是孤立的"我",而是融合了整个句子上文信息之后的"我"。
矩阵形式:一次性算完
上面的计算在实际中是所有词元并行、一次矩阵乘法搞定的:

上图:把全部词元的嵌入堆成矩阵 ,一次性乘以 、、 得到 、、 矩阵,然后 。图片来源:Jay Alammar
# 实际的计算(伪代码)
Q = X @ W_Q # (3×4) × (4×3) = (3×3)
K = X @ W_K # 同上
V = X @ W_V # 同上
scores = Q @ K.T / sqrt(d_k) # (3×3) — 每对词元之间的相关性
weights = softmax(scores, dim=-1) # 每行归一化为概率
output = weights @ V # (3×3) — 加权融合后的新表示Softmax 之前的 score 矩阵长这样:
K₀ K₁ K₂
Q₀ [ 2 3 3 ] ← "我"关注"爱"和"你"最多
Q₁ [ 2 4 4 ] ← "爱"也关注后面最多
Q₂ [ 1 2 3 ] ← "你"最关注自己(因为在句末,前文都是它的上下文)经过 Softmax 后每行的和 = 1,变成了一套"关注度分配方案"。这就是 Attention 的魔力——每个词都在问"我应该多看谁?",然后用别人提供的信息来更新自己。
为什么要除以 √dₖ
先看问题本身。Q 和 K 的每个维度在初始化时是独立的随机数(均值 0,方差 1)。点积 是把 个独立随机变量的乘积加起来:
每个 的方差是 1(两个独立标准正态变量的乘积方差 = 1), 个加起来,点积的方差 ≈ 。
这意味着维度越大,Q·K 的值就越大,而且越分散。
举个具体例子
假设 ,Q 和 K 的每个分量都在 [-1, 1] 附近随机取值:
Q = [0.5, -0.3] K₀ = [0.2, 0.8] → Q·K₀ = 0.5×0.2 + (-0.3)×0.8 = -0.14
K₁ = [-0.7, 0.1] → Q·K₁ = 0.5×(-0.7) + (-0.3)×0.1 = -0.38
K₂ = [0.6, 0.4] → Q·K₂ = 0.5×0.6 + (-0.3)×0.4 = 0.18
三个得分: [-0.14, -0.38, 0.18] → Softmax → [0.31, 0.24, 0.44]
分布比较均匀,每条都分到了一些关注。现在把 加大到 128(每项仍在 [-1, 1] 附近),方差 ≈ 128:
三个得分可能变成: [-5.2, 8.7, -3.1] → Softmax → [0.000, 0.999, 0.000]
↑
8.7 把其他全压没了,几乎独热!Softmax 输出接近 one-hot 时,梯度接近零——因为改变那个唯一的大数值之前的值,Softmax 输出几乎不变。模型无法从这样的梯度中学到任何东西。
为什么是 √dₖ 而不是 dₖ 或其他值?
目标是把点积的方差从 拉回 1。方差的性质:。
用 而不是 ,是因为我们要的是对方差的线性缩放,而要缩放的量是标准差()。除以 会把方差压到 ,矫枉过正了。
总结
| 没有缩放 | 除以 后 |
|---|---|
| 点积方差 ≈ (越大越分散) | 点积方差 ≈ 1(稳定) |
| Softmax 趋向 one-hot | Softmax 输出柔和 |
| 梯度接近零,学不动 | 梯度健康,正常学习 |
| 大模型直接崩 | 大模型正常训练 |
这个 是原始 Transformer 论文中最不起眼却最关键的细节之一——没有它,深层 Attention 模型根本训不起来。
第四站:Multi-Head Attention(多头注意力)
一句话定义:单头只能学一种"关系模式",多头并行就能同时关注语法、语义、指代等多个维度。

上图:Multi-Head Attention 的结构。每个 Head 有自己独立的 W^Q、W^K、W^V,在同样的输入上并行计算,最后把结果拼起来。图片来源:dvgodoy / CC BY
为什么一个头不够?
先回到单头的场景。单头 Attention 会产出一套注意力权重——"我"关注"爱"42%,关注"你"42%,关注"自己"16%。这套权重同时编码了所有关系类型:语法、语义、指代……全部搅在一起。
问题在于:Softmax 是一种竞争机制。权重总和强制为 100%,如果一个关系想要更多,就必须挤占其他关系的份额。比如"我"需要关注动词"爱"(语法需求),同时也需要关注后面的宾语(语义需求),但单头的蛋糕就这么大,只能折中分配。
多头解决了这个问题:每个头有自己的蛋糕(各自做独立的 Softmax),互不抢份额。"语法头"可以给动词 80% 的关注,"语义头"可以给宾语 70% 的关注——同时满足,无需妥协。
具体怎么做
把 Q、K、V 沿维度方向均切为 份,每个头拿一份缩小版的子空间:
原始: Q, K, V 各是 [seq_len, d_model]
切分: Q → [Q₁, Q₂, ..., Q_h] 每个 Q_i 是 [seq_len, d_model / h]
K → [K₁, K₂, ..., K_h] 每个 K_i 是 [seq_len, d_model / h]
V → [V₁, V₂, ..., V_h] 每个 V_i 是 [seq_len, d_model / h]每个头独立做 Attention:
最后把所有头的输出拼接,过一个线性层 融合:
的形状是 ,负责把各头的信息"搅拌"成一个统一表示。
动手算一个迷你多头
还是"我爱你"那个例子,嵌入维度 d=6,头数 h=2,每个头拿到 d/h = 3 维:
输入 X (3 个词元,6 维):
"我" = [1.0, 0.0, 1.0, 0.0, 0.5, 0.5]
"爱" = [0.0, 2.0, 0.0, 2.0, 1.0, 0.0]
"你" = [1.0, 1.0, 1.0, 1.0, 0.0, 1.0]
Head 1 拿前 3 维: Head 2 拿后 3 维:
"我"₁ = [1.0, 0.0, 1.0] "我"₂ = [0.0, 0.5, 0.5]
"爱"₁ = [0.0, 2.0, 0.0] "爱"₂ = [2.0, 1.0, 0.0]
"你"₁ = [1.0, 1.0, 1.0] "你"₂ = [1.0, 0.0, 1.0]两个头各自走一遍 Q·K → Softmax → ×V 流程:
Head 1 的注意力权重: Head 2 的注意力权重:
"我" → [自己:30%, 爱:40%, 你:30%] "我" → [自己:10%, 爱:10%, 你:80%]
"爱" → [我:25%, 自己:50%, 你:25%] "爱" → [我:40%, 自己:20%, 你:40%]
"你" → [我:30%, 爱:30%, 自己:40%] "你" → [我:45%, 爱:10%, 自己:45%]
输出 Head 1: [3×3] 输出 Head 2: [3×3]拼接后:
"我" 的输出 = [Head1的3维, Head2的3维] = 6 维向量注意两个头给出了非常不同的关注方案——Head 1 相对均衡,Head 2 中"我"把 80% 的注意力给了"你"。这两套视角拼接后,6 维输出里同时携带了两类信息。
各头真的学到了不同的东西吗?
是的。研究者通过可视化 Attention 权重发现,不同头确实自发分化了分工。以下是一些被反复观察到的模式:
| 头类型 | 行为特征 | 例子 |
|---|---|---|
| 语法头 | 关注相邻位置的句法搭配 | 形容词 → 它修饰的名词 |
| 指代头 | 关注前文中提到过的实体 | "它" → 三句话前的"那只猫" |
| 分隔头 | 关注标点或从句边界 | 逗号、句号之间的片段 |
| 位置头 | 关注固定偏移距离的词 | 总是关注前一个或前两个词 |
| 冗余头 | 和其他头学到几乎相同的模式 | 约占 10-30%,可能浪费但不会损害性能 |
研究还发现:底层(靠近输入)的头分工更明显,高层(靠近输出)的头趋向均匀关注——底层在做具体的模式匹配,高层在做全局语义融合。
多头 vs 单大头:为什么必须"切"?
一个自然的疑问:多头的总参数量和"一个 d_model × d_model 的大头"一样,为什么非要分成多个小头?
关键是 Softmax 是全局归一化。一个大头只有一套 Softmax,所有维度的信息共享同一套竞争:语法关系必须和指代关系争夺注意力份额。 个小头有 套独立的 Softmax,每个可以有自己的"赢家"。
打个比方:一个评审委员会评审一个项目——
单头(一个大评委): 要同时评估"技术方案""团队能力""市场规模""财务预测"
→ 总分 100%,技术占了 40%,市场规模就只能拿 20%,各维度互相挤占
多头(h 个专业评委):
Head 1(技术评委): 只看技术方案,技术相关的 token 拿高分
Head 2(团队评委): 只看团队背景,人名、履历类 token 拿高分
Head 3(市场评委): 只看市场数据,数字、百分比类 token 拿高分
→ 每个评委独立打分,互不干扰这就是为什么要切:不是因为参数量不够,而是因为 Softmax 的归一化范围限制。多头 = 多套独立的 Softmax 通道。
第五站:完整的 Transformer 层
一个完整的 Transformer 层把三样东西串联起来:Multi-Head Attention → Feed-Forward Network → 配套的残差 + 归一化。这三个组件互相配合,缺一不可。
残差连接(Residual Connection)
一句话:把输入原封不动地加回输出,给梯度一条不衰减的"直达通道"。
没有残差会怎样?
先借一个概念——下一站(第六站)会讲到,实际的 LLM 会把上面这个结构重复堆叠几十层。这里先用 48 层作为例子,看看多层叠加时会出什么问题:
最后一个 token 的预测误差要传给第一层的参数,在没有残差的情况下,必须穿过 48 层里的每一个矩阵乘法:
梯度₀ = 误差 × W₄₈ × W₄₇ × ... × W₂ × W₁如果每个 让梯度乘上 0.9(小于 1 在深层网络中很常见),传到第一层时还剩:
梯度只剩原来的 0.6%,第一层的参数几乎不受更新。这叫梯度消失——网络越深,前面的层越学不动。反过来,如果乘数大于 1(比如 1.1),,梯度爆炸,训练直接崩。
加了残差之后
输出 = x + F(x) (F 是 Attention 或 Feed-Forward Network——后面马上会讲,先把它理解为"对每个词元单独做处理的模块")反向传播时:
那个 +1 就是关键。即使 非常小,梯度至少有一个保底值 1 能从输出直接传到输入。48 层过后,梯度不会衰减到 0。
这就是"高速公路"的含义—— 学的是输入需要改变多少,而不需要从头学整个输出。如果某层 F(x) 暂时没学到有用的东西,它的输出趋近于 0,残差连接保证 x 原样传到下一层,不会造成灾难性破坏。相当于每一层都在说:"先原样保留,我再试着优化一点。"
不止防衰减
残差连接还有一个意外好处:当网络有多层时(下一站会讲到),初始化时所有层的 输出接近 0(权重初始值很小),信号几乎纯粹通过残差通道往后传——相当于训练一开始时后面的层暂时"休眠",随着训练推进,各层的 逐渐"醒来"。残差让网络可以安全地从浅到深逐步学习。
Layer Normalization(层归一化)
它在做什么
LayerNorm 对一个样本内所有特征维度做标准化。公式:
其中:
- (均值)
- (标准差)
- 是可学习参数(让模型自己决定要缩放到什么范围)
一个具体例子。假设某个 token 经过 Attention 后的向量是:
x = [0.2, 8.5, -3.1, 1.4] ← 数值范围 -3.1 到 8.5,差距很大
μ = (0.2+8.5-3.1+1.4)/4 = 1.75
σ = sqrt(((0.2-1.75)²+(8.5-1.75)²+(-3.1-1.75)²+(1.4-1.75)²)/4)
= sqrt((2.40+45.56+23.52+0.12)/4) = sqrt(17.90) ≈ 4.23
标准化后: [-0.37, 1.59, -1.15, -0.08] ← 均值 0,标准差 1数值不再"爆炸"也不"消失",始终在合理范围内。当网络堆叠多层时,每一层的激活值都可能偏离初始分布——LayerNorm 在每一层结束时把它们拉回来,防止逐层累积的数值漂移。
为什么是 LayerNorm 而不是 BatchNorm?
**BatchNorm(批归一化)**是归一化的另一种流派。在 2012-2017 年的 CNN(卷积神经网络)时代,BatchNorm 是标配——那个时期深度学习的主战场是图像识别(ImageNet 竞赛),ResNet、VGG 这些经典架构全都靠 BatchNorm 稳定训练。但 2017 年 Transformer 诞生后,NLP 成为新主战场,序列数据的变长特性让 BatchNorm 水土不服,LayerNorm 顺势成为新标准。进入 2024-2026 年,几乎所有前沿模型都进一步简化为 RMSNorm。它的做法是:对同一个特征维度,跨 batch 中所有样本求均值和方差,然后标准化。
用一个具体例子区分两者。假设有 3 个样本,每个样本是一个 4 维向量:
样本 1: [0.2, 8.5, -3.1, 1.4]
样本 2: [1.1, 2.3, 4.2, -0.5]
样本 3: [3.0, 1.2, 0.8, 2.1]LayerNorm(逐样本): BatchNorm(逐特征维度):
对样本 1 的 4 个数字求 μ,σ 对维度 0 的 [0.2, 1.1, 3.0] 求 μ,σ
→ 样本 1 标准化后的 4 个值 → 维度 0 的 3 个值标准化
对样本 2 的 4 个数字求 μ,σ 对维度 1 的 [8.5, 2.3, 1.2] 求 μ,σ
→ 样本 2 标准化后的 4 个值 → 维度 1 的 3 个值标准化
... ...对比:
| LayerNorm | BatchNorm | |
|---|---|---|
| 归一化方向 | 沿特征维度(横向) | 沿 batch 维度(纵向) |
| 统计范围 | 一个样本内部 | 一个特征跨所有样本 |
| 受 batch size 影响 | 完全不受 | 严重依赖大 batch |
| 训练/推理一致 | 完全一致 | 需要切换(训练时用当前 batch 统计,推理时用历史 moving average) |
| 序列长度变化 | 天然兼容 | 每个位置需要单独统计,变长时很麻烦 |
Transformer 选择 LayerNorm 的原因很直接:输入是变长序列,每个 token 位置独立计算,BatchNorm 跨序列统计时既会遇到变长带来的 padding 问题,又依赖足够大的 batch size 才能让统计量稳定。LayerNorm 只在每个 token 自己内部操作,天然适合 NLP 场景。
2026 年:从 LayerNorm 到 RMSNorm
现代 LLM 大多已改用 RMSNorm。它砍掉了"减均值"这一步,只做"除标准差":
省了一步计算(均值的加减),对几百亿参数的大模型来说每一丁点节省都很可观——推理时快约 5-10%,而效果几乎没差别。Llama 全系列、DeepSeek V3/V4、Kimi K3 都用 RMSNorm。
Pre-LN vs Post-LN
还有一个设计选择:LayerNorm 放在子层前面还是后面?
Post-LN(原始论文): Pre-LN(现代做法):
x + Attention(LN(x)) LN(x + Attention(x)) ← 先加残差再归一化
或更常见的: 或写成:
LN(x + Attention(LN(x))) x + Attention(LN'(x)) ← LN 在前,残差在外Pre-LN 的优势是训练更稳定——梯度不需要穿过 LayerNorm 的倒数,可以直接通过残差通道传播。所有 2026 年主流模型都用 Pre-LN(或 Pre-RMSNorm)。
Feed-Forward Network(前馈网络)
为什么 Attention 之后还需要 FFN?
Self-Attention 本质是线性加权——把不同 token 的 V 向量按权重加起来。加完之后,每个 token 的表示都是其他 token 的线性组合。但语言中的很多规律是非线性的:
"not good" ≠ "not" + "good" 的平均
"very small" ≠ "very" + "small" 的加权和
"如果下雨,我就不去" → 条件和结果的逻辑关系无法用加权求和表达更关键的是,Attention 的运算是逐位置对称的——不管多少层 Attention 摞在一起,只要没有非线性,本质上还是在做加权求和(多个线性变换的复合仍是线性的)。FFN 负责打破这个限制:给每个 token 单独加入非线性变换,让它能从自己的表示里"提炼"出更高级的特征。
如果说 Attention 是"把相关的信息从别处拉过来",FFN 就是"把拉来的信息消化掉"。还有一个重要区别:FFN 是逐位置独立的——处理 token 时只看 token 自己的向量,同一个权重矩阵对所有位置通用。这意味着 FFN 学到的是"通用的知识提炼能力",不依赖上下文位置。
结构:膨胀再收缩
传统 FFN = 两个全连接层,中间夹激活函数,形状像一个沙漏——先膨胀 4-8 倍,再缩回原尺寸:
输入: [1, d_model]
↓ W₁ 投影
中间: [1, d_ff] ← d_ff 通常是 d_model 的 4-8 倍
↓ Activation
↓ W₂ 投影
输出: [1, d_model]用公式表达:
以一个简化的非 MoE 配置为例(d_model = 4096,膨胀 4 倍):
d_model = 4096, d_ff = 4096 × 4 = 16384
W₁: [4096, 16384] ← 约 6710 万参数
W₂: [16384, 4096] ← 约 6710 万参数光一个 FFN 层就约 1.34 亿参数。同层的 Attention(Q、K、V、O 四个矩阵)只有约 万。FFN 占了模型参数的大头(约 2/3)——中间一膨胀,两个大矩阵吃掉大量参数。
现代模型大多使用 MoE(混合专家)架构,FFN 层被拆成多个专家,下一站会展开讲。这里先用简化数字建立直觉。
为什么膨胀之后再缩回去?高维中间层给模型足够的"容积"去记忆海量训练数据中的知识模式。具体机制见下一小节"键值记忆假说"。
激活函数详解:ReLU → GELU → SwiGLU
激活函数是 FFN 的灵魂——没有它,两层全连接矩阵乘在一起还是一个矩阵,非线性就无从谈起。
ReLU:最简单的激活函数,。
输入: [-3, -1, 0, 2, 5]
输出: [ 0, 0, 0, 2, 5] ← 负值全被"截断"优点是计算极快(就是一个 if 判断),缺点也很明显:负值直接归零,梯度也为零。一旦某个神经元输出的加权和变成了负数,ReLU 就会让它"死掉"——从此不再更新。在早期 CNN 时代这问题不大(图像像素本身就是非负的),但对语言模型来说,很多有意义的信号恰好是负值(比如表示否定、转折的向量维度)。
GELU(Gaussian Error Linear Unit):ReLU 的平滑改良版。不再一刀切地清零,而是根据输入值的大小概率性地决定通过多少:
其中 是标准正态分布的累积分布函数——输入 越大,越确定它应该通过(接近 1);输入 越负,越确定它应该被抑制(接近 0);中间态的按比例衰减。这种平滑性让 GELU 在反向传播时梯度更稳定,BERT 和 GPT-2/3 时代的标配。
实践中用 这个近似公式计算,真实 太贵。
SiLU / Swish:进一步改进,用 Sigmoid 替代正态分布 CDF:
x = -3 → SiLU(-3) = -3 × 0.047 ≈ -0.14 (轻微负值,不归零)
x = 0 → SiLU( 0) = 0 × 0.5 = 0
x = 3 → SiLU( 3) = 3 × 0.953 ≈ 2.86 (几乎直线通过)注意和 ReLU 的关键区别:负值不会被完全截断,而是逐渐衰减到接近零。这保留了负值携带的信息(比如否定语义),同时天然实现了"自门控"——Sigmoid 部分 充当了软开关。
SwiGLU:在 SiLU 基础上加入显式门控机制,把 FFN 从两层升级为三层:
然后经过第三层投影回原始维度:
这里有三步:
- Gate 分支: 经过 投影到高维,再通过 SiLU 算出 0~1 之间的"门控系数"
- Up 分支: 经过 投影到同一高维,作为待筛选的"信息候选"
- 逐元素相乘:门控系数 信息候选 = "选择性通过"
- Down 投影: 把筛选后的高维表示压回

注意中间的维度从 变成了 14336。这不是随便选的——SwiGLU 有三个权重矩阵而不是两个,为了让总参数量保持和传统 FFN 一致,中间维度调整为 。数学上可以证明这个数字恰好让 SwiGLU FFN 和传统 FFN 的参数量相当。
门控的核心价值是什么?它让 FFN 学会根据输入内容动态选择激活哪些特征通道。"动词时态"相关的维度可能只在处理动词时被门控放行,其他时候被抑制;"数字计算"相关的维度在处理数学表达时打开。这种条件激活比 ReLU 的"一刀切"精细得多。
| 激活函数 | 公式 | 特点 | 代表模型 |
|---|---|---|---|
| ReLU | 最快,但负值归零 | 早期 CNN | |
| GELU | 平滑,概率性抑制 | BERT, GPT-2/3 | |
| SiLU/Swish | 自门控,负值不归零 | Llama 系列(SwiGLU 的组件) | |
| SwiGLU | 显式门控,动态通道选择 | Llama 3/4, DeepSeek V4, Qwen 3, Kimi K3 |
FFN 为什么能存储知识:键值记忆假说
这是理解 FFN 最重要的一篇论文——2021 年 Anthropic 团队发现,Transformer 的 FFN 层可以理解为可训练的键值存储(Key-Value Memory)。
简化来看,传统 FFN 的公式:
- 的每一行相当于一个"键"(Key)——它检测输入向量中是否存在某个特定模式。如果输入 和某行的内积很大,说明输入匹配了这个模式。
- 的每一列相当于一个"值"(Value)——当对应的键被激活时,这个值就被写入输出向量。
用一个具体例子来建立直觉:
输入 x 中包含了 "埃菲尔铁塔" + "位于" + 地点询问模式
W₁ 第 k 行的 pattern ≈ "首都/地标 + 位置关系"
↓ 内积很大 → 激活该行
↓ Activation 输出该行的激活值给 W₂
W₂ 对应列输出 → "巴黎" + "法国" + "欧洲" 等维度被增强换句话说,一个 FFN 神经元就是一条"如果输入满足 A 条件,就输出 B 信息"的规则。训练数据中反复出现的模式("巴黎是法国首都"、"水的化学式是 H₂O"、"1+1=2")会被编码进 和 的权重中。
这就是为什么膨胀倍数很重要。 越大,每层就能存储越多的键值对。一个 d_model=4096、d_ff=16384 的 FFN 层有 16384 个"知识槽位"。48 层加起来就是约 78 万个潜在的知识存储位置。
这也解释了为什么 FFN 占模型参数的大头:语言模型的知识存储在 FFN 里,Attention 负责的是"当前这段文本在说什么"——注意力决定从上下文的哪些位置提取信息,FFN 决定提取出来的信息意味着什么、应该转换成什么输出。
这一假说也催生了 模型编辑(Model Editing)方向——如果能精确定位"埃菲尔铁塔的高度是 330 米"存在 FFN 的哪一层哪一行,就可以直接修改权重来纠正错误事实,而不需要重新训练整个模型。
为什么是 4 倍膨胀?
业界普遍采用 4 倍膨胀(Llama 系列、DeepSeek、Qwen 都如此),这是大量实验收敛的结果:
- 2 倍:中间容量太小,知识存储受限,模型表现下降明显
- 4 倍:性价比甜点——再往上加容量,收益递减
- 8 倍:GPT-3 用过(d_model=12288, d_ff=49152),效果高但训练成本飙升
SwiGLU 模型因为有三个矩阵,中间维度算的是 倍,但等效"知识容量"仍然约等于传统 4 倍膨胀。
一个完整层的全景图

上图:一个完整 Transformer 层的内部结构(原论文 Encoder 层视角,Decoder 层也完全一样,只是 Attention 里多了一层 Causal Mask)。先 Attention + 残差 + LayerNorm,再 FFN + 残差 + LayerNorm。图片来源:dvgodoy / CC BY
把这一层画成流程图(现代 Pre-LN 风格):

这就是一层。总结三个关键洞见:
- 残差连接是保底的: 让梯度不至于消失,也允许每层"保守地"只做微小改动
- LayerNorm/RMSNorm 是稳定的:把激活值始终控制在合理范围,防止逐层漂移
- FFN 是知识的容器:Attention 负责"问"(从上下文聚合信息),FFN 负责"答"(从自己的参数中提取知识)
把这一层重复堆叠 N 次,就是一台越来越"懂"语言的机器。接下来第六站就讲:怎么从单层搭出一个完整的模型。
第六站:从层到模型
堆叠:层数越多越"聪明"?
GPT-3 有 96 层,每层 12,288 维,12,288/64 = 192 个注意力头。各模型的层数和维度:
| 模型 | 层数 | 隐藏维度 | 头数 | KV 头数 | 总参数 | 激活参数 |
|---|---|---|---|---|---|---|
| GPT-2 Small (2019) | 12 | 768 | 12 | — | 1.24 亿 | 1.24 亿 |
| GPT-3 (2020) | 96 | 12,288 | 96 | — | 1,750 亿 | 1,750 亿 |
| LLaMA-7B (2023) | 32 | 4,096 | 32 | — | 70 亿 | 70 亿 |
| Llama 4 Scout (2026) | 48 | 5,120 | 40 | 8 | 1,090 亿 | 170 亿 |
| DeepSeek V4 Flash (2026) | 43 | 4,096 | 64 | 1 | 2,840 亿 | ~130 亿 |
| DeepSeek V4 Pro (2026) | 61 | 7,168 | 128 | 1 | 1.6 万亿 | ~490 亿 |
| Kimi K3 (2026) | 未公开 | 未公开 | 未公开 | 未公开 | 2.8 万亿 | ~500 亿 |
注:Kimi K3 完整架构参数将于 2026/07/27 技术报告中公开。所有 2026 年模型均为 MoE(混合专家)架构,激活参数远小于总参数。

上图:Encoder 和 Decoder 各自由 N 个相同结构(但参数不同)的层堆叠而成。GPT 系只用了右边的 Decoder 部分。图片来源:dvgodoy / CC BY
可以这样理解层的作用:底层学的是表面的语法特征("形容词后面常跟名词"),中层学的是句法和语义("谁对谁做了什么事"),高层学的是抽象的推理和规划("这段话的论点是……")。
参数是怎么算出来的
这也是第一阶段提过的问题——参数到底长什么样?以 GPT-2 Small 为例,手算一遍:
| 组件 | 计算公式 | 数量 |
|---|---|---|
| 词嵌入矩阵 | 词表大小 × 隐藏维度 | 50,257 × 768 = 3860 万 |
| 位置嵌入矩阵 | 最大长度 × 隐藏维度 | 1024 × 768 = 79 万 |
| 每层的 Attention | 4 × (d × d)(W^Q, W^K, W^V, W^O) | 4 × (768 × 768) = 236 万 |
| 每层的 FFN | d × 4d + 4d × d(两层全连接) | 2 × (768 × 3072) = 472 万 |
| 每层 LayerNorm | 2 × (2 × d)(两个 LN,各有 γ 和 β) | 约 1.2 万 |
一层 ≈ 236 万(Attention) + 472 万(FFN) + 1.2 万(LN) ≈ 709 万。12 层 ≈ 8,508 万。加上嵌入矩阵 ≈ 3,939 万,总计约 1.24 亿。
可以看到:参数的大头在 FFN(约占 2/3),其次是 Attention(约占 1/3),Embedding 占比不大但对于大词表也不可忽视。
GPT-2 Small 是一个稠密模型(dense model)——每次推理所有参数都参与计算。现代模型普遍采用 MoE(混合专家)架构:FFN 层被拆成多个"专家",每次只激活一小部分。
以 DeepSeek V4 Flash 为例:总参数 2,840 亿,256 个专家,每次只激活 6 个 + 1 个共享专家 ≈ 仅激活 ~130 亿参数。Kimi K3 更极端:总参数 2.8 万亿,896 个专家,每次激活 16 个 ≈ 仅激活 ~500 亿参数(不到总参数的 2%)。
这就是 MoE 的核心优势——用"总参数"换"知识容量",但推理成本只和"激活参数"挂钩,不随总参数线性增长。
第七站:自回归生成
理解了模型内部结构后,回到它的"日常工作"——怎么输出文字。
逐 token 循环
输入 "中国的首都是"
→ 模型内部全部算一遍 → 输出概率分布 → 抽样得到 "北京"
输入 "中国的首都是北京"
→ 模型内部全部算一遍 → 输出概率分布 → 抽样得到 "。" 或 EOS
输入 "中国的首都是北京。"
→ 模型内部全部算一遍 → 输出 EOS(停止) → 生成结束每次生成一个 token,就把它拼回输入末尾,整个序列重新过一遍模型。这就是自回归(autoregressive)——自己生成的东西成为自己下一步的输入。

上图:自回归采样的完整循环。虚线箭头表示生成的 token 被追加回上下文。图片来源:PLOS One (CC BY 4.0)
KV Cache:为什么不用每次都重算?
上面的循环描述了一个朴素的做法——每生成一个新 token,就把整个序列重新喂给模型,重新算一遍 Attention。这意味着:
第 1 步: 算 1 个 token 的 Attention
第 2 步: 算 2 个 token 的 Attention(重复了第 1 步的计算)
第 3 步: 算 3 个 token 的 Attention(重复了 1、2 步的计算)
...
第 n 步: 算 n 个 token 的 Attention(前 n-1 个全是重复劳动)生成 100 个 token,实际做了 1+2+3+...+100 ≈ 5000 个 token 的 Attention 计算,其中 99% 是重复的。
KV Cache 的思路:Attention 中每个 token 的 K(Key)和 V(Value)只依赖自己,不依赖其他 token。一旦算出来,后续步骤里它们不会再变。所以:
第一步: 输入 "中国的首都是"
→ 算出所有 token 的 K₀, K₁, K₂, K₃, V₀, ..., V₃
→ 用最后一个位置的 Q₃ 去 attend → 生成 "北京"
→ 把 K 和 V 存进缓存
第二步: 新 token 只有 "北京"
→ 只算 "北京" 的 Q_new, K_new, V_new
→ Q_new 去 attend 缓存里的 K₀...K₃ + 新算的 K_new
→ 用 V₀...V₃ + V_new 加权融合
→ 把 K_new, V_new 追加到缓存
第三步及以后: 每步只算新 token 的 Q、K、V,历史 K、V 全部从缓存读生成 100 个 token:第一步算 4 个 token 的 K/V,之后每一步只算 1 个新 token——总计约 4 + 99 ≈ 103 次 K/V 计算,而不是没有缓存时的 ~5050 次。
图解对比:
没有 KV Cache(每步重算全部): 有 KV Cache(只算新 token):
Step 1: [中][国][的][首都] Step 1: [中][国][的][首都]
算 K,V × 4 个 算 K,V × 4 → 存入缓存
Step 2: [中][国][的][首都][北京] Step 2: 缓存[K₀K₁K₂K₃] + [北京]
算 K,V × 5 个 只算 K,V × 1 → 追加缓存
前 4 个白算了…
Step 3: [中][国][的][首都][北京][是] Step 3: 缓存[K₀K₁K₂K₃K₄] + [是]
算 K,V × 6 个 只算 K,V × 1显存代价
KV Cache 省了计算,但产生了新的显存开销。对于生成中的每一个 token,缓存需要存储:
每层 KV Cache 大小:
( 是因为 K 和 V 各存一份)
以一个 2026 年中档模型为例来算(近似 Llama 4 Scout 配置):
层数: 48
KV 头数: 8
头维度: 128
上下文长度: 10,000 token(已生成这么多)
batch_size: 1
单层 KV Cache = 2 × 1 × 10000 × 8 × 128 × 2 bytes (FP16) = 40.96 MB
48 层总计 ≈ 1.97 GB当上下文到 100K token 时,仅 KV Cache 就接近 20 GB。如果 batch_size > 1(并发服务多个用户),还要翻倍。
这就是为什么长上下文模型尽管"窗口"很大,实际推理却很吃显存——显存瓶颈不在模型参数,而在 KV Cache。
应对方案
2026 年主流模型的针对性优化:
| 技术 | 原理 | 效果 | 代表模型 |
|---|---|---|---|
| GQA(分组查询注意力) | 多个 Q 头共享一组 K/V 头,减少 K/V 头数 | KV Cache 缩减 4-8× | Llama 4 Scout (8 KV 头 / 40 Q 头) |
| MQA(多查询注意力) | 所有 Q 头共享唯一的 K/V 头 | KV Cache 缩减到 1/头数 | DeepSeek V4 全系 (1 个 KV 头) |
| 滑动窗口注意力 | 每个 token 只能看到附近固定范围的 token,远程 K/V 不缓存 | KV Cache 有固定上限 | DeepSeek V4 混合注意力中的滑动窗口部分 |
| CSA / HCA 稀疏注意力 | 按固定间隔压缩远距离 token 的 K/V,大幅减少存储 | 亚线性 KV Cache 增长 | DeepSeek V4 Pro (压缩率 4× 和 128×) |
DeepSeek V4 Pro 同时用了 MQA + 稀疏注意力——KV 头只有 1 个,而且远程 token 还被压缩了。这解释了为什么它能跑 1M 上下文但显存不爆炸。
Causal Mask(因果遮罩)
在 Self-Attention 那步,当前 token 不能看到未来的 token,否则就是"作弊"——提前知道了答案。解决方式是给 score 矩阵加一个上三角遮罩(把未来的 score 设成 -∞,Softmax 后变 0):
不加 Mask 的 Attention: 加了 Causal Mask 的 Attention:
K₀ K₁ K₂ K₀ K₁ K₂
Q₀ [ 2 3 3 ] Q₀ [ 2 -∞ -∞ ]
Q₁ [ 2 4 4 ] Q₁ [ 2 4 -∞ ]
Q₂ [ 1 2 3 ] Q₂ [ 1 2 3 ]
Softmax([2, -∞, -∞]) → [1.0, 0.0, 0.0] ← Q₀ 只能看自己
Softmax([2, 4, -∞]) → [0.12, 0.88, 0.0] ← Q₁ 只能看 Q₀ 和自己
Softmax([1, 2, 3]) → [0.09, 0.24, 0.67] ← Q₂ 能看所有人采样策略:Temperature、Top-p、Top-k
模型输出的是一组概率(logits → softmax → probabilities),但"怎么从这个概率里挑一个 token"大有讲究:
Greedy(贪心):永远选概率最高的那个。问题:容易陷入重复循环("我今天去了超市,买了东西,超市里有东西,东西很多……")。
Temperature(温度):在 Softmax 之前把 logits 除以 T。
adjusted_logits = logits / temperature
probs = softmax(adjusted_logits)| T 值 | 效果 | 适用场景 |
|---|---|---|
| T = 0 | 等价于 Greedy(永远选最高) | 代码生成、数学计算 |
| T ≈ 0.3-0.5 | 保守,极少"冒险" | 事实性问答、翻译 |
| T ≈ 0.7-1.0 | 平衡,有一定创意 | 日常对话 |
| T ≈ 1.2-1.5 | 创意丰富,偶现胡言乱语 | 头脑风暴、写诗 |
| T > 2.0 | 近乎随机 | 基本没用 |
原理:T < 1 时,高概率 token 被进一步放大("马太效应");T > 1 时,概率分布被"抹平",低概率 token 也有机会被选中。
Top-k 采样:只保留概率最高的 k 个 token,其余直接砍掉(概率设 0),再按概率采样。比如 k=50,就从"最有希望的 50 个候选"里抽。
Top-p 采样(Nucleus Sampling / 核采样):从高到低累加概率,累加到 p 为止(比如 p=0.9),超出部分的 token 砍掉。和 Top-k 相比,Top-p 能动态调整候选数——在确定性强的位置可能只保留 3 个候选,在模糊的位置可能保留 100 个。
实践中,大多数 API 默认使用 Top-p + Temperature 的组合。
本阶段小结
来回顾一下一个 token 的完整旅程,以及每个站点学到的核心直觉:

下一阶段(第三阶段:训练过程)会讲这些参数是怎么"学"出来的——预训练、SFT 监督微调、RLHF 对齐——也就是模型从"什么都不会"到"能跟你聊天"的完整生产工艺。
延伸阅读与可视化资源
必看交互工具
| 资源 | 说明 |
|---|---|
| The Illustrated Transformer | Jay Alammar 手绘图解,Transformer 入门圣经 |
| The Illustrated GPT-2 | 同上作者的 GPT-2 深度拆解 |
| bbycroft.net/llm | 3D 交互式 LLM 推理全过程可视化 |
| Cornell BPE Visualizer | BPE 分词一步步演示 |
| Tokenizer Playground | 对比 GPT-2/BERT/T5 三种分词器 |
深入阅读
| 资源 | 说明 |
|---|---|
| "Attention Is All You Need" | Transformer 原始论文(2017),值得通读 |
| Andrej Karpathy - Let's build GPT from scratch | 从零手写 GPT,代码逐行讲解 |
| Lilian Weng - Attention? Attention! | OpenAI 研究员的 Attention 机制技术综述 |
| Sebastian Raschka - Understanding LLMs | LLM 论文阅读清单,按主题分类 |
图片附录
以下是本文涉及的 Transformer 子结构详解图,供仔细研究:
Self-Attention 完整计算(简化版):

图片来源:dvgodoy / CC BY
Self-Attention 完整计算(详细版):

图片来源:dvgodoy / CC BY
Decoder Self-Attention(带 Causal Mask):

图片来源:dvgodoy / CC BY
完整的 Transformer 层内部展开(Encoder 视角):

图片来源:dvgodoy / CC BY
参考文献与图片来源
论文
| 标题 | 作者 | 年份 | 链接 |
|---|---|---|---|
| Attention Is All You Need | Vaswani et al. | 2017 | arXiv:1706.03762 — Transformer 架构原始论文 |
核心参考
| 标题 | 作者/来源 | 类型 | 链接 |
|---|---|---|---|
| The Illustrated Transformer | Jay Alammar | 博客 | jalammar.github.io — Transformer 手绘图解,入门圣经 |
| The Illustrated GPT-2 | Jay Alammar | 博客 | jalammar.github.io — GPT-2 深度拆解 |
| Let's build GPT from scratch | Andrej Karpathy | 视频 | YouTube — 代码逐行讲解 |
| Attention? Attention! | Lilian Weng | 博客 | lilianweng.github.io — Attention 机制技术综述 |
| Understanding LLMs (Reading List) | Sebastian Raschka | 博客 | sebastianraschka.com — 论文阅读清单 |
| LLM Visualization | Brendan Bycroft | 交互工具 | bbycroft.net/llm — 3D 推理全过程可视化 |
| BPE Visualizer | Cornell CS4782 (2026) | 交互工具 | cs.cornell.edu — BPE 分词一步步演示 |
| Tokenizer Playground | Context Lab | 在线工具 | context-lab.com — 多分词器对比 |
图片来源
| 图片 | 来源 | 许可 |
|---|---|---|
| Self-Attention QKV、矩阵计算、多头注意力、残差连接、Decoder 架构、推理动图 | Jay Alammar, The Illustrated Transformer / GPT-2 | 版权保留,教学引用 |
| 完整 Transformer 架构、Encoder/Decoder 堆叠、Self-Attention 简化与详细版、多头注意力、位置编码、子层结构、K-Q 匹配 | dvgodoy / dl-visuals | CC BY 4.0 |
| Transformer 原始架构图 (Fig.1) | Vaswani et al., "Attention Is All You Need" (2017) | arXiv 论文 |
| 自回归采样流程图 | PLOS One | CC BY 4.0 |
| 词嵌入 3D/2D 可视化 | Embedding Plot (Wikipedia) | CC BY-SA |