ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

残差连接在Attention时代的范式跃迁:从梯度捷径到信息路由骨架

残差连接在Attention时代的范式跃迁:从梯度捷径到信息路由骨架

1. 先说结论:残差在Attention时代已经被重新定义了

如果你问我,这几年做Transformer相关的模型训练和调优,哪个组件最不起眼却又最不能动,我的答案一定是残差连接(Residual Connection)。至少在五年前,大家提到残差,脑子里浮现的还是ResNet那一套“跨层相加、梯度高速公路”的经典叙事。但等你真正把Attention机制拆开、把深层Transformer的训练稳定性问题一个个排查完,你会发现残差连接在注意力机制里扮演的角色,已经远远超出了“缓解梯度消失”这个初始动机。

我拿一个自己实测过的现象开场:把某个主流Transformer结构的Block里的残差分支权重调成0,模型还能正常前向计算,Loss不会立刻爆炸,但训练曲线会进入一种“缓慢但必然”的退化状态——表现为:前几步Loss下降还正常,随后开始震荡,最终在某个略高于随机猜测的位置徘徊。这个现象,用传统ResNet的经验完全解释不了。因为在ResNet里,即使你把残差分支的scale调小,网络顶多变成“浅层网络叠加”,性能略降但不会出现这种神经质的训不动状态。Transformer里的残差,显然承担着比“梯度通道”更复杂的职责。

这篇文章我想从“范式跃迁”的角度,把残差连接在Attention体系里经历的几次角色变化讲清楚:它如何从一条梯度捷径,演变为信息路由的主干道,再演变为整个架构的“功能模块化骨架”。同时,我会把自己在训练、调参、复现论文过程中踩过的坑和验证过的结论一并放进来,希望能帮到正在研究注意力机制、或者正在手工调Transformer训练稳定性的朋友。

2. 从ResNet到Transformer:残差的第一次跃迁

2.1 残差的原始使命:梯度高速公路

要理解残差的“范式跃迁”,先得回到它最初解决的问题。在残差连接被提出之前,深层网络的训练困难是一个共识性问题。网络越深,梯度在反向传播时经过的连乘链越长,梯度幅值要么指数级衰减到接近零,要么指数级膨胀到溢出——前者让浅层几乎收不到有效更新信号,后者让优化过程剧烈震荡。传统解决方案包括各种初始化技巧、归一化手段,但本质上都只是在“缓解”连乘效应,并没有在结构上给梯度一条绕行的路径。

残差连接做的事情非常直接:既然梯度在多层连乘中容易消失,那就在结构上开辟一条“短路”,让梯度可以不经过权重矩阵的连乘,直接从深层传回浅层。数学上,前向计算从“函数叠加”变成“身份映射加增量修正”,反向传播时浅层梯度也自然多了一项直接“透传”的路径。这让研究者可以放心地把网络堆到几十层、上百层甚至上千层,而不用再担心梯度传不回去。

这套逻辑下,残差连接在ResNet时代本质上是个“基础设施”——它保证的是优化可达性,而不是去改变网络表达什么。换句话说,残差的存在让“深层网络”这个选项第一次变得真正可训练,但网络本身仍然是逐层加工信息的流水线结构。信息处理的主链路是那条串行的层间连接,残差只是辅助。

2.2 Attention来了以后,残差的角色彻底变了

真正改变格局的是Transformer这类注意力架构的出现。在Attention机制里,信息不再只是“逐层流水线加工”,而是变成了“全局交互+加权聚合”。每一个Token都会和序列中的其他Token计算相关性,然后把信息按相关性加权融合。这种机制带来一个以前没有的新问题:经过Softmax归一化后的注意力输出,天然偏向于低秩表达,而且会不断压缩信息多样性。

这里有个关键的观察点:注意力头输出的向量,本质上是“对输入Value向量的加权平均”。加权平均这个操作,天然就是“信息压缩”而不是“信息增广”——它把很多Token的信息揉成一个向量。如果深层网络里每一层都做这种压缩操作,又没有某种机制把原始输入信息一路保留下去,那么深层表达会迅速退化:所有Token的表征趋同,Rank不断下降,最终整个网络的表达能力崩塌。这个现象就是工程界常说的Rank Collapse,和“过度平滑”是同源问题。

