Skip to content
waylongo's log
Go back

大模型 Prompt Cache 指南

这篇文章从 Transformer 原理到 API 计费,再到 Claude Code 的缓存设计拆一遍。看完你会知道大模型账单里的钱花在哪、为什么缓存能省 80%,以及调用习惯怎么影响账单。

Prompt Cache 费用对比:无缓存 vs 有缓存


导读

本文比较长,按兴趣挑着看:


一、背景

下面是我上个月的 Mify 账单:

真实账单截图

第一次看到这个账单的人通常会懵:缓存写入是什么?为什么有 5 分钟和 1 小时两种?缓存命中的费用为什么这么低/高?

这篇文章就是要把这几个数字背后的机制讲清楚。

核心结论先放这里:同样的调用量,理解缓存机制的人比不理解的人,API 费用差 3~5 倍。


二、没有缓存的世界

大模型没有记忆。

每一轮对话,你都要把完整的历史消息重新发给模型,它才知道之前聊了什么。就像一个失忆的专家,每次见面都要从头自我介绍。

第 1 轮:[系统提示] + [你的问题1]
第 2 轮:[系统提示] + [你的问题1] + [AI回复1] + [你的问题2]
第 3 轮:[系统提示] + [你的问题1] + [AI回复1] + [你的问题2] + [AI回复2] + [你的问题3]
...

每一轮的输入都在膨胀:

假设:系统提示 20K tokens,每轮对话增加 ~1K tokens

  Turn 1:  20K + 1K = 21K tokens      全价
  Turn 2:  20K + 2K = 22K tokens      全价
  Turn 3:  20K + 3K = 23K tokens      全价
  ...
  Turn 10: 20K + 10K = 30K tokens     全价
  ────────────────────────────
  10 轮总计:~255K tokens(全价)       ← 二次增长,越聊越贵

10 轮对话,你为系统提示付了 10 次全价。那 20K tokens 的内容一个字都没变,每次都要重新”读”一遍。

无缓存的 Token 增长

没有缓存,你在为重复的内容反复付费。


三、KV Cache:大模型的”记忆芯片”

3.1 先看实验数据

有人在本地用一个 8B 级别模型跑了一个多轮对话测试:先喂一篇 670 token 的文章,然后连续追问 5 个问题。

                 Prompt 处理时间    生成时间
Turn 1 (喂文章):   24,458ms         5,095ms
Turn 2 (追问1):    31,036ms        22,653ms
Turn 3 (追问2):       253ms  ←!!    2,511ms
Turn 4 (追问3):       203ms         2,029ms
Turn 5 (追问4):       165ms         1,870ms
Turn 6 (追问5):       176ms         1,235ms

Turn 2 到 Turn 3,输入处理时间从 31 秒直降到 0.25 秒。100 倍。生成速度全程稳定在 13-20 tok/s,没有变化——加速只发生在”消化输入”阶段。

再看一个 0.8B 级别小模型的对比:

                 Prompt 处理时间
Turn 1 (喂文章):   566ms
Turn 2 (追问1):    173ms
Turn 3 (追问2):    182ms
Turn 4-6:          200ms 上下,波澜不惊

小模型全程 200ms,平得很。

两个问题:那个 100x 加速是什么?为什么大模型受益巨大,小模型却无感?

本地 KV Cache 实验:8B 级别模型 vs 0.8B 级别模型

3.2 答案:注意力机制里的 KV

大模型用的是 Transformer 注意力机制。核心公式:

Attention(Q, K, V) = softmax(Q · Kᵀ / √d) · V

用图书馆类比:

KV Cache 就是把标签(K)和内容(V)存起来。下次来人,只需要看他的纸条(Q),直接查,不用重新贴标签。

3.3 为什么这行得通?因果掩码

这之所以可行,是因为用于文本生成的主流大模型通常按 自回归 方式工作:生成下一个 token 时,只依赖前面的 token。

