4、性能瓶颈分析:为什么 SwiGLU 是推理瓶颈?

好,咱们直接切入正题。

SwiGLU 这个算子,说白了就是 LLaMA 系列模型里那个又爱又恨的角色。爱它,是因为它确实能提升模型效果;恨它,是因为它在推理时真的太慢了。我刚开始接触大模型推理优化时,第一个让我头疼的算子就是它。

为什么会这样?

咱们得从计算和访存这两个维度来拆解。

4.1 访存密集型 vs 计算密集型

先问个问题:你平时写 CUDA 或者做算子优化时,最怕什么?

我个人最怕的是「计算没算多少,数据搬运却花了大把时间」。SwiGLU 就是这种典型。

咱们来看看 SwiGLU 的计算流程:

// SwiGLU 伪代码
hidden_states = linear_gate(x)   // 门控线性层
hidden_states = silu(hidden_states) // SiLU 激活函数
hidden_states = hidden_states * linear_up(x) // 逐元素乘
output = linear_down(hidden_states) // 输出线性层

你看,这里面有三个线性层(矩阵乘法),一个 SiLU 激活,一个逐元素乘。矩阵乘法本身是计算密集型的,但问题出在哪?

问题出在「中间结果」上。

每次做完一个线性层,都会产出一个巨大的中间张量。比如 hidden_size=4096,intermediate_size=11008,那中间结果就是 [batch, seq_len, 11008]。这个张量有多大?

参数 数值
batch_size 1
seq_len 2048
intermediate_size 11008
数据类型 float16 (2 bytes)
单次中间结果大小 1 × 2048 × 11008 × 2 ≈ 45 MB

45 MB!这还只是单层。LLaMA-7B 有 32 层,你想想看,光中间结果就要搬来搬去多少次?

核心矛盾:SwiGLU 的计算量其实不算大,但它的访存量太大了。每次计算都要把中间结果写回显存,下次计算再读出来。这种「写-读」模式,让显存带宽成了瓶颈。

4.2 为什么它成了推理瓶颈?

我记得有一次在优化 LLaMA-13B 的推理时,用 Nsight Systems 做了 profiling。结果让我很惊讶——SwiGLU 的三个线性层加起来只占了 30% 的 GPU 时间,但数据搬运(memcpy 和 DMA)占了 60% 以上。

说白了,GPU 的计算单元大部分时间都在「等数据」。这就像你请了个顶级大厨,结果食材半天送不到,大厨只能干等着。

具体来说,瓶颈体现在三个方面:

  • 中间结果爆炸:intermediate_size 通常是 hidden_size 的 2.5~4 倍(LLaMA 是 11008/4096 ≈ 2.69 倍)。这意味着中间结果比输入输出大得多。
  • 算子碎片化:一个 SwiGLU 被拆成了 5 个小算子(3个 matmul + 1个 silu + 1个 mul)。每个小算子都要启动一次 kernel,启动开销 + 数据搬运开销叠加起来很可观。
  • 无法融合:silu 和 mul 虽然可以融合,但和前面的 matmul 很难融合。因为 matmul 的输出需要先写回显存,才能被下一个算子读取。

避坑指南:我曾经天真地以为,把 SwiGLU 的三个线性层合并成一个大的矩阵乘法就能解决问题。结果发现,合并后虽然减少了 kernel 启动次数,但中间结果反而更大了,访存压力一点没减。嗯,这条路走不通。

4.3 计算密集型 vs 访存密集型的权衡

你可能会问:「那矩阵乘法不是计算密集型吗?为什么 SwiGLU 反而成了访存密集型?」

好问题。咱们来看一个对比:

算子 计算量 (FLOPs) 访存量 (Bytes) 计算访存比 瓶颈类型
单个大矩阵乘法 (4096x4096) ~137 GFLOPs ~134 MB ~1024 计算密集型
SwiGLU 整体 ~200 GFLOPs ~400 MB ~500 访存密集型

你看,SwiGLU 的计算访存比只有大矩阵乘法的一半。这意味着同样的计算量,SwiGLU 需要搬运两倍的数据。在显存带宽固定的情况下,SwiGLU 自然就成了瓶颈。

我个人习惯用「屋顶线模型」(Roofline Model)来分析这类问题。SwiGLU 在屋顶线图上,通常落在「访存受限区」,而不是「计算受限区」。

注意:这个结论在 batch_size=1 时尤其明显。当 batch_size 增大时,计算量会线性增长,而访存量增长较慢,瓶颈会逐渐从访存转向计算。但在大模型推理场景下,尤其是流式输出时,batch_size 通常很小(1~4),所以 SwiGLU 的访存瓶颈非常突出。

4.4 一张图看懂 SwiGLU 的瓶颈

下面我用一张流程图来展示 SwiGLU 的数据流动和瓶颈点:

SwiGLU 数据流与瓶颈分析 输入 hidden_states linear_gate (矩阵乘) linear_up (矩阵乘) SiLU 激活 中间结果 (45MB) 逐元素乘 (mul) 瓶颈1 中间结果 写回显存 瓶颈2 kernel碎片化

从这张图可以清楚看到:

  • 红色框是矩阵乘法,计算量大但访存也大
  • 灰色虚线框是中间结果,每次都要写回显存
  • 红色虚线标注了两个主要瓶颈点

4.5 小结

总结一下,SwiGLU 成为推理瓶颈的根本原因:

  1. 中间结果太大——intermediate_size 是 hidden_size 的 2.69 倍,导致访存压力剧增
  2. 算子碎片化——5 个小算子各自为战,kernel 启动开销和数据搬运开销叠加
  3. 计算访存比低——在屋顶线模型上落在访存受限区

嗯,知道了问题在哪,下一步就是怎么优化了。我个人觉得,SwiGLU 的优化核心就两个字:融合。把能融合的算子尽量融合,减少中间结果的搬运次数。具体怎么融合,咱们下一节再聊。

一句话记住:SwiGLU 不是算得慢,而是搬得慢。优化方向就是减少数据搬运,让 GPU 的计算单元别闲着。


专注资料整理