1. Prompt 缓存到底在解决什么问题第一次接触 Prompt 缓存这个概念是在做一个多轮对话应用的时候。当时用户量不大但账单跑得飞快排查下来发现大量请求的 system prompt 是完全一样的——同一个角色设定、同一套输出格式约束、同一批少样本示例每次调用都原封不动地重新传一遍。这就好比你每次去餐厅点菜都要把整本菜单从头到尾念给服务员听哪怕你点的还是昨天那道菜。Prompt 缓存要解决的就是这件事把重复出现的 prompt 前缀在服务端存下来后续请求命中缓存时直接复用不再重复计算。它带来的收益有两块一块是钱一块是速度。计费上缓存命中的 token 通常按一个远低于正常输入 token 的单价结算速度上省掉了前缀的预填充计算首 token 延迟能明显下降。但这里有个关键前提缓存不是自动生效的它依赖你在请求里显式地打断点breakpoint告诉服务端“从这里往前的部分值得缓存”。断点打在哪里、打几个、什么时候该失效直接决定了你是省钱还是白忙活。这篇文章就把计费规则和断点策略这两件事拆开讲清楚适合正在做 LLM 应用、被 token 账单困扰、或者想优化首字延迟的开发者参考。哪怕你只是刚接触 prompt engineering看完也能明白缓存这套机制该怎么用。2. 缓存计费的核心逻辑拆解2.1 为什么缓存能便宜从预填充说起要理解计费得先理解模型处理一次请求的两个阶段。第一个阶段叫预填充prefill模型把你输入的整段 prompt 一次性读进去计算每一层的注意力键值KV这个过程是并行的但计算量正比于输入长度。第二个阶段叫解码decode模型一个 token 一个 token 地往外吐这个过程是串行的每吐一个都要重新算一遍注意力。缓存省的是第一阶段。如果两次请求的 prompt 前缀完全一致那么这段前缀算出来的 KV 就是一样的服务端完全可以把它存起来复用跳过重复的预填充。这就是为什么缓存命中的部分能打折——它确实少干了活。这里有个容易混淆的点缓存的是KV不是文本本身。文本只是用来做前缀匹配的键真正被复用是那些中间计算结果。所以缓存能不能命中取决于前缀是否逐 token 完全一致差一个字符都不行。2.2 计费口径命中、写入、未命中三档不同服务商的计费口径略有差异但基本都围绕三档来设计我把它整理成一张表方便对照计费档位触发条件单价特征说明缓存命中前缀与已有缓存完全匹配最低通常为正常输入的 10% 左右真正省钱的部分缓存写入首次建立缓存断点略高于正常输入相当于“存一次”的成本未命中无匹配缓存正常处理标准输入单价没省到注意缓存写入这一档经常被忽略。很多人以为缓存是免费的实际上第一次写入往往要额外付费只是后续命中能把成本摊薄。如果你的 prompt 前缀只用一次就再也不出现那打缓存断点反而是亏的。所以判断要不要用缓存本质是一道算术题写入溢价 N 次命中成本对比 N1 次标准输入成本。只要前缀会被复用足够多次缓存就是划算的。经验上同一个前缀复用超过 2 到 3 次基本就开始回本了。2.3 缓存的生命周期与失效缓存不是永久的。服务端通常会设置一个存活时间TTL常见的是几分钟到一小时不等具体取决于服务商策略。超过这个时间没有请求命中缓存就被回收了。这意味着如果你的应用流量稀疏缓存可能在你两次请求之间就过期了等于白写。另一个失效来源是前缀变化。只要你在断点之前的内容里改了一个字整段缓存就作废。这一点在动态拼接 prompt 的时候特别容易踩坑——比如你把时间戳、用户 ID 这类每次都变的内容放在了断点前面那缓存永远命中不了。3. 断点策略打在哪里才不浪费3.1 断点的本质是“前缀边界”断点breakpoint这个概念说白了就是你在 prompt 里插一个标记声明“这个标记之前的内容请帮我缓存”。服务端会以这个标记为界把它之前的所有 token 作为缓存单元。这里要建立一个心智模型缓存是前缀式的不是片段式的。你不能只缓存中间某一段只能缓存从头到某个断点的整段前缀。所以断点的位置选择本质上是在回答“哪一段前缀最稳定、复用最频繁”。3.2 典型的分层结构一个结构良好的 prompt通常可以分成几层从稳定到易变依次是系统级指令角色设定、能力边界、安全约束。这部分几乎不变是最值得缓存的前缀。工具与格式定义函数签名、输出 schema、少样本示例。变化频率低。会话历史多轮对话的累积上下文。随对话推进而增长。当前用户输入每次都不同绝对不能放在断点前面。合理的做法是把断点打在第 2 层和第 3 层之间也就是“稳定的系统部分”和“动态的会话部分”的分界处。这样系统部分被缓存会话部分正常处理。3.3 多断点与断点数量有些服务商支持在一条请求里打多个断点比如最多 4 个。多断点的意义在于分层缓存系统指令一个断点工具定义一个断点会话历史再一个断点。这样当会话历史增长时前面的系统部分依然命中缓存只有新增的对话轮次需要重新计算。但断点不是越多越好。每个断点都意味着一次缓存查找和可能的写入断点太密反而增加开销。我的经验是优先保证最长的稳定前缀有一个断点其余断点按实际复用情况再加。如果一条 prompt 里只有系统指令是稳定的那就只打一个断点别硬凑。4. 实操从零配置一套可用的缓存方案4.1 请求结构长什么样以常见的消息数组结构为例缓存断点通常通过一个额外的字段来标记比如cache_control。下面是一个示意性的请求体注意断点标记的位置{ model: your-model, messages: [ { role: system, content: [ { type: text, text: 你是一个严谨的技术助手回答需分点、给依据……此处省略大段系统指令, cache_control: { type: ephemeral } } ] }, { role: user, content: 帮我解释一下 KV 缓存 } ] }这里的cache_control就是断点标记ephemeral表示这是一个临时缓存。标记打在 system 消息的最后一个内容块上意味着“从开头到这个块结束”的整段前缀都纳入缓存。4.2 参数选择与计算过程假设你的系统指令有 2000 token正常输入单价是每百万 token 3 美元缓存命中单价是 0.3 美元缓存写入单价是 3.75 美元。我们来算一笔账。单次请求不缓存2000 token 全部按标准价成本是 2000 / 1,000,000 × 3 0.006 美元。启用缓存后第一次请求写入 2000 token成本 2000 / 1,000,000 × 3.75 0.0075 美元。之后每次命中2000 / 1,000,000 × 0.3 0.0006 美元。假设这个前缀一天被复用 100 次不缓存总成本100 × 0.006 0.6 美元缓存总成本0.0075 99 × 0.0006 0.0669 美元差了将近 9 倍。这就是为什么系统指令这种大段稳定内容一定要缓存。反过来说如果这个前缀一天只用 1 次缓存成本 0.0075 反而比不缓存的 0.006 高那就别开。4.3 断点位置的实操判断我在实际项目里总结了一个简单的判断流程先看 prompt 里有没有超过 1000 token 的稳定段落。有就值得考虑缓存。再看这段稳定内容会不会被同一批请求反复使用。会就打断点。最后确认断点之后的内容里没有把每次都变的东西时间戳、随机 ID、用户输入混进前缀。提示很多框架会把当前时间自动拼进 system prompt比如“现在是 2025 年某月某日”。这种动态内容一旦落在断点前面缓存命中率直接归零。排查缓存不生效时第一个要检查的就是这类隐藏的动态字段。5. 常见问题与排查技巧实录5.1 缓存明明配了却不命中这是最高频的问题。按我的排查顺序通常是这几个原因现象可能原因排查方法命中率始终为 0前缀里有动态内容打印完整 prompt逐段比对两次请求偶尔命中偶尔不命中缓存过期或并发写入检查 TTL 设置和请求间隔命中率突然下降上游改了 system prompt对比版本变更记录完全没反应断点标记位置错误确认标记打在正确的消息块上我踩过最坑的一次是模板引擎在渲染时给每段文本末尾加了一个不可见的换行符导致两次请求的前缀差了一个字符缓存死活不命中。后来把渲染结果做了一次规范化才解决。所以排查这类问题一定要看最终发给服务端的原始字节不要只看模板源码。5.2 缓存写入的成本反噬前面算过写入是有溢价的。如果你的应用场景是“每个用户一套独立的 system prompt”那这些前缀彼此不同谁也命中不了谁缓存写入的钱就白花了。这种情况下正确的做法是把用户个性化内容从 system 里挪出来放到 user 消息里让 system 保持全局统一这样才能共享缓存。5.3 多轮对话里的缓存管理多轮对话是最能体现缓存价值也最容易出问题的场景。会话历史不断增长如果每轮都把整段历史重新传成本会线性上升。合理的做法是系统指令打一个断点会话历史在增长到一定长度后再打一个断点。这样旧的历史被缓存只有最新几轮需要重新计算。但要注意会话历史一旦被缓存后续如果对历史做了编辑比如摘要压缩缓存就会失效。所以摘要压缩的时机要选好别频繁触发。5.4 缓存与一致性的取舍缓存本质上是拿“可能读到旧结果”换成本。对于大多数对话场景前缀内容是不变的不存在一致性问题。但如果你的 system prompt 会动态更新比如从配置中心拉取那就要考虑更新后旧缓存还在 TTL 内的情况。这时候要么等 TTL 过期要么主动让前缀变化来强制失效。我的建议是给 system prompt 加一个版本号更新时改版本号天然让旧缓存失效。6. 几个容易被忽略的细节6.1 缓存的最小长度门槛多数服务商对可缓存的 prompt 有最小长度要求比如至少 1024 token 或 2048 token。低于这个长度的前缀即使打了断点也不会被缓存。所以短 prompt 就别折腾缓存了省不了多少还增加复杂度。6.2 断点标记的粒度断点标记是打在消息级别还是内容块级别不同服务商要求不同。有的要求打在 content 数组的某个块上有的直接打在 message 上。配置前一定要看清楚文档打错位置等于没打。6.3 监控命中率缓存配好之后一定要监控命中率。大多数服务商的响应里会返回缓存命中的 token 数把这个数字和总输入 token 做比值就能算出命中率。命中率长期低于预期说明断点策略有问题得回头调整。我在实际使用中的体会是缓存这件事的收益上限很高但前提是你得把 prompt 的结构设计对。系统指令和动态内容一定要物理隔离断点打在两者之间这是最稳的姿势。至于多断点、会话历史缓存这些进阶玩法等基础命中率稳定了再逐步加别一上来就搞复杂否则出了问题很难定位。