从kv cache到prompt caching
背景
- 你的Agent跑起来是否token费用消耗快的吓人?像下面这样,对于长轮次的对话,不做优化的情况下,成本累积的速度是线性的,随着上下文越来越长,一开始几毛钱,到后面几块甚至几十块。

kv cache
transformer计算过程
要理解这个概念,首先要了解transformer计算序列的流程。transformer之所以能称霸现代AI,最主要的是它赋予了模型一种能力,在处理每个词的时候,都能参考整个句子里面的其他词,从而动态调整自己的含义。
举个例子,对于“我今天吃了一个苹果”这句话,模型读到苹果的时候,会想 谁和我的关系最大,它发现“吃“和”一个“关联性比较高,所以确定这里的苹果是一种水果而不是手机。这个过程的数学实现,依赖下面三个向量。
● Q(查询):当前词发出的信号,“我需要什么上下文”。
● K(键):每个词携带的“标签”,我能提供什么信息。
● V(值):词的实际内容。
注意力计算可以简单理解为用当前词的Q去和所有词的K做点积得到一个分数,再把这个分数用SoftMax归一化成权重,最后用这些权重去加权汇总所有词的V。这样,每个词就获得了一个融合了全句信息的新表示。
如果让句子中的所有词同时做这件事,他们就互相“看”到了对方,再加上多头注意力(多组Q、K、V分别关注不同层面的关系),前馈网络和残差连接,一个transformer层就成型了,堆叠多层,就构成了如今的大语言模型。
生成式模型的特点
像GPT这样的生成式模型,使用的是Transformer解码器的变体,它有一条铁律:生成第t个词时,只能看见它前面的第t-1个词,不能偷看未来的词,这叫因果注意力。生成过程就像一个接龙游戏:
a. 输入“我今天吃”,模型输出“了”;
b. 将“了”拼回去,输入变成“我今天吃了”,模型再输出“一个”;
c. 再拼回去,如此循环,直到形成一个完整的句子。

问题来了。每次生成一个新词,模型都要把当前完整序列从头到尾重新跑一遍自注意力。比如在步骤 b,计算“一个”的时候,前面的“我”、“今天”、“吃”、“了”的 K 和 V 明明在步骤 a 已经算过一次了,但模型还是会固执地重新算一遍。序列越长,浪费的算力就越大,复杂度随序列长度呈二次方增长。
破局点:kv cache,把“笔记留下来”
聪明的研究者很快发现了一个关键特性:在因果注意力下,已经生成的 token 的 K 和 V,在后续步骤中完全不会改变。因为它们的值只取决于自己以及更早的 token,未来 token 的加入并不会影响过去 token 的表示。既然如此,我们为什么不把那些算过一次的 K 和 V 缓存起来呢?这就是 KV Cache。每生成一个新 token,我们只计算这个新 token 的 Q、K、V。然后,新 token 的 Q 会去“查询”两部分内容:缓存里的所有历史 K(来自之前 token);自己这个新 token 的 K。得到的注意力权重再对两部分 V 进行加权求和:缓存里的历史 V,以及新 token 自己的 V。这样一来,每一步的计算量从正比于序列长度的平方,降到了正比于序列长度。生成速度大大加快,且完全无损。用一个比喻:作家每写下一个字,都要把前面写过的字重新读一遍,以确保连贯。KV Cache 就是让他把前面内容的“关键印象”记在草稿本上,写新字时直接看草稿本,不再反复通读全文。
prompt caching
从单词请求到多次请求:prompt caching的诞生
KV Cache 解决了单个生成任务内部的重复计算,但如果我们把视角放大,看多个请求之间的情况呢?
想象你是一个出版社编辑,你请作家写 1000 篇书评,但每篇书评开头都是一模一样的 50 页行业规范。如果作家每次写新书评,都重新读这 50 页规范,重新在自己的草稿本上记一遍笔记,那简直是灾难。
大模型的 API 调用场景正是如此。在 Agent 应用中,几乎每个请求都包含一段完全相同的长系统提示(角色设定、输出格式、规则约束),可能还有一堆工具定义和少样本示例。按照常规流程,每个请求到达时,模型都要为这部分“公共前缀”计算一次 KV Cache,算完即焚,下一个请求来了再算一次。
这就催生了 Prompt Caching(提示词缓存)。它的思路很简单:把多个请求之间共享的那部分前缀的 KV Cache,从临时的显存里拿出来,存到一个共享的、持久化的缓存层中。当新请求到来时,推理引擎会先检查它的前缀文本是否与缓存中的某段前缀完全匹配。如果命中,就直接从缓存读取对应的 KV Cache,模型只需要继续计算后面不同的部分。用前面的比喻:编辑把行业规范的“标准笔记”印成了活页,每个作家开始写书评时直接发一份,他们自己只需要补充后文的新笔记。这节省了大量翻书和记笔记的时间。
prompt caching的底层实现细节
从原理上,Prompt Caching 是 KV Cache 的跨请求复用,但工程上要考虑几个关键问题:
- 缓存粒度和命中规则
最常见的策略是前缀匹配。服务端会把请求的提示词从开头起,与缓存中的条目进行逐 token 比较。一旦出现不同,后面的内容就算作“新部分”,需要重新计算。因此,为了让缓存生效,必须把完全不变、完全相同的部分放在 Prompt 的最开头。
例如,你的系统提示是:“你是一个专业的生图提示词优化师,擅长……”,那么所有请求都必须以这段话开头,且一字不差,才能命中缓存。如果某个请求在系统提示前多加了一个“Hi”,缓存就会完全失效。 - 缓存存储和生命周期
缓存可以存放在 GPU 显存、CPU 内存甚至 SSD 上。服务提供商会根据访问频率决定缓存保留多久。通常,最近被使用过的缓存会被保留,长时间不用的会被淘汰。像 Anthropic Claude、Google Gemini 等 API,一般会为同一用户保留缓存几分钟到几小时;开源框架如 vLLM、SGLang 则允许自定义缓存大小和策略。 - 价格与性能收益
读写缓存本身也有开销,但远比重新计算 KV Cache 小得多。服务商通常会对写入缓存的请求加收一点费用,但对缓存命中的读取给予大幅折扣。延迟方面,一个被充分复用的长前缀可以让首 token 延迟降低 50% 以上。
实践
-
对于openai、deepseek等模型,只需要在请求的时候,保持前缀稳定即可。

