文章

KV Cache 复用实战:Prefix Caching、Offloading 与长上下文成本治理

KV Cache 复用正在成为长上下文大模型服务的核心优化点。本文从 Prefix Caching、RadixAttention 到多级 Offloading,系统梳理命中率、延迟、成本与上线检查方法,帮助团队在同样 GPU 预算下承载更多请求。

背景:长上下文服务为什么会卡在 KV Cache

大模型推理通常分为两个阶段:prefilldecode

  • prefill:模型一次性处理用户输入、系统提示词、工具说明、RAG 文档、历史对话等上下文,并为每一层 Attention 生成 Key / Value 状态。
  • decode:模型逐 token 生成输出,每生成一个 token 都会读取之前保存的 Key / Value 状态。

这批 Key / Value 状态就是 KV Cache。它的价值很直接:已经算过的历史 token 不必在每个生成步重复计算。但它也带来一个工程问题——上下文越长、并发越高、模型层数越多,KV Cache 占用的显存就越大。

在 RAG、Agent、代码助手、长文档问答、多轮客服等场景里,请求经常包含大量重复内容。例如同一份技术文档被多人反复提问,同一个系统提示词被每次请求重复携带,同一个 Agent 工具说明被放进每次上下文。此时,如果每个请求都从头 prefill,同样的 prefix 会被重复计算,GPU 时间和显存带宽都会被浪费。

因此,生产环境的大模型服务不能只关注模型权重量化和 batch size,还要把 KV Cache 复用 当成一等优化对象。

核心问题:不是”缓存越多越好”,而是”缓存是否值得恢复”

KV Cache 复用听起来像普通缓存,但它比 Web 缓存复杂得多。普通缓存通常只需要判断 key 是否命中;KV Cache 还要考虑:

  1. 前缀是否完全一致:token 序列、tokenizer、模板、系统提示词、工具说明都可能影响缓存 key。
  2. 恢复成本是否低于重算成本:从 GPU、CPU、远端内存或 SSD 恢复 KV Cache 都有数据搬运成本。
  3. 缓存是否挤占 GPU 显存:保留太多缓存可能降低当前可用 batch size。
  4. 命中是否发生在 prefill 瓶颈上:如果请求主要慢在 decode,prefix 命中并不一定改善整体延迟。
  5. 是否影响正确性:跨模型、跨 LoRA、跨 tokenizer、跨位置编码复用 KV Cache 都可能出错。

一句话:KV Cache 复用不是”把中间结果存起来”这么简单,而是一个横跨 调度、显存管理、I/O、路由、模板规范和线上指标 的系统问题。

核心原理:从 Prefix Caching 到多级 KV Offloading

1. Prefix Caching:复用共享前缀,跳过重复 prefill

Prefix Caching 的基本思路是:如果新请求和历史请求拥有相同前缀,就直接复用已经计算过的 KV Cache,让新请求跳过共享部分的 prefill。

典型适用场景:

  • 固定系统提示词 + 不同用户问题
  • 同一份长文档 + 多个问题
  • 多轮对话中反复携带历史上下文
  • RAG 中多个请求命中相同文档片段
  • Agent 框架中稳定的工具说明、角色设定和约束说明

vLLM 的 Automatic Prefix Caching 就是这类能力的代表。它会缓存已有请求的 KV Cache,当新请求共享相同 prefix 时,直接复用缓存,跳过共享部分的计算。

但 Prefix Caching 的收益边界也很明确:它主要减少 prefill 时间,不减少生成新 token 的 decode 时间。如果用户请求没有共享前缀,或输出非常长、decode 才是主瓶颈,它的加速效果会被稀释。

2. RadixAttention:用前缀树管理复杂复用模式

真实业务里的共享前缀不总是”完整 system prompt 一样”这么简单。Agent、树搜索、多轮对话、few-shot 示例、自一致性采样会产生更复杂的复用结构。

SGLang 提出的 RadixAttention 选择用 radix tree 管理 token 前缀和对应 KV Cache。这样系统不仅能识别完整前缀命中,还能在复杂调用链中自动做 prefix matching、cache insertion 和 eviction。

它的工程意义在于:前端不需要手写大量缓存逻辑,运行时可以根据 token 前缀自动维护可复用 KV Cache。对于 LLM 程序化调用,这比”每个接口自己拼缓存 key”更可靠。

