Tsonglew's Blog

Back

补上每个缺口:Transformer 通俗进阶七讲#

这不是一篇零件清单。这是一条河——从最简单的“猜词游戏”出发,每遇到一个障碍就补一个缺口,缺口补完,Transformer 就长出来了。七讲,七步,每一步都只解决一个具体问题。


第一讲:起点——语言模型就是猜下一个词#

你玩过“成语接龙”吗?我说“亡羊补”,你接“牢”。你说“牢不可”,我接“破”。规则就一条:根据已经出现的字,猜下一个字

语言模型做的和这一模一样。给它“今天天气真”,下一个字大概率是“好”。它不“理解”天气,只是从海量文本中学会了什么后面跟什么。后面所有的复杂机制,都是让这个预测更准。

但计算机不认得字,所以第一步是把词变成数字——每个词分配一串坐标,意思相近的词靠得近。词先变成数字,模型才能算。

插图1:语言模型猜下一个词

假设你来设计这个“猜词机器”,最笨的办法是只看上一个词——“天气”后面跟“好”的概率多少,查表就行。但“今天天气真”后面跟“好”,如果只看上一个词“真”,你完全不知道前面有没有“今天天气”。句子可以无限长存不下所有词,那就退一步:把前文压缩成一段摘要带着走,每来一个新词就把旧摘要和新词揉一揉。恭喜,你刚发明了 RNN 的核心思想。

猜词真需要这么复杂的机器吗?“一推六二五”——前四个字定了第五个字几乎唯一确定。但“他推了推眼镜”和“他推了推门”,“推”字相同后面完全不同。猜词的难度不在词本身,而在前文有多长、哪些信息跟当前有关。


第二讲:RNN、长句困境与 Attention 的诞生#

具体怎么揉?想象一队人排成一列传话。第一个人听到“今天”在纸条上写“今天”传下去,第二个人听到“天气”就把纸条打开看一眼,结合自己听到的重新写一行摘要再传。这个不断改写传递的“小纸条”就是 RNN 的隐藏状态

插图2:RNN的隐藏状态像小纸条

但句子长了纸条越来越不够用。50 个词传到第 50 个人,第一个人写的“今天”还在吗?大概率被挤没了——每步都在揉,揉了 50 次最早的信息早被覆盖了。纸条大小固定不能无限扩大。这不是彻底消失,而是像复印 50 次,第一次的笔迹淡到几乎看不见。数学上叫梯度消失:反向传播的修正信号每传一层衰减一次,传到第一层时几乎为零。LSTM 和 GRU 用“门控”缓解(让纸条选择性遗忘和保留),但治标不治本——只要还在“逐词压缩传递”这条路上,长距离信息丢失就是结构性的通病。

这个缺陷在翻译任务里更致命。RNN 做翻译时,先用 Encoder 把整句中文读到尾,最后那张纸条就是整句话的“总摘要”,然后 Decoder 拿着它一个词一个词往外吐英文。一个固定大小的总行李箱,装着整句的意思,全程不变。 句子一长就开始丢三落四。

你会想:Decoder 吐每个词时不应该只盯着总行李箱,应该能“回头看”原句。但原句那么长,总不能每次从头到尾重读。更聪明的办法是:每吐一个词,就自动算一下“原句哪个位置对我现在最有用”,重点看那个位置。 粗线代表高度相关,细线代表关系不大。这个“按需回看、按重要性加权”的机制,就是 Attention

插图3:Attention按需回看原句

Attention 和 RNN 是绑定的吗?在最初的 Encoder-Decoder 里它确实是 RNN 的小跟班。但有人很快发现了一件惊人的事:“回看”这个动作本身,并不依赖逐词传递。 这正是下一讲的起点。


第三讲:Attention 独立——QKV 与 Self-Attention#