残差连接在这里完成了一次角色跃迁:它不再只是“梯度的高速公路”,而成了“信息的主干道”。在Transformer Block内部,残差支路连接的是输入和Attention输出,核心语义变成了“在保留输入完整信息的前提下,加上注意力计算产生的一个增量”。增量负责建模Token之间的关系,而主干道负责把原始信息一路送到深层。这就好比过去修路是为了让车辆不堵车(梯度流动),现在修路本身成了运输主线,两侧的工厂(Attention层)只是往主路上装卸货物。

这一跃迁的实质是:残差让深层网络从“信息加工流水线”变成了“信息路由网络”。每一层做的事情,不是对输入做不可逆的变换,而是在完整保留输入的条件下,叠加一个修正量。这个修正量可以是正、可以是负、也可以接近零——学习过程因此变得更灵活,模型也首次具备了“选择性遗忘”和“选择性增强”的能力。

2.3 两种范式阶段的关键差异对照

维度ResNet时代的残差Attention时代的残差
首要目标让深层网络可训练(梯度可达)维持信息多样性与表征Rank
信息角色辅助旁路,主链路是层间变换主信息干道,层内计算是修正量
失效症状梯度消失,浅层不更新输出坍缩,Rank下降,过平滑
关键配对与卷积/BatchNorm配合与Attention/Norm配合
堆叠深度百层级别,深但功能简单千层级别,且层间功能分化明显

这个表格里的差异,基本就是我对“范式跃迁”的判断依据。可以这么说:如果你现在训练一个深层Transformer,脑子里想的还是“残差就是用来防止梯度消失”,那你在面对Pre-LN还是Post-LN的选择、面对初始化标准差调整、面对残差Branch的Scale设置时,大概率会做出错误判断。因为这些问题本质上都和信息路由的效率有关,而不仅仅和梯度可达性有关。

3. 为什么Attention离不开残差:三个肉眼可见的现象

3.1 没有残差,深层表达会坍缩成“复读机”

我自己做过一个消融实验:把标准Transformer的每个Block里的残差连接全部去掉,然后观察中间层输出。模型的深度控制在12层,参数量不变,训练数据不变。结果是——不光最终指标崩了,中间层的表征也肉眼可见地“退化”:后几层的输出彼此之间相似度极高,每个Token的输出向量几乎变成同一个方向。

这个现象背后是Attention的Softmax机制在起作用。Softmax的输出是归一化的概率分布,注意力输出是所有Value向量的凸组合。凸组合有个特性:它永远不会超出输入向量张成的凸包。连续多层做凸组合,信息的“极值”会被不断磨平,所有向量会慢慢靠向同一个质心。这就是过平滑的数学本质。

残差在这里的作用,相当于每一层都在说:我不要求你完全保留我的输入,但你必须至少允许我的输入原样通过。Attention层输出的增量,只在凸组合的“平均值”方向上做修正,而个体特征依然由主干道承载。没有残差,深层网络就失去了承载个体特征的通道,最后所有Token都会变成“复读机”。

3.2 去掉残差,训练曲线会进入“死循环”式震荡

前面提到过,去掉残差后Loss不会立刻爆炸,这其实才是最迷惑人的地方。我一开始以为模型只是变浅了,性能下降是正常的。但持续观察发现,训练过程中Loss在某个平台期反复震荡,规则而密集,像是被困在某种“局部吸引子”里出不来。

用优化视角解释,原因在于:没有残差时,Attention层输出直接成为下一层的输入,而Attention的输出分布和输入分布存在系统性偏移。层与层之间被迫不断互相补偿——前一层输出往某个方向偏了,后一层就得学一个反向偏置把它拉回来。这种“互相补偿”的动态,本质上是在一个高维空间里玩追逐游戏,优化器很难找到一个全局一致的解。

而有了残差之后,每一层只需要学一个“相对输入的小修正”。修正的分布比绝对输出稳定得多,训练动态从“层层追逐”变成了“层内自治”。这也是为什么残差连接配合LayerNorm时,训练会异常平稳——因为Norm负责控制增量尺度,残差负责提供稳态基线,各司其职。

3.3 残差是“模块化”的物理前提

第三个现象可能更抽象,但我觉得它才是范式跃迁里最核心的一点。如果你长期观察Transformer内部运转,会发现不同深度的层其实在分工:浅层比较擅长捕捉局部模式和词法特征,中层负责语义整合,深层更接近任务相关的抽象表达。这种功能性分化,只有在残差存在的条件下才可能出现。