3. PagedAttention:让 KV Cache 以块为单位管理

KV Cache 的另一个问题是显存碎片。不同请求的上下文长度不同,生成长度也不同,如果提前为每个请求保留一大块连续显存,会浪费大量空间。

PagedAttention 借鉴操作系统分页思想,把 KV Cache 拆成固定大小的块,并用逻辑块到物理块的映射管理缓存。这样请求不需要占用连续物理显存,系统可以按需分配、复用和释放 KV block。

这解决的是 显存布局和碎片管理 问题,不等同于 Prefix Caching,但两者经常配合使用:前者让 KV Cache 更容易管理,后者让共享前缀能被复用。

4. KV Offloading:把缓存放到更大的层级里

当上下文很长、并发很多时,只靠 GPU 显存保存 KV Cache 不够。此时可以把完成的 KV block offload 到 CPU 内存、文件系统、远端内存或 SSD,再在命中时恢复到 GPU。

这类方案的核心取舍如下:

存储层级优势劣势
GPU 显存延迟最低,恢复最快容量最小、成本最高
CPU 内存容量更大,延迟可控恢复需 GPU-CPU 传输
SSD/文件系统容量极大,成本低I/O 延迟高,随机访问影响 TTFT
远端缓存可跨实例复用网络延迟、路由与一致性问题

vLLM 的 KV Offloading Usage Guide 已经把 OffloadingConnector 描述为对 prefix cache 的扩展:已完成 KV blocks 可被 offload 到 CPU host memory 和可选二级存储,命中时再提升回 GPU。LMCache 进一步把 KV Cache 作为跨引擎、跨请求共享的中间层,服务于 prefix reuse 和 prefill-decode disaggregation。

工程落地:一套可执行的 KV Cache 复用方案

第一步:先规范 prompt 结构

KV Cache 复用的第一前提是 prefix 稳定。如果上游每次请求都动态拼接时间戳、随机 ID、无序 JSON、临时工具说明,缓存命中率会很差。

建议把 prompt 分成四段:

  1. 稳定系统段:角色、规范、输出格式、工具说明。
  2. 稳定知识段:长文档、产品手册、代码库摘要、FAQ。
  3. 会话历史段:多轮对话中可复用的历史部分。
  4. 当前请求段:用户本轮问题和临时变量。

只有前三段适合被重点复用,第四段通常不应纳入长期缓存策略。

示例结构:

[stable_system_prompt] [stable_tool_schema] [stable_retrieved_document]
[conversation_history] [current_user_question]

第二步:先开 Prefix Caching,再看命中率

以 vLLM 为例,可以先在实验环境开启 Automatic Prefix Caching:

from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    enable_prefix_caching=True,
)

params = SamplingParams(temperature=0.2, max_tokens=512)

prompts = [
    shared_prefix + "\n问题:请总结第一章。",
    shared_prefix + "\n问题:请列出关键术语。",
]

outputs = llm.generate(prompts, params)

上线前不要只看单次响应时间。更重要的是关注以下指标:

  • prefix cache hit rate
  • 平均 TTFT
  • P95 / P99 TTFT
  • prefill tokens per second
  • GPU KV cache 使用率
  • eviction 次数
  • cache hit 后是否真的减少 prefill 时间

第三步:把可复用内容路由到同一组实例

如果请求被负载均衡随机分发,即使命中前缀,也可能落到没有对应缓存的实例上。生产环境需要 cache-aware routing

简单策略:

  • 按租户 ID、文档 ID、知识库 ID、会话 ID 做一致性哈希。
  • 同一份长文档的问题尽量路由到同一组推理实例。
  • 热点文档可以预热到多个实例。
  • 对高复用业务和低复用业务分池,避免互相污染缓存。

这一层通常比模型参数调优更容易被忽略,但对命中率影响很大。

第四步:再评估 Offloading,而不是一开始就上 SSD

Offloading 不是默认收益项。它适合 GPU 显存留不住足够 KV Cache,但重复上下文又足够多的场景。

推荐上线顺序:

  1. 只开 GPU 内 Prefix Caching,测命中率和 TTFT。
  2. 加 CPU tier,观察恢复延迟是否低于重算成本。
  3. 再评估 FS / SSD / 远端 KV Store。
  4. 对不同业务设置不同 max_offload_tokens,只 offload 高复用前缀。