Attention 的核心是三步:算相关性(Query 跟 Key 匹配,算出相似度分数)→ 加权(分数高的多取,低的少取)→ 取回(按权重把 Value 混合)。这三步全是矩阵乘法,不需要“先读第一个词、再读第二个词”这种串行步骤。原句所有位置的 Key 可以同时算好,Query 也可以同时和所有 Key 做匹配。没有纸条传递,没有逐词压缩,没有信息被挤掉。2017 年那篇论文(《Attention Is All You Need》)做的就是把 RNN 拆掉只留 Attention,结果不仅没变差反而更好更快。

那 Query、Key、Value 从哪来?打个比方:你去图书馆找书,心里有个检索条件是 Query,书架上每本书的标签是 Key——你拿 Query 和每本书的 Key 比对,匹配度高的重点看。但你要的不是标签本身,而是书的内容——这就是 Value。三个角色可以从同一份数据出发,通过不同的线性变换“长成”不同形状,参数是模型自己学的:

Q=XWq,K=XWk,V=XWvQ = X W_q, \quad K = X W_k, \quad V = X W_v

Attention(Q,K,V)=softmax ⁣(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{Q K^T}{\sqrt{d_k}}\right) V

Q 和 K 算匹配分数 → softmax 归一化成权重 → 拿权重混合 V。sqrtdk\\sqrt{d_k} 是缩放因子,防止分数太大导致 softmax 变成非 0 即 1 的极端分布。

插图4:QKV的三种职责

为什么从同一份数据变换出来?翻译时确实不是同一份——Query 来自 Decoder,Key 和 Value 来自 Encoder。但更常见的是一句话内部的词互相注意(Self-Attention),三者来自同一批词。通过可学习矩阵分出不同职责,比硬指定灵活得多:一个词可以“因为某个特征被选中”,但“交出的是另一个特征”。

Self-Attention 就是让一句话里的词两两互相询问。“那只猫没过马路因为它太累了”——“它”指谁?在 Self-Attention 里,“它”的 Query 去和句子里所有词的 Key 做匹配,和“猫”的 Key 匹配度高,和“马路”低。于是“它”的输出向量里混入了大量“猫”的 Value。“它”就“看到”了“猫”。 每个词既是 Query(主动找相关词),又是 Key(等着被匹配),又是 Value(被选中后交出内容)。一轮跑完,每个词的向量都变成了“吸收了全句相关信息的增强版自己”。指代消解、语境消歧全在这一步自然发生,不需要专门规则。

代价是计算量 n2n^2(n 是句子长度)——1000 个词就是 100 万次匹配。后来的变体(Longformer、Linformer)在想办法降低,但经典 Transformer 宁可算量大也要全连接。还有一个矛盾:Self-Attention 全互联,位置 3 能看到位置 5,做生成时位置 5 是未来词——不能偷看。怎么管住?第六讲的因果 Mask 会解决。


第四讲:位置编码——告诉模型“谁在第几号座位”#

Attention 只看向量匹配,不管顺序。“狗咬人”和“人咬狗”向量完全一样只是排列不同,Attention 算出来一模一样。

既然 Attention 自己不感知位置,那就人为把位置信息编码进每个词的向量里。想象看电影——座位号印在椅背上不是印在观众身上。让“狗”在第 1 号座位和第 3 号座位时的向量不一样,不是改“狗”的意思,而是叠加一个“我在第几号”的信号。

插图5:位置编码就是座位号

原始论文用正弦和余弦函数按位置编号生成一段固定向量,和词向量直接相加。“第 3 号座位的狗” = “狗”的词向量 + “第 3 号位置”的编码向量,同时包含“是什么词”和“在第几个位置”。能不能让位置编码也可学习?BERT 就是这么做的,但训练时最长 512,第 513 个位置就没学过。正弦余弦编码的好处是可以外推——理论上任意长度都能生成。后来又有相对位置编码、旋转位置编码(RoPE)等变体,但核心不变:把“我在第几号”塞进向量里。

位置编码和词向量直接相加不会互相干扰吗?会混,但这正是模型想要的。相加后是一个“在某个位置上的某个词”的整体表示,后续 Attention 自然把语义成分和位置成分一起用——就像看地图时地名和坐标叠在一起看。


