背景问题:为什么 KV Cache 会成为推理系统的隐性瓶颈
Transformer 自回归生成时,每生成一个 token 都需要访问前面所有 token 的 Key 和 Value。为了避免每一步重新计算历史上下文,推理系统会将中间结果缓存下来,这就是 KV Cache。
KV Cache 有三个典型特征:
- 大:随层数、hidden size、head 数、上下文长度和 batch size 线性增长。
- 动态:请求长度不同,输出长度不可预知,显存需求持续变化。
- 强占显存:KV Cache 通常驻留在 GPU 显存中,直接挤压可并发请求数量。
在没有分页管理时,系统往往需要为每个请求预留一段连续显存,或按最大长度估算空间。这会带来两类浪费:
- 内部碎片:序列最后一段没有填满,但对应显存已经分配。
- 外部碎片:显存还有总量,但不一定有足够大的连续空间给新请求。
当请求长度分布很不均匀时——如同一个服务同时处理短问答、长文档总结、RAG 检索问答和多轮 Agent 任务——这个问题会进一步放大。即使 GPU 看起来还有显存,调度器也可能无法安全地塞入更多请求。
核心原理:把 KV Cache 当成”分页内存”管理
PagedAttention 的设计可以用三个对象理解:
- Logical blocks:一个请求在逻辑上连续的 token 块。
- Physical blocks:GPU 显存中真实分配的 KV Cache 块。
- Block table:把逻辑块映射到物理块的表。
普通 KV Cache 管理更像”为一个请求分配一整段连续数组”。PagedAttention 则更像操作系统的虚拟内存:一个请求看到的是连续 token 序列,但底层显存块可以不连续。
Request tokens: [ t0 t1 t2 t3 ][ t4 t5 t6 t7 ][ t8 t9 ... ]
Logical block id: [ block 0 ][ block 1 ][ block 2 ]
Block table:
block 0 -> physical block 17
block 1 -> physical block 03
block 2 -> physical block 42
当请求继续生成 token,系统按需申请新 block;当请求结束,block 释放回池中;当多个请求共享同一段前缀,block 也有机会复用。
这带来的好处不是”attention 算法复杂度变成常数”,而是显存管理效率提升。它让系统能更稳定地把 GPU 显存转化为有效 batch,而不是浪费在碎片和保守预留上。
与连续批处理的关系:PagedAttention 不是单点优化
PagedAttention 常和 continuous batching 一起出现,但二者不是同一件事。
| 技术 | 解决的问题 |
|---|---|
| Continuous batching | 调度问题:请求完成时新请求可立刻进入 batch,不必等整个 batch 结束 |
| PagedAttention | 显存组织问题:请求进入、退出、增长时,KV Cache 按块动态分配和回收 |
二者结合后,服务端可以更大胆地做动态调度:
Incoming requests
↓
Scheduler groups active sequences
↓
Paged KV blocks allocated on demand
↓
Attention kernel reads blocks by block table
↓
Completed requests release blocks
↓
New requests fill freed capacity
如果只有 continuous batching 而没有高效 KV Cache 管理,系统仍可能被显存碎片卡住。如果只有 PagedAttention 而没有合理调度,显存效率也不一定能转化为吞吐收益。
工程落地:适合哪些生产场景
PagedAttention 最适合以下类型的服务:
1. 请求长度差异大的在线推理
例如同一个 LLM API 同时处理:
- 短指令问答
- 长文档总结
- RAG 检索增强问答
- 多轮会话
- Agent 工具调用链
这类负载的上下文长度分布不稳定,传统连续显存分配很容易浪费空间。
2. 多并发、吞吐优先的模型服务
如果目标是提高单卡或多卡吞吐,PagedAttention 可以帮助系统容纳更多 active sequences。它不保证每个请求都更快,但通常能改善整体容量与稳定性。
3. 长上下文和 prefix reuse 场景
长系统提示词、固定模板、多轮会话和 RAG 管线都可能出现共享前缀。分页 KV Cache 为块级共享、复用和缓存淘汰策略提供了基础。
4. 多输出采样
并行采样时,多个候选输出可能共享同一段 prompt 的 KV Cache。块级共享可以减少重复存储,提高并行生成效率。
上线架构:从”能跑”到”可控”
生产环境不要只把 vLLM、TGI 或 TensorRT-LLM 启动起来就结束。更稳妥的方式,是把 PagedAttention 放入整体推理架构中治理。
推理服务层
推理服务需要明确:
- 模型上下文长度上限
- 最大 batch token 数
- 最大并发序列数
- block size
- prefill 和 decode 阶段的调度策略
- 是否启用 prefix caching 或 KV cache reuse
路由层
如果有多个推理实例,应避免完全随机路由。对于共享系统提示词、多轮会话或相同知识库任务,可以考虑 KV-aware routing,让更可能命中缓存的请求进入同一个实例或同一组实例。
限流层
KV Cache 不是无限资源。限流不应只按 QPS 计算,还应考虑:
- prompt token 数
- max output token 数
- 当前显存压力
- 请求是否为长上下文
- 是否允许流式输出
更合理的做法是建立 token budget,而不是只限制请求数。
常见误区
误区一:PagedAttention 等于降低所有请求延迟
PagedAttention 的主要收益是显存效率和吞吐容量。单个短请求在低并发情况下,未必能明显变快。甚至因为多了一层 block table 访问和 kernel 适配,在某些场景下还可能有额外开销。
判断效果时,不要只看单请求 benchmark,要看生产负载下的:
- TTFT(Time to First Token)
- TPOT(Time per Output Token)
- tokens/s
- GPU utilization
- OOM 次数
- p95 / p99 延迟
误区二:block size 越小越好
block 越小,显存浪费越少,复用粒度越细;但 block 太小可能增加管理开销,也可能影响 kernel 访问效率。block size 是典型权衡项:
| block size | 可能收益 | 可能代价 |
|---|---|---|
| 较小 | 更细粒度分配,更少尾部浪费,更容易局部复用 | 元数据更多,调度和访问开销更高 |
| 较大 | kernel 访问可能更友好,管理开销更低 | 尾部浪费更高,复用命中条件更粗 |
实际选择应结合模型、上下文长度分布、并发模式和框架默认值测试。
误区三:只要启用 PagedAttention,就不需要容量规划
PagedAttention 能减少浪费,但不能改变 KV Cache 线性增长的事实。长上下文、长输出、高并发仍会让显存成为瓶颈。上线前仍然要做容量模型。
可以用以下简化公式做初步估算:
KV cache memory ≈ layers × 2 × hidden_size × tokens × bytes_per_element × batch_factor
这里的 2 对应 Key 和 Value。真实实现还会受到 GQA/MQA、量化、张量并行、padding、block size 和框架内存池影响,所以该公式只能用于方向性判断。
上线检查清单
1. 负载画像
上线前先统计真实请求,而不是只用平均值:
- prompt token p50 / p95 / p99
- output token p50 / p95 / p99
- 多轮会话比例
- RAG 请求比例
- 共享 system prompt 比例
- 流式输出比例
2. 压测矩阵
至少覆盖以下组合:
| 场景 | 观察重点 |
|---|---|
| 短 prompt + 短输出 | 调度开销是否可接受 |
| 长 prompt + 短输出 | TTFT 和 prefill 压力 |
| 短 prompt + 长输出 | decode 阶段 KV 增长 |
| 长 prompt + 长输出 | OOM、p99 延迟、吞吐上限 |
| 多轮会话 | prefix reuse 和缓存命中 |
| RAG 模板 | 系统提示词和检索内容的复用机会 |
3. 监控指标
建议至少监控:
- GPU 显存使用率
- active sequences
- waiting queue length
- prefill tokens/s
- decode tokens/s
- TTFT p50 / p95 / p99
- TPOT p50 / p95 / p99
- cache hit ratio
- block allocation failure
- eviction count
- OOM count
- request cancellation rate
4. 回滚策略
推理系统变更要保留回滚路径:
- 保留旧版本 serving 配置
- 控制单实例灰度比例
- 将长上下文请求单独路由
- 对 max tokens 设置保守上限
- 出现 OOM 或 p99 激增时自动降级
适用边界
PagedAttention 对以下场景收益可能有限:
- 低并发、短上下文服务:显存碎片不是主要问题。
- 严格追求单请求最低延迟:需要同时优化 kernel、batch、模型量化和调度。
- 缓存复用很少的随机请求:block sharing 价值有限,但按需分配仍可能有用。
- 框架或模型 attention 类型特殊:需要确认当前 serving 框架是否支持对应模型结构。
对于新模型,尤其是混合 attention、sliding window attention、Mamba-like 结构或特殊位置编码模型,需要检查框架文档中的兼容性说明,不要简单假设所有模型都能获得同样收益。