把推理成本打下来有四把锤子:量化、分页注意力、投机解码、连续批处理。它们各自砸的是不同的钉子——有的是显存带宽,有的是显存容量,有的是串行依赖,有的是硬件利用率。搞混了就会在错误的地方使劲。
编辑部注 · 本章难度较高,建议在读完前四章后再读。重点是「每个手段针对哪个瓶颈」,而不是记名词。
前面四章建立了三个事实:注意力是平方的、KV Cache 是线性的但占显存、解码是逐步采样的。这一章回答工业界最关心的问题——在这些约束下,怎么把单位 token 的成本压下去。
一、先分清两个阶段,再谈优化
推理分两个阶段,它们的瓶颈完全不同,因此优化手段也不同。这是本章最重要的一张表。
| Prefill(处理输入) | Decode(逐 token 生成) | |
|---|---|---|
| 做什么 | 把整段输入一次算完,建立 KV Cache | 每步生成一个 token,读取已有缓存 |
| 并行度 | 高(n 个位置可并行) | 低(第 t 步必须等第 t−1 步) |
| 主要瓶颈 | 算力(FLOPs) | 显存带宽(Memory Bandwidth) |
| 典型表现 | 首 token 延迟(TTFT)高 | 吞吐(tokens/s)受限,GPU 利用率低 |
| 对应手段 | 算子融合、FlashAttention、分块计算 | 量化、连续批处理、投机解码 |
Decode 阶段的 GPU 利用率通常只有个位数百分比。原因不是算力不够,而是每生成一个 token,都要把整个模型权重和 KV Cache 从显存搬到计算单元一次。数据搬运的时间远超计算时间——这就是为什么 decode 阶段是「显存带宽受限」而非「算力受限」。
结论:针对 decode 的优化,本质上都是在减少搬运量或增加搬运的并行度。量化减少搬运量,连续批处理让一次搬运服务更多请求,投机解码让一次搬运产出更多 token。
二、四把锤子
三、MLA:结构层面的一次改写
除了上面四个「工程手段」,还有一条更彻底的路:改注意力本身的结构。
DeepSeek 提出的 MLA(Multi-head Latent Attention)思路是:不直接缓存完整的 K/V,而是把它们压到一个低维的潜在向量里缓存,用的时候再投影回来。
用前面学过的语言描述:它压的是 KV Cache 这张「线性增长的账单」的系数。
- 常规 MHA:缓存量 ∝
n_layers × n_heads × head_dim - GQA:把
n_heads换成更小的n_kv_heads,系数降到 1/8 左右 - MLA:缓存的是压缩后的潜在向量,维度远小于
n_kv_heads × head_dim,系数进一步下降
这解释了为什么在同样的显存预算下,采用 MLA 的模型能支撑显著更长的上下文与更高的并发。这也是「结构创新本身就是成本创新」的最好例子。
优化的第一步不是选手段,是量瓶颈。没有测量就上手段,等于在黑暗中拧螺丝。本刊编辑部
四、动手:测一次,而不是猜一次
下面这段代码用纯 Python 模拟三种 decode 策略的「搬运次数」,用来建立数量级直觉。真实基准请用专业压测工具,但模型足够说明问题。
"""
用「显存搬运次数」估算 decode 成本。
核心假设:decode 每步都要把权重搬运一遍(带宽受限),
所以「总搬运量 = 步数 × 权重体积」,优化就是减少这两个因子。
"""
def weight_gb(n_params_b: float, bytes_per: int = 2) -> float:
"""权重体积:参数量 × 每参数字节数"""
return n_params_b * 1e9 * bytes_per / 1024**3
def naive_decode(n_params_b, out_tokens, bytes_per=2):
"""朴素逐 token:每一步搬一次全量权重"""
w = weight_gb(n_params_b, bytes_per)
return out_tokens * w, out_tokens # (总搬运 GiB, 前向次数)
def continuous_batching(n_params_b, out_tokens, concurrent, bytes_per=2):
"""连续批处理:一次搬运服务 concurrent 个请求(假设长度对齐)"""
w = weight_gb(n_params_b, bytes_per)
steps = out_tokens # 步数不变
# 但每一步服务 concurrent 个请求,等于摊薄了单位 token 的搬运量
return steps * w, steps * concurrent
def speculative(n_params_b, out_tokens, accept_rate, draft_tokens=4, bytes_per=2):
"""投机解码:小模型起草 k 个,大模型一次前向验证"""
calls = out_tokens / (1 + draft_tokens * accept_rate)
return calls * weight_gb(n_params_b, bytes_per), calls
def quantized(n_params_b, out_tokens, bytes_per, baseline_bytes=2):
"""量化:直接按字节比例缩小搬运量"""
return naive_decode(n_params_b, out_tokens, bytes_per)
print('=== 同一个 70B 模型,输出 512 token,fp16 ===')
base, calls = naive_decode(70, 512)
print(f'朴素 decode 搬运 {base:8.1f} GiB 前向 {calls:.0f} 次')
b, c = continuous_batching(70, 512, concurrent=16)
print(f'连续批处理 ×16 搬运 {b:8.1f} GiB 有效产出 {c:.0f} token')
b, c = speculative(70, 512, accept_rate=0.7)
print(f'投机解码 接受率 0.7 搬运 {b:8.1f} GiB 前向 {c:.0f} 次 ← 少了一半以上的搬运')
b, _ = quantized(70, 512, bytes_per=1)
print(f'fp8 量化 搬运 {b:8.1f} GiB ← 直接减半')- 把上面三个函数组合起来(量化 + 连续批处理 + 投机解码),算出叠加后的搬运量,理解为什么这几种手段可以叠加而不会互相抵消;
- 把
accept_rate从 0.9 降到 0.2,观察投机解码何时变成负优化; - 用一句话回答:为什么连续批处理能提升吞吐但可能恶化单请求延迟?
五、自测
六、小结
| 手段 | 针对的瓶颈 | 主要收益 | 主要代价 |
|---|---|---|---|
| 量化 | 显存容量 + 搬运量 | 装得下、跑得快 | 精度损失 |
| PagedAttention | 显存碎片 | 并发数提升 | 实现复杂度、寻址开销 |
| 连续批处理 | 硬件空转 | 吞吐成倍提升 | 单请求延迟可能变差 |
| 投机解码 | 串行依赖 | 单请求延迟下降 | 低接受率时反而变慢 |
| MLA 类结构优化 | KV Cache 系数 | 长上下文 + 高并发 | 训练与实现复杂度 |
这一章的最终落点是那个决定性的问题:当一个成本问题出现时,你的第一反应是「换个更强的模型」,还是「先量一下瓶颈在哪」? 面试官几乎一定会用这个问题来区分这两类人——而这道题,正好是下一部分(Harness 工程)的入口。
◇ 面试官会怎么问
共 3 条 · 其中 1 条高频 · 先自己答一遍,再展开对照
如果让你把推理成本砍一半,你会动哪几个杠杆? 高频
① 先量瓶颈:是显存不够(并发上不去)、还是首 token 延迟高、还是吞吐被批处理限制?三个瓶颈对应完全不同的杠杆。
② 缓存与上下文侧(几乎无质量损失,优先级最高):提升前缀缓存命中率、裁剪工具结果、压缩历史。
③ 调度侧:连续批处理提高批内利用率、分页管理 KV Cache 降低碎片。
④ 模型侧:量化(FP8/INT8)、分级路由(简单任务走小模型)。
⑤ 采样侧:投机解码用小模型起草、大模型验证。
顺序很关键:能在上下文层解决的问题,不要动到模型精度。
追问链
- 每一档的代价是什么?
- 怎么验证优化没有伤害质量?
量化的代价到底是什么?
① 精度损失,且不是均匀的——对数值敏感的环节(长链推理、精确算术、结构化输出)更容易先崩;
② 长尾能力退化,平均分可能只掉一点,但某些能力会明显下滑,所以必须看分项而不是看总分;
③ 工程复杂度,不同量化格式对推理引擎、算子支持、硬件的要求不同,实际部署时"能不能跑"和"跑得快不快"是两回事。
结论:量化必须配评测,用平均分判断量化是否可行是危险的。
投机解码(speculative decoding)为什么能加速?
因为大模型的一次前向是并行的,验证 k 个 token 的成本接近验证 1 个,所以只要草稿的接受率够高,就能在一次前向上推进多个 token,端到端提速。
前提条件:草稿模型的分布要与目标模型足够接近(否则接受率低、反而更慢),且任务可预测性较强(代码补全、格式化输出这类收益最明显,自由创作收益有限)。
结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。