第五讲:Multi-Head 与 Transformer Block——多副眼镜,再垒成楼#

一句话里的关系是多维的。“那只猫没过马路因为它太累了”——“它”和“猫”有指代关系,“猫”和“过”有主谓关系,“马路”和“过”有动宾关系。一轮 Attention 只能学出一组匹配模式,重点学了指代就可能顾不上主谓。一副眼镜看不全。

Multi-Head 的思路粗暴而有效:别只做一轮,做多轮,每轮用不同参数。 8 个头就是 8 副不同参数的眼镜,各自独立做一轮 Attention,最后拼接融合。计算量不增——原来 512 维切成 8 份每头 64 维,总维度还是 512。可视化研究发现许多头确实学到了不同功能:有的关注相邻词,有的关注句法依赖,有的关注标点。即使有冗余,容错性也更好。

插图6:Multi-Head多副眼镜看不同线索

有了 Multi-Head Self-Attention,但单独不够。一个完整的 Transformer Block 还需要残差连接LayerNormMLP

做一轮 Attention 后每个词吸收了其他词的信息,但可能“用力过猛”把原始语义冲淡了。残差连接就是把 Attention 的输出和原始输入直接相加——像改稿时原稿留着在旁边批注,不是覆盖原文。但相加后数值可能越来越乱,LayerNorm 就是给数值“稳一下”——对每个位置的向量做归一化让均值和方差稳定。然后是 MLP:Attention 做的是词间信息交换(横向),MLP 做的是位置内特征加工(纵向)——对每个位置独立做一次非线性变换。非线性是关键:没有它,不管叠多少层线性变换数学上等价于一层。

输出=LayerNorm(x+MultiHeadAttention(x))\text{输出} = \text{LayerNorm}\Big(x + \text{MultiHeadAttention}(x)\Big)

最终输出=LayerNorm(z+MLP(z))\text{最终输出} = \text{LayerNorm}\Big(z + \text{MLP}(z)\Big)

先 Attention 残差归一化,再 MLP 残差归一化。这样的 Block 一层叠一层——6 层(原始论文)、12 层(BERT-base)、96 层(GPT-3)。

插图7:Transformer Block结构

叠那么多层信息不会丢吗?残差连接的第二个好处就在这里:每一层都有一条“直通车道”绕过本层变换,信息可以从第 1 层直接跳到第 20 层。这让堆叠几十层甚至上百层成为可能。为什么用 LayerNorm 不用 BatchNorm?BatchNorm 按“一批样本的同一维度”归一化,需要大批次才有意义。NLP 里句子长度不一、batch 小,统计量很不稳定。LayerNorm 只看当前这个位置自己的向量,和 batch 大小无关。


第六讲:训练与 GPT——错题变调整,再砍到极简#

模型搭好了,但权重是随机的,猜词纯靠瞎蒙。训练的逻辑和做错题本一样:遮住最后一个词让它猜,算猜的和答案之间的差距——这就是损失(loss)。然后反向传播从最后一层往回算“每层权重该往哪个方向调、调多少”,这个方向和幅度就是梯度。最后用梯度更新权重,每个参数往“让损失更小”的方向挪一小步,步长由学习率控制。这个循环反复跑几百万次,模型就从瞎蒙变成行家。

如果每次只遮最后一个词,一句话 10 个词只能练 1 次。实际上 Transformer 同时练 10 次:给“今”猜“天”、给“今天”猜“气”、给“今天气”猜“真”、给“今天气真”猜“好”——每个位置都在算损失。这叫自回归训练,效率极高。代价是每个位置只能看到前面的词,但这正好就是生成式模型该做的事。

训练时看整句、推理时逐词生成,矛盾吗?不矛盾。训练时通过因果 Mask,每个位置只能看到前面的词。整句一起算只是并行加速,每个位置实际能看到的信息和推理时一模一样。

因果 Mask 正是 GPT 的核心。原始 Transformer 有两种 Attention: Self-Attention(一句话内部互看)和 Cross-Attention(Decoder 看 Encoder,翻译用). GPT 做了一件极简的事:把 Cross-Attention 整个砍掉。 不做翻译了,GPT 只做一件事:给前文,续写下一个词。只剩纯 Self-Attention + MLP 的 Block 叠在一起。