因果掩码(causal mask):
        T₁   T₂   T₃   T₄
  T₁    ✅    ❌    ❌    ❌
  T₂    ✅    ✅    ❌    ❌
  T₃    ✅    ✅    ✅    ❌    ← T₃ 的 KV 永远不变
  T₄    ✅    ✅    ✅    ✅    ← 新增 T₄ 不影响 T₁₂₃

前面 token 的 KV 算完就固定了,后面怎么追加都不影响。你在第 100 页写了新内容,前 99 页不会因此改变。

BERT 这类 encoder-only 模型用的是双向注意力,加一个新 token 会改变已有 token 的表示,无法像自回归生成模型这样直接复用整段前缀的 KV。这也是 BERT 更适合做理解类任务,而不是直接做逐 token 生成的原因。

3.4 回到实验数据

模型越大,KV 计算越贵,缓存收益越大。

                    8B 级别模型              0.8B 级别模型
未命中               ~25,000ms               ~566ms
命中                 ~170ms                  ~173ms
加速比               148x                    3.3x

命中时两个模型速度几乎一样——都是从内存读取,计算量可以忽略。

KV Cache 原理图


四、Prompt Cache:API 层的缓存

KV Cache 是推理引擎内部的机制,让生成速度更快。还有另一层缓存直接影响账单——Prompt Cache。

4.1 两层缓存,别搞混

KV CachePrompt Cache
在哪一层模型推理内部API / 平台层
作用加速单次请求的逐字生成跨请求复用重复输入,降低费用
用户感知”回复变快了""账单变少了”
需要操作吗不需要,引擎自动管理需要设计(或使用自动缓存)
生命周期请求结束即清空5 分钟 ~ 1 小时

KV Cache 让你用得更快,Prompt Cache 让你花得更少。

两层缓存架构:Prompt Cache vs KV Cache

4.2 Prompt Cache 的核心思路

很多场景里,每次请求都携带一段固定内容:几千字的系统提示、用户上传的长文档、固定的 few-shot 示例。每次都让模型重新处理,慢且按全量计费。Prompt Cache 的做法:

第一次处理后缓存 KV 结果,后续请求前缀相同则直接复用,只收极低的”读取费”。

关键是前缀匹配——从第一个 token 开始连续匹配,中间任何一个字节变了,后面的缓存全部失效。

请求 1: [系统提示 A][工具定义][消息1]           → 全部写入缓存
请求 2: [系统提示 A][工具定义][消息1][消息2]     → 前缀命中缓存,只有消息2全价
请求 3: [系统提示 B][工具定义][消息1][消息2]     → 系统提示变了!全部重新计算
                ↑ 这里不同,后面全废

4.3 各平台定价

以 Claude 系列为例(价格单位:美元 / 百万 Token):

模型正常输入缓存写入(5min)缓存写入(1h)缓存命中输出
Opus 4.7$5$6.25$10$0.50$25
Sonnet 4.6$3$3.75$6$0.30$15
Haiku 4.5$1$1.25$2$0.10$5

规律很简单:缓存写入 = 正常输入 × 1.25(5min)或 × 2(1h);缓存命中 = 正常输入 × 0.1,相当于 90% 折扣。

OpenAI 的策略不同:自动缓存,不需要显式写 cache_control,也不单独收写入费;命中缓存的输入 token 会按 cached input 价格计费。控制权少一些,但省心。

4.4 一个具体的费用计算

场景:10 轮对话,系统提示 20K tokens,每轮新增 ~1K tokens。使用 Sonnet 4.6。

没有缓存(每轮全价):

  Turn 1:  21K × $3/M = $0.063
  Turn 2:  22K × $3/M = $0.066
  ...
  Turn 10: 30K × $3/M = $0.090
  ────────────────────────────
  10 轮输入总计:255K tokens × $3/M = $0.765

有缓存(前缀命中 1/10 价格):

  Turn 1:  21K × $3.75/M = $0.079    ← 首次写入缓存(贵25%)
  Turn 2:  21K × $0.30/M + 1K × $3/M = $0.0093
  Turn 3:  22K × $0.30/M + 1K × $3/M = $0.0096
  ...
  Turn 10: 29K × $0.30/M + 1K × $3/M = $0.0117
  ────────────────────────────
  10 轮输入总计:≈ $0.18

