← 返回画廊

LLM 推理是怎么变快的:从 KV Cache 到投机解码的系统性拆解

面向工程师的推理优化全景图。读完你应该能回答三个问题:LLM 推理慢在哪?每种优化手段到底在优化什么?代价是什么?<br><br>文中的硬件数字以 NVIDIA A100/H100 公开规格为准,模型结构数字以 Llama 系列公开配置为准;标注「量级估算」的地方表示不必纠结个位数精度,抓住数量级即可。


0. 一个反直觉的事实:推理大部分时间 GPU 在「等数据」

很多人对 GPU 的直觉是「算力怪兽,瓶颈在计算」。但对 LLM 的生成阶段来说,这个直觉是错的:大部分时间里,计算单元在空转,等着显存把数据搬过来

理解这一点,需要一个贯穿全文的分析工具:算术强度(Arithmetic Intensity)

算术强度 = 计算量 (FLOPs) / 访存量 (Bytes)

任何一块 GPU 都有两个关键指标:

两者相除得到一个「分界点」(Roofline 模型里的 ridge point):

A100: 312 TFLOPS / 2039 GB/s ≈ 153 FLOPs/Byte

含义:你的程序每从显存读 1 字节,至少要做约 153 次浮点运算,才能把算力吃满。算术强度低于这个值,就是内存带宽瓶颈(memory-bound)——GPU 再强也快不起来,因为它在等数据;高于这个值才是算力瓶颈(compute-bound)

H100 的这个比值更极端(FP16 约 989 TFLOPS / 3.35 TB/s ≈ 295 FLOPs/Byte)。硬件迭代中算力增长快于带宽增长,所以「喂饱 GPU」只会越来越难。

记住这个数字,下面所有优化手段的动机都从它出发。


1. Prefill 与 Decode:一次推理的两副面孔

一次 LLM 推理请求分两个阶段:

1.1 Prefill(预填充):处理你的输入

用户发来一段 prompt(比如 2000 个 token),模型需要对这 2000 个 token 做一次完整的前向传播,产出第一个输出 token。

关键特点:2000 个 token 可以并行处理。它们组成一个 [2000, hidden_dim] 的大矩阵,与权重做矩阵乘。权重从显存读一次,被 2000 行输入复用——算术强度很高,是典型的 compute-bound。GPU 火力全开,这一阶段的耗时决定了首 token 延迟(TTFT, Time To First Token)

1.2 Decode(解码):逐个吐字

从第二个输出 token 开始,模型进入自回归循环:拿上一个 token,前向一次,得到下一个 token;再前向一次……每次只处理 1 个 token

这就是灾难所在。做个粗略估算(量级估算):

对参数量为 P 的稠密模型,生成 1 个 token 的计算量约为 2P FLOPs(每个参数参与一次乘加)。而访存量呢?每生成一个 token,都必须把全部权重从显存完整读一遍(权重太大,放不进片上缓存)。FP16 下权重占 2P 字节。于是:

decode (batch=1) 的算术强度 ≈ 2P FLOPs / 2P Bytes = 1 FLOP/Byte

1,对比分界点 153。低了两个数量级。此时 A100 的实际算力利用率只有 1% 左右,99% 的时间在等显存。

由此可以直接推出 batch=1 时生成速度的理论上限:

每 token 耗时 ≥ 权重字节数 / 显存带宽

例:7B 模型,FP16 权重 14 GB,A100 带宽 ~2 TB/s
    每 token ≥ 14 / 2039 ≈ 6.9 ms  →  上限约 145 token/s

实测 vLLM 等框架跑 7B 模型单请求能到 100+ token/s,正好是这个上限的七八成——验证了 decode 就是在「用带宽换 token」

类比:prefill 像一辆坐满 50 人的大巴跑一趟高速(油钱摊薄,效率高);decode 像同一辆大巴每趟只载 1 个乘客,来回跑几百趟。车(算力)没变,浪费在「跑趟」(搬运权重)上了。

1.3 两阶段的优化目标不同