但第三讲留下的矛盾还在:Self-Attention 全互联,位置 3 能看到位置 5,做生成时位置 5 是未来词。GPT 的办法简单粗暴:给 Attention 矩阵盖一块遮罩。 把“未来位置”的分数设成负无穷,softmax 后变成 0 权重——第 3 个词只能看到第 1、2、3 个词。这个三角形遮罩叫因果 Mask

插图8:GPT因果Mask不偷看未来

有了它,训练时可以放心整句并行。推理时逐词来:算前文猜下一个词,拼到末尾再算,循环往复。生成时还有几个“调词旋钮”:Temperature 控制随机性——高温度更敢冒险选非标准答案,低温度更保守;Top-k 只从前 k 个概率最高的候选里选;Top-p 更聪明——选到“累计概率达到 p”为止,分布集中时少选分散时多选。工程上还有 KV Cache:前面词的 Key 和 Value 算过一遍就缓存复用,不这样每生成一个词就要把整段前文重算一遍。

因果 Mask 让第一个词只能看到自己——确实信息量最少,这是生成式模型的结构性特点,后来的模型用各种方式缓解。那 GPT 砍掉 Cross-Attention 不就退化了吗?恰恰相反。原始 Transformer 为翻译设计,但大多数语言任务没有“原文”——写文章、聊天、写代码,给前文续写就行。GPT 的极简设计匹配了这些任务,极简意味着更大的参数规模、更通用的训练方式、更广的适应性。这不是退化,是聚焦。


第七讲:收尾——一张表串起全文#

回头看这七讲,每一步都长着同一个骨架:遇到了一个具体问题 → 想出一个办法 → 办法被起了个名字。

问题办法名字
计算机不认得字,怎么算?把词变成数字向量词嵌入(Embedding)
只看上一个词不够,怎么记住更长的前文?把前文压缩成摘要逐词传递隐藏状态(RNN)
句子一长,早期信息被挤掉怎么办?别只带总摘要,吐每个词时按需回看原句Attention
回看这件事需要逐词传递吗?不需要,匹配和加权可以全并行Self-Attention
拿什么去找、凭什么被找到、交出什么?同一份数据变换出三种角色QKV
词相同顺序不同意思不同,Attention 不感知顺序怎么办?给每个位置叠一个位置信号位置编码
一轮 Attention 只能学一种关系怎么办?多组并行参数同时看Multi-Head
叠多层时旧信息容易丢、数值越算越乱怎么办?残差连接保旧信息 + LayerNorm 稳数值残差 & 归一化
词间信息交换够了,位置内特征怎么加工?对每个位置独立做非线性变换MLP
模型权重是随机的,怎么变聪明?猜词 → 损失 → 梯度 → 反向传播训练
生成时不能偷看未来怎么办?把未来位置的注意力权重设为 0因果 Mask
偷看问题解决了,做翻译还用得着 Cross-Attention 吗?砍掉,只留 Self-Attention 堆栈GPT

每一个“名字”背后都站着一个“问题”。先有问题,再有办法,最后才有术语。理解 Transformer 时抓住三件事就够了:为什么需要按需回看前文、为什么需要顺序线索、为什么需要稳定堆叠。

而 GPT 最终做的事,和第一讲一模一样:根据已出现的词,猜下一个词。 区别只在于规模——几十层 Block、几千亿参数、几万亿个词的训练数据,让猜词的过程中“涌现”出写文章、做数学、写代码、聊天的能力。但它的心脏,从头到尾,就是那个最简单的游戏。


全文完。Transformer 不是零件清单,而是一连串补上的缺口——每个机制都对应一个具体问题,而最终模型做的事和第一讲一样简单。

补上每个缺口:Transformer 通俗进阶七讲
https://tsonglew.github.io/blog/transformer-seven-lectures
Author Tsong Lew
Published at 2026年8月4日