2017年那篇《Attention Is All You Need》出来的时候,我正在埋头调一版LSTM机器翻译模型。当时的直觉是:把注意力从辅助模块提成唯一骨干,是不是太激进了?几年后再回头看,这一刀不止砍断了RNN的递归结构,也把整个深度学习的骨架从“逐步顺序建模”改成了“并行化堆叠”。这篇文章想聊的,就是从Transformer最初的多头注意力开始,位置编码、归一化、稀疏注意力、混合专家这些核心算法分别走了哪几步演进,每一步解决什么问题,以及哪些改动真正影响到了落地效果。适合正在研究Transformer理论和代码、做分类回归任务,或者想把手头模型从经典结构升级到新一代结构的同学参考。
1. 从单头注意力到多头机制:原始Transformer的第一性原理
1.1 缩放因子√d_k是第一个算法设计决策
先回到最基础的自注意力公式:Attention(Q,K,V) = softmax(QK^T / sqrt(d_k)) V。
很多人看到sqrt(d_k)会直接跳过,但这是Transformer里第一个真正的算法设计决策。假设Q和K的每个元素均值近似为0、方差近似为1,那么Q和K做点积之后,结果的期望长度会随着维度d_k的增大而变大,标准差接近sqrt(d_k)。维度越大,分数值就越大,softmax输入越接近饱和区,梯度会急剧变小。当年另一个经典做法是加性注意力,就是对Q和K的拼接结果做一次线性投影,效果稳定但计算量更大。Vaswani他们选缩放点积注意力,既拿到了点积注意力在矩阵运算上的高效性,又避免了维度增大后softmax进入饱和区的问题。
我实测下来,缩放因子在FP16/BF16训练里尤其关键。低精度下,大分数会在softmax里疯狂放大数值,轻则梯度不稳,重则直接出NaN。如果哪天某个Transformer训练的loss突然变成inf,先别查数据和学习率,回头看看注意力分数有没有按时缩放。
1.2 多头注意力在算力和表达力上的双赢
单头注意力一次只能在一个“特征空间”里做交互,而多头注意力把d_model拆成h个维度为d_k的头,每个头在独立投影空间里计算注意力,最后拼接再投影。比如d_model=512、h=8,每个头的维度是64,实际计算量几乎不变,但表达力发生了质变。
我习惯给初学者打的比方是:一头注意力像单个人在读书,他只能盯一条线索;多头是让8个人分头读同一个文本,有人盯主语和谓语,有人盯指代关系,有人盯修辞衔接,最后凑在一起再综合判断。这正是后来的可解释性研究反复看到的结论——不同头确实会学到不同的句法和语义模式。
不过头数不是越多越好。我曾经把d_model=768的模型头数从12调到24,每头维度变成32,结果下游任务分数反而掉了。头维度太窄,单头拿到的语义信息不够,最后的拼接投影又很难完全补回来。主流模型的配置基本上是d_model=768配12头、d_model=1024配16头、d_model=4096配32头,每头维度稳定在64左右,这个比例是有道理的。
1.3 相比RNN/CNN,注意力真正革掉了什么
RNN的问题不只是不能并行,更麻烦的是“顺序路径”太长。输入序列里的第1个词影响第100个词,信息必须沿着递归路径一步步传递,路径长度为100,梯度要么爆炸要么消失。LSTM和GRU只是加了几道门来缓解,并没有从根上解决长依赖。
注意力机制直接把第1个词和第100个词的关联路径压缩成一步,任何两个位置之间都是“直接通道”,梯度可以通过这一步直接回流。再加上训练时所有位置可以同步计算,分布式训练和加速器利用率比RNN时代高出一个量级。这也是后来大批算法演进都紧紧围绕“怎么把一个序列里的全局关系算得更高效”展开的原因,根子就在这个原始设计上。
2. 位置编码的演进:正弦、可学习、相对与旋转
2.1 正弦绝对位置编码的2017年方案
注意力本身是“位置无关”的,如果把词向量原样丢进去,模型分不清“我打你”和“你打我”。原版Transformer用了一组周期函数:把位置pos、维度下标i同时塞进正弦和余弦函数,让不同维度拥有不同波长,形成一个可区分的“位置指纹”。
这个设计的巧妙之处在于,任意位置的编码都可以看作一组有序频率信号,后面某个位置可以近似表示为前面位置的线性组合,这为模型学习相对位置关系提供了理论上的方便。但它毕竟是绝对位置的产物,注入方式是“加”到词嵌入里,注意力计算的时候位置信息已经被混进语义信息,模型需要自己从中提取出有用的相对距离。这个隐式过程并不总是可靠。
2.2 相对位置编码和后续改进
BERT时代普遍改用可学习位置嵌入表,直接训一组位置向量,实现简单,短文本效果也不差。可学习表的问题是外推能力弱,训练时设了512个位置,推理时想扩展到2048,新位置就是没有嵌入可用,只能随机初始化或者插值,效果很容易崩。
相对位置编码的思路更贴近人的直觉:模型关心的是“词A和词B隔了多远”,而不是“词A在第50位、词B在第70位”。Transformer-XL那一类方案把位置信息从输入注入改成注意力分数注入,在计算QK点积时额外加入一个相对距离偏置。这个改动对长文本很有效,后来的很多生成式模型也验证了“相对信息比绝对信息更好用”。
2.3 RoPE与ALiBi成为长上下文时代的两条主线
这是现在大模型时代最重要的演进之一。RoPE(旋转位置编码)把位置信息“旋转”进Q和K本身,位置差通过旋转角度差来体现,做点积时自然携带相对位置。它的数学性质非常漂亮:对位置m和n的Q、K做点积,结果只依赖m−n的相对距离,而且完全兼容“远程衰减”的直觉——距离越大,点积收益越小。
我实际用RoPE的感觉是,它比正弦编码更“物理”,像把每个token放在旋转的轮盘上,位置变化就是角度变化。模型微调长上下文时,高频维度的旋转频率容易被新位置扰乱,所以现在很多扩展长上下文的做法都在调整基准频率。
ALiBi则是另一条路线,它不往输入里塞位置信息,也不改Q、K内容,只在注意力分数上减去一个|i-j|乘以斜率的惩罚项。距离越远的token被扣分越多,结构极简,几乎没有额外参数,而且外推能力在短训练长推理场景下表现很稳。缺点是它只强调“近的好”,如果需要精细的方向性或周期性位置关系,RoPE的可学习潜力更大。两者各有主场,选择的关键看你是要在训练长度内提精度,还是要超过训练长度去外推。
2.4 实践中的插值与外推尺度
很多开源模型训练长度6k到8k,但实际推理想跑到20k甚至100k,这里要分清物理可用的注意力窗口和位置编码外推能力。直接硬拽会导致分词阶段都还没开始,RoPE高频维度已经乱成一锅粥。粗调方法包括线性插值、NTK-aware缩放、动态缩放,核心思想都是把训练时的频率范围映射到新长度上。
我给个可复现的经验值:如果训练长度是6k、推理需要12k到16k,线性插值加一点蒸馏通常能救回来;如果直接翻5倍以上,首先要检查是否有相对位置偏置或ALiBi之外的绝对位置组件,它们往往是崩溃的主要原因。
3. 长序列困境与稀疏注意力图谱:窗口、全局与随机
3.1 O(n²)复杂度是悬在每个Transformer头上的天花板
标准全注意力的复杂度是O(n²),这里的n是序列长度。你要算每个位置和其他所有位置的注意力,存储也吃紧,n=1k时还好,n=10k就已经要上百G的注意力矩阵。这种复杂度在图像上更加直观,一张224×224的图切成16×16的patch,序列长度是196,算起来还行;但如果是高分辨率图像或者视频帧序列,序列长度轻松上万,全注意力直接没法训。
所以整个长文本和视觉领域,有一条明确的演进主线:想办法把“每个位置看全部位置”降成“每个位置看一部分位置”,但尽量保留全局信息的通道。
3.2 Longformer与BigBird的稀疏模式组合
Longformer给出的方案是滑动窗口注意力:每个token只和自己前后w个token做注意力,复杂度从O(n²)降到O(n×w),同时在特定位置(比如分类token或问题句)保留全局注意力。当时我在做长文档阅读模型,对它的第一感受是“便宜了很多”,但远程依赖确实比全注意力弱一些,需要靠窗口外信息和那些全局token来兜底。
BigBird更进一步,把注意力分成三种分量:随机注意力、窗口注意力、全局注意力。随机连接让信息可以通过多条捷径传递,窗口保证局部建模,全局token提供全局视角。三种分量叠加之后,理论上每个位置仍然有足够的信息可达路径,复杂度却保持在线性级别。这告诉我们一件事:稀疏注意力的本质不是去掉信息,而是设计一个“信息路由图”,让每个位置都能低成本地连接到其他位置。
3.3 Swin Transformer的窗口移动如何解放视觉网络
Swin把稀疏注意力的思想带到了视觉Transformer里。它先把图像划分为若干不重叠的局部窗口,在每个窗口内部做自注意力,再在相邻层把窗口移位,让前一层窗口边界外的token在下一层能和边界内token交互。这个“移位窗口”设计非常聪明,既绕开了全局自注意力的高复杂度,又通过分层结构逐步扩大感受野。
这个设计还有一层隐性优势:局部窗口注意力天然带上了图像的空间局部性,这正好是CNN一直享有的归置偏差。所以Swin在图像分类、目标检测、语义分割上的表现常优于ViT,尤其在小数据集上。我在实验里也发现,Swin对分辨率变化的鲁棒性好不少,换输入尺寸通常只要重算窗口偏移,不需要重新训练位置编码。
3.4 稀疏注意力落地时要注意什么
第一,稀疏模式要用对地方。文档太长、需要全局联系的任务,BigBird这类带全局token的方案更稳;图像这类局部强相关的任务,窗口注意力省算力也保精度。
第二,稀疏注意力的实际加速依赖工程实现。理论复杂度低了,但稀疏索引、块稀疏矩阵计算在GPU上不见得比稠密计算快多少,没有好好做顾及高效内核的稀疏运算,反而更慢。想做长期运行,得看框架有没有对应的稀疏注意力算子。
第三,别为了线性复杂度牺牲精度。我把窗口宽度调到很小时遇到过明显的性能回退,后来用“小窗口+大模型宽度”的方式做补偿,效果比硬上全注意力更划算。这其实就是结构算法演进里永恒的矛盾:复杂度降了,有效容量也可能降了,需要用其他设计补回来。
4. 主干网络微结构的演进:归一化、激活与残差
4.1 Post-LN、Pre-LN到RMSNorm:从“难训”到“可扩展”
原版Transformer用的是Post-LN,也就是在每个子层(注意力或前馈网络)之后做层归一化。这个结构在浅层模型上效果很好,但一旦深到几十层甚至上百层,训练就极其不稳定。Pre-LN的改动看起来很小:把归一化放到子层之前,并让主路径上直接残差叠加,训练稳定性立刻好转,深层模型也能训起来。
但Pre-LN也不是没有代价,我用下来觉得它在部分任务上收敛速度比Post-LN稍慢,而且最终精度不一定占优。后来的RMSNorm干脆走简化路线,不再计算均值,只对均方根做归一化。它的好处非常实在:省掉均值计算、少了一组偏置参数,数值更稳,而且效果和LayerNorm基本持平。现在大多数新模型,尤其是大模型和MoE,默认就用RMSNorm,这就是微结构演进的结果。
我把三种方案放在一起比较,方便快速决策:
| 归一化方案 | 主要特点 | 适用场景 |
|---|---|---|
| Post-LN | 深层不稳定,但浅层效果好 | 小模型、浅层、精度优先 |
| Pre-LN | 深层稳定,但可能轻微损失表征能力 | 深层编码器、大规模训练 |
| RMSNorm | 去掉均值中心化,计算快,效果接近LN | 大模型、生成式模型、MoE |
4.2 GELU与SwiGLU:前馈网络被“门控”改写了
原始前馈网络是两层线性加ReLU,FFN(x) = ReLU(xW1+b1)W2+b2。ReLU的优点是简单稀疏,但它在0点处不可导,负数直接置零,信息和梯度的利用都不够平滑。GELU把ReLU的硬截断换成了基于标准正态分布积分的软门控,负区间还保留一点小输出,梯度能继续流淌。很多经典Transformer模型里的FFN都用它,这也是早期带权重初始化偏好下比ReLU更稳的原因之一。
SwiGLU则是更大的改动:它把FFN的一部分输出当作“门”,即对两组线性变换结果做逐元素相乘,其中一组经过Swish激活。这种门控结构让FFN能更灵活地选择信息通过比例。实际使用中SwiGLU参数比标准FFN多,通常会把中间维度从4倍压缩到约8/3倍,效果却比同等参数量下的ReLU/GELU版本更好。我做过一组对照实验,同规模下SwiGLU的困惑度明显更低,训练也更快稳定。
所以不要只把注意力当Transformer的核心,前馈网络里的激活函数和维度比例同样在演进。它是表达力的第二个发动机,只是藏得比较隐蔽。
4.3 DeepNorm残差缩放:深层Transformer的定心丸
当模型的深度从几十层走向几百层甚至上千层,Pre-LN虽然稳,但效果会逐渐被“正态累积加残差传播”的退化问题影响。DeepNorm的做法是对每个残差分支加一个与深度有关的缩放系数,再归一层。它的设计很直接:把残差更新的幅度压在一个很小的范围内,让主路径以接近恒等映射的方式过深,同时每条分支都能稳定学一点新信息。
我做深层对比实验时看到,普通Pre-LN在200层以上loss会出现波动,而加了DeepNorm缩放后,400层的Transformer还能平稳训练。这算是微观算法演进里一个不怎么起眼、但直接决定模型能否“更深十倍”的关键点。如果你计划训练超深Transformer,或者把现有模型从几十层扩到几百层,只调学习率大概率不够,归一化和残差缩放必须是同一套方案。
5. 参数扩容与压缩并行的算法路线:MoE、剪枝与蒸馏
5.1 MoE路由算法如何让模型容量“不涨计算”
MoE(混合专家)把原来的一个大FFN换成一堆专家FFN,每个token经由路由网络挑出top-k个专家,把这些专家的输出按门控权重加权。它的核心变化是“稀疏激活”:模型总参数可以到几千亿,但每个token只走极少几个专家,推理和训练的计算量远低于全量参数模型。
路由算法是MoE的命门。最经典的方案是Softmax门控加上Top-k选择,再配一个辅助平衡损失:惩罚那些经常被选中和几乎不被选中的专家,让负载尽量均匀。我踩过的坑是辅助损失权重设置过高,模型的表达能力会被拉平均,效果变差;设置过低,少部分专家又会被“饿死”。现在也有不少无辅助损失的负载均衡路线,本质上是在优化“路由分配”这个组合优化问题。
实际落地时要注意工程问题:MoE在单机单卡上收益不大,甚至因为路由和专家内存访问不连续而变慢。它更适合分布式大模型训练,通过张量并行、专家并行把不同专家放到不同GPU上,配合all-to-all通讯把token发给对应设备。这也是为什么搜索引擎热词里“分布式架构”和“MoE架构”总被一起提到,两者是伴生关系。
5.2 剪枝算法在Transformer上的效果差异
剪枝分成结构化剪枝和非结构化剪枝。非结构化剪枝把不重要的权重逐个置零,稀疏度可以做到90%以上,但常规GPU上很难真正加速,因为稀疏矩阵的存储和计算还依赖特殊库。结构化剪枝直接移除整个注意力头、整列神经元或卷积通道,优点是维度规整,真实推理速度有明显收益。
对Transformer来说,早期研究里有个经典思路:先让模型收敛,再评估哪些注意力头不重要,然后剪掉并微调。我曾经按这个流程剪掉BERT里约30%的头,某些任务精度几乎不掉,模型却轻了不少。但要注意,注意力头的“重要度”高度依赖任务,同一批头在分类任务上重要,换到序列生成任务上可能完全无用。所以剪枝一定要结合最终任务做重要性评估,不能拿通用的“头重要性排序表”直接套。
5.3 知识蒸馏:把算法的“行为”迁移给更小的模型
知识蒸馏不直接改结构,但它是Transformer算法演进里很重要的“搬运工”。父模型(教师)在一个温度参数下输出带概率分布的软标签,子模型(学生)在同样的温度下拟合这个分布,同时再用正常交叉熵拟合真实标签。软标签比硬标签多携带“类别间相似度”信息,比如区分“猫”和“狗”的同时,还会告诉学生“猫和狐狸比猫和卡车更像”。
在Transformer压缩链路中,蒸馏往往是第一步,先把大模型的行为迁移到较小模型,再做剪枝和量化。这种组合比三步孤立进行要稳得多。我常用的顺序是:蒸馏压缩到目标尺寸,再做结构化剪枝,最后做低比特量化,每步都微调一小段恢复精度。这样做出来的部署模型在工程上很可靠。
5.4 部署链路里我常用的组合策略
做过实际落地的同学都知道,改动越靠后风险越大。我的标准套路是:
- 先诊断瓶颈:推理是计算密集还是内存带宽密集,决定走定量化还是剪枝优先。
- 再用蒸馏做一轮“降配”,把模型结构先缩下来,保留行为原型。
- 然后结构化剪枝,只剪那些在验证集上影响小于某个阈值的头和神经元。
- 最后用FP16或INT8量化,前提是归一化方式和残差设计都兼容低精度运算。
这套链路每次都能把模型压到原先三分之一左右,同时把精度控制在可接受范围内。
6. 交叉注意力与多模态应用:从NLP向外横向演进
6.1 交叉注意力决定了Transformer能否“跨域对接”
Transformer的编码器-解码器架构里,解码器每一层除了自注意力,还有一个交叉注意力模块:Query来自解码器自己的输出,Key和Value来自编码器的输出。这个设计原本是为了做机器翻译,但它后来成了多模态架构的基础——只要你有两个模态,想建立关联,就可以把其中一个模态当作Query,另一个模态当作Key和Value。
比如做图文生成,能把文本特征作为Query,图像特征作为Key和Value,让文本生成过程实时“关注”图像细节;反向也可以做图像条件生成。交叉注意力的妙处在于不对称性:不是把两种特征硬加在同一个语义空间,而是保留了两者各自的表示,只交换“信息读取”的过程。这比简单拼接要细得多,也更能防止模态之间的特征冲突。
6.2 面向遥感与交通场景的多模态融合Transformer
多模态融合Transformer已经不只是NLP圈的事了。我的一个印象很深的例子是遥感语义分割:一个场景里有高分辨率影像、光谱数据、数字表面模型等多种源数据,针对每个数据单独训练编码器,再通过交叉注意力把不同层级的多模态特征融合起来。跨视看,看AISTATS净文本是一种提炼。
交通预测也是在用同样的思路。搜索里可以看到类似PD-Former这种为传播时延感知设计的动态长距离Transformer,它把不同路网位置的交通流数据作为节点,利用注意力去捕捉路网节点之间随时间和传播延时而变化的依赖关系。这类任务本质上和语言很像:位置是空间网格上的节点,时序是多个历史时间步,模型要在“图结构+时序窗口”里建模动态关联。Transformer在这儿取代了之前常用的图神经网络加循环网络组合,靠的就是交叉注意力能灵活处理多头关联信息。
6.3 分类、回归与序列预测:同一个算法内核的三种用法
很多人以为Transformer只能做文本分类,其实它的核心算法稍作调整就能切到各种任务。做图像分类,ViT把图切成patch当序列,Swin用窗口注意力做分层表示;做时间序列预测,把历史窗口作为序列输入,用Masked自注意力做自回归预测,甚至有人直接拿Transformer预测正弦波数据,先让模型看到一段波形,再预测后续走势,效果比传统序列模型干净很多。
不论哪种用法,基础的三件套是固定的:嵌入层把语义单元映射成向量,位置编码或相对位置机制给出时序或空间位置信息,自注意力把全局关系建模出来。分类就在最后加个分类token或全局池化,回归就把输出层换成线性层。这也是Transformer能统一各个领域的原因:它不是某个具体任务的专用网络,而是一套通用相关建模算法。
7. 关键参数与避坑笔记:给正在落地Transformer的同学
7.1 头数、维度与窗口大小的参数手感
如果你是从零开始搭Transformer,我会建议把每头维度锁在64附近。d_model和head一起缩放时,边界效应会比单独调一个参数小得多。窗口注意力里,Swin的窗口大小我常用7到12;窗口再大,GPU显存上涨快,精度提升却很缓,性价比很差。
训练时还有一个容易被忽略的点:初始化标准差要和d_model挂钩,很多框架的默认初始化和深度一深就配合不上,表现为前期loss下降极慢。我用std = sqrt(2 / (5 * d_model))这类适度缩放初始化,深层模型收敛快不少。
7.2 RoPE微调长上下文时最容易炸的坑
如果你拿6k长度训练的模型直接去跑12k,很多模型上游可能已经崩溃。这时候去调整位置编码频率或其他外部组件,不要一个劲堆数据。一个常见修法是RoPE的NTK-aware缩放,把基准频率从10000改成更大的值,在不过度插值高频细节的前提下扩展长度窗口。
还有个容易被忽略的细节:量化模型的长上下文对位置编码更敏感,因为低比特表示放大了高频旋转的舍入误差。我遇到过一次INT8下长文本全乱的情况,排错到最后就是RoPE部分在低精度下严重失真,最后把那部分保留FP16才解决。
7.3 MoE负载不均和辅助损失的选择
MoE训练最常见的现象是几个专家被抢疯,其余专家长期空转。避免进入这种“富者愈富”的最直接手段是辅助平衡损失,但你一定要控制权重,别让它主导主任务损失。实验里我发现,权重在0.001到0.01之间是个比较窄的安全区,再高模型整体效果往往会明显下滑。
负载不均还有一个隐蔽原因:路由和专家卸载的顺序不对。你在分布式环境里做专家并行,通讯顺序错一次,Google算法层面的节能也扛不住。必要时要看路由统计指标,比如每个专家平均接收多少token、标准差多大,把它当作训练日志的一部分,连续几千步过高就必须介入。
7.4 我对架构演进方向的一点个人判断
从这几年的变化看,Transformer的算法演进越来越不像“换一个结构”,而是“在统一框架下优化容量、效率和泛化三个维度”。注意力从稠密走向稀疏,位置编码从绝对走向相对和旋转,前馈从ReLU走向门控,容量从稠密FFN走向混合专家,每个改动都不是对原版的否定,而是对某一类瓶颈的定向拆解。
如果让我给正在做模型选型的人一句个人建议:不要盲目追新,先弄清你面对的是长序列、多模态、高精度还是低延迟问题,然后从对应的演进线里取那一个模块去替换。Transformer能演进到今天,靠的就是每个环节都能被解耦、替换、再组合。你只要把每条演进线吃透,自己也能在这个框架里做出新的变体。