Skip to content

Residual ​

残差连接(Residual Connection)是 Decoder-only LLM 中 Attention 和 MLP 子层的外部数据通路。它保留当前 hidden states,并把子层输出作为增量写回,使多层 Decoder 能在同一条残差流(Residual Stream)上持续更新 Token 表示。

1. 设计目的 ​

如果第 l 层只执行 xl+1=Fl(xl),输入信息和梯度都必须连续穿过每一层 Attention、MLP 与归一化计算。随着 Decoder 层数增加,每个子层既要保留已有表示,又要完成新的特征变换,深层信号传播和优化都会更加困难。

残差连接把子层的任务改为计算更新量:

y=x+Δx,Δx=F(x).

在 LLM 中,x 是进入 Attention 或 MLP 前的 hidden states,Δx 是子层产生的上下文或特征更新。这样设计有三个直接目的:

  • 表示保留:原有 Token 表示可以沿恒等路径继续传递,子层不必重新构造完整表示;
  • 增量更新:Attention 只负责写入跨 Token 上下文,MLP 只负责写入逐 Token 特征变换;
  • 深层传播:反向传播包含一条不经过复杂子层变换的直接路径,降低深层 Decoder 的优化难度。

残差连接不会跳过 Attention 或 MLP 的实际计算。“捷径”指信息和梯度的传递路径,不代表减少这些子层的 FLOPs。

2. 计算原理 ​

2.1 两次残差更新 ​

设第 l 个 Decoder Layer 的输入为 xl∈RB×T×D,其中 B 为批大小,T 为本次参与计算的 Token 数,D 为 hidden_size。常见的 Pre-Norm Decoder Layer 执行:

al=Attention(Norm(xl)),hl=xl+al,ml=MLP(Norm(hl)),xl+1=hl+ml.

Attention 的输出投影和 MLP 的降维投影都会把最后一维还原为 D,因此 al、ml 能与各自的残差基准逐元素相加。两次加法使用不同的基准:Attention 输出与 xl 相加,MLP 输出与已经包含 Attention 更新的 hl 相加。

text
x_l
  → Norm → Attention → Add(x_l) → h_l
  → Norm → MLP       → Add(h_l) → x_{l+1}

2.2 Pre-Norm ​

现代 Decoder-only LLM 通常采用 Pre-Norm,即先归一化残差流,再执行子层计算和残差加法:

y=x+F(Norm(x)).

此时恒等路径中的 x 不会在每个子层后再次归一化。局部雅可比为:

∂y∂x=I+∂F(Norm(x))∂x.

恒等项 I 为梯度提供直接路径。它有利于深层 LLM 的训练,但不能单独保证数值稳定;初始化、学习率、模型深度和计算精度仍会影响优化结果。

Pre-Norm 的最后一次残差加法不会自动产生归一化输出,因此模型通常会在全部 Decoder Layers 之后、LM Head 之前增加 Final Norm。完整输出流程参见 Output。

2.3 Residual Stream ​

将各层的两次更新展开,可以得到:

xL=x0+∑l=0L−1(al+ml).

x0 来自 Token Embedding,al 和 ml 分别是第 l 层 Attention 与 MLP 写入的更新量。这条从 Token Embedding 延伸到 Final Norm、形状始终保持为 [B, T, D] 的激活路径就是残差流。

“恒等路径”不表示 x0 会原样到达最后一层;每个子层都会向残差流累加新信息。它只表示已有表示可以直接参与下一次更新,不必先经过当前子层的非线性变换。

3. 推理实现 ​

3.1 Qwen2 Decoder Layer ​

Qwen2 使用 RMSNorm 和两次标准残差加法。下面的 hidden_states、Attention 输出和 MLP 输出最后一维均为 hidden_size:

📄 Qwen2DecoderLayer
python
residual = hidden_states
hidden_states = self.input_layernorm(hidden_states)
hidden_states, _ = self.self_attn(
    hidden_states=hidden_states,
    attention_mask=attention_mask,
    past_key_values=past_key_values,
    position_embeddings=position_embeddings,
    **kwargs,
)
hidden_states = residual + hidden_states