vLLM 的 offloading 配置示例:

vllm serve <model> \
  --kv-transfer-config '{
    "kv_connector": "OffloadingConnector",
    "kv_role": "kv_both",
    "kv_connector_extra_config": {
      "block_size": 64,
      "cpu_bytes_to_use": 1000000000
    }
  }'

这里最容易犯的错误是把所有 token 都 offload。更好的做法是只缓存稳定 prefix,例如系统提示词、工具 schema、长文档正文,而不是把当前用户问题和一次性中间推理都放进长期缓存。

适用场景

场景复用模式预期收益
长文档问答同一文档 + 不同问题prefill 时间显著下降
多轮对话历史会话复用TTFT 降低明显
Agent 与工具调用稳定工具说明与策略减少重复 prefill
RAG 热点知识库高频文档片段按文档 ID 路由可获高命中率
Few-shot 与评测固定示例 + 变化问题批处理收益突出

长文档问答

同一份 PDF、手册、合同、代码说明文档会被反复提问。此时文档内容是稳定 prefix,用户问题只是尾部变化。Prefix Caching 收益通常明显。

多轮对话

每轮请求都携带完整历史会话时,历史部分会不断增长。如果服务端能识别并复用前几轮历史的 KV Cache,TTFT 可以显著降低。

Agent 与工具调用

Agent 框架经常在每个请求中带上长工具说明、JSON schema、行为约束和系统策略。这些内容稳定且重复,适合缓存。

RAG 热点知识库

如果大量用户查询命中同一批知识片段,按文档 ID 做路由和缓存预热,能减少重复 prefill。

Few-shot 与评测任务

固定示例 + 不同问题的 few-shot 任务天然适合 prefix reuse。评测平台、批处理任务、分类任务都可以受益。

常见误区

误区真相
Prefix Caching 能加速所有阶段只节省共享 prefix 的 prefill,长输出 decode 效果有限
缓存命中率高就一定省钱命中后还要恢复 KV Cache,慢 I/O 可能超过重算成本
文本一样就能复用 KV Cache还需 tokenizer、模板、模型版本、LoRA、dtype、block size 一致
Offloading 越多越好占用 CPU 内存和 I/O,低复用内容反而增加延迟
只看平均延迟容易改善平均值却恶化 P95/P99,须跟踪尾延迟

误区一:Prefix Caching 能加速所有阶段

不能。它主要节省共享 prefix 的 prefill 计算。如果瓶颈在长输出 decode,效果有限。

误区二:缓存命中率高就一定省钱

不一定。命中后还要恢复 KV Cache。如果恢复路径经过远端存储、慢 SSD 或网络,成本可能超过重算。

误区三:只要文本一样就能复用 KV Cache

不够。还要保证 tokenizer、chat template、模型版本、位置编码策略、LoRA adapter、并行配置、dtype 和 block size 等一致。

误区四:Offloading 越多越好

Offloading 会占用 CPU 内存、I/O 带宽和恢复调度资源。对一次性请求、低复用内容、短 prompt,offload 反而可能增加延迟。

误区五:只看平均延迟

KV Cache 相关优化很容易改善平均值,却恶化尾延迟。必须看 P95 / P99 TTFT、I/O queue、eviction storm、GPU stall。

上线检查清单

Prompt 与缓存 key

  • 固定系统提示词是否完全稳定
  • 工具 schema 是否排序稳定
  • RAG 文档是否有稳定 document ID 和 chunk ID
  • chat template 是否版本化
  • tokenizer、模型版本、LoRA adapter 是否纳入缓存 key

路由与实例池

  • 是否按会话、文档或租户做 cache-aware routing
  • 热点知识是否有预热策略
  • 高复用业务和低复用业务是否分池
  • 实例重启后是否接受缓存冷启动

显存与 Offloading

  • GPU KV cache watermark 是否可观测
  • CPU tier 容量是否大于实际可复用 prefix 规模
  • FS / SSD 线程数是否压测过
  • offload block size 是否和 GPU block size 兼容
  • 是否限制只 offload 前 N 个稳定 token

指标与告警

至少应有这些指标:

