For the complete documentation index, see llms.txt. This page is also available as Markdown.

10.1 推理瓶颈分析:计算密集还是访存密集

理解推理优化的第一步,是弄清楚大语言模型推理的瓶颈到底在哪里。本节把 prefill 与 decode 两个阶段分开算账,并用 Roofline 图把「访存密集」变成一个可定量的判据。第九章末尾把扩散语言模型的收益归结为「瓶颈转换,而不是计算量下降」,并说这与 FlashAttention、投机解码是同一个模式;但要判断一次优化究竟转换了什么瓶颈,得先有这样一把尺子。

10.1.1 两个阶段的不同特性

LLM 推理可分为两个截然不同的阶段:

预填充阶段(Prefill):处理用户输入的所有词元,一次性计算它们之间的注意力。这个阶段是计算密集型的——GPU 的算力是瓶颈,类似训练中的前向传播。

生成阶段(Decode/Generation):逐个生成新的词元,每次只计算一个新词元与之前所有词元的注意力。这个阶段是访存密集型(Memory-Bound)的——每生成一个词元,都需要从 GPU 显存中加载完整的模型权重和 KV 缓存,但实际计算量很小。

10.1.2 访存瓶颈的数学解释

在生成阶段,每个词元的计算涉及:

  • 加载所有层的权重矩阵(总大小 = 模型参数量 × 字节数/参数)

  • 加载 KV 缓存(总大小随已生成序列长度线性增长)

  • 实际的矩阵-向量乘法运算

以一个 70B 参数的 FP16 模型为例:完整权重约 140 GB,而单张 H100 SXM 只有 80 GB 显存,因此这个模型不能在单张 80 GB H100 上以 FP16 完整驻留。若把 140 GB ÷ 3.35 TB/s 作为理论下界,纯权重读取也约需 42 毫秒;真实部署通常需要多卡张量并行、H100 NVL/H200/B200 级别显存、量化或分层卸载,并且还要计入 KV 缓存读取、跨卡通信和 batch 形状。这个例子的重点不是推荐单卡部署,而是说明 decode 阶段对显存带宽极其敏感。

这就是为什么生成阶段的算术强度(每字节访存对应的浮点运算数)极低,GPU 的计算单元大部分时间在等待数据加载。

把算术强度放进 Roofline 图

「算术强度低」这句话可以画成一张图,从而变成可定量的判据。Roofline 模型把硬件抽象成两条边界:横轴是算术强度 II(FLOPs/Byte),纵轴是可达到的浮点吞吐。当 II 很小时,性能被带宽卡住,上界是一条斜率为峰值带宽的直线 I×BpeakI\times B_{\text{peak}};当 II 足够大时,性能被算力卡住,上界是一条高度为 FpeakF_{\text{peak}} 的水平线。两条线的交点称为拐点(ridge point):

Iridge=FpeakBpeakI_{\text{ridge}} = \frac{F_{\text{peak}}}{B_{\text{peak}}}

以 H100 SXM 为例,BF16 稠密峰值约 990 TFLOPs/s、显存带宽 3.35 TB/s,拐点约 295 FLOPs/Byte。这个数字解释了一切:decode 阶段每加载 2 字节的 BF16 权重只做一次乘加(2 FLOPs),算术强度约为 1,比拐点低两个多数量级——工作点深深地钉在斜线上,离水平的算力屋顶还差得远。

由此可以直接读出批处理为什么有效:decode 的算术强度大致正比于批大小。权重只需加载一次,却可以同时服务批内所有请求,于是分子(FLOPs)随批大小线性增长而分母(权重访存量)几乎不变。加大批就是把工作点沿着那条斜线向右推,一路逼近拐点——这正是连续批处理(11.2 节)能把吞吐提上去的第一性原因。

同样能读出它的代价:批越大,单个请求要等的同批伙伴越多、每步的计算也越重,TPOT(每输出词元时间)随之变差。吞吐与延迟的取舍在 Roofline 图上就是「工作点往右推多远」这一个自由度。而 prefill 阶段是矩阵-矩阵乘法,算术强度天然就高、工作点本来就在水平段上,所以它对批大小远没有这么敏感——这也是为什么把两个阶段拆开调度会有收益(11.3 节)。

10.1.3 优化方向

基于这一分析,推理优化技术大致可分为三类:

  1. 减少访存量:量化(用更少的位数存储权重)、剪枝(减少参数数量)

  2. 减少重复计算:KV 缓存(避免重新计算之前词元的注意力)

  3. 提高并行度:投机解码(一次验证多个词元)、连续批处理(更好地利用 GPU 计算资源)

10.1.4 另一层瓶颈:执行中的空转

上面两小节讨论的是单卡内部的瓶颈:算术强度太低,计算单元等着显存喂数据。但真实集群里还有一层性质完全不同的浪费——GPU 明明处在“执行中”,功耗很高,可见活动却接近零。CMU 的一项实测研究把这个状态命名为 execution-idle(执行时空转),并指出它与 CPU 的关键差异:GPU 即使在可见活动几乎为零时也可能维持高功耗,因此这段时间既不产出,也不省电。

该研究对一个学术 AI 集群做了逐秒遥测,覆盖 756 张 NVIDIA GPU(200 张 A6000、52 张 RTX 6000 Ada、408 张 L40(S)、64 张 A100、24 张 H100、8 张 B200)、31 天。总体口径是:execution-idle 占在执行时间的 19.7%能耗的 10.7%。论文另在单张 L40S 上回放五组公开 trace(采用 replay-specific accounting,与上面的集群遥测是两组实验),各 trace 的分化很大(时间/能耗):

负载
时间占比
能耗占比

Azure Chat

29%

17%

BurstGPT Chat

72%

52%

Azure Code

76%

65%

Qwen Reason

18%

8%

Qwen Chat

14%

7%

分化的主因是请求到达模式而非任务类别:Azure Code(76%/65%)远高于 Qwen Chat(14%/7%),但同为 chat 的 BurstGPT 也高达 72%/52%。空转的成因与 10.1.1 的阶段划分直接相关:prefill 和 decode 交替越频繁、请求间隔越不规则,GPU 就越常回到“通电但没活干”的状态。

这一层瓶颈的解法不在本章——本章的量化、剪枝、KV 缓存都优化的是“单次计算怎么更省”,而空转要靠调度来消除:让请求之间不留空隙(连续批处理,11.2)、让 prefill 和 decode 不互相拖累(分离式服务,11.3)。把两层放在一起看才完整:单卡把每次计算做到硬件极限,调度层保证卡不闲着

上表是该研究在其特定集群与回放 trace 下的快照,不是所有部署的通用比例;论文本身也报告了跨五组回放 7%–65% 的能耗区间。引用时应连同集群构成和负载类型一起说明(见附录 A.4 参考文献,以及附录 A.5 对新近论文快照数字的记录要求)。

最后更新于