文章

PagedAttention 生产实战:用分页式 KV Cache 提升 LLM Serving 吞吐

系统讲解 PagedAttention 的原理、KV Cache 分页管理、连续批处理协同、监控指标和上线检查,帮助你在大模型推理服务中降低显存碎片并提升吞吐。

PagedAttention 生产实战:用分页式 KV Cache 提升 LLM Serving 吞吐

背景问题:LLM Serving 的瓶颈经常不是权重,而是 KV Cache

在大模型在线推理中,模型权重通常是固定的,真正随请求动态变化的是 KV Cache。一次自回归生成会经历两个阶段:

  • Prefill:处理输入 prompt,为每一层注意力计算 Key / Value,并写入 KV Cache。
  • Decode:每次生成一个或多个新 token,复用历史 KV Cache,避免重复计算历史上下文。

当上下文长度、并发请求数、输出长度同时增长时,KV Cache 会迅速成为显存压力的主要来源。更麻烦的是,不同请求的 prompt 长度和输出长度差异很大,传统连续显存分配方式很容易出现两类浪费:

  1. 预留浪费:为了支持最大序列长度,系统提前按上限为每个请求预留 KV 空间,但多数请求并不会真正用满。
  2. 碎片浪费:请求不断进入、生成、结束、释放,显存中出现难以复用的小空洞,导致明明还有剩余显存,却无法继续放入足够多的活跃请求。

这会直接限制 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 与连续批处理的关系

很多工程实践会把 PagedAttentionContinuous Batching 放在一起讨论,但它们不是同一个层次的问题。

机制解决的问题核心动作
PagedAttentionKV 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 usageKV 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 推理服务时绕不开的基础能力。

主要参考资料

常见问题

PagedAttention 主要解决什么问题?
它主要解决 LLM 推理中 KV Cache 动态增长带来的显存碎片、过度预留和批处理容量受限问题,通过固定大小 KV block 和逻辑到物理映射提升显存利用率。
PagedAttention 和连续批处理是一回事吗?
不是。PagedAttention 是 KV Cache 内存管理机制,连续批处理是请求调度机制。两者常一起使用:前者让显存更容易装下更多活跃请求,后者让 GPU 每轮迭代保持更高利用率。
生产环境启用 PagedAttention 后还需要关注哪些指标?
至少要观察 KV cache usage、waiting requests、preemption、TTFT、TPOT、prefix cache hit rate、prompt/generation token throughput 和显存 OOM 情况。
PagedAttention 会改变模型输出吗?
理论上不会。PagedAttention 是 KV Cache 存储方式的工程优化,不应改变语义输出。但生产环境仍需做回归测试,因为 kernel 实现、精度配置、prefix cache、并发调度都可能引入边界问题。