$0.765 vs $0.18,缓存省了 76%。轮数越多省得越多,20 轮以上的对话节省比例可达 85%+。

有缓存 vs 无缓存费用对比

4.5 缓存有效期

平台默认有效期可扩展至续期机制
Claude5 分钟1 小时(2x 价格)每次命中自动续期
OpenAI5-10 分钟最长可达 24 小时(不同模型和负载下不保证)自动管理

缓存在每次被读取时自动续期。持续对话就不会过期,但停下来喝杯咖啡超过 5 分钟,缓存就没了,下一轮要重新付写入费。1 小时 TTL 基本消除这个问题,后面 5.7 节会讲 Claude Code 的两档 TTL。


五、Claude Code 的缓存工程

Claude Code(以下简称 CC)把缓存用到了一个相当极端的程度。Anthropic 团队在复盘文章里提到,Claude Code 的系统设计高度围绕 prompt caching 展开,缓存命中率下降几个百分点都会被当成需要排查的线上问题。

这一章基于官方分享、公开文档和日常使用观察,拆解它的缓存设计。

5.1 每轮对话的真实开销

每发一条消息,CC 都会把以下内容完整打包发给 API:

  1. 系统指令(角色定义、行为准则)
  2. ~40 个工具的完整 schema 定义
  3. CLAUDE.md 项目上下文
  4. Git 状态快照
  5. 完整对话历史
  6. 本轮消息

第 30 条消息的实际输入量 = 前 29 条的全部内容 + 新消息。没有缓存,这个成本会失控。

5.2 Prompt 的多层结构

CC 发出的 prompt 不是一整块,而是专门为缓存优化过的多层结构:

┌──────────────────────────────────────────────────────────┐
│ system(系统提示词,~20K tokens)                           │
│                                                          │
│   Block 1-2: 计费归因头 + CLI 前缀  → 不缓存                │
│   Block 3:   静态指令(行为规则等)   → 跨 session 复用      │
│   ──── DYNAMIC_BOUNDARY ────                             │
│   Block 4:   动态内容(CLAUDE.md 等) → 项目级复用           │
├──────────────────────────────────────────────────────────┤
│ tools(工具 schema,session 内冻结)                        │
├──────────────────────────────────────────────────────────┤
│ messages(对话历史 + 本轮消息)                             │
│   最后一条消息上放 cache_control 标记                       │
└──────────────────────────────────────────────────────────┘

静态内容在前,动态内容在后——最大化前缀匹配的长度。

Block 3 是所有 CC 请求共用的同一段静态内容,理论上命中率最高。注意:Anthropic 的缓存按组织隔离,不同组织之间不会共享缓存;同一组织内的不同 session,只要前缀相同,就有机会命中同一份缓存。

Claude Code Prompt 多层结构

5.3 三个缓存断点

每个请求精确放置 3 个缓存断点(Anthropic 最多允许 4 个):

断点位置作用
#1静态指令末尾(Block 3)跨 session 复用,命中率最高
#2动态内容末尾(Block 4)同一项目内复用
#3最后一条消息同一 session 内增量缓存

工具定义没有单独的断点,但通常不需要单独处理——Anthropic 的工具定义本来就在 prompt 结构的靠前位置,只要后续断点命中,工具定义也会一起受益。

历史消息也不需要每条都打断点。更关键的是控制旧消息和大型工具输出的体积,避免让每轮请求都背着越来越长的历史。

5.4 围绕缓存的设计哲学

CC 团队有几个”反直觉但为了缓存不得不这样做”的设计决策:

Plan Mode:不切换工具集,用工具来切换模式

直觉做法是进入 plan mode 时,把工具集换成只读工具(只保留 Read、Grep 等)。但这会改变工具定义,缓存前缀变了,整个缓存失效。

