Skip to content

필사 모드: LLM 缓存的原理 — 提示词缓存与 Prefix Cache 为什么能省钱

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

引言 — 「前缀相同就便宜」的谜团

LLM API 的提示词缓存,第一次看会觉得很怪。费用变便宜靠的不是把同一个问题问两次,而是提示词的前半部分相同——即使后半部分完全不同也没关系。更奇怪的是,有的提供商对缓存的"写入"额外收费,"读取"费用却只要十分之一。这份怪异的价目表不是营销手段,而是Transformer 内部物理规律的直接体现

本文从这套物理规律的底层往上讲起:Transformer 生成文本时内部到底发生了什么,KV 缓存是什么、为什么只对前缀(prefix)生效,vLLM 和 SGLang 这类推理服务引擎是怎么实现它的,以及在实战中该如何为 Anthropic、OpenAI 的提示词缓存做设计。

第一步 — 推理分为两个阶段:Prefill 与 Decode

Transformer 推理有两个性质完全不同的阶段。

  • Prefill(预填充) — 一次性处理整个输入提示词的阶段。提示词的所有 token 都可以并行计算,把 GPU 的运算能力用满(compute-bound)。第一个 token 出现前的延迟(TTFT)基本上是在这里决定的。
  • Decode(解码) — 逐个 token 生成答案的阶段。每生成一个 token 都要把模型完整地过一遍,这时瓶颈不是运算而是内存带宽(memory-bound)。

提示词缓存节省的,正是prefill 阶段的运算。如果一个 10 万 token 的系统提示词已经在缓存里,那这 10 万 token 就不用再重新 prefill 一次。所以缓存命中省下的不只是成本,还会大幅缩短 TTFT。

第二步 — 注意力与 K/V:每个 token 都会印一张名片

为什么 prefill 可以被跳过?得看注意力(attention)内部。

在 Transformer 的每一层,每个 token 都会被转换成三个向量 — Query(查询)、Key(键)、Value(值)。直观地说,每个 token 会先印好介绍自己的名片(K)内容物(V),新来的 token 则用自己的问题(Q) 去翻看之前所有 token 的名片、计算相关度,再按相关度把内容物混合进来。

这里有一个核心性质。GPT 类模型的注意力是因果的(causal)——每个 token 只能看见排在自己前面的 token。这意味着:

token i 的 K 和 V,只是 token 1 到 i 的函数。不管后面来什么 token,它们都绝不会改变。

这条性质是一切缓存的根基。只要"到目前为止的文本"相同,每个 token 的 K/V 向量就会逐比特(bit)算出完全相同的值。既然如此,算过一次的东西为什么还要再算一次?存起来就行了。

第三步 — KV 缓存:存起来就好

KV 缓存 正是那个存储的地方。在 decode 阶段生成新 token 时,模型需要用到之前所有 token 的 K/V。如果没有缓存,每生成一个 token 就得把目前为止的整个序列重新算一遍,生成成本会随序列长度呈平方级爆炸。有了 KV 缓存,只需要计算新 token 的 Q/K/V,之前的部分直接从缓存里读。

这不是免费的。KV 缓存很吃内存。一个 token 占用的 KV 缓存大小,大致是这样算出来的:

每 token 的 KV 缓存 = 2(K 和 V)× 层数 × KV 头数 × 头维度 × 字节数

例:7B 级模型(32 层,隐藏维度 4096,fp16)
   = 2 × 32 × 4096 × 2 字节 ≈ 每 token 约 512KB
   → 一段 4,000 token 的对话 ≈ 约 2GB
   → 几十个并发用户,GPU 内存就会瞬间见底

这种内存压力解释了不少最新架构的设计——GQA(多个 Q 头共享 K/V 头)和 MQA 都是通过减少 KV 头数来把缓存压缩到几分之一的设计,KV 缓存量化(FP8/INT4)也是出自同样的压力。在长上下文时代,真正的成本往往不是权重,而是 KV 缓存。

第四步 — Prefix Cache:跨请求也能复用

到目前为止的 KV 缓存说的都是一次请求之内的事。Prefix Cache(前缀缓存) 把这个概念扩展到了请求之间

如果两个请求共享相同的前半部分(比如同一个系统提示词 + 同一份文档),那这部分的 K/V 就是完全相同的——这正是前面说的因果性带来的好处。所以没必要在第一个请求算完就把 K/V 块丢掉,留着它,等下一个请求的前缀一致时,跳过 prefill 直接接着用就行。

为什么只有"前缀"才行? 这是因果性的另一面。token i 的 K/V 是 1..i 的函数,所以哪怕中间只改了一个 token,那个位置之后所有 token 的 K/V 都会变。在提示词中间插入一个时间戳,后面 10 万 token 的缓存就会整体失效,原因就在这里。缓存反应的不是"内容是否包含",而是"前缀是否逐字节相同"。

