GPT-J(2021)和 PaLM(2022)用了同一个省时间的巧劲:标准 block 里 attention 和 FFN 是串行的,
$$ y = x + \mathrm{FFN}\big(\mathrm{LN}(x + \mathrm{Attn}(\mathrm{LN}(x)))\big), $$
FFN 必须等 Attn 算完;改成并行结构后,两个子层读同一个输入、各自独立计算、输出相加:
$$ y = x + \mathrm{Attn}(\mathrm{LN}_1(x)) + \mathrm{FFN}(\mathrm{LN}_2(x)). $$
每个 block 省掉一次"子层串行等待",参数和 FLOPs 一克没少。这篇讲它为什么有效、代价是什么。
关键路径账
看一步 block 的时间构成:LN 和残差加法是逐元素的小算子,真正的大头是两个矩阵密集子层(Attn 的 $QKV$/$O$ 投影与 FFN 的 $W_1$/$W_2$)。串行结构里它们首尾相接,block 延迟 ≈ $T_{\text{attn}} + T_{\text{ffn}}$(加上两个 LN 串在其中);并行结构里两者同槽执行,block 延迟 ≈ $\max(T_{\text{attn}}, T_{\text{ffn}}) + T_{\text{add}}$。FFN 的 FLOPs 通常占全层约 2/3,理想情况下延迟从 5 份压到 3 份多一点。
PaLM 报告的实际收益是 吞吐 +15% 左右,比理论上限小,因为张量并行(TP)的通信无法完全重叠:并行块把两个 all-reduce 从"两个串行同步点"变成可以放在加法前后各做一次,调度空间反而变大了。训练侧的次要收益是激活内存略省——中间激活的峰值时段错开了。GPT-NeoX、Falcon 等一批模型跟进采用,成为 PaLM 之后事实标准之一。
为什么几乎不掉点
直觉上"串行改并行"砍掉了 FFN 对 Attn 输出做二次加工的机会,表达力应该受损。实测(PaLM 的消融)困惑度差异在小数点后两三位。为什么?回到上一篇的残差流图景:串行结构里 FFN 读的是 Attn 写回后的总线,并行结构里读的是原始总线——两者的差别只是 FFN 少看了"Attn 这一层的增量"。但注意总线本身是全程可读的:下一层 block 的 Attn 和 FFN 都会看到这个增量(它被加进总线了)。信息没有丢失,只是 FFN 的利用时机推迟了一层。对几十层的网络,这种一层级的时序错位很小。
第二个原因是 LN 的解耦:串行版里两个子层共享一个 LN 统计量(FFN 的 LN 输入受 Attn 输出影响,分布更杂);并行版各用独立的 $\mathrm{LN}_1,\mathrm{LN}_2$,各自输入分布更干净,某种程度上还补偿了错位损失。需要注意它与上一篇"pre-LN"的关系:并行块必须搭配 pre-LN——post-LN 的并行化会在输出端引入两个需要顺序的归一化,结构上就矛盾。
边界与搭配
并行块不是万能优化,三条边界要记牢:
- 质量敏感任务慎用。有些微调/迁移实验报告并行结构在下游任务上有可测但很小的差距;对 base 预训练几乎无感,对容量本来就紧的小模型(< 1B)建议先消融。
- 和张量并行天然契合:Attn 与 FFN 都是"列并行 → 行并行"的标准切分模式,两路并行执行时各段的通信可以流水重叠;这也是 PaLM 在 6144 张 TPU 上跑出 57.8% 硬件利用率的配套之一。
- 与流水线并行无冲突:层间依赖原样保留,流水段按 block 划分不受影响。
小结
并行块是一个典型的"表达力换时延"交易:把 block 内唯一的串行依赖(Attn→FFN)砍掉,让两个子层同槽执行。数学上它把两子层输出从复合改成叠加,残差流视角下信息只是推迟一层被读,所以预训练几乎无损;工程上它压缩关键路径约 15% 吞吐,还给张量并行提供了更好的通信调度空间。前提是 pre-LN——这也是为什么它和上一篇的归一化讨论捆在一起出现。接下来两篇换完全不同的路线:不再改 block 内部,而是把注意力和 KV cache 整个换掉。