所谓"缓存命中便宜、不命中贵",省的不是 token 本身,是模型把你的 prompt 跑一遍预计算(prefill)的算力。命中就是复用上次算好的中间成果、跳过重复计算;不命中就得从头再算一遍。同一段 system prompt,第一次老实算,后面次次白嫖------账单差就在这。
一、先用一个比喻搞懂
把大模型想成一个赶作业的学霸。你每次交的"卷子",开头都要写一段一模一样的 2000 字个人陈述(这就是你的 system prompt)。
- 笨办法:每次都把那 2000 字重新手写一遍。
- 聪明办法:第一次写完后** photocopy(复印件)**了一份订在卷子头上,以后每次直接把复印件订上去,只手写后面不一样的部分。
这里的**"复印件"就是缓存(KV cache)**------模型算好的中间成果。
- 命中 :这次卷子开头和上次复印件一字不差 → 直接订复印件,省得重写 → 这部分按低价计费。
- 不命中 :开头改了一个字 → 复印件用不了,2000 字重写 → 按原价计费。
为什么只有"开头 "能复印?因为模型是逐字往后读 的,后面每个字的理解都依赖前面的字。只要前面一样,前面的计算成果就能整个搬来用;前面一改,后面全得重来。所以缓存只认前缀。
这笔账有多夸张
拿 Anthropic 定价举例(base $5 / 百万 token):一段 2000 token 的 system prompt,每次再带 50 token 的用户问题。
| 方式 | 单次成本 | 10 次总成本 |
|---|---|---|
| 不缓存 | 2050 token 全原价 ≈ $0.0103 | ≈ $0.103 |
| 缓存(首写 1.25x,后续读 0.1x) | 首 0.0128,后续0.0013 | ≈ $0.024 |
省了 3/4 以上。 请求越多越省,百次级能砍掉 85%+。这还只是 2000 token 的 system prompt------如果你的 prompt 里塞的是一整份几万 token 的文档(RAG、长文档问答),差距会拉到 10 倍。
二、硬核原理:KV Cache 与为什么前缀能复用
2.1 预计算(prefill)到底算了什么
Transformer 处理你的 prompt 时,会把整段文字过一遍所有层。每一层都给每个 token 算出两组向量:Key(K)和 Value(V) ,合称 KV。生成回答时,模型靠 attention 去"回顾"前面所有 token 的 K 和 V。为了不每次重算,推理框架把算过的 KV 留在显存里------这就是 KV cache。
关键点:attention 是单向的,只往后看。 第 1 到第 N 个 token 的 KV 向量,只取决于这前 N 个 token 自己,跟后面跟了什么话没关系。所以只要两个请求的前 N 个 token 一模一样,它们前 N 个位置的 KV cache 就分毫不差。第二个请求直接把这块 KV 捞出来用,省掉前 N 个 token 的预计算。
这块显存其实挺贵。拿 Llama-2 70B 算一笔账:每个 token 每层 KV 约 4KB(FP16,GQA 后 8 个 KV head),80 层就是 320KB/token。一个 2000 token 的 system prompt,KV cache 要占 ~640MB 显存。每次请求都重算再扔掉,纯属浪费------prefix caching 就是把这块显存留住、复用。
2.2 实际是按"块"复用,不是按 token
推理框架不会傻到逐 token 比对,而是把 KV 切成固定大小的块(vLLM 用 PagedAttention,16 token 一块;SGLang 用 RadixAttention,前缀树组织)。匹配靠链式哈希:
ini
key_0 = hash(tokens[0 : 16])
key_1 = hash(key_0 || tokens[16 : 32])
key_2 = hash(key_1 || tokens[32 : 48])
...
为什么搞链式?因为两个 prompt 可能末尾一样、前面不一样 。比如"A 国首都是巴黎,2+2=?"和"B 国首都是巴黎,2+2=?",最后那句"2+2=?"的 KV 其实不同------前面上下文变了,它 attend 到的东西就不同。链式哈希保证:只要前面某一块对不上,后面所有块的 key 全变,自然不会误命中。
2.3 从调用栈看,这事儿分四层
我们平时说的"token 缓存命中",落到最底下就是物理层那块 KV 被复用了。
三、命中 / 不命中,流程上差在哪
- 命中 :前缀的 KV 直接复用,这截 token 不进预计算,计费走"cache read"低价档。延迟(TTFT,首 token 时延)也跟着降------缓存越长,首 token 越快。OpenAI 实测过,长 prompt(150K+)命中能比不命中快约 67% 的 TTFT。
- 不命中:整段重算。Anthropic 和 Gemini 还会顺手把前缀写进缓存(收"cache write"费);OpenAI 在 GPT-5.6 之前的模型写入免费,GPT-5.6 起写入收 1.25x。
两个反直觉、但必须记住的点:
- 缓存只对"前缀"生效。 一旦 prompt 从某个位置开始跟缓存版本不一样(换了 user 消息、换了一份 RAG 文档),那个位置之后的缓存就废了。
- 字节级精确匹配。 多一个空格、改个标点,缓存直接失效。不是"意思差不多就行"。
四、三家实现和定价,差别比想象大
| 厂商 | 触发方式 | 最小可缓存 | 命中折扣 | TTL | 写入费 |
|---|---|---|---|---|---|
| Anthropic | 显式 cache_control |
1024 token | 90% off(0.1x) | 5min(默认)/ 1h(付费) | 5min 写 1.25x / 1h 写 2x |
| OpenAI | 全自动(>1024) | 1024 token,128 递增 | 50%~90% 不等,越新越深 | 5--10min,1h 内清 | 老模型免费;GPT-5.6+ 写 1.25x |
| Gemini | 隐式自动 + 显式 API | 隐式 1024 / 显式 32768 | 隐式 75% off / 显式约 90% | 显式可配,默认 1h | 存储 $1.00/M token/小时 |
几个我踩过或观察到的坑:
- Anthropic 的写入费是真实成本。 5min TTL 写一次收 1.25x。盈亏平衡点在 1~2 次命中:写贵 25%,但每次命中省 90%,第二次命中就回本。所以"5 分钟内请求 ≥2 次同一前缀"的负载,闭眼开。低于这个频率,缓存老过期,你一直在交写入费------这时要么上 1h TTL(写 2x),要么干脆别用。
- OpenAI 全自动但你不控制路由。 它按前缀哈希把请求路由到"最近算过同样前缀的机器",必须落到同一台才命中。多租户高峰期缓存可能被挤掉,没保证。共享长前缀的流量可以传
prompt_cache_key帮它路由命中。另外它返回usage.prompt_tokens_details.cached_tokens,这是验证命中的唯一真实信号,别只看账单。 - Gemini 显式缓存按小时收存储费。 32K 起,你开一个缓存对象,每小时按 token 数收钱,跟命中多少次无关。低频请求可能"省了计算、赔了存储"。它的
cachedContent是显式资源,TTL 默认 1 小时,适合"一份大文档被反复问"的场景。
五、实战:怎么更容易命中(按性价比排序)
回到开头的"抄作业"比喻------你想 photocopy 的那段,必须每次都一模一样、且放在最前面。
1. 静态内容放最前,动态内容放最后。 这是铁律。system prompt、few-shot 示例、工具定义、RAG 文档这些不变的,全堆在 prompt 头部;用户每次不同的输入放尾巴。缓存只看前缀,尾巴随便变不影响前缀命中。
2. 顺序要稳定。 system → tools → 记忆 → 历史对话 → 当前消息,这个顺序每次保持一致。工具定义按名字排序(确定性顺序)。OpenAI 的匹配是"最长精确前缀",你 reorder 一下前面,整段缓存就断了。
3. 别在缓存前缀里塞"会变的量"。 时间戳、请求 ID、随机种子、当次日期------这些只要出现在被缓存的前缀段里,每次都不同,缓存必失。需要的话把它们挪到尾部动态区。我见过有人把 当前时间:{now()} 写进 system prompt,结果缓存一次都没命中过,排查了半天才发现。
4. Anthropic 把 breakpoint 打在稳定边界上。 最多 4 个:system prompt 结尾、工具定义结尾、静态文档结尾、历史对话前缀结尾。每个 breakpoint 之前到上一个 breakpoint 之间的内容被缓存。让"会变的部分"恰好落在最后一个 breakpoint 之后。
5. 低频负载的处理。 5 分钟 TTL 对低频接口是诅咒------请求间隔一长,缓存早过期,你每次都交写入费。两个办法:用 1h TTL(写贵一倍,但命中赚更多);或者搞个缓存 warmer,定时用同一前缀打一个低成本请求把缓存焐热。
6. 一定要监控命中率。 别凭感觉。Anthropic 看 cache_read_input_tokens / cache_creation_input_tokens;OpenAI 看 cached_tokens;Gemini 看缓存对象的复用统计。命中率为 0 但你以为开了------多半是前缀没对齐或没过最小 token 阈值(1024)。
7. 不在每次都变的内容上浪费缓存。 缓存只对"会被重复的前缀"有意义。一份每请求都不同的 RAG 上下文塞进缓存,等于每次都写、从不读,纯亏写入费。判断标准一句话:这个前缀,后面还会有请求用一模一样的开头吗?不会就别缓存。
六、收尾
Token 缓存不是玄学,就是"相同前缀别重复算 "这一件事,被三家包装成了不同的 API 和价目表。要把它用明白,记住三件事:静态前置、字节一致、盯着命中率。做到了,长 system prompt / 多轮对话 / 大文档问答这类负载,账单砍掉 60%~90% 很常见;做不到,你可能一直在交 1.25x 的写入费,还以为自己省了。