从 RNN 到 Transformer--序列建模架构的演进与变革(二)
目录
- 三、Transformer:抛弃循环,只留注意力
- 3.1 先建立一个心智模型:它到底在干什么?
- 3.2 宏观架构:两层楼的小工厂
- 3.3 每层楼里,一场”讨论会”的标准流程
- 3.4 跟着一个词走完全程(具体例子)
- 3.5 数据流:形状追踪(写给要实现它的人)
- 3.6 对照表:Transformer 逐个击破 RNN 的痛点
- 3.7 三条演化路线:搭积木的不同搭法
- 3.8 诚实地说:Transformer 自己的新麻烦
- 四、Attention 机制详解:从”加权平均”说起
- 4.1 第一步:Attention 的本质就是”加权平均”
- 4.2 第二步:Q / K / V 是怎么被”逼”出来的?
- 4.3 第三步:手算一遍(4 个词的小例子)
- 4.4 一个关键细节:为什么要除以 √d_k?
- 4.5 Self-Attention 的真面目:每个词都做一次上面的事
- 4.6 Multi-Head Attention:为什么要”多头”?
- 4.7 Transformer 里的三种注意力(一句话区分)
- 4.8 复杂度对比:论文那张表的现代解读
- 4.9 最小可读实现(对着 4.3 的例子看,逐行都能对上)
- 五、总结:架构演进的底层逻辑
- 5.1 全景对比表
- 5.2 三条架构洞察
- 5.3 结语
- 参考与延伸阅读
三、Transformer:抛弃循环,只留注意力
这一章的目标只有一个:让你在不看任何公式的情况下,先弄明白 Transformer 这台机器是怎么运转的。公式排在后面,用来验证直觉,而不是制造障碍。
3.1 先建立一个心智模型:它到底在干什么?
忘掉神经网络,先想象一家翻译公司接到一份英译中的任务。
老办法(RNN):一个人从头读到尾
小王拿到英文稿 → 逐字读,边读边在脑子里记笔记
→ 读完后,脑子里只剩一份"笔记"
→ 凭这份笔记,开始逐字写中文
问题显而易见:
- 他必须一个字一个字读,不能跳读,不能几个人分工(串行);
- 文章一长,前面的内容就从笔记里挤掉了(遗忘);
- 写中文时,他手上只有那份”笔记”,原文已经不在手边了(信息瓶颈)。
新办法(Transformer):一个团队协同作业
【理解组】全员同时读英文稿,边读边互相讨论
→ 每个人手里的笔记都变成了"结合全文理解后的笔记"
【写作组】写中文时,每写一个词,就回头翻阅理解组的全套笔记,
挑出当前最相关的部分重点参考 → 写出下一个词
这个新流程有三个关键改进,正好对应 Transformer 的灵魂:
| 改进 | 对应机制 |
|---|---|
| 全员同时读、互相讨论 | Self-Attention(自注意力):所有位置并行,彼此直接通信 |
| 每个人手里的笔记都包含全文上下文 | 上下文相关的表示 |
| 写的时候回头翻阅原文笔记 | Cross-Attention(交叉注意力) |
一句话心智模型:Transformer 就是”反复开讨论会”。 每开一次会,每个词都把所有相关词的信息收集过来,更新自己的理解;会开得越多(层数越深),理解越充分。最后带着这份理解去生成答案。
这个”开会”的动作,就是 Attention。 理解了这一点,剩下的都是工程细节。
3.2 宏观架构:两层楼的小工厂
现在把上面那家公司画成建筑图。Transformer 是 Encoder-Decoder 结构,可以看成一栋两层的小楼:
graph TB
subgraph B1["一楼:理解组 Encoder"]
direction TB
IN["英文原文<br/>The cat sat on the mat"] --> EMB1["第一步 编号加座位牌<br/>Embedding 加 Positional Encoding"]
EMB1 --> BLK1["第二步 讨论室 重复 6 层<br/>每层 开会 后 各自整理笔记"]
BLK1 --> MEM["第三步 成品笔记柜<br/>每个词一份笔记 全套保留"]
end
subgraph B2["二楼:写作组 Decoder"]
direction TB
PREV["已写出的中文<br/>猫 坐 在"] --> EMB2["第一步 编号加座位牌"]
EMB2 --> BLK2["第二步 写作间 重复 6 层<br/>每层 内部开会 再 查阅一楼笔记柜"]
BLK2 --> OUT["第三步 猜下一个词<br/>Linear 加 Softmax"]
end
MEM -.->|"随时查阅 Cross-Attention"| BLK2
OUT --> NEXT["下一个词 垫子"]
NEXT -.->|"补进已写内容 继续生成"| PREV
请记住这张图里的三个反直觉之处,它们是初学者的常见卡点:
| 🤔 常见误解 | ✅ 实际情况 |
|---|---|
| Encoder 把句子压成一个向量 | 不压! 每个词都保留一份独立的笔记向量,全部传给 Decoder |
| Decoder 只接收 Encoder 最后一层 | Decoder 每一层都能去查一次笔记柜 |
| 输出是一次性生成的 | 一次只生成一个词,生成完再补回输入,循环往复(自回归) |
3.3 每层楼里,一场”讨论会”的标准流程
无论一楼二楼,每个”房间”(层)里干的事情都是同一套三件套。用一楼的房间举例:
进门:每个人带着自己的笔记 x
│
├─ 第 1 步:开会(Multi-Head Self-Attention)
│ 每个人发言:"我需要什么信息?" → 全场匹配 → 收集到别人的信息
│ 得到:会议收获 a
│
├─ 第 2 步:合并(Add & Norm)
│ 新笔记 = 旧笔记 + 会议收获,再归一化一下
│ 即:x + a,然后 LayerNorm
│ ⭐ 这就是"残差连接":永远不丢掉自己原来的想法
│
├─ 第 3 步:独自消化(Feed Forward Network)
│ 每个人回到座位,独自把刚收集来的信息加工、提炼一遍
│ 得到:消化结果 f
│
└─ 第 4 步:再合并(Add & Norm)
最终笔记 = 上一步结果 + 消化结果
即:(x + a) + f,然后 LayerNorm
出门:带着更新后的笔记去下一个房间
写成公式就是:
x_out = LayerNorm( x + Attention(x) ) # 开会 + 合并
x_out = LayerNorm( x_out + FFN(x_out) ) # 消化 + 合并
为什么每个子层都要包一层”残差 + 归一化”? 这是 CNN 时代 ResNet 留下的宝贵经验:
- 残差(+ x):给信息留一条”直通车道”。就算这一层的会议开得一塌糊涂,原始信息也不会丢,梯度也能原路返回。这是 Transformer 能堆到上百层的根本原因。
- LayerNorm(归一化):把数值拉回稳定范围,防止层层放大后失控。
打个比方:残差连接就像开会时保留原始笔记的复印件,新收获单独记在活页上。无论会议结论多离谱,你手里的原始材料都还在。
3.4 跟着一个词走完全程(具体例子)
光看结构图还是抽象。我们来跟着一个词走一遍,把所有零件串起来。
任务:翻译 The cat sat on the mat → 猫 坐 在 垫子 上
此时:已经写出了「猫 坐 在」,要预测第 4 个词。
第 0 步:进场前的准备
| 处理 | 做什么 | 为什么要做 |
|---|---|---|
| 分词 | The / cat / sat / on / the / mat | 模型只认词表里的编号 |
| 编号(Embedding) | 每个词 → 一个 512 维向量 | 把符号变成可计算的数字 |
| 发座位牌(位置编码) | 向量 + 位置信息 | ⚠️ Attention 本身不看顺序,必须手动告诉它”你是第几个” |
为什么 Attention 会丢顺序? 因为它的核心运算是”两两互相看”,这个动作对顺序不敏感——把 {A, B, C} 换成 {C, A, B},两两之间的关系还是那些,输出集合不变(置换不变性)。所以必须显式加上位置信息。
位置编码怎么做?用一个不需要学习的固定公式,给每个位置的 512 个维度分别填入不同频率的 sin/cos 值:
PE(pos, 2i) = sin( pos / 10000^(2i/512) )
PE(pos, 2i+1) = cos( pos / 10000^(2i/512) )
可以把它想成一块手表:有的指针转得快(区分相邻的词),有的指针转得慢(区分段落首尾)。把快针慢针的组合拼起来,就能唯一标定”第几个位置”。
现代大模型主流改用 RoPE(旋转位置编码),思路更优雅:不额外加一个向量,而是直接把 Q、K 向量”旋转”一个与位置相关的角度。两个向量各自旋转后再做点积,结果只取决于它们的相对距离——天生就是在表达”相隔多远”。这也是它能更好地外推到长文本的原因。
第 1 步:一楼 —— 理解组开会(跑 6 层)
对 The / cat / sat / on / the / mat 这 6 个词,同时地、并行地做注意力。
开会过程中会发生什么?举几个真实会出现的现象:
| 词 | 它重点看了谁 | 为什么 |
|---|---|---|
sat(坐) | 重点看 cat、mat | 要搞清楚”谁坐、坐在哪” |
the(第二个) | 重点看 mat | 冠词要跟它修饰的名词绑在一起 |
on | 重点看 sat、mat | 介词要连接动作和地点 |
经过 6 层会议后,每个词的笔记都不再是”这个词本身的意思”,而是**“这个词在整句话语境下的意思”**。比如 sat 的笔记里已经融合了”主语是猫、地点是垫子”。
✅ 6 份笔记全部保留,摆进笔记柜。这就是”没有信息瓶颈”的关键。
第 2 步:二楼 —— 写作组动笔(也跑 6 层)
输入是已写出的「猫 坐 在」。这一层有两个额外机制:
(a)戴眼罩写作:因果掩码(Causal Mask)
写作组给自己立了条规矩:写第 4 个词时,不许看第 5、6 个词(因为还没写出来呢)。
实现极其简单粗暴——把”未来位置”的分数直接填成 -∞,softmax 之后权重就变成 0:
| 正在生成的位置 ↓ / 能否看到 → | 猫 | 坐 | 在 | 垫子 |
|---|---|---|---|---|
| 猫(第 1 个词) | ✅ 能看 | ❌ 屏蔽 | ❌ 屏蔽 | ❌ 屏蔽 |
| 坐(第 2 个词) | ✅ 能看 | ✅ 能看 | ❌ 屏蔽 | ❌ 屏蔽 |
| 在(第 3 个词) | ✅ 能看 | ✅ 能看 | ✅ 能看 | ❌ 屏蔽 |
| 垫子(第 4 个词) | ✅ 能看 | ✅ 能看 | ✅ 能看 | ✅ 能看 |
读法:每一行是一个”正在写的位置”,每一列是”它想看的位置”。左下角能看、右上角全屏蔽,形成一个下三角。
这个下三角矩阵就是因果掩码。GPT 之所以叫”自回归”模型,全靠它。没有它,模型训练时会直接抄答案。
(b)查阅笔记柜:Cross-Attention
这是 Decoder 比 Encoder 多出来的那一层,也是翻译这件事能成立的关键:
- Q(我要什么) 来自二楼:「我现在要写介词后面的宾语了,需要什么信息?」
- K / V(有什么) 来自一楼的笔记柜:全部 6 个词的笔记
于是模型会自动把注意力集中到 mat 上——它自己学会了”垫子”这个词该对齐到 mat。这就是机器翻译里”词对齐”的全过程,没有任何人工规则。
第 3 步:出口 —— 猜下一个词
最后一层输出的向量,投影到整个词表(比如 5 万个词),softmax 后得到概率分布:
垫子 0.71 ← 选中
地毯 0.12
沙发 0.06
... ...
选出「垫子」,把它补进已写内容 → 「猫 坐 在 垫子」→ 回到第 2 步继续生成 → 直到输出结束符。
这里有个重要的工程事实:每生成一个词,都要把前面所有词重新算一遍(所以有 KV Cache 这个优化——把算过的 K、V 缓存起来,避免重复计算)。这也是为什么 LLM 的”首字延迟”和”后续吐字速度”感受完全不同。
3.5 数据流:形状追踪(写给要实现它的人)
从工程实现角度,Transformer 有一个极其友好的性质:从进到出,张量形状始终不变。
输入 token ids : (B, L) B=batch, L=序列长度
↓ Token Embedding : (B, L, 512)
↓ + 位置编码 : (B, L, 512)
↓ ┌──── 以下重复 6 层 ────┐
│ Q/K/V 投影 + 切头 : (B, L, 512) → (B, 8, L, 64)
│ 注意力分数 QKᵀ/√d_k : (B, 8, L, L) ← O(L²) 就出自这里
│ 加权求和 × V : (B, 8, L, 64)
│ 拼回 + 输出投影 W_O : (B, L, 512)
│ 残差 + LayerNorm : (B, L, 512)
│ FFN 512→2048→512 : (B, L, 512) ← 参数量占全模型约 2/3
│ 残差 + LayerNorm : (B, L, 512)
↓ └────────────────────┘
Encoder 输出 : (B, L, 512)
“保形”为什么重要? 因为输入输出的形状一样,模块就可以像乐高一样任意堆叠、任意插拔——想堆 6 层就 6 层,想堆 96 层就 96 层,不用改任何代码。这是 Transformer 可扩展性的工程根基。
顺带说一个常被忽略的事实:FFN 的参数量约占整个模型的 2/3(每层注意力约 1.05M,FFN 约 2.1M)。所以业内有种说法——Attention 负责”路由”,FFN 才是”知识库”。后来的 MoE 正是在 FFN 上做稀疏化。
3.6 对照表:Transformer 逐个击破 RNN 的痛点
| RNN 的痛点 | Transformer 怎么解决 | 效果 |
|---|---|---|
| 必须一个字一个字读 | 全场同时开会 | 训练速度提升一个数量级 |
| 读到后面忘了前面 | 任意两位置直接对话 | 信息通路 O(1) |
| 全句压成一个向量 | 每个词的笔记全部保留 | 长句翻译质量跃升 |
| 堆到 4 层就堆不动 | 残差连接提供梯度直通车道 | 可堆 上百层 |
| 不知道模型在看哪 | 注意力权重可直接可视化 | 可解释性大幅提升 |
3.7 三条演化路线:搭积木的不同搭法
原版 Transformer 是”理解组 + 写作组”齐全的完整版。但后来大家发现,拆开来单独用反而更好:
| 路线 | 代表 | 注意力怎么遮 | 预训练任务 | 擅长 |
|---|---|---|---|---|
| 只用理解组 Encoder-only | BERT、RoBERTa | 不遮,双向全看 | 完形填空(遮住词猜词) | 理解类:分类、NER、检索、句向量 |
| 只用写作组 Decoder-only | GPT、LLaMA、Qwen | 因果掩码,只看左边 | 续写(预测下一个词) | 生成类:对话、写作、代码 |
| 两组都用 Encoder-Decoder | T5、BART | 组内遮 + 跨组查 | 去噪重建 | 条件生成:翻译、摘要 |
为什么 Decoder-only 成了今天大模型的绝对主流? 三个原因:
- 预训练任务和真实任务同构——“续写”就是”生成”,训练和推理没有割裂;
- KV Cache 让推理最高效——单向注意力可以最大限度地复用缓存;
- Scaling 曲线最平滑——规模越大效果越好,规律最稳定。
顺带解释一个常见困惑:BERT 为什么不能用来聊天? 因为它是双向的,训练时”看到了答案”。让它逐词生成,等于让一个习惯了看着全文做填空的人去闭着眼睛续写——能力不对口。
3.8 诚实地说:Transformer 自己的新麻烦
它不是终点,而且引入了自己的代价:
| 新问题 | 大白话 | 主流对策 |
|---|---|---|
| O(n²) 复杂度 | 文本长度翻倍,计算量变 4 倍 | FlashAttention(不改数学,改显存读写)、稀疏/滑窗注意力 |
| KV Cache 吃显存 | 生成时要把所有历史的 K、V 存着,长对话非常吃显存 | GQA(多个头共用一份 K/V)、MLA(压缩后再存) |
| 超出训练长度就变笨 | 训练时最长 4K,给你 100K 就懵了 | RoPE 外推、YaRN 等长上下文技术 |
| FFN 太耗参数 | 模型一大,参数量爆炸 | MoE:拆成多个”专家”,每次只激活少数几个 |
有意思的是,最后一条催生了一次螺旋式回归:Mamba 这类状态空间模型重新捡起了 RNN 的”线性复杂度”优点,但保留了并行训练能力。架构演进从来不是简单的谁替代谁,而是取舍的重新平衡。
四、Attention 机制详解:从”加权平均”说起
Attention 被讲得玄学,是因为大家都从 Q、K、V 开始讲。本章反过来:先讲你早就会的加权平均,再看 Q/K/V 是怎么自然而然被逼出来的。
4.1 第一步:Attention 的本质就是”加权平均”
假设你要理解句子里的”它”指的是谁:
「猫 追 老鼠 , 因为 它 饿 了」
人一眼就知道”它”是猫,不是老鼠。这个判断过程拆开其实是三步:
- 扫一遍:把候选对象(猫、追、老鼠…)都过一遍;
- 打相关性分:猫和”饿”最相关 → 高分;“追”是动词 → 低分;
- 按分混合:把高分对象的信息多掺一些进来。
这三步用数学写出来,就是:
结果 = 分数₁ × 信息₁ + 分数₂ × 信息₂ + ... (分数加起来 = 1)
这就是 Attention 的全部。 剩下的 Q、K、V 只是让这个”打分”和”信息”变得可学习而已。
4.2 第二步:Q / K / V 是怎么被”逼”出来的?
上面的三步有个问题:分数怎么打?信息又是什么? 如果靠人工规则,就退回符号主义老路了。于是设计者做了个巧妙的拆分——把每个词的向量拆成三种角色:
| 角色 | 通俗理解 | 类比 | 干什么用 |
|---|---|---|---|
| Q(Query,查询) | “我在找什么” | 手里的购物清单 | 用来跟别人比对,算分数 |
| K(Key,键) | “我能提供什么” | 商店门口的招牌 | 被 Q 比对,决定匹配度 |
| V(Value,值) | “我实际给的东西” | 货架上的实际商品 | 被加权平均,构成输出 |
关键设计:K 和 V 是分离的。 招牌(K)只需要简洁、便于比对;商品(V)才是真正要传递的内容。这个分离让模型可以”用一种标准去检索,用另一种内容去输出”,表达能力强了很多。
一个精妙的类比:可微的字典查询。 查字典时,你用拼音(Q)去匹配索引(K),拿到释义(V)。这个过程的”匹配”是离散的、不可求导的。 Attention 做的事,是把这套流程变成连续可微的——不是”精确命中某一条”,而是”按相似度混合所有条目”。于是”检索”这个动作第一次能被塞进神经网络里端到端训练。
Q、K、V 从哪来?
答案是:从输入向量各自做一次线性变换得到。三个不同的矩阵 W_Q、W_K、W_V 都是可训练参数:
同一个词 x ──× W_Q──→ q (我要找什么)
──× W_K──→ k (我的招牌)
──× W_V──→ v (我的货)
为什么要投影而不直接用 x?因为同一个词在”被查询”和”去查询别人”时的角色应该不同。经过训练,模型会自己学会该怎么拆。
4.3 第三步:手算一遍(4 个词的小例子)
这是全篇最重要的一节。跟着算一遍,Attention 就再也不会忘。
句子:猫 / 追 / 老鼠 / 它,我们计算第 4 个词「它」的输出。
简化:真实维度是 64,这里用 2 维,方便手算。
Step 1|生成 Q、K、V(假设网络已经学好了,给出数值)
| 词 | K(招牌) | V(货) |
|---|---|---|
| ① 猫 | [1.0, 0.0] | [2.0, 1.0] |
| ② 追 | [0.0, 1.0] | [0.0, 3.0] |
| ③ 老鼠 | [0.7, 0.7] | [1.0, 2.0] |
| ④ 它 | [1.0, 0.0] | [0.0, 0.0] |
「它」的查询向量:q₄ = [1.0, 0.0](可以理解为”我在找一个动物”)
Step 2|打分:用 q 去点乘每一个 k
分数 = q · k (点积越大 = 越相似)
猫 : [1,0]·[1,0] = 1×1 + 0×0 = 1.0 ← 高度匹配
追 : [1,0]·[0,1] = 1×0 + 0×1 = 0.0 ← 完全不相关(动词)
老鼠 : [1,0]·[0.7,0.7] = 1×0.7 + 0×0.7 = 0.7 ← 部分匹配(也是动物)
它 : [1,0]·[1,0] = 1×1 + 0×0 = 1.0 ← 自己看自己
注意这里「老鼠」得分 0.7 低于「猫」的 1.0——这正是我们希望的结果:“它”应该更倾向于指猫。这个偏好不是人工设定的,而是模型从海量语料里学出来的。
Step 3|缩放:除以 √d_k = √2 ≈ 1.41(为什么除,见 4.4)
1.0/1.41 = 0.71 0.0/1.41 = 0.00 0.7/1.41 = 0.50 1.0/1.41 = 0.71
Step 4|softmax 归一化:把分数变成加起来等于 1 的权重
e^0.71 = 2.03 e^0.00 = 1.00 e^0.50 = 1.65 e^0.71 = 2.03
总和 = 6.71
猫 : 2.03 / 6.71 = 0.30 (30%)
追 : 1.00 / 6.71 = 0.15 (15%)
老鼠 : 1.65 / 6.71 = 0.25 (25%)
它 : 2.03 / 6.71 = 0.30 (30%)
Step 5|加权求和:用权重去混合 V
输出 = 0.30 × [2,1] + 0.15 × [0,3] + 0.25 × [1,2] + 0.30 × [0,0]
= [0.60, 0.30] + [0.00, 0.45] + [0.25, 0.50] + [0.00, 0.00]
= [0.85, 1.25]
结果解读:「它」这个词的向量,从原来的 [0,0] 变成了 [0.85, 1.25]。
这个新向量里,混入了 30% 的”猫” + 25% 的”老鼠” + 15% 的”追”。也就是说——“它”这个位置的表示,现在包含了它所指向的实体的信息。 后续层就能基于这个表示做出正确判断。
✅ 这就是 Attention 的全部魔法:把一个词的表示,替换成”按相关性混合了全场信息”的新表示。
第三步的矩阵形式
上面只是算了「它」一个词。实际上所有词同时做,就是一次矩阵乘法:
┌ ┐
Attention = │ Q Kᵀ │
│ ───── V │ ← 一行公式,就是上面五步
│ √d_k │
└ ┘
Scores = Q·Kᵀ (L, L) ← 所有词对所有词的分数矩阵
Weights = softmax(Scores) (L, L) ← 每行和为 1,行 = "我在看谁"
Output = Weights · V (L, d) ← 每个词的新表示
那个 (L, L) 的分数矩阵,就是可以被可视化的注意力热力图——横轴是”被看的人”,纵轴是”看的人”。
4.4 一个关键细节:为什么要除以 √d_k?
这是面试和工程里的高频问题,也是理解”为什么 Transformer 能训得动”的钥匙。
推导(很简单):假设向量各分量独立、均值 0、方差 1,则点积
q·k = q₁k₁ + q₂k₂ + ... + q_dk_d
是 d 个独立项的和,所以它的方差 = d_k,即标准差 = √d_k。
真实情况:d_k = 64 → 标准差 = 8。也就是说分数典型落在 ±8 这个范围,甚至更大。
不除会怎样? softmax 对大数极其敏感(指数级放大):
分数 [8, -3] → e^8 = 2981, e^-3 = 0.05 → softmax ≈ [1.000, 0.000]
分布被推成了 one-hot。而 one-hot 的 softmax 梯度几乎为 0——梯度消失,模型训练初期根本学不动。
除以 √d_k 之后,方差被拉回 1,softmax 待在”温和”的区域,梯度健康。
一句话记忆:分数会因维度升高而”膨胀”,不缩放就会把 softmax 压成 one-hot,导致梯度消失。除以 √d_k 就是给分数做标准化。
4.5 Self-Attention 的真面目:每个词都做一次上面的事
回看 4.3 的例子,我们只算了「它」一个词。而 Self-Attention 是对每个词都做一遍:
猫 → 看看全场 → 更新自己(可能重点看了"追":我是动作发起者)
追 → 看看全场 → 更新自己(可能重点看了"猫"和"老鼠")
老鼠 → 看看全场 → 更新自己(可能重点看了"追":我是动作承受者)
它 → 看看全场 → 更新自己(重点看了"猫")
而且这四件事是完全并行的一次矩阵乘法,不是循环四次。这就是 Transformer 快的根本原因。
“Self(自)“的含义:Q、K、V 全部来自同一个序列自己——自己查自己。与之相对的 Cross-Attention,K、V 来自别的序列。
4.6 Multi-Head Attention:为什么要”多头”?
单头 Attention 有个隐患:一次注意力只能表达一种相关关系。但语言里的关系是多维的:
- 「它」要看「猫」——这是指代关系
- 「追」要看「猫」「老鼠」——这是语法依存关系
- 「的」要看前一个词——这是修饰关系
挤在一个空间里,这些关系会互相干扰。于是有了多头:把 512 维切成 8 份,每份 64 维,在 8 个独立的子空间里各做一次注意力,最后拼起来。
graph LR
X((输入 x)) --> H1["头 1<br/>子空间 1"]
X --> H2["头 2<br/>子空间 2"]
X --> H8["头 8<br/>子空间 8"]
H1 --> C["拼接<br/>Concat"]
H2 --> C
H8 --> C
C --> WO["W_O 线性混合"] --> OUT((输出))
三个必须知道的要点:
| 要点 | 说明 |
|---|---|
| 不增加计算量 | 每头维度是 d_model / h,8 个 64 维加起来 = 512 维,FLOPs 与单头基本一致。多头是免费的午餐。 |
| 像多组卷积核 | 和 CNN 用多个卷积核提取不同特征完全同构——多视角看同一份输入 |
| 头会自发分工 | 研究者可视化后发现,不同头确实学到了不同模式:有的专盯下一个词,有的专盯句首,有的专做指代消解 |
一个直觉:单头 Attention 像一个人开会,多头像 8 个人同时开 8 个专题会。 会后把 8 份纪要拼接汇总,信息自然比一份纪要丰富。
4.7 Transformer 里的三种注意力(一句话区分)
| 类型 | 谁在提问(Q) | 查谁的字典(K/V) | 可否看未来 | 用在哪 |
|---|---|---|---|---|
| Encoder Self-Attention | 源句的每个词 | 源句自己 | ✅ 双向全看 | 理解源句 |
| Decoder Masked Self-Attention | 已生成内容的每个词 | 已生成内容自己 | ❌ 戴眼罩 | 建模已写部分 |
| Encoder-Decoder Cross-Attention | 解码中的每个位置 | Encoder 的输出 | — | 把源句信息注入生成 |
Cross-Attention 是这个架构里最精妙的设计,值得多说一句:
它把”理解”和”生成”彻底解耦成两个独立阶段,中间只靠一个注意力层连接。这个模式后来成了**“条件注入”的通用接口**——图像生成模型 Stable Diffusion 用它注入文本提示,多模态模型用它注入图像特征。凡是”在 A 的条件下生成 B”,都可以用 Cross-Attention 实现。
4.8 复杂度对比:论文那张表的现代解读
| 层类型 | 信息通路长度 | 每层计算量 | 串行操作数 |
|---|---|---|---|
| Self-Attention | O(1) | O(n² · d) | O(1) |
| RNN | O(n) | O(n · d²) | O(n) |
| CNN | O(log n) | O(k · n · d²) | O(1) |
三个结论,第三个最反直觉:
- 并行性:Self-Attention 串行操作数 O(1),RNN 是 O(n) → 这就是训练速度差一个数量级的根源。
- 通路长度:Self-Attention O(1),任意两位置一步直达 → 这就是长程建模能力的根源。
- 其实更省算力:当
n < d(现代 LLM 的典型情况:上下文几千,模型维度几千到上万)时,n²·d < n·d²,Self-Attention 的计算量反而比 RNN 小。
也就是说:Transformer 不只是”并行更快”,在”中等长度序列 + 大模型”这个主战场上,它的绝对计算量也更优。只有当 n 极大时,O(n²) 才真正成为负担。
4.9 最小可读实现(对着 4.3 的例子看,逐行都能对上)
import math
import torch
import torch.nn.functional as F
def attention(Q, K, V, mask=None):
"""
Q: (B, h, L_q, d_k) 查询
K: (B, h, L_k, d_k) 键
V: (B, h, L_k, d_v) 值
"""
d_k = Q.size(-1)
# ① 打分:每个 q 与所有 k 点乘 → (B, h, L_q, L_k)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
# ② 掩码:把不该看的位置填 -∞(因果掩码 / padding)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# ③ 归一化:每行变成和为 1 的权重
weights = F.softmax(scores, dim=-1)
# ④ 加权求和:混合所有 V
return torch.matmul(weights, V), weights # 输出 + 权重(可用于可视化)
class MultiHeadAttention(torch.nn.Module):
def __init__(self, d_model=512, h=8):
super().__init__()
self.d_k = d_model // h # 每头 64 维
self.h = h
self.W_Q = torch.nn.Linear(d_model, d_model) # 生成 Q
self.W_K = torch.nn.Linear(d_model, d_model) # 生成 K
self.W_V = torch.nn.Linear(d_model, d_model) # 生成 V
self.W_O = torch.nn.Linear(d_model, d_model) # 拼接后混合
def forward(self, query, key, value, mask=None):
B = query.size(0)
# 投影 → 切分成 h 个头: (B, L, 512) → (B, h, L, 64)
Q = self.W_Q(query).view(B, -1, self.h, self.d_k).transpose(1, 2)
K = self.W_K(key).view(B, -1, self.h, self.d_k).transpose(1, 2)
V = self.W_V(value).view(B, -1, self.h, self.d_k).transpose(1, 2)
out, w = attention(Q, K, V, mask)
# 拼回头: (B, h, L, 64) → (B, L, 512)
out = out.transpose(1, 2).contiguous().view(B, -1, self.h * self.d_k)
return self.W_O(out), w
代码与前面章节的对应关系:
| 代码行 | 对应章节 |
|---|---|
matmul(Q, Kᵀ) | 4.3 Step 2 打分 |
/ sqrt(d_k) | 4.4 缩放,防 softmax 饱和 |
masked_fill(-inf) | 3.4 因果掩码(戴眼罩写作) |
softmax | 4.3 Step 4 归一化 |
matmul(weights, V) | 4.3 Step 5 加权求和 |
view / transpose | 4.6 多头切分与拼接 |
五、总结:架构演进的底层逻辑
5.1 全景对比表
| 维度 | RNN | LSTM | GRU | Transformer |
|---|---|---|---|---|
| 核心机制 | 隐状态循环传递 | 三门控 + 细胞状态 | 双门控 + 线性插值 | Self-Attention |
| 时间维度并行 | ❌ O(n) 串行 | ❌ O(n) 串行 | ❌ O(n) 串行 | ✅ O(1) 并行 |
| 最长信息通路 | O(n) | O(n)(但梯度可无损) | O(n) | O(1) |
| 每层复杂度 | O(n·d²) | O(n·d²),常数 ×4 | O(n·d²),常数 ×3 | O(n²·d) + O(n·d²) |
| 长程依赖 | 极弱 | 较强 | 较强 | 强 |
| 可堆叠深度 | 2–4 层 | 2–4 层 | 2–4 层 | 数十至上百层 |
| 可解释性 | 差 | 差 | 差 | 注意力权重可可视化 |
| 可扩展性 | 差 | 差 | 差 | 极强(Scaling Law 成立) |
| 主要短板 | 梯度消失 | 慢、参数多 | 记忆容量有限 | O(n²) 复杂度、KV Cache 显存 |
5.2 三条架构洞察
洞察一:架构演进的本质是”缩短信息通路”
从 RNN 的 O(n) 串行传递,到 Seq2Seq+Attention 的”跳读”,再到 Transformer 的 O(1) 全连接,每一次质变都是信息通路的缩短。
背后的原理很朴素:梯度与信息在长路径上必然衰减。谁能把通路做短,谁就能训练得更深、更大、更好。这条线索同样解释了 ResNet 的成功(残差连接为梯度开了一条捷径)——ResNet 与 Transformer 用的是同一个思想:提供捷径。
洞察二:并行化能力决定了架构能吃到多少算力红利
RNN 不是被”效果”打败的,而是被算力利用率打败的。在 GPU 算力指数增长的 2010 年代,一个无法并行的架构,无论理论上多优雅,都注定被时代抛下。
Transformer 的真正历史功绩是:它让模型首次能够”吃下”整个数据中心的算力。这也是为什么 Scaling Law 能在 Transformer 上成立,而在 RNN 上从未真正成立过。
洞察三:好的架构 = 可组合的简单模块
Transformer 的每个组件都不复杂:矩阵乘、softmax、残差、LayerNorm、ReLU。但它通过统一维度(保形)+ 残差连接 + 模块化封装,实现了极强的可组合性:
- 编码器与解码器可以拆开单独用(BERT / GPT);
- 层数、宽度、头数可以自由缩放;
- 注意力可以替换(稀疏、线性、MoE),位置编码可以替换(RoPE、ALiBi);
- 跨模态只需加一个 Cross-Attention 分支。
这种”乐高式”的可组合性,才是 Transformer 能统一 NLP、CV、语音、多模态的深层原因。
5.3 结语
回顾这段历程:
符号主义(人写规则)
↓ 知识获取瓶颈
统计学习(数据驱动 + 人工特征)
↓ 特征工程瓶颈
深度学习(表征学习 + RNN/CNN)
↓ 并行化与长程依赖瓶颈
Transformer(注意力 + 并行 + 可堆叠)
↓ 算力与长上下文瓶颈
稀疏化 / 混合架构(MoE、Mamba、长上下文技术……)
RNN 解决了”如何处理序列”,LSTM 解决了”如何记住久一点”,而 Transformer 解决的是”如何让模型规模化”——最后一个问题的答案,最终把人工智能推入了大模型时代。
架构没有终局,只有取舍。 今天我们在超长序列上重新审视 O(n²) 的代价,就像 2017 年人们重新审视 O(n) 串行一样。理解这套取舍逻辑,比记住任何一个具体架构都更重要。
参考与延伸阅读
| 主题 | 文献 |
|---|---|
| RNN / BPTT | Rumelhart et al., Learning representations by back-propagating errors, 1986 |
| LSTM | Hochreiter & Schmidhuber, Long Short-Term Memory, 1997 |
| GRU | Cho et al., Learning Phrase Representations using RNN Encoder-Decoder, 2014 |
| Seq2Seq | Sutskever et al., Sequence to Sequence Learning with Neural Networks, 2014 |
| Attention 雏形 | Bahdanau et al., Neural Machine Translation by Jointly Learning to Align and Translate, 2014 |
| Transformer | Vaswani et al., Attention Is All You Need, NeurIPS 2017 |
| BERT | Devlin et al., BERT: Pre-training of Deep Bidirectional Transformers, 2018 |
| FlashAttention | Dao et al., FlashAttention: Fast and Memory-Efficient Exact Attention, 2022 |
| RoPE | Su et al., RoFormer: Enhanced Transformer with Rotary Position Embedding, 2021 |
| Mamba | Gu & Dao, Mamba: Linear-Time Sequence Modeling with Selective State Spaces, 2023 |
评论