> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/llm_internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/llm_internals/di-san-bu-fen-tui-li-yu-bu-shu-pian/10_inference_optimization/10.1_bottleneck.md).

# 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 模型**把硬件抽象成两条边界：横轴是算术强度 $$I$$（FLOPs/Byte），纵轴是可达到的浮点吞吐。当 $$I$$ 很小时，性能被带宽卡住，上界是一条斜率为峰值带宽的直线 $$I\times B\_{\text{peak}}$$；当 $$I$$ 足够大时，性能被算力卡住，上界是一条高度为 $$F\_{\text{peak}}$$ 的水平线。两条线的交点称为**拐点**（ridge point）：

$$I\_{\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 节](/llm_internals/di-san-bu-fen-tui-li-yu-bu-shu-pian/11_serving/11.2_continuous_batching.md)）能把吞吐提上去的第一性原因。

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

## 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%**。更值得注意的是它在不同负载上的分化（时间/能耗）：

| 负载            | 时间占比 | 能耗占比 |
| ------------- | ---: | ---: |
| Azure Chat    |  29% |  17% |
| BurstGPT Chat |  72% |  52% |
| Azure Code    |  76% |  65% |

代码类负载的空转比例远高于聊天类，原因与 10.1.1 的阶段划分直接相关：这类请求的 prefill 和 decode 交替更频繁、请求间隔更不规则，GPU 反复回到“通电但没活干”的状态。

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

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