Residual
残差连接(Residual Connection)是 Decoder-only LLM 中 Attention 和 MLP 子层的外部数据通路。它保留当前 hidden states,并把子层输出作为增量写回,使多层 Decoder 能在同一条残差流(Residual Stream)上持续更新 Token 表示。
1. 设计目的
如果第
残差连接把子层的任务改为计算更新量:
在 LLM 中,
- 表示保留:原有 Token 表示可以沿恒等路径继续传递,子层不必重新构造完整表示;
- 增量更新:Attention 只负责写入跨 Token 上下文,MLP 只负责写入逐 Token 特征变换;
- 深层传播:反向传播包含一条不经过复杂子层变换的直接路径,降低深层 Decoder 的优化难度。
残差连接不会跳过 Attention 或 MLP 的实际计算。“捷径”指信息和梯度的传递路径,不代表减少这些子层的 FLOPs。
2. 计算原理
2.1 两次残差更新
设第 hidden_size。常见的 Pre-Norm Decoder Layer 执行:
Attention 的输出投影和 MLP 的降维投影都会把最后一维还原为
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,即先归一化残差流,再执行子层计算和残差加法:
此时恒等路径中的
恒等项
Pre-Norm 的最后一次残差加法不会自动产生归一化输出,因此模型通常会在全部 Decoder Layers 之后、LM Head 之前增加 Final Norm。完整输出流程参见 Output。
2.3 Residual Stream
将各层的两次更新展开,可以得到:
[B, T, D] 的激活路径就是残差流。
“恒等路径”不表示
3. 推理实现
3.1 Qwen2 Decoder Layer
Qwen2 使用 RMSNorm 和两次标准残差加法。下面的 hidden_states、Attention 输出和 MLP 输出最后一维均为 hidden_size:
📄 Qwen2DecoderLayer
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 残差加法的结果。该执行顺序对应公式中的
这是 Qwen2 对标准 Pre-Norm Decoder Layer 的具体实现。其他 LLM 可以更换归一化方法、Attention 或 MLP 结构,但只要子层输出回到 hidden_size,残差加法的数据流保持不变。
3.2 Prefill 与 Decode
残差公式在 Prefill 和 Decode 阶段相同,区别在于本次处理的 Token 数:
| 阶段 | 语义形状 | 残差更新 |
|---|---|---|
| Prefill | [B, S, D] | 并行更新 Prompt 中的 |
| Decode | [B, 1, D] | 更新每个序列当前生成位置的 1 个 Token |
连续批处理引擎可能把有效 Token 展平为 [N, D],其中
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:
其中
📄 SGLang fused_add_rmsnorm
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 表示。推理侧的优化重点则是保持分片布局一致,并通过算子融合降低这条高频逐元素路径的访存成本。