residual = hidden_states
hidden_states = self.post_attention_layernorm(hidden_states)
hidden_states = self.mlp(hidden_states)
hidden_states = residual + hidden_states

第一次 residual 保存本层输入;第二次重新赋值后,它保存 Attention 残差加法的结果。该执行顺序对应公式中的 xl→hl→xl+1。

这是 Qwen2 对标准 Pre-Norm Decoder Layer 的具体实现。其他 LLM 可以更换归一化方法、Attention 或 MLP 结构,但只要子层输出回到 hidden_size,残差加法的数据流保持不变。

3.2 Prefill 与 Decode ​

残差公式在 Prefill 和 Decode 阶段相同,区别在于本次处理的 Token 数:

阶段语义形状残差更新
Prefill[B, S, D]并行更新 Prompt 中的 S 个 Token
Decode[B, 1, D]更新每个序列当前生成位置的 1 个 Token

连续批处理引擎可能把有效 Token 展平为 [N, D],其中 N 是本轮实际调度的 Token 数;这只改变张量布局,不改变逐 Token、逐隐藏维度相加的语义。

Decode 阶段的 Attention 会读取历史 KV Cache,但残差流只保存当前步骤正在处理的 hidden states。它不会随上下文长度增长,也不会向 KV Cache 增加额外状态。

3.3 并行与算子融合 ​

残差加法要求子层输出与残差基准具有兼容的形状、数据类型、设备和分片布局。张量并行下,Attention 输出投影或 MLP 降维投影可能产生各 Rank 的部分和,需要先通过 All-Reduce、Reduce-Scatter 或等价融合通信得到正确结果,再写回残差流。若两者已经采用相同的 Token 分片,则残差加法可以在本地执行。

残差加法的计算量很小,但需要读写完整 hidden states,通常受内存带宽限制。Pre-Norm LLM 会在残差更新后立即归一化下一子层的输入,因此推理引擎常把 Residual Add 与 RMSNorm 融合为一个 Kernel:

r←r+u,z←RMSNorm(r).

其中 u 是当前子层输出,r 是更新后的残差流,z 是下一子层的归一化输入。融合主要减少中间张量读写和 Kernel Launch,不改变模型计算结果。

📄 SGLang fused_add_rmsnorm
python
def fused_add_rmsnorm(input, residual, weight, eps=1e-6, ...):
    """
    residual[i] += input[i]
    input[i] = (residual[i] / RMS(residual)) * weight[i]
    """
    ...

该接口原地更新 residual,并把归一化结果写回 input,从而同时得到更新后的残差流和下一子层的输入。

4. 实际影响 ​

维度结果边界
表示传播Token Embedding、Attention 更新和 MLP 更新沿同一条残差流逐层累加恒等路径保留已有表示,但不会阻止后续更新改变它
深层训练梯度可以通过恒等项直接跨过子层,Pre-Norm 下的传播路径更直接残差连接不能替代合理的初始化、优化器和数值稳定措施
推理状态常规推理只需保留当前层的残差流,不增加模型参数或 KV Cache 容量显式返回所有层 hidden states 时仍会产生额外显存开销
推理性能Add 与 Norm 融合可以减少显存读写和 Kernel Launch残差连接不会减少 Attention、MLP 或张量并行通信的主体开销

因此,残差连接对 LLM 的核心效果不是让单层计算更便宜,而是让深层 Decoder 能以增量方式持续构造 Token 表示。推理侧的优化重点则是保持分片布局一致,并通过算子融合降低这条高频逐元素路径的访存成本。

5. 参考 ​

  1. Attention Is All You Need
  2. On Layer Normalization in the Transformer Architecture
  3. Transformers:Qwen2 实现
  4. SGLang:Fused Add RMSNorm 实现