PrefillDecode
瓶颈算力 (compute-bound)显存带宽 (memory-bound)
用户感知指标首 token 延迟 TTFT吐字速度 TPOT (Time Per Output Token)
优化思路减计算:FlashAttention、chunked prefill、prefix caching减访存:量化、batch、投机解码

后文的每个技术,都可以问一句:它在优化哪个阶段的哪个瓶颈?


2. KV Cache:一切的起点

2.1 为什么需要它

Transformer 的自注意力里,第 t 个 token 要和前面所有 token 的 Key/Value 做交互:

Attention(q_t, K_{1..t}, V_{1..t}) = softmax(q_t · K^T / √d) · V

关键观察:由于因果掩码(causal mask),历史 token 的 K、V 一旦算出来就永远不变。如果每生成一个新 token 都从头对整个序列重算一遍,生成第 t 个 token 的成本是 O(t),生成长度为 n 的序列总成本 O(n²) 次完整前向——完全不可接受。

KV Cache 就是把每层算出的 K、V 存下来,decode 时每步只为新 token 算一份新的 K/V 追加进去,再和缓存做 attention。这样每步的前向就只处理 1 个 token,整体从 O(n²) 降到 O(n)。

它不是「锦上添花的优化」,而是自回归推理能够实用的前提。所有现代推理框架默认开启,后续一切优化(分页、量化、GQA、投机解码)都建立在它之上——所以说它是起点。

2.2 显存占用怎么算(这个公式值得背下来)

KV Cache 字节数 =
  2 (K 和 V)
  × num_layers(层数)
  × num_kv_heads(KV 头数)
  × head_dim(每头维度)
  × seq_len(序列长度)
  × dtype 字节数
  × batch_size

代入几个真实模型(FP16,即 2 字节):

Llama-2-7B(32 层,32 个 KV 头,head_dim=128,标准多头注意力 MHA):

每 token: 2 × 32 × 32 × 128 × 2 B = 512 KiB
4096 token 的一条序列: 512 KiB × 4096 = 2 GiB

一条 4K 上下文的对话就吃掉 2 GB 显存——而整个模型权重才 14 GB。如果想同时服务 32 个这样的请求,光 KV Cache 就要 64 GB,一张 A100 直接爆掉。

Llama-3-8B(32 层,但 KV 头从 32 降到 8,用了 GQA):

每 token: 2 × 32 × 8 × 128 × 2 B = 128 KiB   (只有 Llama-2 的 1/4)
8K 上下文一条序列: 1 GiB

Llama-3-70B(80 层,8 个 KV 头):

每 token: 2 × 80 × 8 × 128 × 2 B = 320 KiB
128K 上下文一条序列: 320 KiB × 131072 ≈ 40 GiB

看到问题了吗?长上下文时代,KV Cache 不是权重的零头,而是和权重同量级甚至更大的显存消耗者,而且它随并发数和上下文长度线性增长,权重却是固定的。

2.3 顺着这个公式做减法:MQA / GQA / MLA

公式里每一项都是优化的靶子:

一句话心智模型:KV Cache 是「用显存换计算」的经典交易。后续所有 KV 相关优化,都是在讨价还价——能不能少付点显存,同时别把计算的账单加回去太多。


3. 显存与批处理:PagedAttention 和 Continuous Batching

第 1 节说 decode 是带宽瓶颈:读一遍 14 GB 权重只服务 1 个 token 太亏。最直接的解法是批处理(batching):一次读权重,同时为 32 个请求各算 1 个 token——权重访存摊薄 32 倍,吞吐量接近提升 32 倍,而每个请求的延迟几乎不变(因为原本瓶颈就不在计算)。

Batch size 是推理吞吐最重要的旋钮。 而能开多大 batch,取决于显存里还能塞下多少 KV Cache。于是问题转化为:怎么高效管理 KV Cache 显存?2023 年之前的系统在这里浪费严重,两项技术改变了局面。

3.1 PagedAttention:给 KV Cache 上一套「虚拟内存」

