推理优化 · KV Cache 与批处理
先建立判断:显存、并发和吞吐互相制约。公式和实现细节看全量笔记。
0. 总图
自回归每出下一个 token,都要和已经出现过的所有 token 做注意力。K、V 只由历史决定,算过就不变。
- 不缓存:每生成一个字,整段历史的 K/V 重算一遍,大约 O(n²)。
- KV Cache:Prefill 把 prompt 的 K/V 算一次并存下;Decode 只算新 token,K/V 追加。用显存换时间。
权重是固定参数;KV 是这次请求的中间结果,请求结束或超出窗口就丢掉(除非前缀复用)。
三层不要说成互相替代:
| 层 | 解决什么 | 不解决什么 |
|---|---|---|
| KV Cache | 重复算历史注意力 | 显存怎么摆、短请求怎么不占满坑 |
| PagedAttention | KV 碎片、按最大长度预留的浪费 | GPU 空等(那是调度) |
| Continuous Batching | 静态 batch 等最长请求、卡白烧 | 凑不出连续大块时新请求进不去(要分页) |
口条:缓存换时间,分页省显存,持续批填满卡。
先查什么
效果差时先分清坏在哪一层:
- 算力被历史吃掉 — 没开 KV,或前缀每次重算 → 看缓存 / Prefix Caching。
- 显存占满但并发上不去 — 按最大长度连续预留,短请求旁边一大段空槽 → 看 PagedAttention。
- 吞吐不涨、短请求空等 — 还在齐步走的静态 batch → 看 Continuous Batching。
- 总空闲够,新请求仍排队 — 外部碎片,凑不出连续 KV → 分页支撑插队,不是把 batch 再开大。
何时用、何时先别动
- KV Cache:生成式服务的默认前提。没有它,长输出会越写越慢。
- PagedAttention:生产吞吐要上去时几乎是标配(vLLM 默认就有)。自己按
max_seq_len连续预留,短请求会把显存利用率打到很低。 - Continuous Batching:线上多条长度不一的流式请求。单请求压测看不出它;并发从 1 扫到 16,吞吐涨、P50 不动,才算生效。
- 先别用束搜索 / 并行采样扛吞吐:它们会分叉 KV。没有写时复制就会整份拷贝。聊天默认贪心或采样即可。
和另外几层怎么叠
分页是基础设施,Prefill 和 Decode 两端都要。持续批是调度:谁结束谁让位,散页才能立刻塞进新请求。
再往上:
- Decode 仍 Memory-Bound、batch 不大 → 才值得看推测解码。
- 长 Prefill 堵住正在吐字的请求 → 先 Chunked Prefill,流量大了再考虑 PD 分离。