prefix_cache_hit_rate
prefix_cache_hit_tokens
prefix_cache_miss_tokens
ttft_avg / ttft_p95 / ttft_p99
kv_cache_gpu_usage_bytes
kv_cache_cpu_usage_bytes
kv_cache_offload_read_latency
kv_cache_offload_write_latency
kv_cache_eviction_count
gpu_stall_due_to_kv_restore

告警重点不是”缓存满了”,而是:缓存满以后是否频繁 eviction,eviction 是否导致命中率下降,命中率下降是否推高 TTFT。

一个实用判断公式

是否值得复用 KV Cache,可以用一个简化判断:

reuse_gain = recompute_prefill_cost - restore_kv_cost - cache_management_cost

reuse_gain > 0 且命中频率足够高时,缓存才值得保留。

更工程化一点,可以按业务维度统计:

worth_cache = shared_prefix_tokens × reuse_count × prefill_cost_per_token
              > restore_bytes / effective_bandwidth + eviction_penalty

这不是精确模型,但能帮助团队避免”只要能缓存就缓存”的误区。

FAQ

Prefix Caching 和 PagedAttention 是一回事吗?

不是。Prefix Caching 解决重复 prefix 的计算复用问题;PagedAttention 解决 KV Cache 的块级显存管理和碎片问题。实际服务框架可能同时使用两者。

RAG 场景应该缓存用户问题吗?

通常不应该。用户问题变化大、复用率低,更适合缓存稳定文档片段、系统提示词、工具说明和固定 few-shot 示例。

KV Offloading 适合小模型吗?

不一定。小模型、短上下文、低复用请求中,重算成本可能低于恢复成本。Offloading 更适合长上下文、高复用、GPU 显存不足的场景。

如何判断缓存策略是否生效?

看四个指标:命中 token 数、TTFT 是否下降、P99 是否稳定、GPU 显存是否支持更高 batch。只看 hit rate 不够。

总结

KV Cache 复用的关键不是”多存一点缓存”,而是把 稳定 prefix、实例路由、显存管理、I/O 恢复和业务命中率 放在一起设计。

一套稳妥的落地顺序:

  1. 先规范 prompt,减少无意义的 prefix 波动。
  2. 开启 Prefix Caching,验证长文档、多轮对话、Agent 工具说明等高复用场景。
  3. 加 cache-aware routing,让相同文档或会话尽量落到同一组实例。
  4. 在 GPU cache 不够时再引入 CPU / FS / SSD Offloading。
  5. 用 TTFT、P99、命中 token 数、I/O 恢复延迟和 eviction 行为判断是否真的省钱。

对于长上下文大模型服务,KV Cache 已经从”推理框架内部细节”变成了”系统架构层面的成本控制点”。谁能把可复用上下文稳定地识别、路由、保存和恢复,谁就能在同样 GPU 预算下承载更多请求。

主要参考资料

  1. vLLM Automatic Prefix Caching: https://docs.vllm.ai/en/latest/features/automatic_prefix_caching/
  2. vLLM KV Offloading Usage Guide: https://docs.vllm.ai/en/latest/features/kv_offloading_usage/
  3. LMSYS: Fast and Expressive LLM Inference with RadixAttention and SGLang: https://www.lmsys.org/blog/2024-01-17-sglang/
  4. PagedAttention paper: https://arxiv.org/abs/2309.06180
  5. LMCache paper: https://arxiv.org/abs/2510.09665
  6. KV Cache Offloading for Context-Intensive Tasks: https://arxiv.org/abs/2604.08426
  7. Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving: https://arxiv.org/abs/2605.03375
  8. Comparative Characterization of KV Cache Management Strategies for LLM Inference: https://arxiv.org/abs/2604.05012

常见问题

Prefix Caching 适合所有大模型请求吗?
不适合。它主要优化共享前缀的 prefill 阶段;如果请求没有重复前缀,或主要时间消耗在长输出 decode 阶段,收益会明显下降。
KV Cache Offloading 一定能降低延迟吗?
不一定。Offloading 能扩大可复用缓存容量,但会引入 CPU、存储和网络传输路径,必须用 TTFT、命中率、I/O 延迟和 SLO 共同评估。
生产环境应该优先做 KV 量化还是 Prefix Caching?
优先顺序取决于瓶颈。如果重复长上下文多,先做 Prefix Caching;如果显存容量限制批量和上下文长度,再评估 KV 量化或多级 Offloading。