在构建基于大语言模型(LLM)的应用系统时,无论是代码库智能审计、百页财报问答还是自主代理(Agent)循环,开发者都会迅速撞上一堵残酷的现实之墙:长上下文太贵了,而且太慢了!

在前文《五款 Flash 大模型横向对比》的 1M tokens 成本实测中,我们展示了一组令人深思的数据: 对同一批 100 万 tokens 的长文档进行 10 轮连续提问:

  • 不开启缓存,每轮都需全量重算 1M tokens 的输入 Prefill,十轮累计输入加上输出账单高达数美元甚至数十美元(如未缓存下仅单次调用就达 $0.22 ~ $0.86),首字延迟(TTFT)长达数秒;
  • 开启 Context Caching,后续 9 轮的输入费用骤降一个数量级(例如 MiMo-V2.5 降至 $0.0028/次,DeepSeek 降至 $0.007/次),十轮总成本直接从数美元压低到只需 $0.19 ~ $0.35(折合人民币仅约 ¥1.38 ~ ¥2.5),TTFT 直接压进几十毫秒!

这篇文章我们就从 GPU 推理框架的显存前缀树聊起,把 Context Caching 的底层细节与前缀设计避坑指南 彻底梳理一遍。


1. 为什么 Prefill 需要缓存?—— 重新审视自注意力算力

在 Transformer 架构中,大模型的推理明确分为两个阶段:

阶段一:Prefill (输入预填充阶段)
  输入 100,000 个 Token ──> 并行计算自注意力 (Self-Attention)
                             生成巨型 Key-Value 向量矩阵 (KV Cache)
                             【算力消耗极大,决定首字延迟 TTFT】

阶段二:Decode (自回归逐字生成阶段)
  每生成一个新 Token ─────> 复用已有的 KV Cache,只计算新 Token 与历史的关系
                             【显存带宽受限,决定每秒吐字速率】

当用户在长文档聊天界面连续发起第二轮、第三轮提问时: 历史对话、系统提示词、长文档内容其实完全没有改变。如果服务器在每次请求到达时,都把历史的 10 万个 token 重新走一遍矩阵乘法,就是在对算力造成极其严重的浪费。

Context Caching(上下文缓存)的核心逻辑极其纯粹: 将已经计算完成的中间层 KV Cache 张量保存在 GPU 显存(或高速内存/本地 NVMe SSD)中。下次请求到来时,只要前缀一致,直接“瞬移”跳过 Prefill 计算,把历史 KV 数据加载进上下文,即刻启动生成!


2. 两大主流流派:自动隐式缓存 vs 显式生命周期控制

目前各大厂商与开源推理引擎的实现主要分为两派:

维度 自动隐式缓存(如 DeepSeek, MiMo, vLLM RadixAttention) 显式声明缓存(如 Anthropic Claude, 阿里云 Qwen 百炼)
接入成本 零改动:普通调用即可,引擎自动寻找最长公共前缀 需要在请求 Payload 中显式标记断点(如 cache_control
计费规则 免创建费:首次输入全价,后续命中自动享受 80%~95% 巨额折扣 收创建费:首次写入需额外支付创建溢价(如 1.25x),命中后再享低价
底层实现 基于前缀树(Radix Tree)做动态 LRU/LFU 显存块驱逐 基于用户分配的显式 Cache ID 做定时 TTL 生命周期管理
最佳适用场景 动态高频多轮问答、社区海量共享前缀、无状态 Agent 调用 稳定的超大长文本常驻库、固定巨型知识库系统

开源内核揭秘:vLLM 的 RadixAttention 是如何工作的?

以目前工业界主流的开源引擎 vLLM 和 SGLang 为例,其内部采用 Radix Tree(基数树/前缀树) 管理整个集群的显存块(PagedAttention Blocks):

               根节点 (Root)

        [System Prompt: 200 tokens]  hash: 0x8a1c...

        [开源项目全量代码: 50,000 tokens]  hash: 0x3f9b...
             /         \
   [用户 A 的提问 1]   [用户 B 的提问 1]
    (独占 50 tokens)    (独占 60 tokens)

只要两个不同的请求共享了相同的开头 token 序列,它们就会顺着基数树匹配到同一个父节点,物理显存中的 KV Blocks 直接实现跨请求指针级零拷贝复用!


3. 常见惨案:你的缓存为什么命中率惨败为 0%?

许多开发者以为加上 Context Caching 就万事大吉,但在生产监控中往往发现缓存命中率(Cache Hit Rate)接近于 0%。最常见的陷阱包括:

陷阱一:在 Prompt 开头塞入动态时间戳(最致命杀手)

很多工程师为了让大模型具备时间感知,习惯在 System Prompt 第一行写上:

你是智能客服助手。当前系统时间是:2026-09-08 12:56:32。
以下是知识库文档(共 50 万字)......

灾难后果: 前缀树的匹配原则是自首个字符起完全精确逐 token 匹配! 因为秒数每一秒都在变动,导致第 0 个 token 的哈希就发生了漂移,整整 50 万字的长文档 KV 缓存被彻底视为无效废弃,每次请求都在被迫全量重新构建!

陷阱二:显存块边界未对齐(Block Boundary Alignment)

主流推理引擎是以“块(Block,通常为 16 或 64 个 tokens)”为最小调度粒度的。 如果一个长文档的切片长度没有对齐整块,或者中间插入了轻微变动的微小字符,可能导致后续整个块无法复用。

陷阱三:工具定义(Tools Schema)的无序序列化

在 Function Calling 场景中,许多框架(如部分 Python 库)在将 JSON Schema 序列化为字符串时,字典的键值对顺序是随机的(如 { "name": "foo", "desc": "bar" } 在下一次请求中变成了 { "desc": "bar", "name": "foo" })。 语义上完全等价,但在 Token 层面完全不同,瞬间造成前缀断裂。


4. 黄金排版法则:如何构建坚不可摧的高命中率 Prompt?

为了最大化榨干 Context Caching 的红利,必须严格执行**“静态在前、动态在后”的漏斗型布局**:

┌─────────────────────────────────────────────────────────────┐
│ 1. 绝对静态的全局核心指令 (System Prompt)                      │
│    角色定义、输出格式约束、禁止事项 (严格禁止任何时间变量)        │
├─────────────────────────────────────────────────────────────┤
│ 2. 稳定的工具函数定义 (Tool Definitions)                     │
│    所有 Schema 严格按字母序固定序列化                         │
├─────────────────────────────────────────────────────────────┤
│ 3. 巨型核心上下文 (Heavy Knowledge Base / Codebase)          │
│    长合同、长代码仓库、API 文档 (占总 Token 90% 以上)          │
├─────────────────────────────────────────────────────────────┤
│ 4. 少样本示例 (Few-Shot Examples)                            │
│    固定的典型问答范式                                       │
├─────────────────────────────────────────────────────────────┤
│ ======【高缓存命中分割线 (Cache Cutoff Line)】============== │
├─────────────────────────────────────────────────────────────┤
│ 5. 多轮历史对话队列 (Rolling Dialogue History)               │
├─────────────────────────────────────────────────────────────┤
│ 6. 动态环境上下文 (Dynamic Variables)                        │
│    当前精准时间、用户设备 IP、当前登录用户信息                │
├─────────────────────────────────────────────────────────────┤
│ 7. 用户当前最新输入 (Current User Query)                     │
└─────────────────────────────────────────────────────────────┘

收益总结

通过将“当前时间”和“用户动态变量”移至 Prompt 的最末尾,前 95% 的超长上下文在整个会话生命周期内将获得近乎 100% 的连续缓存命中

  1. API 账单降低 80% ~ 90%:绝大部分输入按照极其便宜的“缓存命中价(Cache Read)”结算;
  2. 首字延迟从 3000ms 暴降至 200ms 内:极大改善长文本问答与 Agent 工具链调用的用户体验。