原因不复杂——残差让每一层都变成了“对当前表征的微调器”,而不是“从零开始构建新表征的加工厂”。微调器的好处是:每一层可以在前一层的成果上做小幅推进,层与层之间的语义空间被天然对齐。没有残差的话,每一层都相当于在“重建”表征空间,层间语义缺乏连续性,模型就很难形成分层抽象的能力。

所以残差不仅是训练稳定性的工具,它实际上定义了Transformer的“组织结构”。这也是为什么主流架构的Block设计里,残差永远从Block的最前端连到最末端,把整个子层包在内部——它在物理上划定了“主路”和“支路”,让Attention和FFN都只是主路上的功能模块。

4. Pre-LN还是Post-LN:残差位置里的实战哲学

4.1 两种配置的行为差异

对于调过Transformer的人,“残差连接应该加在Norm前面还是后面”是个绕不开的抉择。Post-LN(先Attention再残差再Norm)是原始Transformer论文的配置,但训练起来出了名的“脆”——学习率稍微调大一点、深度稍微加几层,Loss就可能突然起飞。Pre-LN(先Norm再Attention再残差)是后来工程界普遍采用的改良方案,训练稳定得多,深层堆叠也不容易炸。

这两种方案的差异,本质上体现在“残差主干道上的信号先被处理还是后处理”上。Post-LN的残差主干道上直接流过未经归一化的信号,数值的绝对尺度由各层输出共同决定,容易累积出大数值波动。Pre-LN则把Norm放在残差汇入之前,主干道上的信号是经过尺度控制的,残差相加后不会出现量级膨胀。

我自己在训练一个12层小型模型时对比过:Post-LN在训练初期Loss下降更快,但一旦学习率超过某个阈值,Loss会出现“尖刺式”跳变,需要重启训练或者降低学习率。Pre-LN虽然前期收敛略慢,但从头到尾都极其平稳,最终指标基本持平或略优。在深层模型(20层以上)上,这个差距会被进一步放大:Post-LN几乎没法用常规学习率训练到底,Pre-LN则一路安稳。

4.2 为什么说“先残差后Norm”更像一种工程妥协

如果你去读各种开源实现,会发现绝大多数框架默认的都是Pre-LN结构。这造成一个假象:Pre-LN是“正确”的配置。但实际工程里选择Pre-LN,更多是出于稳定性和易调参的考虑,而不是因为它表达能力强。

Pre-LN有一个被很多人忽视的副作用:Norm会压缩主干道上的信号,导致每个Block的“有效修正量”更容易被限制在一个较小范围内。这意味着深层模型的每一层都倾向于做小幅修改,模型学习速度会变慢,尤其是浅层特征提取阶段。为了解决这个问题,很多工作在初始化阶段会把Norm层的Scale参数调大、或者给残差分支加一个可学习的加权系数。这说明Pre-LN的稳定是有代价的,代价就是表达能力被限制,需要额外手段去补偿。

从我个人的调参经验来说,如果你训练的是中小规模模型、追求稳定预算内的收敛,Pre-LN是无脑选择。如果你在训练超大模型并且有足够的调参空间,Post-LN在深层的表达上限可能更高,但需要极其精细的学习率和Warmup配合,一般人不建议轻易尝试。

4.3 残差Branch上的Scale:一个太容易被忽略的旋钮

不管Pre-LN还是Post-LN,残差分支上的Scale参数都是控制“每层修正力度”的关键旋钮。很多框架里它默认是1,但如果你做的是极度深层的模型,把Scale设成小于1的值(比如0.1到0.5之间)反而能提升训练稳定性。

这里有个简单的直觉:每一层修正量如果一直保持在较高幅度,深层模型就会像“每层都在大声说话”,互相干扰。把残差Scale调小,相当于让每层“小声一点”,信息积累的主要动力来自主干道,层内修正只是微调。这种设计思想在ReZero、LayerScale等变体里被发挥到了极致——直接让残差Scale从一个接近零的值开始学,训练完成后模型的深度可以堆到一千层以上。

但Scale也不是越小越好。Scale过小意味着层内计算对最终输出影响微弱,模型的“有效深度”会下降,最终指标可能不如正常Scale。这个参数本质上是在“深度可训练性”和“深度利用率”之间做权衡。如果你在训练某个深层模型时发现性能上不去,尝试把残差Scale从1往下降,有时会有奇效。