-
在请求完成后,返回的usage里面,会记录了当前请求的token以及被缓存的token数量,下面是一个完整的请求返回值
{
"choices": [
{
"finish_reason": "tool_calls",
"index": 0,
"message": {
"content": "",
"reasoning_content": "The skill path is different from what was mentioned. Let me check the actual path:\n\n`/skills/analyse/skill-mqoslw6b-i47juu/SKILL.md`\n\nThis is the \"商品评价总结生成\" skill, but the user wants me to use SKILL.md from the analyze-creative skill. Let me check if there's a different path structure. The user said \"技能 /skills/onboard/analyze-creative/SKILL.md\" but the ls shows only `/skills/analyse/skill-mqoslw6b-i47juu`.\n\nLet me try to read the skill file at the path the user mentioned, and also check the analyse directory more carefully.",
"role": "assistant",
"tool_calls": [
{
"function": {
"arguments": "{\"path\": \"/skills/analyse/skill-mqoslw6b-i47juu\"}",
"name": "ls"
},
"id": "call_afe85765665a4fe493377f8b",
"index": 0,
"type": "function"
},
{
"function": {
"arguments": "{\"pattern\": \"/skills/**/*.md\"}",
"name": "glob"
},
"id": "call_91e47949f87b44d583984eac",
"index": 1,
"type": "function"
}
]
}
}
],
"id": "26b670ed-3e6a-95fd-9a05-b013f0b88008",
"created": 1784081208,
"object": "chat.completion",
"request_id": "26b670ed-3e6a-95fd-9a05-b013f0b88008",
"usage": {
"prompt_tokens": 19027,
"completion_tokens": 240,
"total_tokens": 19267,
"prompt_tokens_details": {
// 这里记录了有多少token被cache, 下次请求可用
"cached_tokens": 18432
},
"output_tokens_details": {
"reasoning_tokens": 146
}
}
}
- 对于claude,单个agent下,需要使用cache_control手动标记缓存断点,目前API支持最多四个断点。打了断点的前缀部分会使用缓存优化,假设标记区域发生变化,则当前cache_control到上一个cache_control这一段缓存失效。

由上图可以看到,使用prompt cache优化之后,同样的token输入,成本降低了近十倍
更多推荐



所有评论(0)