老方案的问题:KV Cache 随生成逐 token 增长,但需要在显存里连续存放。系统不知道每个请求最终会生成多长,只好按「最大可能长度」(比如 2048 或 4096)预分配一整块连续显存。结果:

  1. 内部碎片:请求实际只生成了 200 个 token,剩下 3800 多个 token 的预留空间全程空置。
  2. 外部碎片:不同请求预留块大小不一,释放后在显存里留下无法利用的空洞。

vLLM 论文(Kwon et al., SOSP 2023)实测:在当时的主流系统里,KV Cache 显存的实际利用率只有 20%~40%,也就是 60%~80% 被浪费

PagedAttention 的解法照抄了操作系统教科书:分页

代价很小:attention kernel 需要按块表做间接寻址,实现更复杂,有个位数百分比的 kernel 开销(量级估算),但换来的 batch size 提升让吞吐提高 2~4 倍(vLLM 论文对比 Orca 的数据)。

3.2 Continuous Batching:别让快的等慢的

有了显存,还要解决调度问题。静态批处理的做法是:攒 32 个请求组成一批,一起跑到全部结束。问题显而易见——有的请求生成 20 个 token 就完了,有的要生成 2000 个,先完成的请求得陪跑到最后,期间它占着的算力和显存全在空转。

Continuous batching(连续批处理,源自 Orca 论文的 iteration-level scheduling,OSDI 2022)把调度粒度从「整个请求」降到「单次迭代」:

GPU 的 batch 因此始终保持饱满。它和 PagedAttention 是天作之合:一个保证「随时能插入新请求」在调度上可行,一个保证在显存上可行。这两项合起来,是 vLLM 相比朴素 HuggingFace 推理拿到 10 倍以上吞吐提升的主要来源。

再往下还有一个精细化问题:新请求的 prefill 是一大坨 compute-bound 计算,直接插进 decode 批次会让所有人卡一下(decode 延迟抖动)。Chunked prefill 把长 prefill 切成小段,和 decode 步混合执行,抹平抖动;更彻底的方案是 P/D 分离(disaggregated prefill/decode)——用不同的 GPU 池分别跑 prefill 和 decode,各自按自己的瓶颈配置硬件,KV Cache 通过高速互联传输(DeepSeek、Mooncake 等生产系统的做法)。

一句话心智模型:这一节全部内容 = 操作系统课的复仇。PagedAttention 是虚拟内存分页,continuous batching 是抢占式调度,prefix caching 是共享内存页。


4. 量化:既省显存,又「直接」提速

4.1 为什么量化对 decode 特别有效

一般理解量化是「省显存的有损压缩」。这没错,但漏掉了对推理更关键的一点:decode 的速度上限 = 权重字节数 / 带宽(第 1 节的公式)。权重从 FP16 压到 INT4,字节数变成 1/4,每 token 的访存量直接变成 1/4,理论生成速度提高近 4 倍——不是靠算得快,是靠读得少。

这也解释了一个实践中的现象:weight-only 量化(只压权重,计算时反量化回 FP16 再算)在小 batch 下就有实打实的加速,尽管它增加了反量化的计算——因为 decode 本来就算力大量闲置,多算一点无所谓,省下的带宽才是真金白银。

4.2 主流方案与取舍

按「量化什么」分两大类:

A. Weight-only(只量化权重)——针对小 batch / decode 场景:

B. Weight + Activation(权重和激活都量化)——针对大 batch / prefill / 高吞吐场景:

C. KV Cache 量化:把缓存的 K/V 存成 FP8/INT8/INT4。同样显存直接省一半到 3/4,能开更大 batch 或更长上下文;FP8 KV 基本无损,INT4 KV 在长文本任务上需要实测把关。

4.3 决策表