真实推理服务引擎的实现:

  • vLLM — Automatic Prefix Caching(APC):vLLM 的 PagedAttention 像操作系统的分页一样,把 KV 缓存按固定大小的块来管理。APC 用"到这个块为止的 token 序列"的哈希来标识每个块,一旦已经存在相同哈希的块,就直接复用。因为是按块处理,部分匹配也能自然地被处理。
  • SGLang — RadixAttention:用radix tree(基数树) 来管理共享的前缀。数百个请求从同一个系统提示词分叉出去的形态(一棵树!),正好被这种数据结构原样反映出来,在多轮对话、并行分支这类负载上格外高效。旧分支用 LRU 策略修剪掉。

第五步 — 提供商的提示词缓存:怎么读价目表

API 提供商的"提示词缓存",就是把这套 prefix cache 商品化的结果。现在这份价目表可以当作物理规律来读了。

Anthropic(Claude) — 显式缓存。在请求里标出 cache_control 断点(最多 4 个)。

  • 缓存写入:基础输入价格的 1.25 倍(5 分钟 TTL)或 2 倍(1 小时 TTL)——这是计算并保留 K/V 的成本。
  • 缓存读取:基础输入价格的约 0.1 倍——因为跳过了 prefill 运算,所以只要十分之一。
  • 收支平衡点:5 分钟 TTL 从第 2 次请求起就划算(1.25 + 0.1 < 2),1 小时 TTL 得复用 3 次以上才划算(2 + 0.2 < 3)。
  • 最小缓存长度:根据模型不同,前缀得达到 1024~4096 token 以上才会被缓存。太短的前缀即使加了标记,也会悄悄地不被缓存。
  • 渲染顺序是 tools → system → messages——工具列表排在最前面,所以增删或重新排列工具,会让整个缓存失效。

OpenAI — 自动缓存。1024 token 以上的提示词只要前缀一致,不需要任何标记就会自动生效,按公开资料,被缓存的输入 token 有 50% 的折扣。没有显式标记用起来省心,但你也就没有"缓存到哪里"的控制权。

两种方式的验证方法是一样的——看响应里的 usage 字段。如果是 Anthropic,cache_read_input_tokens 不为 0,才说明缓存真的被读取了。反复请求却一直是 0,说明某处藏着一个沉默的缓存破坏者。

第六步 — 实战设计规则

搞懂原理之后,规则自然就出来了。

  • 稳定的放前面,会变的放后面。 固定的系统提示词、文档、工具定义放前面,用户问题、时间戳、请求 ID 放后面。组装提示词的代码里的"顺序",决定了缓存性能。
  • 揪出沉默的缓存破坏者。 系统提示词里嵌的 datetime.now()、每次请求都重新生成的 UUID、没排序的 JSON 序列化(键的顺序每次都不一样)、因用户而异的工具列表——这些都会改变前缀的字节,把缓存打碎。
  • 对话就是不断累积的前缀。 在多轮对话里,之前的所有轮次都会变成前缀,所以轮次累积得越多,缓存收益就越大。标准做法是把断点放在最新追加那一轮的末尾。
  • 换模型,缓存也跟着消失。 K/V 是用特定模型的权重算出来的值,所以缓存是按模型区分的。中途换模型,就得从头重新写。
  • 自己搭建服务的话,别忘了打开它。 光是启用 vLLM 的 APC 或者用 SGLang,就能大幅降低聊天类负载的 prefill 成本。尤其是"对同一份文档问多个问题"这种模式,效果格外明显。

结语 — 价目表就是物理规律

浓缩成一句话就是这样。注意力是因果的,所以前面 token 的 K/V 不会变;KV 缓存在一次请求内复用它,prefix cache 在请求之间复用它;提示词缓存的价目表(写入溢价、读取折扣、只认前缀、有最小长度)不过是把这种复用的成本结构原样换算成了价格。"为什么只有前面部分才行?"这个问题的答案,从来不是营销政策,而是一行数学:token i 的 K/V 只是 1..i 的函数。

服务成本的整体图景,在 AI 模型开发生命周期 的服务小节,以及把 GPU 拆分使用的 MIG 指南 中继续展开。

参考资料

현재 단락 (1/47)

LLM API 的提示词缓存,第一次看会觉得很怪。费用变便宜靠的不是把同一个问题问两次,而是**提示词的前半部分相同**——即使后半部分完全不同也没关系。更奇怪的是,有的提供商对缓存的"写入"额外收费...

작성 글자: 0원문 글자: 4,218작성 단락: 0/47