PagedAttention 生产实战:用分页式 KV Cache 提升 LLM Serving 吞吐
背景问题:LLM Serving 的瓶颈经常不是权重,而是 KV Cache
在大模型在线推理中,模型权重通常是固定的,真正随请求动态变化的是 KV Cache。一次自回归生成会经历两个阶段:
- Prefill:处理输入 prompt,为每一层注意力计算 Key / Value,并写入 KV Cache。
- Decode:每次生成一个或多个新 token,复用历史 KV Cache,避免重复计算历史上下文。
当上下文长度、并发请求数、输出长度同时增长时,KV Cache 会迅速成为显存压力的主要来源。更麻烦的是,不同请求的 prompt 长度和输出长度差异很大,传统连续显存分配方式很容易出现两类浪费:
- 预留浪费:为了支持最大序列长度,系统提前按上限为每个请求预留 KV 空间,但多数请求并不会真正用满。
- 碎片浪费:请求不断进入、生成、结束、释放,显存中出现难以复用的小空洞,导致明明还有剩余显存,却无法继续放入足够多的活跃请求。
这会直接限制 batch size,并进一步影响吞吐、排队时间和 Time To First Token(TTFT)。PagedAttention 的价值就在这里:它把 KV Cache 从”连续大数组”改造成类似操作系统虚拟内存的分页式 block 管理。
核心原理:把 KV Cache 拆成固定大小的页
PagedAttention 的核心思想很直接:不要要求一个请求的 KV Cache 必须存放在一段连续物理显存中,而是把它拆成多个固定大小的 KV block。
可以把一个请求的 token 序列看成逻辑地址空间:
Request A logical tokens:
[0..15] [16..31] [32..47] [48..63] ...
B0 B1 B2 B3
每个逻辑 block 再通过 block table 映射到 GPU 显存中的物理 block:
Logical block table:
B0 -> physical block 18
B1 -> physical block 04
B2 -> physical block 27
B3 -> physical block 09
这些物理 block 不需要连续。新 token 生成时,只要当前 block 写满,就从空闲 block 池中再申请一个;请求完成后,其占用的 block 被释放回池中,供其他请求复用。
这套机制带来三个直接好处:
1. 降低显存碎片
传统连续分配需要为变长序列找到一块足够大的连续空间。PagedAttention 只需要若干固定大小的空闲 block,不再强依赖连续地址,因此能显著缓解外部碎片。
2. 降低过度预留
系统不必一开始就为每个请求按最大长度预留 KV Cache。请求真正生成到某个长度时,再按 block 逐步分配。这种 on-demand allocation 更贴近实际用量。
3. 支持更灵活的共享和复用
当多个请求共享相同前缀时,底层 KV block 可以被复用或引用计数管理。vLLM 的 Automatic Prefix Caching 就建立在 KV block 可识别、可映射、可复用的基础之上。
PagedAttention 与连续批处理的关系
很多工程实践会把 PagedAttention 和 Continuous Batching 放在一起讨论,但它们不是同一个层次的问题。
| 机制 | 解决的问题 | 核心动作 |
|---|---|---|
| PagedAttention | KV Cache 内存管理 | 将 KV Cache 拆分为固定大小 block,通过 block table 映射 |
| Continuous Batching | 请求调度 | 每轮 decode 迭代动态组合新进入、进行中、即将结束的请求 |
两者组合后,才更接近生产环境需要的 LLM Serving 形态:
Incoming requests
-> Scheduler / continuous batching
-> Prefill and decode iterations
-> Paged KV block allocation
-> Attention kernel reads block table
-> Streaming response
-> Block release / reuse
没有分页式 KV Cache,连续批处理很容易被显存碎片和预留浪费限制;没有连续批处理,PagedAttention 只能解决”放得下”的问题,不能充分解决”跑得满”的问题。
工程落地:从内存模型到服务配置
1. 先确认业务负载是否适合
PagedAttention 对以下场景价值较高:
- 多租户在线推理服务,请求长度差异明显。
- Chat / Agent 场景,输入上下文和输出长度波动较大。
- 长上下文任务,如文档问答、代码分析、报告生成。
- 需要持续 streaming 输出,并且要提升并发吞吐。
- 显存经常被 KV Cache 占满,而不是单纯被模型权重占满。
如果服务主要是短 prompt、短输出、低并发、离线批处理,PagedAttention 仍可能有价值,但收益不会像长上下文在线服务那么明显。
2. 理解 block size 的影响
PagedAttention 通常会以固定 token 数作为一个 KV block。block size 太小,会增加 block table 和调度管理开销;block size 太大,又会增加最后一个 block 的内部浪费。
工程上不要只看单次 benchmark 的吞吐峰值,还要观察:
- 不同 prompt 长度分布下的显存占用。
- 不同输出长度下的 block 分配和释放频率。
- 高并发时 waiting requests 是否下降。
- TTFT 和 TPOT(Time Per Output Token)是否同时改善,还是只改善了吞吐。
3. 配合 prefix caching 使用
PagedAttention 本身不等于 prefix caching,但它为 prefix caching 提供了更自然的基础设施。vLLM 的 prefix caching 会把一个 KV block 通过前缀 token 与当前 block token 组合出的 hash 标识出来,从而在后续请求共享相同前缀时复用缓存。
对于 RAG、Agent、系统提示词较长的应用,建议把稳定内容放在 prompt 前部:
[system prompt / tool schema / policy / fixed examples]
[user-specific dynamic question]
这样更容易形成稳定前缀,提高缓存命中概率。反过来,如果每次请求都在开头插入时间戳、随机 trace id、动态用户信息,prefix cache 命中率会被严重破坏。
4. 与 chunked prefill 协同
长 prompt 的 prefill 会一次性消耗大量计算和显存带宽。如果把超长上下文一次性塞进 prefill,可能会挤压 decode 阶段,造成 streaming token 抖动。
一些推理框架会结合 chunked prefill 或类似机制,把长上下文拆分成多个 chunk,让 prefill chunk 与 decode 请求交错调度。PagedAttention 提供的是 KV block 管理基础,chunked prefill 提供的是更细粒度的调度控制。
一个简化的服务配置思路
下面是一个以 vLLM 风格为例的启动思路。实际参数要以模型、GPU、版本和业务负载压测为准。
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--host 0.0.0.0 \
--port 8000 \
--max-model-len 32768 \
--gpu-memory-utilization 0.90 \
--enable-prefix-caching
参数解读:
| 参数 | 作用 | 注意点 |
|---|---|---|
max-model-len | 控制最大上下文长度 | 直接影响可承载的 KV Cache 上限 |
gpu-memory-utilization | 控制推理引擎为模型、运行时和 KV Cache 分配显存的策略 | 不宜设满,需留安全余量 |
enable-prefix-caching | 启用自动前缀缓存 | 适合存在稳定前缀复用的场景 |
上线前必须用真实请求分布压测,不要只用单一固定长度 prompt 评估。
生产监控:不能只看 GPU 利用率
PagedAttention 的收益主要体现在显存利用率、队列长度和 token 延迟上。因此监控要覆盖至少四类指标。
1. 内存与 KV Cache
| 指标 | 说明 |
|---|---|
| KV cache usage | KV Cache 使用比例是否长期接近上限 |
| Preemption / Eviction | 是否频繁发生请求抢占或缓存淘汰 |
| Prefix cache hit rate | 启用 prefix caching 后是否真的命中 |
| Block lifetime / idle time | 是否有大量长期占用但低复用的 block |
2. 调度与队列
| 指标 | 说明 |
|---|---|
| num_requests_running | 当前正在执行的请求数 |
| num_requests_waiting | 等待调度的请求数 |
| Queue time | 请求在进入模型执行前等待多久 |
| Iteration tokens | 每轮引擎 step 中实际处理的 token 数 |
3. 延迟
| 指标 | 说明 |
|---|---|
| TTFT(Time To First Token) | 用户首次看到 token 的时间 |
| TPOT / ITL(Inter-Token Latency) | 生成阶段每个 token 的间隔 |
| E2E latency | 完整请求端到端耗时 |
如果吞吐提高但 P95 TTFT 明显变差,说明调度策略可能过度追求 batch 效率,牺牲了交互体验。
4. 质量和稳定性
PagedAttention 不应该改变模型语义输出,但工程层面的调度、prefix cache、chunked prefill、并发压测仍可能暴露实现 bug 或边界问题。上线前应准备固定回归集,覆盖:
- 短问答
- 多轮对话
- 长文档摘要
- RAG 带引用回答
- 工具调用或结构化输出
- 高并发 streaming
常见误区
误区一:以为 PagedAttention 会让单请求必然更快
PagedAttention 主要优化的是并发吞吐和显存利用率。单请求延迟可能改善,也可能变化不明显。它的核心价值不是”让一个请求更快”,而是”在同等显存下承载更多活跃请求,并减少动态内存浪费”。
误区二:只看平均延迟,不看 P95 / P99
在线服务最容易被长尾拖垮。PagedAttention 与连续批处理可能让平均吞吐变好,但如果调度参数不合理,P95 TTFT 或 TPOT 仍可能变差。上线判断应以 P95 / P99、等待队列和错误率为主。
误区三:把 prefix caching 当成自动收益
Prefix caching 依赖稳定前缀。只要 prompt 前部有动态字段,就可能破坏缓存命中。系统提示词、工具 schema、示例、检索模板应尽量稳定,并放在前缀区域。
误区四:把显存打满当成最优
KV Cache usage 长期接近 100% 不是好事。它可能意味着请求稍有波动就会排队、抢占、OOM 或触发更激进的缓存淘汰。生产环境通常要保留安全余量。
误区五:忽略版本差异
vLLM、TensorRT-LLM、TGI 等框架都在快速演进。Paged KV cache、prefix caching、chunked prefill、metrics 命名和默认参数可能随版本变化。上线前必须固定版本并记录配置快照。
上线检查清单
负载建模
- 收集真实 prompt length、output length、并发、QPS 分布。
- 区分 Chat、RAG、Agent、批量生成、长文档任务。
- 单独记录 P50 / P95 / P99 输入长度和输出长度。
压测设计
- 使用真实长度分布,而不是固定短 prompt。
- 分别测试冷启动、稳定流量、突发流量和长输出流量。
- 对比启用与关闭 prefix caching 的结果。
- 记录吞吐、TTFT、TPOT、queue time、KV cache usage。
配置治理
- 固定模型版本、推理框架版本和 GPU 型号。
- 记录 max model length、memory utilization、batch/token 限制。
- 为长上下文和短问答设置不同路由或不同实例池。
- 准备一键回滚到旧推理服务的方案。
监控告警
建议至少配置以下告警:
alerts:
- name: HighKVCacheUsage
condition: kv_cache_usage_perc > 0.90 for 5m
- name: WaitingRequestsGrowing
condition: num_requests_waiting p95 keeps increasing for 5m
- name: TTFTRegression
condition: p95_time_to_first_token increases by 30% over baseline
- name: TPOTRegression
condition: p95_inter_token_latency increases by 30% over baseline
- name: PrefixCacheHitDrop
condition: prefix_cache_hit_rate drops below expected baseline
阈值不要直接照抄,应按业务基线确定。
结论
PagedAttention 的本质不是一个”单点加速开关”,而是一套面向 LLM Serving 的 KV Cache 虚拟内存管理机制。它把动态增长的 KV Cache 拆成固定大小 block,通过逻辑到物理映射降低显存碎片和过度预留,让更多活跃请求可以被连续批处理调度起来。
在生产环境中,PagedAttention 最适合和 continuous batching、prefix caching、chunked prefill、Prometheus 指标、真实长度分布压测一起使用。只要监控和回滚机制准备充分,它通常是构建高吞吐、长上下文、低成本 LLM 推理服务时绕不开的基础能力。
主要参考资料
- Efficient Memory Management for Large Language Model Serving with PagedAttention, arXiv: https://arxiv.org/abs/2309.06180
- vLLM Automatic Prefix Caching Design: https://docs.vllm.ai/en/v0.9.2/design/automatic_prefix_caching.html
- vLLM Production Metrics: https://docs.vllm.ai/en/v0.20.0/usage/metrics/
- vLLM Metrics Design: https://docs.vllm.ai/en/stable/design/metrics/
- Hugging Face Text Generation Inference Documentation: https://huggingface.co/docs/text-generation-inference/en/index
- NVIDIA TensorRT-LLM GPT Attention / In-flight Batching / Paged KV Cache: https://github.com/NVIDIA/TensorRT-LLM
- vAttention: Dynamic Memory Management for Serving LLMs without PagedAttention, arXiv: https://arxiv.org/abs/2405.04437