CC 的做法:始终保留全部工具,新增 EnterPlanMode 和 ExitPlanMode 作为工具。进入 plan mode 时,在消息里告诉模型”你现在只探索不执行”。工具定义永远不变,缓存永远不断。顺带一提,因为 EnterPlanMode 是工具,模型可以自主决定进入 plan mode,不需要用户手动触发。

Tool Search:不删除工具,用 stub 替代

CC 可以加载几十个 MCP 工具,全部放进请求太贵,但删除工具会破坏缓存。解决方案是延迟加载:不直接改变前缀里的完整工具集合,而是先给模型轻量入口,模型需要时再按需加载完整定义。

用消息更新信息,不改系统提示

时间变了、文件被修改了?直觉是更新系统提示,但这会破坏缓存。CC 在下一条用户消息或工具结果里加一个 <system-reminder> 标签,把更新信息放在消息流里。前缀不变,缓存不断。

5.5 Compaction 的缓存安全设计

/compact 把整个对话历史压缩成摘要,以摘要重新开始对话。但压缩本身需要一次 API 调用——如果这次调用用了不同的 system prompt 和工具集,就无法复用主对话的缓存,等于白白多付一次全价。

CC 的做法:Cache-Safe Forking

压缩请求使用完全相同的 system prompt、工具定义、历史消息,只在末尾追加压缩指令。从 API 视角看,这个请求和主对话上一个请求几乎一模一样,缓存前缀被完整复用。

主对话最后一个请求:
  [system] [tools] [msg1] [msg2] ... [msg30] [user: "帮我重构这个函数"]

压缩请求:
  [system] [tools] [msg1] [msg2] ... [msg30] [user: "帮我重构这个函数"]

  [assistant: "上一轮回答 / 工具结果"] [user: "请压缩以上对话为摘要"]
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  这整段前缀和主对话完全一致,缓存命中!只有最后的压缩指令是新的。

压缩时 CC 还会自动:

完成一个子任务或历史明显变长后再 /compact,不要在上下文还很短时过早压缩。可以附加指令:/compact 保留 API 设计决策和文件修改记录。

5.6 缓存断裂检测

CC 监控每次调用的 cache_read_input_tokens。如果比上次下降 >5% 且绝对值 >2000 tokens,判定为缓存断裂,自动分析原因:系统提示变了?工具增减了?TTL 过期了?模型切换了?

5.7 两档 TTL (Time To Live)

用户类型缓存有效期条件
所有用户5 分钟默认
API 显式设置 extended cache1 小时需要在缓存断点上设置更长 TTL,写入价格更高

5 分钟 TTL 下,停下来喝杯咖啡再回来,缓存可能已经失效,系统提示要重新付写入费。1 小时 TTL 基本消除这个问题,但代价是缓存写入更贵,适合长任务或中间会频繁中断的工作流。

如果通过第三方中转使用 API,还要确认对方是否完整透传 cache_control 和使用量字段,否则账单里看不到真实的缓存命中情况。

5.8 子智能体

CC 在处理复杂任务时会启动子智能体:

Explore Agent(代码库探索):搜索文件和代码时自动启动,用 Haiku 而不是主对话的 Sonnet 或 Opus。按当前定价,Haiku 比 Sonnet 便宜约 2/3,比 Opus 便宜约 80%,同时跳过 CLAUDE.md 加载,省掉数千 token。注意:sub-agent 的工具集和消息历史都和主线程独立,不共享主线程缓存,每次启动等于一次”迷你冷启动”。

描述越模糊,Explore Agent 跑的轮数越多,给精确路径比让它猜便宜得多:

Plan Agent(架构设计):复杂任务中也可能出现独立规划上下文。它的好处是让主线程更干净,代价是这类上下文通常不能直接复用主线程的完整消息缓存。

缓存链条:断在哪里后面全废


六、缓存杀手:哪些操作在悄悄烧钱

缓存基于严格的前缀匹配。前缀任意一个字节变化,后面的缓存全部失效。以下是最常见的 3 个杀手。

杀手 1:会话中切换模型

