Prompt缓存实战:cache_control断点标记与计费优化指南
1. 从一次账单异常说起Prompt 缓存到底在解决什么问题去年年底帮一个做 AI 应用的朋友排查账单问题他们团队接了一个大模型的 API做的是文档摘要类的产品。上线第一周还好第二周开始账单突然翻了三倍但用户量并没有明显增长。把请求日志拉出来一看问题很清晰他们的系统提示词system prompt有将近 2000 个 token每次用户请求都要重新发一遍而用户实际输入的问题往往只有几十个字。也就是说每次调用里 95% 以上的 token 都是重复的但计费系统不管你重不重复照单全收。这就是 Prompt 缓存要解决的核心问题。大模型的推理成本里输入 token 的处理占了相当大一块尤其是当你的提示词里包含大量固定内容——系统角色设定、few-shot 示例、知识库片段、格式约束——这些内容在多次请求之间几乎不变却每次都要重新走一遍完整的编码和注意力计算。Prompt 缓存做的事情就是把这部分不变的前缀在服务端记住后续请求命中缓存时直接复用中间计算结果从而降低延迟、降低费用。关键词里的cache_control、断点、计费三个词恰好对应了这项能力的三个核心维度怎么标记哪些内容该缓存cache_control、缓存的作用范围到哪里为止断点、以及缓存命中后钱怎么算计费。这三个维度不是孤立的它们互相牵制——断点位置决定了缓存粒度缓存粒度又直接影响计费模型而计费模型反过来会指导你怎么设计 cache_control 的标记策略。这篇文章适合两类人看一类是正在用大模型 API 做产品、被账单和延迟困扰的开发者另一类是想搞清楚缓存这个概念在 LLM 场景下和传统 Web 缓存、Redis 缓存到底有什么本质区别的技术人。我会尽量把原理讲透同时给出可以直接抄的实操配置和踩坑经验。需要说明的是不同厂商的具体实现细节和计费规则会有差异文中涉及具体参数的地方我会以常见实践的方式说明你落地时务必对照自己所用平台的官方文档。2. Prompt 缓存的底层机制它和 Redis 缓存根本不是一回事2.1 传统缓存 vs Prompt 缓存缓存的对象完全不同很多人第一次听到Prompt 缓存下意识会往 Redis、Memcached 那套思路上靠——把某个 key 对应的 value 存起来下次用同样的 key 直接取。这个类比方向对但细节全错而且错得很危险因为它会导致你对命中率产生完全错误的预期。传统缓存缓存的是结果。比如一个数据库查询SQL 语句作为 key查询结果作为 value下次同样的 SQL 直接返回结果连数据库都不碰。而 Prompt 缓存缓存的是中间计算状态具体来说是 Transformer 模型在处理输入序列时产生的 Key-Value 矩阵也就是常说的KV 缓存。热词里出现的kv缓存正是这个意思。这个区别带来一个关键后果传统缓存要求 key 完全一致才能命中而 Prompt 缓存只要求前缀一致。你的提示词是你是一个专业的法律助手……2000字……用户问题XX只要前面那 2000 字一模一样哪怕后面的用户问题千变万化前缀部分的 KV 缓存都能复用。这就是为什么它对摘要类、客服类、RAG 类应用效果特别明显——这些场景的固定前缀占比极高。2.2 为什么必须是前缀自回归生成的因果性约束这里有个绕不开的问题为什么缓存只能作用于前缀不能缓存中间某一段答案藏在 Transformer 的注意力机制里。模型生成每一个 token 时都要对前面所有 token 做注意力计算。第 N 个 token 的计算依赖于第 1 到 N-1 个 token 的全部信息。这意味着 KV 缓存是严格累积的——你要复用第 500 个 token 的缓存就必须先有第 1 到 499 个 token 的缓存。中间挖掉一段后面的缓存就全部失效了。打个比方这就像你读一本书做笔记笔记是逐页累积的。你可以从第 1 页开始复用之前的笔记但你不能跳过前 100 页直接复用第 101 页的笔记因为第 101 页的理解建立在前 100 页之上。这个约束直接决定了 cache_control 的设计逻辑你标记的缓存断点实际上是在告诉系统从这里往前的所有内容请帮我缓存起来。断点不是缓存某一段的起点而是缓存整个前缀的终点。2.3 断点breakpoint的真实含义断点这个词在热词里和代码断点断点续训断点续播混在一起容易让人误解成调试用的断点。在 Prompt 缓存的语境下断点的准确含义是缓存边界标记——你在提示词的某个位置打一个标记表示这个位置之前的内容构成一个可缓存的单元。一次请求里通常可以打多个断点形成多级缓存。比如断点 1系统角色设定 全局约束几乎永不变断点 2few-shot 示例偶尔调整断点 3检索到的知识库片段每次请求可能不同这样设计的好处是当只有知识库片段变化时前两级缓存依然有效你只需要为第三级重新计算。如果只打一个断点放在最末尾那知识库一变整个前缀缓存全废。注意断点数量通常有上限常见是 4 个而且断点之间的内容越稳定缓存收益越高。不要为了多缓存而乱打断点每个断点都会带来额外的元数据开销。2.4 缓存的生命周期TTL 与失效Prompt 缓存不是永久的。服务端通常会设置一个 TTL生存时间常见范围是几分钟到一小时不等。超过 TTL 没有命中缓存就被回收下次请求需要重新建立。这里有个容易被忽略的点缓存的建立本身是有成本的。第一次请求时系统不仅要正常推理还要额外把 KV 状态写入缓存这个写入操作可能比普通请求稍慢或稍贵。所以缓存策略的本质是一场赌博——你赌这个前缀在 TTL 内会被再次使用。如果某个前缀一天只用一次那缓存它纯属浪费。热词里的缓存失效缓存一致在 Prompt 场景下也有对应含义当你修改了系统提示词的一个字整个前缀的哈希就变了之前所有缓存全部失效需要重新建立。这就是为什么提示词版本管理在 LLM 应用里格外重要——一次不小心的文案微调可能让缓存命中率从 90% 掉到 0。3. cache_control 标记实战怎么打标记才能省钱3.1 cache_control 的基本语法与放置位置cache_control 是标记缓存断点的字段通常以 JSON 对象的形式附加在消息内容块上。以常见的消息结构为例它的形态大致是这样{ role: system, content: [ { type: text, text: 你是一个专业的法律助手请根据以下规则回答……此处省略2000字, cache_control: { type: ephemeral } } ] }关键点在于cache_control是挂在内容块上的而不是挂在整条消息上。这意味着你可以对同一条消息里的不同段落分别打标记。ephemeral表示这是临时缓存遵循服务端的 TTL 策略。放置位置有一条铁律标记要打在缓存内容的最后一个块上。因为断点的语义是到此为止前面的都缓存所以你把标记打在系统提示词的末尾就表示整个系统提示词都被纳入缓存。3.2 多级断点的划分策略我一般建议按变化频率来划分断点层级而不是按内容长度。下面这张表是我在实际项目里总结的划分参考层级内容类型变化频率是否打断点预期命中率第一级系统角色、全局规则、输出格式约束几乎不变是95%第二级few-shot 示例、领域知识模板按版本迭代是70%-90%第三级RAG 检索片段、用户画像每请求变化视情况20%-50%第四级用户当前问题每请求必变否0%第三级要不要打断点取决于你的检索策略。如果你的 RAG 是固定知识库 动态检索且检索结果经常重复比如热门问题总是命中同一批文档那第三级打断点是划算的。但如果检索结果高度分散每个用户查到的片段都不一样那第三级缓存基本不会命中打断点反而增加开销。3.3 一个真实的优化案例从 0 命中到 85% 命中回到开头那个朋友的案例。他们最初的提示词结构是这样的[系统角色 500字] [few-shot 示例 800字] [用户上传文档 3000字] [用户问题 50字]他们一开始把 cache_control 打在了最末尾也就是用户问题后面。结果命中率极低因为用户上传的文档每次都不同导致整个前缀缓存全部失效。调整后的结构[系统角色 500字] ← 断点1 [few-shot 示例 800字] ← 断点2 [用户上传文档 3000字] [用户问题 50字]把断点前移到 few-shot 示例末尾让系统角色和示例这两块稳定内容独立成缓存单元。调整后前两级缓存命中率稳定在 85% 以上账单直接降了六成多。这个案例说明一个道理断点位置的选择比缓存本身的技术实现更影响最终收益。3.4 标记时的常见错误我见过几种典型的错误标记方式这里列出来供你对照自查错误一把断点打在用户问题上。用户问题每次都变缓存它等于没缓存还白白占用一个断点名额。错误二断点之间内容太短。如果两个断点之间只有几十个 token缓存收益可能覆盖不了元数据开销。一般建议单个缓存单元至少几百 token 起步。错误三在动态内容中间打断点。比如在 RAG 片段中间打断点导致后半段片段每次都要重算缓存了个寂寞。错误四忘记同步更新缓存版本。修改了系统提示词却没意识到缓存已失效还在纳闷为什么命中率突然归零。提示每次修改提示词后建议在日志里记录一个提示词版本号方便对照缓存命中率的变化。这个习惯能帮你快速定位是不是我改了提示词导致的。4. 计费模型拆解缓存到底能省多少钱4.1 缓存写入、缓存命中、普通输入的三档定价Prompt 缓存的计费通常分三档理解这三档的差异是算清账的前提计费类型说明相对价格常见区间普通输入未命中缓存的输入 token1x基准缓存写入首次建立缓存时的输入 token1.25x 左右缓存命中命中缓存复用的输入 token0.1x - 0.25x注意缓存写入是比普通输入更贵的。这是很多人算账时容易忽略的点——你为了建立缓存第一次要多付 25% 左右的费用。所以缓存能不能省钱取决于这个前缀被复用的次数。4.2 盈亏平衡点怎么算假设某前缀有 P 个 token缓存写入溢价为 25%缓存命中折扣为 90%即命中价是普通价的 10%。设这个前缀在 TTL 内被使用 N 次。不用缓存的总成本N × P × 1用缓存的总成本P × 1.25 (N-1) × P × 0.1令两者相等N 1.25 (N-1) × 0.1 N 1.25 0.1N - 0.1 0.9N 1.15 N ≈ 1.28也就是说只要这个前缀在 TTL 内被使用超过 2 次缓存就开始省钱。这个门槛其实很低所以对于高频调用的固定前缀缓存几乎是稳赚的。但这里有个隐藏变量TTL。如果 TTL 是 5 分钟而你的前缀平均 10 分钟才被用一次那缓存永远等不到第二次命中就失效了你反而多付了 25% 的写入溢价。所以TTL 长度和调用频率的匹配是缓存策略里最需要实测的部分。4.3 输出 token 不参与缓存计费需要特别澄清一点Prompt 缓存只作用于输入输出 token 的计费不受影响。因为输出是模型实时生成的每个 token 都是新的没有复用的可能。所以如果你的应用输出很长比如长文生成缓存能帮你的比例就相对有限反之如果你的应用输入很长、输出很短比如分类、抽取、问答缓存的收益就非常显著。这个特性决定了缓存策略的适用边界。我一般会先算一个输入输出比输入 token 除以输出 token。这个比值大于 5 的场景缓存值得重点优化小于 2 的场景优化缓存的投入产出比就不高了。4.4 一个容易踩的计费坑缓存写入的重复触发有些平台的缓存写入是按缓存单元计费的如果你在短时间内反复修改提示词每次修改都会触发一次新的缓存写入每次都要付 1.25x 的溢价。我见过一个团队在调试阶段频繁改提示词一天之内触发了上百次缓存写入调试成本比正常调用还高。规避方法很简单调试阶段关掉缓存标记等提示词稳定后再开启。或者把调试用的提示词和线上提示词分开调试走不带 cache_control 的通道。5. 断点续训与断点续播别被热词带偏了方向5.1 这些断点和 Prompt 缓存没关系热词列表里出现了断点续训断点续播代码断点错误查询qtcreator怎么下数据断点这些词它们和 Prompt 缓存里的断点只是共享了同一个中文词技术含义完全不同。断点续训指模型训练过程中保存 checkpoint中断后从 checkpoint 恢复继续训练。它缓存的是模型权重和优化器状态。断点续播指视频播放中断后从上次位置继续。它缓存的是播放进度。代码断点指调试器在指定代码行暂停执行。它是调试工具的功能。之所以要专门澄清是因为我见过有人把这几件事混为一谈以为断点续训的 checkpoint 机制能用来优化 Prompt 缓存结果方向完全跑偏。Prompt 缓存的断点是提示词内部的边界标记不是训练或调试概念。5.2 真正相关的邻近概念KV 缓存与分布式缓存如果说有哪个概念和 Prompt 缓存最接近那是KV 缓存。前面讲过Prompt 缓存缓存的就是 KV 矩阵。在单机推理场景下KV 缓存是显存里的一块区域在多机分布式推理场景下KV 缓存可能被放到分布式存储里共享这就和分布式缓存产生了交集。热词里的分布式缓存redis缓存java 轻量缓存 ttl这些属于传统后端缓存范畴。它们和 Prompt 缓存的关系是同名不同物——都是缓存但缓存的对象、失效机制、命中条件完全不同。理解这个区别能帮你在技术选型时不被名词误导。5.3 一个实用的心智模型我给团队新人讲这块时会用这样一个类比传统缓存像图书馆的借阅记录——你借过的书下次来直接给你书的内容没变。 Prompt 缓存像你读书时做的笔记——笔记是你理解过程的中间产物下次读同一本书的前半部分你可以直接看笔记跳过重读但笔记只对同一本书的前半部分有效换本书就作废。这个类比抓住了两个关键缓存的是中间状态而非最终结果以及缓存严格依赖前缀一致性。6. 落地时的实测经验与避坑清单6.1 先测量再优化我强烈建议在动手改 cache_control 之前先做一轮基线测量。具体要采集的数据包括每次请求的输入 token 数、输出 token 数提示词各段的长度占比系统角色多少、示例多少、动态内容多少请求的时间分布判断 TTL 内能否命中当前的实际命中率如果平台提供这个指标没有这组数据你的优化就是盲猜。我见过太多人一上来就打断点结果因为前缀本身就不稳定命中率上不去还以为是缓存功能不好用。6.2 提示词结构要为缓存而设计缓存友好型的提示词结构和人类可读的提示词结构往往不完全一致。为了最大化缓存收益我通常会把提示词组织成稳定在前、动态在后的顺序[最稳定的系统角色] → [较稳定的示例] → [半动态的上下文] → [完全动态的用户输入]这个顺序既符合缓存的前缀复用逻辑也符合模型的理解习惯——先给背景再给具体任务。反过来说如果你把动态内容放在前面稳定内容放在后面缓存基本无从谈起。6.3 监控命中率设置告警缓存命中率是个会悄悄劣化的指标。提示词改一个字、TTL 调短一点、流量模式变化都可能让命中率下滑。建议把命中率纳入日常监控设置一个阈值告警比如低于 60% 就报警。这样你能在账单暴涨之前发现问题。6.4 常见问题速查表现象可能原因排查方向命中率突然归零提示词被修改对比提示词版本号命中率持续偏低断点位置不当检查断点是否打在动态内容上账单不降反升缓存写入溢价超过命中节省计算盈亏平衡点检查调用频率延迟没有改善缓存未命中或 TTL 太短检查 TTL 设置与请求间隔部分请求报错断点数量超限检查断点数量是否超过平台上限6.5 一个反直觉的经验最后分享一个我踩过的坑不是所有场景都适合开缓存。有一次我帮一个低频内部工具接缓存那个工具一天调用不到 20 次每次间隔几小时。结果缓存从来没命中过反而每次都要付写入溢价成本比不开缓存还高。后来我把 cache_control 去掉账单立刻恢复正常。这件事让我明白缓存是个高频场景的优化手段不是万能省钱开关。判断要不要开先问自己三个问题这个前缀稳定吗它在 TTL 内会被复用吗复用次数够覆盖写入溢价吗三个都是是再开不迟。对于提示词工程本身缓存只是其中一环。真正决定效果和成本的还是提示词的内容质量和结构设计。缓存能帮你把好的提示词用得更便宜但救不了一个本身就设计糟糕的提示词。