5. 注意力残差的变体进化:当残差本身变成可学习参数

5.1 从固定残差到可学习残差Scale

既然残差Scale对训练稳定性和模型深度都有影响,一个自然的问题是:能不能让模型自己去学这个Scale?这条路有代表性的工作包括ReZero和LayerScale。ReZero的做法最激进:把残差分支乘上一个初始化为0的可学习参数,让模型从“纯主干道”开始训练,随着训练推进逐渐打开残差分支。这么做的好处是训练初期网络等价于一个极浅的网络,优化难度最低,后期再逐步加深“有效深度”。

LayerScale则更温和:它不把残差Scale初始化为0,而是初始化成一个较小的值(比如1e-4或1e-1这个量级),同样可学习。在视觉Transformer上,LayerScale被证明能有效提升深层模型的收敛稳定性和最终精度。我实际复现过LayerScale的效果,发现一个很有意思的点:它的收益在模型深度较大(24层以上)时尤其明显,浅层模型加了LayerScale反而有时会掉点。这与“深层模型更需要稳定的残差通道”的判断完全一致。

5.2 残差进入到Norm内部的尝试:Sandwich-LN

还有一种思路是调整残差和Norm的搭配顺序和次数。传统Pre-LN是“Norm → Attention → 残差”,Sandwich-LN则多加了一次Norm:在Attention处理前先Norm一次,残差相加后再Norm一次。这样可以更精细地控制残差主干道和修正支路之间的尺度平衡。

我测试过Sandwich-LN的变体,它在训练稳定性和最终收敛精度上和Pre-LN差别不大,但计算开销增加了一组Norm,训练速度略微下降。它的真正价值可能不在普通Transformer上,而在某些对尺度敏感的结构里——比如混合了多种注意力机制的复杂Block,或者引入了外部记忆模块的网络。在这些场景里,残差信号和修正信号的尺度差异会很大,额外的Norm能起到“镇流”作用。

这里我多说一句:残差连接的进化方向,本质上都在解决同一个问题——“主干道信息保持”和“支路增量修正”之间的尺度平衡。无论是Init Scale、可学习Scale还是额外Norm,都是在不同的位置对这个平衡做调节。理解了这一点,你去看文献里的各种变体,会发现它们并没有多么玄妙,只是在寻找更好的“配平方案”。

5.3 扩散模型和状态空间模型里的“残差新用法”

如果你关注更前沿的方向,会发现残差连接的思想已经溢出到Transformer之外。扩散模型里的UNet结构、以及近期常见的扩散Transformer架构,都大量使用残差连接来保持输入图像的底层结构信息。在扩散模型里,残差连接的作用更像“把每一步的去噪增量叠加到当前噪声图像上”,这和“模态间信息保持”有点类似——模型需要确保去噪过程不丢失原始图像的结构先验。

状态空间模型(SSM)则在另一个层面使用了残差思想。SSM本身就是线性动态系统,它在序列建模时需要保持状态向量的连续性,残差连接在这里的作用是让状态更新变成“当前状态+可学习的增量”,从而保证长期依赖不会因为矩阵连乘而衰减殆尽。这和Transformer残差的底层逻辑一脉相承:信息必须有一条不经过非线性压缩的直达通道,才能保证深层传播的质量。

这些新领域里的残差变体,让我更确信“范式跃迁”的判断:残差连接已经从一项训练技巧,变成了一种通用的架构设计原则——凡是要在深层结构中保持某种连续性和信息完整性,残差连接都可以作为默认的基础设施。

6. 工程落地:初始化、数值精度与残差State的实战检查

6.1 初始化标准差必须跟着残差走

很多人调Transformer时会在初始化上踩坑,尤其是当模型深度增加时,如果初始化标准差设置不当,训练初期就会遇到“输出分布爆炸”或“完全无法收敛”的问题。这里的核心原因其实也和残差有关:深层模型的前向传播要叠加大量的残差项,每个残差项都携带一定的方差,叠加次数多了,总方差自然增长。如果不控制每层的输出方差,深层输出的数值范围会很快失控。