方案位宽显存加速场景精度风险适用
FP16/BF1616基线显存充裕、追求无损
FP8 (W8A8)8~0.5×prefill + decode 都加速很低有 H100/H200,首选
INT8 SmoothQuant8~0.5×大 batch 吞吐低,需校准A100 等无 FP8 硬件
GPTQ / AWQ (W4A16)4~0.3×小 batch decode中,4bit 需评测单卡跑大模型、本地部署
KV Cache FP8/INT8KV 减半+更大 batch/更长上下文与上述任意方案叠加

经验法则:7B 以下的小模型对 4bit 更敏感,70B 级别的大模型 4bit 后往往仍强于同显存下能放的 FP16 小模型——「大模型 + 低精度」通常优于「小模型 + 高精度」,但这是一条经验规律而非定理,上线前必须用自己的评测集验证,尤其是数学、代码这类对精度敏感的任务。


5. 投机解码:让「串行」也能并行

前面的手段都在「摊薄」访存(batch)或「减少」访存(量化),但没有动一个根本约束:decode 是串行的,第 t+1 个 token 必须等第 t 个出来才能算。投机解码(speculative decoding,Leviathan et al. 与 Chen et al. 于 2023 年分别提出)直接攻击这个约束。

5.1 核心思路:便宜地猜,昂贵地验

关键洞察有两个:

  1. 验证比生成便宜。让大模型「检查 5 个给定的 token 对不对」,只需一次前向(5 个 token 并行进网络,像一次 mini-prefill);而「生成 5 个 token」需要 5 次串行前向。每次前向的主要成本是读一遍权重,所以验证 5 个 ≈ 1/5 的生成成本。
  2. 很多 token 很好猜。语言有大量低熵片段——代码的缩进和括号、"United" 后面的 "States"、格式化的模板文字。这些用小模型猜也八九不离十,何必让 70B 大模型逐个亲自写?

于是流程变成:

循环:
  1. 草稿模型(如 1B)快速串行生成 k 个候选 token(如 k=5)
  2. 目标模型(如 70B)一次前向,并行验证这 k 个 token
  3. 从左到右接受,遇到第一个「不合格」的 token 停下,
     由目标模型在该位置给出修正 token
  4. 回到 1

最好情况:一次大模型前向产出 k+1 个 token;最差情况:草稿全被拒,也能产出 1 个 token(大模型的修正)——永远不会比不用投机更慢地产出 token(不计草稿开销时;实际上草稿开销存在,见 5.3)。

5.2 为什么「完全不损失质量」——不是玄学,是数学

这是投机解码最漂亮的地方。它不是「小模型答案差不多就凑合用」,而是通过投机采样(speculative sampling,一种修正的拒绝采样)保证:输出序列的概率分布与只用目标模型逐 token 采样完全一致

规则(对每个草稿 token x,草稿模型概率 q(x),目标模型概率 p(x)):

以 min(1, p(x)/q(x)) 的概率接受 x
若拒绝:从修正分布 norm(max(0, p - q)) 中重新采样一个 token

直觉:草稿模型比目标模型「更爱」这个 token(q > p)时,按比例打折接受,多出来的概率质量被拒绝路径上的修正分布精确补回。两条路径合起来,每个 token 的边际分布严格等于 p。这是可以三行代数验证的恒等式,不是近似。

所以投机解码是本文唯一一个数学上零质量损失的加速手段(贪心解码下则退化为简单规则:草稿 token 等于目标模型的 argmax 就接受)。

5.3 加速比与代价

设草稿 token 的平均接受率为 α,每轮草稿长度 k,则每次目标模型前向平均产出的 token 数约为:

E[tokens/轮] = (1 - α^(k+1)) / (1 - α)

α = 0.8、k = 5 时约 3.7 个 token/轮,扣掉草稿模型自身的开销(通常是目标模型的 5%~10%),端到端加速 2~3 倍是文献和生产中的典型区间(量级估算)。

代价与适用条件:

类比:资深工程师(大模型)审代码远快于自己写代码。让实习生(草稿模型)先写,工程师批量 review,通过的直接合入,不通过的从出错处自己改——只要实习生靠谱率够高,团队产出翻倍,且代码质量标准丝毫未降(每一行都经过了工程师之手)。