缓存绑定具体模型。不同模型的 KV 张量完全不同,零复用。一个 20,000 token 的系统提示:切模型前每轮 $0.006(缓存读取),切模型后当轮 $0.075(缓存写入),一次切换多付 12 倍。

需要换模型,开新会话,别在当前会话里切。

杀手 2:会话中修改 CLAUDE.md

CLAUDE.md 在系统提示的 Block 4 里。内容一改,哈希就变了,Block 4 之后的所有缓存立即失效。开会话前写好,开始后不动。

杀手 3:注入精确时间戳

CC 在系统提示里注入当前日期,但只精确到天。如果精确到秒,每次请求的系统提示都不同,缓存永远不中。通过 API 集成时,不要把每秒变化的内容塞进系统提示——把动态信息放在消息里(参见 5.4 节)。

缓存失效矩阵(按 Anthropic 官方文档整理)

什么变了工具缓存系统提示缓存消息缓存
工具定义❌❌❌
开关 Web Search✅❌❌
开关 Citations✅❌❌
切换 Fast Mode✅❌❌
tool_choice 参数✅✅❌
添加/删除图片✅✅❌
Thinking 参数变化✅✅❌

规律:缓存层级是 tools → system → messages,上游变了,下游全废。

3 大缓存杀手


七、最佳实践:怎么省钱

核心原则:别碰前缀

保护缓存的(🟢 绿灯):

操作为什么安全
连续对话前缀不变,增量缓存
复用同一个 session共享缓存
定期整理 CLAUDE.md好习惯,但在会话间隙做

破坏缓存的(🔴 红灯):

操作代价
开新 session冷缓存,~20K tokens 全价重算
改 CLAUDE.mdBlock 4 起全部失效
加减 MCP 工具工具 schema 变化 = 缓存断裂
切换模型完全失效,12 倍惩罚
过早 /compact消息历史变了 = 部分断裂,等历史明显变长或子任务完成后再用
发呆超过 TTL缓存过期,下轮重新写入

架构层策略(节省 50–80%)

1. 一个会话一个任务

话题切换后,旧对话历史变成每轮都要付费的噪音。新任务开新会话。

2. 主动 /compact,但别太早

完成子任务或历史明显变长时压缩,附上保留指令:/compact 保留 API 设计决策和文件修改记录。太早压缩会损失消息缓存,太晚压缩会让历史持续膨胀。

3. 固定模型,不中途切换

需要换模型就开新会话,当前所有缓存保留。

4. 开会话前写好 CLAUDE.md

会话中改 CLAUDE.md 等于主动让系统提示缓存失效。用 /init 初始化好再开始。

提示词层策略(节省 20–50%)

1. 一次说完比追问省 token

三条消息 = 三次完整上下文加载,一条消息 = 一次。

✅ "总结这个文件,列出要点,建议标题"
❌ "总结这个文件" → "列出要点" → "起个标题"

2. 编辑原始消息,不要发新消息纠正

每条新消息永久追加进历史,后续每轮都要为它付费。CC 支持直接编辑历史消息(两次 Esc 回滚)。

3. 给精确路径,不让 AI 搜索

模糊描述触发 Explore Agent 多轮搜索,精确路径直接读取。

4. 在 CLAUDE.md 里排除大型生成文件

JSON 文件的 token 密度是普通代码的 2 倍(2 字节 ≈ 1 token vs 4 字节 ≈ 1 token)。package-lock.json 动辄数万 token,一旦被读入上下文就是巨大浪费。CC 默认已排除 package-lock.json、yarn.lock 等,自定义的大型生成文件需要手动排除。

5. 分段工作

把大任务拆成几个独立会话,每段各自压缩,比一条龙跑到底更可控。


总结

省 Token 的本质是让操作习惯对齐缓存架构。

同样的调用量,理解缓存机制的人比不理解的人,API 费用差 3~5 倍。



Previous Post
Wearables Tech Frontiers:一个可控的穿戴健康情报系统
Next Post
运动健康 Autoresearch 探索:为什么你需要 Harness