
每次和同行聊 LLM 应用成本最后几乎都会落到同一个话题上跑大模型的钱到底烧在哪了很多人第一反应是生成 token 贵但实际拉一下账单就会发现真正的大头往往是你压根没在意过的前缀计算——也就是每次请求都要把那一大段系统提示词、历史对话、参考文档交给模型重新“读”一遍。Prompt caching 技术就是冲着这部分成本来的而它确实能把推理开销打到一折左右。这篇文章我结合自己在实际项目里的接入经验把它的原理、定价逻辑、接入姿势和容易踩的坑一次说清楚。1. 为什么大模型推理这么贵先从 KV Cache 说起1.1 每一轮对话都在重新“读题”要理解 Prompt caching 的价值得先搞明白推理过程里最烧算力的环节在哪。Transformer 架构的模型在生成每个 token 时都要对整个输入序列做一次注意力计算也就是说不管你的输入是不是和上一条请求高度重复模型都会老老实实地把每个 token 都“看”一遍。我举个直观例子假设你做一个人工智能客服机器人系统提示词写了两千字里面包含各种业务规则、回复风格约束、敏感词处理策略用户每次提问时这部分内容都会完整地拼到输入最前面。那么用户问一次“订单怎么退”模型要先读完这两千字的系统提示词再开始往前推导回复。下一个用户问“发票怎么开”模型又把这两千字原封不动地读了一遍。这就是问题所在——这两千字的内容每次都没变但每次都在被重复计算。实际的推理过程中这种浪费比你想的还要夸张。大模型服务商给出的 token 计费是把输入和输出分开算的输入 token 按量计费这部分钱就是这类重复计算的直接体现。很多企业对账的时候发现自己明明没开多少并发账单却高得离谱一查 Prompt 平均长度四五千 token大部分还是固定话术和产品文档。1.2 KV Cache 是什么Transformer 不做前缀重算的底层逻辑刚才说的“每次都要重新读一遍”严格来讲不完全准确。实际上Transformer 的推理过程里有一个叫 KV Cache 的机制它在自回归生成时会把已经算好的 Key 和 Value 矩阵缓存下来这样在生成第 N1 个 token 时不需要重新计算前 N 个 token 的注意力投影。但这里面有一个关键前提KV Cache 是在单次请求内部生效的。也就是你发一次请求模型一边生成一边缓存前文等输出结束这一轮的 KV Cache 就释放了。下一条请求进来无论你的前缀和上一条有多像都得从零开始重新计算前缀部分。打个比方KV Cache 就像是你在考场里做一道很长的阅读理解题你每做完一道小题就可以把前面看过的内容记在草稿纸上不用反复重读原文。但问题是——每场考试结束草稿纸就收走了。下一场考试虽然发的阅读材料一模一样你还得从头再看一遍。Prompt caching 做的事情就是把这份草稿纸从“单次考试内有效”变成“跨多次考试复用”。1.3 推理成本里被忽略的大头我在分析过不少项目的成本结构之后发现一个共性规律只要 Prompt 里带了长文本、历史会话、工具定义这些内容输入 token 的消耗量通常会远超输出 token。你写一个 Agent每轮对话要带过去五轮的消息记录再加上工具描述和系统指令一次请求输入动辄三千 token输出可能只有一两百 token。从计费模型上看输入 token 单价确实通常比输出 token 便宜比如常见的 1:3 或 1:5 的价差但架不住输入量是指数级增长的。多轮对话场景下每次轮询都要把整个历史记录重新发送一遍这就造成了大量的“无效重复计费”。Prompt caching 的切入点正是把这类高度重复的输入前缀变成可命中的缓存跳过重复计算同时按缓存命中后的低单价计费。从服务商的定价表上看命中缓存的价格通常只有未命中的十分之一左右——这就是“1 折优化”的由来。2. Prompt Caching 的核心原理命中缓存跳过该重算的部分2.1 前缀命中像浏览器缓存一样的工作方式Prompt caching 的实现机制从抽象层面讲其实和浏览器 HTTP 缓存非常像。服务端拿到你的请求之后会先对输入序列的前缀部分做哈希然后在缓存池里找有没有完全一样的 KV 结果可用。如果找到了就直接把缓存结果接到当前请求上只需要计算没有命中的那部分后缀生成阶段也能直接从已经算好的状态往后推。这里的关键词是“前缀”。它要求命中的部分必须是从输入的最开头开始连续的、逐 token 完全一致的序列。只要中间有任何一点点差异比如多了一个空格、改了一个字、调换了某个参数的位置那么从差异点开始之后的所有内容都算作未命中整条请求的前缀缓存都会失效。这就解释了为什么 Prompt caching 不是“你随便怎么组织 Prompt 都能省钱的魔术”而是一门非常讲究输入结构设计的工程活儿。你在设计 Prompt 时必须把稳定不变的内容尽量放在前面把动态变化的内容尽量往后挪。2.2 自动缓存和显式缓存各家实现路径的差别目前各家大模型厂商的实现方式大概分成两类自动缓存和显式缓存通过参数控制。先说说自动缓存。OpenAI 的做法比较省心——你不需要改任何代码服务端会自动检测输入前缀里有没有可以被复用的部分。但它也有限制条件你必须在请求里带上 cache_control 之类的参数或者满足它预设的缓存条件模型才会在推理时帮你做缓存处理。而且它默认的缓存时间一般是 5 分钟到 1 小时这个区间具体取决于模型供应商的实现。再说显式缓存。Anthropic Claude 的 prompt caching 就是典型的显式缓存。你在构建请求时需要在希望被缓存的内容块上标注一个 cache_control 标记。比如你可以在 system 消息里加上cache_control: {type: ephemeral}服务端就会把这个 system 块的内容缓存起来后续请求只要前缀命中就能直接用。实现路径代表接入成本缓存粒度注意事项自动缓存OpenAI 部分模型低无需改代码服务端自动识别需要满足前缀一致性要求显式缓存Anthropic Claude中需要改请求结构按内容块标注最多可缓存 4 个 block混合模式各家在演进中随着前缀匹配同时受命中率和 TTL 约束2.3 TTL缓存能活多久直接影响省钱效率TTLTime To Live是 Prompt caching 里另一个非常关键的参数。它决定了缓存的有效时间窗口。在这段时间内相同前缀的请求可以直接命中缓存按低价计费超过这个时间缓存会被清除再来的请求就要全额计费了。不同的服务商 TTL 差异很大有的只有 5 分钟有的能到 1 小时还有些更灵活的可配置方案。TTL 短的好处是无需担心缓存内容过旧坏处是如果你两次请求的间隔稍长一点省钱效果就大打折扣。我自己的经验是如果你做的是对话频率很密集的客服机器人并发高且连续5 分钟 TTL 基本够用但如果是那种用户隔十几分钟才来一次提问的场景你还指望靠缓存省钱就大概率会落空。这个点在下文成本测算部分我会专门展开。3. 一折的账是怎么算的定价模型与成本测算3.1 缓存命中与未命中的价格差异逻辑大模型服务商把 Prompt caching 做成“低价命中”的计费模型逻辑上并不复杂命中缓存的请求服务端省掉了前缀部分的重复预填充prefill算力只需要做后缀的增量计算成本本来就低所以能给你一个明显更便宜的单价。以 Anthropic 的定价为例Claude 的缓存读取cache read输入价格约为未命中输入价格的 10%。也就是说同样是一万 token 的输入未命中时按标准价计费命中后按标准价的一折计费。这个折扣力度就是“1 折优化”说法的直接来源。不过要注意缓存写入cache write这个过程本身不是免费的它比普通输入还要稍贵一点。因为服务端需要额外完成缓存索引、hash 比对这些操作。所以你的成本结构会变成这样一个状态首次请求按 cache write 价格计费比原来略贵。后续命中请求按 cache read 价格计费约是原来的 10%。未命中请求按标准输入价格计费没省到钱。因此Prompt caching 的省钱本质是“用首次的略高成本换取后续大量命中的低价”。如果同一个前缀只被请求了一两次就换掉了那整体反而是亏的。3.2 实际项目成本测算命中率影响到手价格我拿一个实际的客服机器人项目来算一笔账。假设系统提示词加固定知识库内容一共 3000 token用户平均问题 200 token回复 250 token。标准输入单价按 $3/1M token输出单价按 $15/1M token 来算接近 Claude Sonnet 的价位cache read 按标准输入的一折即 $0.3/1M tokencache write 按 $3.75/1M token 算。如果不启用 Prompt caching每次请求的成本为输入费用(3000 200) / 1M × 3 $0.0096输出费用250 / 1M × 15 $0.00375单次总成本约 $0.01335如果启用缓存且假设前缀命中单次请求成本为首次请求 cache write(3000 200) / 1M × 3.75 ≈ $0.012后续命中请求 cache read3000 / 1M × 0.3 $0.0009输出费用不变$0.00375后续单次总成本约 $0.00465其中缓存命中相对未命中场景省了约 65%你没看错只算单次请求的话实际上并不是一折因为输出 token 的费用没有被缓存覆盖。但如果你的输出很短、输入极长比如 RAG 场景里把整篇文档作为上下文塞进去输出可能就一两百 token这时候一折优化在输入端的作用就很明显了。为了更直观我做了另一个极端的测算输入 8000 token输出 100 token。未命中8000/1M × 3 100/1M × 15 $0.0255命中8000/1M × 0.3 100/1M × 15 $0.0039看总成本确实能压到原来的 15% 左右。对 Prompt 特别长的场景一折的省幅不是夸张说法。3.3 连续对话场景下的成本衰减问题多轮对话场景里Prompt caching 的命中率和省钱效果会随着轮次增加出现一个有意思的变化。第一轮你发的是“系统提示词 用户问题”整个前缀被缓存第二轮你发的是“系统提示词 第一轮对话 第二轮问题”这时只有系统提示词部分是命中的第一轮对话部分需要重新计算并建立新的缓存区。假设每轮对话新增 500 token那在 N 轮对话时每轮命中的缓存依旧只有那个稳定不变的 3000 token 前缀新增部分虽然也在生成答案时被模型处理并写入缓存但它的“寿命”只有一轮——因为下一轮的输入里它的位置不再与上一轮的 token 序列完全对齐前面插入了新的对话内容。这就引出一个工程判断如果用户平均对话长度只有三四轮那么命中的还是固定前缀长输入的优势不会衰减但如果动辄几十轮历史消息每轮都在无限膨胀缓存命中能为整体输入提供的折扣比例会越来越小因为不断增加的历史部分需要不断地重新计算。所以长对话场景想持续省钱单靠 prompt caching 不够通常得配合历史消息裁剪或摘要压缩把历史控制在合理范围内。这部分我在第 5 节再展开讲。4. 把 Prompt Caching 用起来接入步骤与代码示例4.1 Anthropic Claude 的 cache_control 用法目前我实际接过的模型里Anthropic 的显式缓存控制是最透明的代码层面你能明确知道哪些内容被缓存了。核心做法是给消息块加cache_control字段。以官方 Python SDK 为例我希望把 system 里的几大段固定内容都变成可缓存前缀from anthropic import Anthropic client Anthropic() response client.messages.create( modelclaude-sonnet-4-20250514, max_tokens1024, system[ { type: text, text: 你是一个专业的电商客服助手负责处理订单查询、退换货、发票等业务。 }, { type: text, text: 以下是我们的售后政策文档\n long_policy_text, cache_control: {type: ephemeral} } ], messages[ {role: user, content: 我想退一件衣服但超过七天还能退吗} ] )注意看我给第二个 system 块加了cache_control服务端就会把你是一个专业的电商客服助手... 售后政策文档这个完整前缀缓存下来。下一次请求只要 system 前缀完全一致就会直接命中缓存。如果有多段内容想缓存官方允许最多添加 4 个 cache_control 断点。这个限制很有用你可以把超长文档拆成多个逻辑块分别缓存避免一个超大块里的微小改动导致整个缓存失效。实测下来拆块缓存对命中率提升还是很明显的。4.2 OpenAI 的自动缓存与参数写法OpenAI 这边目前走的是 automatic 路线。以较新的模型接口为例你的请求只要设置cache_control或者满足它的自动条件输入前缀就会被服务端缓存。实际使用中我的习惯是仍然显式声明避免踩到自动判定不生效的边界情况。在 Responses API 里可以通过text前缀块配cache_control来声明缓存意图from openai import OpenAI client OpenAI() response client.responses.create( modelgpt-4.1, input[ { role: developer, content: [ { type: input_text, text: 你是一个资深法律顾问回答需要引用法条原文。, }, { type: input_text, text: long_law_document, cache_control: {type: on} } ] }, {role: user, content: 劳动合同到期不续签需要赔偿吗} ] )OpenAI 的cache_control是硬性要求不写它就不会缓存。和 Anthropic 的 ephemeral 不同OpenAI 用的是on。另外要记住这类缓存默认有效期很短——官方文档给的是 5 到 10 分钟设计上就是为了高频重复请求服务的。4.3 一个要注意的细节缓存块里的内容不能“动”接入过程中最容易犯的错误就是把动态内容也塞进了缓存块。比如有同学会把当前时间datetime.now()拼进 system prompt然后发现缓存命中率一直上不去查了好久才意识到时间字符串一变整个前缀的哈希就变了缓存瞬间全部失效。还有一些 Agent 会把用户所在城市、天气信息、随机推荐语拼到系统提示词里这些都属于动态内容。正确做法是把这类动态内容放到 messages 的 user 消息里或者放到 system 里但不要放在 cache_control 标记的块内。也就是说你的 system 结构应该是完全静态的指令和知识文档可缓存动态信息不可缓存放在后面的块里这样既保留了动态能力又不破坏缓存前缀。这个意识在第四节的“命中率优化”里还会反复强调。5. 命中率才是真正的省钱关键工程优化经验5.1 稳定前缀设计把“不变”和“变”分离Prompt caching 的省钱效率和命中率直接挂钩。命中率越高你能享受的一折价格就越多。而决定命中率的唯一因素就是输入前缀的稳定性。我的做法是把所有 prompt 内容按照“变化频率”分成三类永久静态系统角色设定、回答规则、行业知识库、工具定义。这些内容几乎不会变应该作为缓存前缀的最核心部分放在最前面。会话级静态同一次会话内不变的内容比如用户的资料、订单信息、当前会话的 ID。这些只在会话内保持一致可以作为二级前缀。请求级动态每轮都可能变化的时序信息、用户具体输入、临时状态。这类必须放在最后绝不能混进缓存块。实际操作中我建议在代码层面就把这三类内容分开管理而不是每次请求才临时拼 Prompt。比如用一个 PromptBuilder 类把静态模板、动态字段、用户输入分步组装。class PromptBuilder: def __init__(self): self.static_blocks [] self.session_blocks [] self.dynamic_blocks [] def add_static(self, text: str) - None: self.static_blocks.append({ type: text, text: text, cache_control: {type: ephemeral} }) def add_dynamic(self, text: str) - None: self.dynamic_blocks.append({type: text, text: text}) def build_system(self): return self.static_blocks self.dynamic_blocks这样做的好处是你永远不会不小心把动态内容拼到缓存块里。5.2 长文档置顶、工具定义紧随其后Prompt 内容的排序也会影响命中率。既然缓存机制是从头开始匹配的那么你就应该把最长、最稳定、对缓存收益贡献最大的内容放在最前面。我通常把完整知识库或产品手册放在第一个缓存块因为这部分往往占了输入 token 的大半。紧接着是工具定义因为 Agent 场景里工具定义也基本稳定。然后是会话级静态内容最后才是用户消息。这个顺序带来的收益有两个一个是长文档置顶可以让单次命中覆盖更多 token另一个是避免工具定义里的微小修改导致大段知识库缓存失效。关于工具定义我还要多说一句如果你经常新增或修改工具最好把不太会变的工具比如计算器、汇率查询放在前面的缓存块里把经常调整的工具放在后面的非缓存块。这样工具迭代不会连累前面的稳定缓存。5.3 关键时段的主动预热策略缓存 TTL 是客观限制你没法改变服务端的缓存过期时间但你可以通过工程手段提高缓存命中概率。最直接的办法是主动预热。比如你的业务有明确的流量高峰时段比如每天早上十点到十二点是客服机器人的使用高峰期那么你可以在九点半左右用一个 dummy 请求把常用前缀打一遍让缓存提前进入“热”状态。这样用户十点来提问时前缀就能直接命中。预热请求的成本很低一次 cache write 可能也就几厘钱但能确保高峰期几万次请求都享受 cache read 的一折价格。这个投入产出比非常可观。另外如果你用的是 5 分钟 TTL 的缓存但业务并发很低两次请求间隔常常超过 5 分钟那预热可能也救不了命。这种情况下就要考虑是不是改用更长的缓存窗口或者根据业务峰谷设计不同的请求策略。5.4 命中率监控没有数据就谈不上优化我强烈建议你在接入 Prompt caching 后把缓存命中情况纳入监控。Anthropic 的 API 返回里会带usage.cache_creation_input_tokens和usage.cache_read_input_tokens这两个字段前者是写入缓存的 token 数后者是命中缓存的 token 数。用这两个值可以算出一个实际的缓存命中率cache_read response.usage.cache_read_input_tokens cache_write response.usage.cache_creation_input_tokens hit_rate cache_read / (cache_read cache_write)我在生产环境里做了日志埋点之后发现很多“我以为会命中”的请求实际并没有命中。排查下来原因五花八门有的是因为消息列表里 user 前缀带了换行符有的是因为某个工具参数在每次请求时都重新生成了一遍 timestamp还有的是因为 system 块里拼接了一个用户名变量。这些问题只在数据面前才会暴露。所以无论你对接哪家服务第一时间把命中率监控做起来比什么都重要。6. Prompt Caching 的限制与坑哪些场景容易“省了个寂寞”6.1 缓存块会占用上下文窗口这是我在接入过程中踩过最大的一个坑。很多同学以为缓存只是“计费层面的优惠”不影响实际上下文。但实际上被缓存的那些前缀 token 依然会占用上下文窗口。也就是说即使你成功命中了 8000 token 的缓存这 8000 token 还是算在上下文长度里的。如果你的模型上下文窗口是 128K缓存占掉 8K模型实际能处理新内容的空间就是 120K。在超长文档处理场景里如果缓存内容动不动就是五六十K上下文可用空间会被压缩得非常严重甚至可能导致新内容放不下而报错。解决思路是在设计缓存策略时就要考虑到缓存块占用的上下文预算。如果知识库超过 100K就不要一次性全塞进缓存考虑拆成多个缓存块、按需加载或者用更细粒度的检索先做一轮筛选再缓存。6.2 前缀抖动一条请求一个样缓存无从谈起“前缀抖动”是我自己总结的叫法指的是请求的前缀部分每次都有一点点不同导致缓存永远无法命中的情况。常见的前缀抖动来源包括动态拼接的用户名、session ID、trace ID时间戳、日期随机生成的 assistant 首句多轮对话里消息顺序不一致对历史消息做了格式化处理导致空白字符不一致这些问题在代码 review 的时候很难发现因为单次请求看起来完全正常但缓存命中率会非常难看。我在 5.4 节提到的监控手段本质上就是在帮你暴露这类问题。一旦发现命中率异常低第一反应就应该是检查前缀稳定性。6.3 Prompt 版本管理改了系统提示词缓存就废了Prompt caching 的另一个隐性成本是版本迭代带来的缓存失效。你改了系统提示词里的任何一个字从那个位置开始整个后续前缀的缓存都会失效。如果你们团队很频繁地调整 Prompt比如一周改好几次那每次改动后的第一次请求都会按全额计费缓存重建完后如果短时间内没有足够多的请求命中这次重建就是纯亏的。因此Prompt 的变更应该走版本管理和小步发布策略。具体来说不要直接修改线上正在使用的 system prompt而是基于版本号创建新的 prompt 版本发布新版本后用预热请求主动触发缓存构建让第一批真实用户的请求能直接命中如果代码支持灰度尽量让同一个 prompt 版本的流量集中在一起避免同一时间内存在多个旧版本缓存降低整体命中率从工程角度上看Prompt caching 让 Prompt 不再只是“模型指令”而变成了也要纳入版本管理和发布运维的代码资产。这一点意识转变非常重要。6.4 非对话场景缓存能帮上忙的地方比你想的多聊完坑再说一个容易被低估的使用场景。很多人觉得 Prompt caching 只适合聊天机器人但我在实际项目里发现它对很多“非对话”的高频调用场景同样有效。比如批量跑数据标注任务每一条数据都带着同一份标注规范再比如做文档批量分类每篇文档都拼接了相同的分类体系和示例还有代码生成工具的静态规则前缀、企业知识库问答的固定系统提示。这些场景的共同特点是请求量大、前缀高度一致、单次请求里前缀占比高正是 Prompt caching 的最佳适用对象。我在给一个法律文档处理系统接入缓存后输入 token 成本下降了约 70%因为那套系统的固定法条库前缀占到了输入总量的 85% 以上。而且不同于多轮对话场景这类批处理任务每次请求之间的间隔通常很短TTL 限制几乎不影响命中率省下来的钱非常可观。总结下来Prompt caching 不是万能的它没有改变生成 token 的成本也不会让你把整个推理成本都压缩到一折。它真正优化的是那些“前缀重复计算”造成的浪费。把最优化位置的前缀设计、命中率监控、版本管理这些工程细节做扎实了它带来的成本优化是实打实的而且接入门槛远低于换模型、上蒸馏这些方案——这也是为什么它现在几乎成了生产环境 LLM 应用的标配能力。