2017年Google那篇《Attention Is All You Need》刚放出来的时候我正在做机器翻译方向的实验。说实话第一反应是这也太激进了连RNN都扔了。后来被现实教育了一整年才发现这句标题式的话不仅不狂甚至有点保守——如今的NLP是BERT、GPT的天下视觉领域也被ViT、Swin Transformer搅得天翻地覆语音、推荐、多模态搜索里到处都是Transformer的变体。这篇内容就是给你把理论层彻底掰开揉碎回答那些好像懂、细问又说不清的问题Q、K、V到底是什么注意力公式里为什么要除以√d_k多头的头多在哪里位置编码为什么是一堆正余弦函数适合正在啃论文、准备动手复现代码、或者想系统梳理Transformer原理的人。我不会贴大段源码但会从设计者的角度告诉你每一个组件存在的理由以及论文之外踩过的坑。1. 从RNN的痛点说起Transformer到底在解决什么问题1.1 序列建模的串行困境在Transformer出现之前处理序列问题的标配是RNN、LSTM或者GRU。它们的工作方式就像一个人按顺序读句子先读第一个词把信息存进记忆隐状态再读第二个词结合之前的记忆更新状态一路推进到句尾。这套机制有一个致命麻烦——第t个词必须等第t-1个词算完才能开始串行就是硬约束。做机器翻译的人对这种感觉最有体会。我在8卡GPU机器上训一个基于LSTM的Seq2Seq模型一个batch里的长句子会让显存占用忽高忽低但GPU利用率经常只有30%左右。不是机器不行是循环结构本身不允许你把一句话里的所有词同时扔进去并行计算。你只能通过增大batch size来填满硬件但batch太大又容易让梯度更新过于激进训练不稳定。这个矛盾在2017年已经是社区共识大家都需要一个能一口气看完整句话的模型。1.2 长距离依赖与梯度消失RNN/CNN的天花板串行之外RNN还有一个更隐蔽的问题长距离依赖。假设句子是The animal didnt cross the street because it was too tired要让模型理解it指代animal它需要把信息从句子前半段一路传递到后半段。在RNN里两个位置的交互路径长度等于它们之间的距离信息每经过一个时间步都要乘一次循环权重矩阵梯度要么指数爆炸、要么指数消失。LSTM用门控机制缓解了梯度消失但路径长度依然随距离线性增长本质上只是拖延而非解决。有人会说那用CNN不行吗1D卷积或者膨胀卷积确实能并行处理序列而且通过多层堆叠任意两个位置的最短路径可以压到O(log n)。但卷积核的局部感受野是固定的它只能按照距离远近来决定聚合范围无法按照内容相关性来动态地决定应该关注哪些位置。换句话说CNN天生倾向于近邻不够聪明。1.3 Transformer的破局思路扔掉循环全靠注意力Transformer的思路简单粗暴既然RNN的问题在于顺序传递CNN的问题在于固定感受野那我干脆让任意两个位置直接通信。自注意力机制Self-Attention让序列中的每个词都去计算自己与其他所有词的相关性然后按相关性加权聚合信息。这样一来任意两个位置之间的交互路径长度是O(1)长距离依赖不再是问题所有位置的计算彼此独立可以并行执行相关性权重由内容动态计算模型可以自行决定重要信息是什么。可以直观对比一下三种序列建模方式的差异模型任意两位置最短路径能否并行信息聚合方式RNNO(n)否顺序传递隐状态CNN/TCNO(log n)是固定局部感受野TransformerO(1)是内容动态加权这张表基本就是《Attention Is All You Need》的立论基石。Transformer并不是在RNN上加了个注意力而是直接用注意力替代了循环结构本身。这也是为什么论文敢叫Attention Is All You Need——在作者看来只要你设计好注意力机制循环和卷积都不是必需品。2. 自注意力机制精讲Q、K、V与缩放点积的前因后果2.1 一上来就是那个公式自注意力最核心的公式只有一个Attention(Q, K, V) softmax(QK^T / √d_k) V初次看到的人往往会懵Q、K、V是哪来的为什么不是直接对输入加权要理解这个公式得先弄明白它背后的检索逻辑。假设输入序列是X形状是n×d_modeln是序列长度。对X做三次线性变换分别乘以三个矩阵W_Q、W_K、W_V得到三个新矩阵Query(Q)、Key(K)、Value(V)。然后计算Q和K的点积、缩放、softmax归一化最后再和V相乘。整个流程看起来有点绕但它本质上做了一件非常朴素的事让每个词根据自己的需求去其他人的内容里提取信息。2.2 Q、K、V的生活化理解一座图书馆我用图书馆检索来类比Q、K、V这套说法也是我自己讲给组里实习生时最好用的一版。Query就是你头脑里的检索目标比如我要找一本介绍注意力机制的书Key是每本书的标题和标签比如深度学习入门、Attention机制详解Value是书的内容本身也就是拿到标签之后真正要读的正文。检索过程是拿Query去和所有Key比对看哪个标签最匹配得到一组相关性分数再用softmax把分数变成权重最后按权重去取每一本书的内容Value并进行加权求和。这样一来检索结果就不是某单一本书而是所有相关书籍内容的加权混合版——相关内容权重高不相关内容权重自然趋近于零。放到句子里也一样。比如处理The animal didnt cross the street because it was too tired时当模型计算单词it的新表示它会把it的Query和句子中所有单词的Key做比对发现animal的Key与Query高度相关softmax后会分配很大的权重最终it的Value输出主要由animal贡献。这就是自注意力做指代消解coreference resolution的微观过程。2.3 为什么要除以√d_k这个缩放救过无数模型QK^T计算出的分数在数学上是一个点积。假设q和k是维度为d_k的独立随机向量每个元素的均值为0、方差为1那么它们的点积的均值是0方差是d_k。也就是说d_k越大点积的数值分布越分散标准差会达到√d_k。如果d_k64两个随机向量的点积标准差就是8这意味着不少点积的绝对值会落在几十这个量级。你把这种量级的数丢进softmax会发生什么softmax里有e^xx稍大一点e^x就成指数级爆炸概率分布会迅速趋向于one-hot同时梯度会变得非常小。这种梯度饱和会让模型几乎学不动而且一旦初始化不好训练很快就崩。除以√d_k之后点积的方差被拉回1的量级softmax能工作在梯度相对平滑的区域。这不是什么精妙绝伦的数学发明而是一个非常务实的工程操作。我自己测试过直接把√d_k去掉小模型靠降低学习率勉强能跑但换到深层模型或者大batchloss经常直接飞到NaN。所以以后面试被问到这个别只说防止值太大要把方差、softmax饱和、梯度消失这层逻辑讲完整。2.4 一次完整的语义融合再看一下融合的过程。对于输入序列中的每个词模型都会生成它自己的Query、Key、Value。自注意力计算可以拆成三句话每个词用它的Query去问遍所有词的Key得到它跟所有词的相关性分数缩放后用softmax把分数转成权重用这些权重对所有词的Value做加权求和得到这个词的新表示。这个新表示并不仅仅是这个词本身而是它在当前上下文中的语义快照。这也是为什么Transformer能被用来建模复杂上下文——每个词每经过一层注意力都会吸收其他词送来的信息越深的层信息融合的范围越大。3. Multi-Head Attention为什么多头比单头强3.1 单头注意力的局限刚接触自注意力时我一度以为一个头就够了既然模型已经能算出一组全局的相关性权重为什么还要搞多个头后来在实际训练里发现单头的容量实在有限。举个例子。同一个句子里词与词之间的关系至少有这么几类语法关系主谓一致、语义关系指代、位置关系前后相邻距离、以及跨句子的长程依赖。如果只用一组W_Q、W_K、W_V模型被迫用同一套相关性度量去同时处理所有这些关系相当于一个人既当语文老师又当数学老师还当体育老师每一类工作都做不精。单头注意力最终的权重往往是各种信号的折中看起来谁都有点关系其实谁都没抓住。3.2 多头设计在多个子空间里并行开会Multi-Head Attention的做法是把所有头放在不同的子空间里并行处理。原论文的配置是d_model512h8每个头的d_kd_v64。每个头都有一套独立的小W_Q、W_K、W_V从不同的角度对输入做一次自注意力计算。h个头会得到h个不同的加权结果拼起来之后再经过一个输出投影矩阵W_O把维度还原成d_model。打个比方单头注意力是一群人在一个大房间里七嘴八舌商量一个方案每个人都能听到所有人的声音最后容易乱多头注意力是让这8个人分别去8个独立会议室每个会议室只讨论一类关系——比如1号会议室只关注相邻词的局部语法2号会议室只关注长距离指代讨论完之后8个会议室的结论汇总到一起再由W_O投影整合成最终决策。从参数量的角度来说多头机制并没有显著增加模型体量8个头每个工作在自己的64维子空间总参数和单头直接算512维差不多但表达空间的结构完全不同。每个头相当于把相关性的概念分解成了多个类型这让模型在同样的数据量下更容易学出多样化的关系模式。3.3 头之间真的各司其职吗论文之外的经验观察这里想泼一点学术冷水。很多文章会说多头注意力中每个头学到不同的语法规则这个说法在严格意义上并不准确。后来有研究对训练好的Transformer做了注意力可视化确实发现部分头有明显分工有的头专门关注前一个词positional head有的头专门关注句法依赖关系。但同时也有另一批研究指出很多头是冗余的把它们直接剪枝掉模型性能都不怎么掉。这说明了什么多头设计给模型提供了学习多类关系模式的能力但并没有保证每一个头都必然学到有意义的分工。训练数据、随机种子、任务类型都会影响最终头部的分化程度。所以面试或者讲原理时准确的说法是多头机制在数学结构上允许多个关系子空间共存实际训练出的模型确实会出现一定的功能分化但同时也存在冗余。别把理想设计说成必然结果。3.4 拼接后的输出投影为什么不能省每个头输出的维度是d_v648个头拼接后回到512维这时还差最后一步乘以W_O。这个输出投影不是可有可无的它负责把各个头的信息从并列变成融合。不同头关注的是不同角度只有经过W_O混合模型才能把局部语法信息和长程语义信息同时写进同一个输出向量供下一层使用。如果省掉W_O等于8份结论只是简单地堆在一起没有开会统一意见下游层就要额外负担这部分融合工作理论上虽然可能学出来但训练难度和效果都会打折扣。4. 位置编码给并行模型注入顺序感的艺术4.1 自注意力天生就是词袋Transformer去掉循环结构之后有一个特别容易忽略的副作用自注意力本质上是对集合的操作。你把输入序列任意打乱顺序再传进模型对于注意力计算来说输出只是跟着做同样的行交换模型根本意识不到顺序变了。这对语言模型是致命的——张三打了李四和李四打了张三在模型眼里可以是同一个输入。所以Transformer必须额外把位置信息注入进去。原论文选择的是在embedding上直接加上位置编码向量让每个词带着我是第几个词的身份标志进入后面的注意力计算。4.2 三角函数编码为什么是这样原论文的位置编码公式是PE(pos, 2i) sin(pos / 10000^(2i/d_model)) PE(pos, 2i1) cos(pos / 10000^(2i/d_model))pos是词在序列中的绝对位置i是维度下标。这个公式初看很神但拆开理解并不复杂。你可以把它想象成一架多频率的坐标雷达维度i越小对应三角函数的频率越高相邻位置之间的编码差异越明显维度i越大频率越低编码变化越平缓。不同频率组合在一起给每一个位置生成一个独一无二的编码向量。这里有一个非常巧妙的性质对于任意固定的偏移量kPE(posk)都可以表示为PE(pos)的线性变换。原因是三角函数恒等式sin(ab)sin a·cos bcos a·sin b意味着位置posk的编码可以由位置pos的编码通过旋转变换得到。虽然模型拿到的是绝对位置编码但这样的设计让模型有能力去学习相对位置关系因为它能看到不同间距对应的规律性模式。另外原论文在把词嵌入和位置编码相加之前会把词嵌入乘以√d_model。为什么因为位置编码的数值范围只有[-1,1]而d_model512时词嵌入向量本身的模长可能很大如果直接相加位置信息的占比会被淹没。先缩放词嵌入可以在量级上match让两者真正叠加而不是覆盖。4.3 后续变体为什么纷纷转向相对位置编码绝对位置编码给每个位置发一个绝对坐标但现实中大部分任务更关心两个词之间的相对距离——比如语法关系强调相邻、指代关系可能跨越很长的距离。后来一系列工作证明了相对位置信息更直接有效Shaw等人把相对位置嵌入加到注意力分数里Transformer-XL提出了相对位置编码的显式版本T5使用了简化后的相对位置偏置RoPE则把位置信息通过旋转矩阵注入到Q和K中。这些变体的共同思路是不再问你绝对位置是几而是问你和他离多远、方向是什么。理解了原版三角函数编码的设计动机再去看这些变体就会很顺它们不是颠覆而是把位置信息的表达方式从绝对坐标改成了更贴合语言建模本质的相对坐标。5. Encoder-Decoder理论数据流残差、归一化与前馈网络的分工5.1 编码器一层的完整数据流理解了自注意力和位置编码之后看整个Transformer的骨架就轻松多了。编码器一共有N层原论文base模型是6层每一层内部的结构完全一致包含两个子层。第一个子层是Multi-Head Self-Attention。输入先经过多头注意力得到一个新的向量表示然后和原始输入做残差连接再做LayerNorm。第二个子层是前馈网络FFN通常是两个线性层夹一个ReLU激活函数中间维度是d_ff2048比d_model大4倍。同样经过残差连接和LayerNorm。用公式表达就是x LayerNorm(x MultiHeadAttention(x)) x LayerNorm(x FFN(x))这里的分工非常清晰注意力层负责信息交换让每个词从整个序列中搜集上下文FFN负责独立思考在固定位置上对每个词的表示做非线性变换。可以类比成开会注意力是大家互相发言交流信息FFN是开完会回到工位各自消化信息并做决策。两者交替进行层层推进。后来有研究甚至发现FFN部分占据了模型的大量参数和语义知识所谓Attention Is All You Need更多是打气口号实际训练里FFN一样不可忽视。5.2 解码器里的Masked Self-Attention与Cross-Attention解码器的结构比编码器多了一个子层一共三层。第一层是Masked Self-Attention第二层是Cross-Attention第三层是FFN。Masked Self-Attention解决的是一个很现实的问题解码器生成第t个词时不应该看到未来的词。训练的时候为了效率我们通常把整个目标句子一次性喂进解码器如果不加mask注意力机制就会偷看后面的词。解决办法是在计算QK^T之后、softmax之前把上三角矩阵包括当前位置之后的未来位置全部设成-∞。这样softmax之后这些位置的注意力权重就会变成0模型在生成第t个位置时只能依赖位置1到t的信息。Cross-Attention是编码器与解码器之间的桥梁。它的Query来自解码器上一层的输出Key和Value都来自编码器的输出。也就是说解码器每次生成词的时候会去翻阅编码器记住的原句信息找出当前最应该关注的原句片段。这一步是机器翻译的语义对齐核心——比如翻译到某个名词时解码器会把大量注意力集中到原句中对应的名词上。5.3 LayerNorm放在残差之前还是之后Post-LN与Pre-LN之争原论文用的是Post-LN也就是x LayerNorm(x Sublayer(x))。这个配置在小模型、6层左右的规模下工作的很好但直接往深层堆叠时会遇到训练不稳定的问题梯度容易出现尖峰。后来社区普遍采用Pre-LN也就是x x Sublayer(LayerNorm(x))把LayerNorm挪到子层之前。Pre-LN在大规模模型里训练稳定得多深层模型也能轻松收敛代价是最终效果上会有极轻微的损失。这是一个典型的论文落地经验。如果你自己从零实现Transformer训练6层左右的模型时Post-LN问题不大一旦层数加深到12层以上、或者训练loss频繁出现尖峰第一反应该就是换成Pre-LN试试。现在主流的开源预训练模型基本都在用Pre-LN或者它的变体这也是为什么你看BERT源码和论文里的示意图会感觉对不上。6. 训练理论之外的工程细节从Warmup到推理加速6.1 为什么Transformer离不开Warmup理论上讲Transformer的训练很依赖学习率调度。原论文用的Adam优化器β10.9β20.98ε1e-9学习率不是固定值而是先线性上升、后按step的倒数衰减warmup_steps4000步。为什么需要warmup深度学习里最怕的就是开局就飞。Transformer刚初始化时LayerNorm的统计量还不稳定注意力分布也处于混沌状态如果第一步就用很大的学习率模型很容易冲进一个不可恢复的坏区域。warmup阶段相当于让模型用很小的学习率先站稳脚跟把参数和梯度方向调整到一个合理区域之后再放开速度训练。实际使用时warmup步数不是拍脑袋定的。我遇到过一个小任务训练数据只有几万条把warmup从4000改成1000之后收敛速度明显加快最终效果还更好。如果你的batch size比较大相应的梯度估计更准一般需要更长的warmup相反小batch可以适当缩短。现代大模型训练中大家都习惯用线性warmup 余弦衰减的组合本质思想原论文里已经打下了。6.2 Label Smoothing与Dropout的作用看似降级、实为提分Transformer训练里有两个容易被忽略的trick一个是Label Smoothing一个是Dropout。Label smoothing在机器翻译里很有效原论文把它设成0.1。它做的事情是不再要求模型对正确词输出概率为1其他词全部为0而是把一部分概率匀给所有词。乍一看这会让训练集的perplexity变差但实际效果是BLEU反而提升了。原因是生成模型太容易记住训练集里的确定对应关系label smoothing相当于给这个记忆过程加入噪声让模型保持一点怀疑和泛化能力。Dropout则分几处一是每个子层输出在加回残差之前做dropout二是对词嵌入加dropout。后来的实现基本还会在注意力权重上再做一次dropout直接对注意力矩阵本身做正则防止某些位置上出现过强的注意力集中。具体dropout率原论文是0.1但在小数据集上我一般会调到0.3甚至更高。6.3 推理阶段的KV Cache理论如何落地最后说一个理论和工程结合得很漂亮的设计KV Cache。Transformer在推理时是自回归的也就是一次生成一个token然后把新的token拼到历史序列后面继续预测下一个。很多人一开始都觉得每次生成都得把整个序列重新过一遍模型显存和算力消耗随着序列长度线性增长。但实际上仔细看注意力的公式就会发现已经生成过的token它们的Key和Value不会因为新token的到来而改变。每次生成只需要为新的token计算Query和历史所有token的Key做点积。于是各种推理框架都会把历史K、V缓存下来每次只算新增部分这就是KV Cache。这个优化也让KV Cache的内存占用成为长文本推理的主要瓶颈。一个10亿参数模型序列长度4096KV cache可能占去一大半显存。后面的MQA、GQA、滑动窗口注意力本质上都是在少存点KV或者少算点KV上做文章。从理论公式到工程优化这条链路非常顺畅——你真正理解了注意力背后的对象就知道哪些量是静态的、可以复用的。7. 从ViT到Swin理论如何长出新分支7.1 ViT把图像当成一组Patch的序列Transformer的理论并不只属于文本。Vision TransformerViT的做法非常直接把一张图像切成16×16的小块patch每个小块拉平后通过线性投影变成一个向量再给它加上位置编码就得到了一串视觉词后面直接接Transformer编码器做分类。有些实现还会在序列开头加一个CLS token最终用CLS token的输出做图像类别预测。ViT刚出来时业内普遍怀疑它能否在图像任务上打过CNN。结果证明只要你有足够大的预训练数据ViT能学得很好但反过来也暴露了Transformer缺少归纳偏置的问题——CNN天生知道图像是二维的、相邻像素有关系而ViT完全不知道它只能靠海量数据自己学出这些规律。这正好呼应了理论篇反复强调的点注意力网络本身不预设任何先验它把所有应该关注谁都留给了数据。7.2 Swin Transformer窗口注意力与层级设计Swin Transformer可以说是对ViT缺少图像先验的一次系统性修正。它做了两件关键的事。第一局部窗口注意力。Swin把特征图分成一个个小窗口例如7×7的网格只在大窗口内部计算自注意力。这样一来复杂度从全局注意力的O(N²)下降为O(N·window²)N是图像patch数量。这个设计其实就类似CNN的局部感受野强行引入了一点相邻区域更相关的先验。第二窗口移位。Swin在相邻层之间移动窗口的位置让原本不重叠的窗口之间有机会互相通信相当于既享受了局部注意力的效率又不丢失全局信息。Swin还通过patch merging逐步把图片的序列长度减半、通道数翻倍形成了类似CNN金字塔式的层级结构这种多尺度特征对目标检测、语义分割这类密集预测任务非常关键。看ViT和Swin的演变你会发现它的核心还是那个注意力公式但如何设计注意力的范围和注意力的位置成为了新的理论战场。7.3 真正理解Transformer的小建议从一个读者变成能自己改Transformer我个人的经验是别急着背公式也别急着抄代码先对着论文把每个设计决策都问一遍为什么。为什么用缩放点积为什么用多头为什么用残差和LayerNorm为什么位置编码长这样每个问题后面都有一段实打实的工程教训。等你把这些为什么都理清了再看当前层出不穷的Transformer变体——GPT、BERT、ViT、Swin、各种线性注意力——它们再花哨也都是在原版理论骨架上做的调整。地基稳了上面盖什么楼都不慌。