6. 三个专题:FlashAttention、MoE、长上下文

6.1 FlashAttention:不减少计算,只减少搬运

朴素 attention 实现的问题不在计算量,在中间结果S = QK^T 是一个 [n, n] 矩阵,n=8192 时单头就是 8192² × 2 B = 128 MiB,必须写回 HBM,softmax 后再读回来乘 V。整个过程对 HBM 读写 O(n²) 的数据——又撞上带宽墙,且 n 越长越严重。

FlashAttention(Dao et al., 2022)的做法:

效果:HBM 访存从 O(n²) 降到 O(n²/M)(M 为 SRAM 块大小相关因子),attention kernel 提速 2~4 倍,且 n² 的中间显存彻底消失——长上下文的 prefill 因此才可行。它是「IO-aware 算法设计」的教科书案例:算得一样多,搬得少得多。FlashAttention-2/3 进一步优化了并行划分和对 Hopper 硬件特性(TMA、FP8)的利用。

顺带一提,第 3 节的 PagedAttention 和这里是正交的:FlashAttention 管「片上怎么算」,PagedAttention 管「HBM 里怎么存」,现代框架两者同时用(如 FlashInfer、vLLM 的融合 kernel)。

6.2 MoE 推理:参数很多,干活的很少

MoE(Mixture of Experts)把 FFN 层换成 N 个「专家」,每个 token 由路由器选 top-k(通常 k=1~2,加共享专家)个专家处理。比如 Mixtral 8x7B:总参数 47B,每 token 激活约 13B。

推理特性因此很拧巴:

所以 MoE 的交易是:用显存容量换「同等速度下更高的模型质量」。这带来两个工程后果:

  1. 小 batch 是甜点,大 batch 有摩擦:batch 大了之后,不同 token 路由到不同专家,一个 batch 几乎会激活所有专家——权重访存的摊薄效果比稠密模型差;且专家负载不均衡时,最忙的专家决定整批的延迟。
  2. 天然适合大规模专家并行:把专家分散到多卡/多机(expert parallelism),token 通过 all-to-all 通信找自己的专家。DeepSeek-V3 这类细粒度 MoE(每层 256 个小专家激活 8 个)在大集群上反而能做出极低的单 token 成本——但这是「数据中心级」玩法,通信工程复杂度极高。

对单机部署者的实用结论:MoE 模型选型时,显存按总参数算,速度按激活参数估,两头都要过一遍。

6.3 长上下文的代价:一笔三重账

上下文从 4K 拉到 128K,付出的不是一种代价,是三种:

  1. Prefill 计算,O(n²):attention 计算量随长度平方增长。128K 是 4K 的 32 倍长,attention 计算量是 ~1000 倍。即使有 FlashAttention(它降访存不降计算量),128K 输入在单卡上的 TTFT 也是几十秒级(量级估算)——这就是长文档场景「转圈久」的根源,也是 prefix caching(缓存复用历史对话/文档的 KV,命中时跳过重算)成为多轮对话和 Agent 场景刚需的原因。
  2. KV Cache 显存,O(n):第 2 节算过,Llama-3-70B 一条 128K 序列的 KV Cache 约 40 GiB。长上下文直接吃掉并发度:显存装得下 100 个 4K 请求的 KV,只装得下 3 个 128K 请求。
  3. Decode 每步访存,O(n):decode 时每步除了读权重,还要读整个 KV Cache。当 KV Cache(40 GiB)超过权重分片大小时,decode 瓶颈从「读权重」变成「读 KV」,吐字速度随上下文变长显著下降。

所以长上下文优化是一个专门的军备竞赛方向:GQA/MLA 压 KV 体积、KV 量化、滑动窗口/稀疏 attention(如 NSA、MInference 类方法)砍 attention 范围、KV 卸载到 CPU/SSD 分层存储、prefix caching 避免重算。使用侧的朴素建议:不要把 128K 窗口当免费午餐,RAG 检索出 4K 相关内容,往往比塞 100K 原文又快又好又便宜