常见的校准方式是:在初始化时,将输出层的权重标准差按深度进行缩放。以某主流框架的做法为例,若把标准初始化标准差设为0.02,当层数增加到很深时,可以按“层数的平方根倒数”缩小标准差。这样做的目的是让每一层残差项贡献的方差增长趋缓,使深层输出的总方差保持在合理范围。

实操中我也验证过:直接把初始化标准差从0.02改成一个更大的值(比如0.2),浅层模型还不明显,一旦超过16层,训练初期Loss就会异常地高,而且前几步更新幅度巨大,极易进入“死亡区域”。所以如果你在调深层Transformer,记得把初始化标准差和深度绑定考虑,而不是用一个固定值走天下。

6.2 低精度训练下的残差累积误差问题

现代大模型训练基本离不开混合精度(BF16/FP16)。低精度下残差连接有一个隐蔽的坑:当残差主干道上的信号幅值远大于残差修正项时,低精度表示会直接把修正项的细节“吃掉”。比如主干道幅值在100量级,而残差修正量只有0.1量级,在FP16下相加,0.1的修正信息会被舍入到几乎为零,等于这一层白算了。

这就是为什么很多大模型训练会在Block内部维持更高的精度,或者对残差分支单独做Scale和偏移,保证修正项在低精度下仍有足够的有效位。我之前踩过这个坑:某个模型在FP16下训练,指标一直上不去,排查了很多地方,最后发现就是残差累积误差在作祟——把残差修正项放大10倍训练,指标立刻恢复正常,然后推理时再缩回去。

所以做低精度训练时,一定要检查你的每一层残差修正量的典型幅值,和主干道幅值对比一下。如果差距在一到两个数量级以上,几乎必然会有精度损失问题。这也是很多框架在混合精度策略上专门对残差连接“网开一面”、保留FP32累加的原因。

6.3 如何快速检查你的模型“残差健康度”

最后分享一个我在实际工作中经常用的“体检”方法。训练刚起步时,可以记录每一层残差修正项的L2范数,和该层输入的L2范数做个比值。如果比值在0.01到0.1之间,说明模型处于健康的“微调”状态;如果比值超过1,说明模型层内计算正在试图大幅改写表征,训练大概率不稳定,优化器Warmup可能需要调整;如果比值趋近于0,说明该层基本没有学到东西,可能是初始化太小、Norm配置不当或者学习率过低。

这个比值也可以随着训练动态监控。正常的Transformer训练中,早期几乎所有层的修正量都较大(模型在快速建立特征),中后期修正量应该逐渐收敛到较小的稳定值。如果你发现某些层始终维持很高的修正量,说明这些层的功能一直没稳定下来,可以考虑检查残差Scale、学习率调度或者层间Norm配置。

这套“残差健康度”检查,帮我定位过好几个“看起来在训练、实际在空转”的模型问题。有时候Loss确实在下降,但某些深层Block的残差修正量几乎为零,说明那里根本没有有效学习发生,资源被浪费在了死层上。对症处理后,训练效率和最终指标往往会有明显改善。

7. 一些研究和工程上的经验总结

个人体会最深的一点:把残差连接理解成“梯度工具”还是“信息路由骨架”,决定了你会不会在关键时刻做出正确的架构决策。如果你把它当梯度工具,遇到训练不稳就会去调学习率、调优化器,而真正的问题可能出在残差通道的数值尺度上。如果你把它当信息路由骨架,自然就会发现调整残差Scale、调整Norm位置、调整初始化标准差这些手段,才是更对症的解决方案。

另一个真切的建议:做消融实验时,不要只关心“去掉残差”这种极端情况,可以多试试“降低残差Scale到0.1”“在Norm前后切换位置”“给残差分支加入可学习参数”这些中间状态。从这些中间状态里你能观察到残差连接的真实行为边界,比单纯开关实验要更有信息量。比如我就发现在某个中等复杂度的任务上,Scale为0.3和Scale为1.0的最终指标几乎没差别,但训练速度和稳定性差距很大——这对实际选型极有参考价值。

最后,如果你想在残差连接这个方向继续深入,可以关注一些跨模态模型和扩散Transformer的最新实现。它们往往会根据自身任务特点重新设计残差连接的形状和位置,思路五花八门。看多了以后你会发现,残差连接这个“老组件”,在注意力范式的带动下,衍生出的设计空间可能远比我们想象的要大。研究也好,工程也罢,值得投入的时间还很长。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进