7. 总结对照表

优化手段攻击的瓶颈核心思路典型收益(量级)代价 / 局限
KV Cachedecode 重复计算缓存不变的历史 K/V,用显存换计算使自回归生成从 O(n²) 前向降为 O(n),是可用性前提显存随上下文和并发线性增长,成为新瓶颈
GQA / MQA / MLAKV Cache 显存 + decode 读 KV 带宽减少/压缩需要缓存的 KVKV 缩小 4~30 倍需训练时采用,无法给现成 MHA 模型后装;MLA 增加少量计算
PagedAttentionKV 显存碎片浪费分页 + 块表,按需分配、支持共享显存利用率 20~40% → 90%+,batch 更大kernel 需间接寻址,实现复杂度高
Continuous batchingGPU 空转(快请求陪跑慢请求)按「迭代」而非「请求」调度,随时进出吞吐 2~10 倍(对比静态 batch)调度器复杂;prefill 插入引起 decode 抖动(需 chunked prefill 缓解)
量化 (weight-only, INT4)decode 读权重带宽 + 显存权重压到 1/4,读得少所以快显存 ~1/4,小 batch 提速 2~3 倍有损,需评测把关;大 batch 下反量化开销显现
量化 (FP8/INT8 W8A8)算力 + 带宽 + 显存用低精度 Tensor Core,算存双降吞吐接近 2 倍FP8 需 Hopper+ 硬件;INT8 需离群值处理(校准)
KV Cache 量化KV 显存 + decode 读 KV 带宽缓存用 FP8/INT4 存KV 显存降 2~4 倍 → batch/上下文更大低位宽下长文本精度需实测
投机解码decode 的串行性小模型猜、大模型并行验,拒绝采样保分布延迟降 2~3 倍,数学上零质量损失需草稿模型/额外头;吃闲置算力,大 batch 下收益消失;接受率看任务
FlashAttentionattention 的 HBM 访存(尤其长序列 prefill)tiling + online softmax,中间结果不落 HBMattention kernel 快 2~4 倍,省 O(n²) 中间显存精确无损;仅优化 attention 部分,kernel 与硬件强绑定
MoE 架构「质量 vs 每 token 计算量」的根本权衡参数多、激活少同等速度下模型质量更高显存按总参数付费;专家负载不均;大规模需专家并行+复杂通信
Prefix caching重复 prefill 计算(多轮对话/共享 prompt)缓存并复用历史 KV命中时 TTFT 大幅下降占显存/需分层存储;依赖前缀完全匹配
P/D 分离prefill 与 decode 瓶颈异质、互相干扰两阶段分池部署,KV 跨机传输消除干扰,两侧独立扩缩容需高速互联传 KV,系统复杂度显著提高

8. 收尾:一个可复用的心智模型

把全文压缩成四条,以后遇到任何新的推理优化论文,都可以往这个框架里放:

  1. 先问阶段:它优化 prefill(compute-bound,TTFT)还是 decode(memory-bound,吐字速度)?两者的药方几乎不通用。
  2. decode 的一切围绕「每 token 搬多少字节」每 token 耗时 ≈ (权重字节 + KV 字节) / 带宽 / batch摊薄。量化减小分子,batching 摊薄分子,GQA/MLA/KV量化减小 KV 项,投机解码则减少「搬运的次数」。
  3. 显存是吞吐的隐形上限:batch 开多大取决于 KV Cache 塞得下多少,所以 PagedAttention 这种「不碰计算、只管内存」的工作能带来数倍吞吐提升。
  4. 免费午餐分两种:严格无损的(KV Cache、FlashAttention、PagedAttention、投机解码——数学等价,只是算得/搬得更聪明)和统计近似的(量化、稀疏 attention——通常没事,但上线前必须评测)。分清这两类,是判断一项优化能不能闭眼上的关键。

参考文献(关键结论的出处)

本文由 AI 撰写。文中标注「量级估算」的数字用于建立直觉,工程决策请以目标硬件上的实测为准。