深入解析KV Cache,以及怎么让你的 Agent 更省钱?

0 评论 103 浏览 0 收藏 17 分钟

大模型API的计费机制背后藏着精妙的缓存设计。本文深入解析KV Cache与Prompt Cache两大核心技术逻辑,揭示为何缓存命中价格能低至常规输入的十分之一。从Transformer架构原理到实际API调用策略,手把手教你优化prompt结构,让你的AI应用成本直降90%。

如果你经常调用大模型 API,应该会注意到一个很有意思的现象:很多平台都会把输入 token 分成普通输入、缓存创建、缓存命中等不同类型,并且缓存命中的价格往往明显低于普通输入价格。

有些平台的缓存命中价格,甚至可以低到普通输入价格的十分之一左右。比如 Anthropic 的 Claude Prompt Caching 文档中,cache read tokens 按基础输入 token 价格的 0.1 倍计费;OpenAI 的 Prompt Caching 文档也提到,缓存可以显著降低延迟和输入 token 成本。

这就带来一个问题:

为什么同样是 token,有的按正常价格收费,有的命中缓存后价格却能低这么多?

更进一步,如果我们自己在做 Agent、编程助手、RAG 应用、自动化工作流,能不能主动利用这个机制,让 API 调用更便宜、更稳定?

要讲清楚这个问题,需要先区分两个概念:KV CachePrompt Cache

一、先区分两个缓存:KV Cache 和 Prompt Cache

很多人看到“缓存命中”时,会下意识把所有缓存都理解成一种东西。但在大模型推理里,至少要区分两类缓存。

第一类是 KV Cache

KV Cache 主要发生在一次模型生成过程中。模型在生成文本时,不是一次性把整段回答都生成出来,而是一个 token 一个 token 地往后预测。

每生成一个新 token,模型都需要回看前面已经出现过的上下文。如果每次都重新计算前面所有 token 的中间结果,成本会非常高。所以模型会把历史 token 在每一层 attention 中计算出来的 Key 和 Value 保存下来,后面继续生成时直接复用。

简单说:

KV Cache 是同一次生成过程里,用来避免重复计算历史上下文的缓存。

第二类是 Prompt Cache

Prompt Cache 更接近我们在 API 计费里看到的“缓存命中”。它主要解决的是跨请求的重复前缀问题。

比如一个 Agent 每次调用模型时,都会带上:

  • 固定 system prompt
  • 工具说明
  • 输出格式要求
  • 项目背景
  • 代码规范
  • 用户当前问题

其中前面的大段内容可能每次都一样,只有最后的用户问题、工具结果、检索内容在变化。如果平台发现多个请求拥有完全相同或高度一致的前缀,就可以复用之前已经计算过的缓存,从而降低 prefill 成本。

OpenAI 文档中也提到,Prompt Caching 会自动作用于 API 请求,并且建议把静态内容放在 prompt 前面,把动态内容放在后面,以提高缓存命中率。 Google Gemini API 的 context caching 文档也把典型场景描述为:反复向模型传入相同 input tokens 时,可以通过缓存优化性能和成本。

简单说:

Prompt Cache 是跨请求复用相同前缀,减少重复 prefill 计算。

所以二者的区别可以这样理解:

  • KV Cache:一次请求内部,生成后续 token 时复用历史 K/V。
  • Prompt Cache:多次请求之间,相同前缀命中后复用已计算结果。

从开发者视角看,我们通常没法直接控制底层 KV Cache,但可以通过 prompt 结构设计,提高 Prompt Cache 的命中率。

二、如果不想看硬核原理,可以直接记住这句话

KV Cache 的原理相对硬核,如果你对这里的深入解释不感兴趣,可以直接看这一段总结:

大模型生成下一个 token,需要最后一个 token 的最终隐藏状态;

为了得到这个隐藏状态,模型要经过很多层 attention;

每层 attention 都需要当前 token 的 Q 去查询历史 token 的 K/V。

因为历史 token 看不到未来,所以它们的 K/V 算完后不会再变。

把这些 K/V 保存起来,后面生成新 token 时直接复用,就能避免重复计算。

这就是 KV Cache 的宏观原理。

三、稍微深入一点:KV Cache 到底在缓存什么?

Transformer 模型可以理解成很多层 Transformer Block 堆叠起来的结构。每一层里通常都有 attention、MLP、残差连接和归一化等模块。

在 attention 里,每个 token 的隐藏状态会被转换成三个向量:

  1. Q:Query,当前 token 想查询什么信息
  2. K:Key,历史 token 如何被找到
  3. V:Value,历史 token 能提供什么信息

生成下一个 token 时,模型会用当前最后一个 token 的 Q,去和它能看到的历史 token 的 K 做匹配,得到注意力权重,然后根据这些权重读取对应的 V。经过很多层这样的处理后,最后一个 token 的隐藏状态就融合了足够多的上下文信息。最后,模型把这个隐藏状态送入 lm_head,得到下一个 token 的概率分布。

这里有一个关键点:GPT 这类自回归模型有因果注意力机制。也就是说,一个 token 只能看到自己和它之前的 token,不能看到未来 token。

比如:

我 喜欢 吃 苹果

在这个序列中:

“我”只能看到“我”

“喜欢”能看到“我、喜欢”

“吃”能看到“我、喜欢、吃”

“苹果”能看到“我、喜欢、吃、苹果”

这意味着,当“吃”这个 token 的隐藏状态、K、V 算出来后,后面再生成“苹果”“。”或者更多内容,都不会改变“吃”原来的 K/V。

所以,历史 K/V 是可以被安全缓存的。

而且大模型通常有很多层 attention,每一层都有自己不同的 K/V。所以 KV Cache 缓存的不是最后一层的一个结果,而是:

  • 每一层
  • 每个 attention head
  • 每个历史 token
  • 对应的 K 和 V

这也是为什么长上下文会非常吃显存:上下文越长、层数越多、模型越大,KV Cache 占用就越高。

四、为什么不缓存 Q?

因为 Q 是“当前问题”,K/V 是“历史资料库”。

当模型生成第 101 个 token 时,需要的是第 101 个 token 自己的 Q,去查询前 100 个 token 的 K/V。

历史 token 的 Q 是它们当时作为“查询者”时用过的,完成那一层 attention 计算后,未来基本不再需要它们。

所以 KV Cache 不叫 QKV Cache,而是只缓存 K 和 V。

可以用一个比喻:

  • Q = 当前提问
  • K = 资料标签
  • V = 资料内容

你现在有一个新问题,需要去资料库里找答案。资料库里需要保存的是标签和内容,而不是以前别人问过的问题。

五、那 Prompt Cache 为什么能让 API 价格下降?

Prompt Cache 省的是另一笔成本:重复 prefill 成本

很多 Agent 请求的前缀很长,而且高度重复。比如编程 Agent 会反复带上系统提示词、工具说明、代码规范、项目结构、输出格式等内容。每次请求如果都重新 prefill 一遍,成本很高。

如果平台发现你这次请求的前几千、几万 token 和之前某次请求完全一致,就可以复用前面已经算好的缓存。这样,这部分输入 token 的成本就可以明显降低。

这也是为什么官方 API 往往会在统计里显示:

  • cache creation tokens
  • cache read tokens
  • cached tokens

缓存创建表示这次新建了缓存;缓存命中表示这次复用了已有缓存。命中的部分通常会比普通输入便宜很多。

但要注意,Prompt Cache 不是永久记忆。缓存通常有生命周期,适合短时间内连续调用。你半个月后再调用同一个 Agent,不能指望上次的缓存还在。

所以它更适合这种场景:

  • 启动 Agent
  • 连续调用模型几十次
  • 固定 system prompt 和工具说明不变
  • 每次只在后面追加用户问题、工具结果、当前任务

而不是:

  • 今天用一次
  • 半个月后再用一次
  • 还希望命中上次缓存

长期知识应该靠本地文件、数据库、RAG、项目索引保存;Prompt Cache 更像是短期加速器。

六、开发者真正应该注意什么?

官方 API 通常已经帮我们做了缓存命中处理,所以我们不需要自己手动保存底层 K/V。真正要做的是:让自己的请求结构更容易被命中。

最重要的一条原则是:

稳定内容放前面,变化内容放后面。

比如一个 Agent 的 prompt 可以这样组织:

固定前缀:

1. system prompt

2. agent 角色说明

3. 工具使用规则

4. 输出格式规范

5. 项目固定背景

6. 代码风格要求

7. 工具 schema

动态后缀:

1. 当前用户问题

2. 本轮检索结果

3. 工具返回结果

4. 当前时间

5. 临时任务参数

不要把时间戳、随机 ID、工具返回、RAG 检索结果放在最前面。因为只要前缀发生变化,缓存命中率就会下降。

还有几个实用建议:

第一,system prompt 不要频繁改。 哪怕只是多一个空格、换一种工具顺序、加一行动态时间,都可能影响前缀匹配。

第二,多轮对话尽量 append-only。 不要每一轮都把历史记录重新总结、重排、改写。如果摘要每次都变化,虽然语义差不多,但 token 序列已经不同,缓存命中会变差。

第三,RAG 内容放后面。 检索结果每轮都不同,不要让它污染固定前缀。

第四,工具 schema 尽量稳定。 Agent 工具列表、参数结构、输出格式最好固定,避免每次动态生成一套不同的工具说明。

第五,批量任务按相同前缀分组。 比如处理 20 个文件,不要每个文件都构造完全不同的 prompt,而是保持前面的系统规则、项目背景、输出格式一致,只替换后面的文件内容。

第六,关注连续工作过程中的命中率,不要太在意隔很久后的命中。 缓存的价值主要在一次连续任务中体现,比如 Claude Code、Codex、Cursor、OpenClaw 这类 Agent 连续调用模型时,稳定前缀会带来很明显的收益。

七、为什么有些“token 经销商”反而更贵?

很多人会发现,直接使用官方 API 时,缓存命中率很高;但换成某些第三方路由 API 或中转 API 后,明明标价看起来不高,实际消耗却更快。

这背后可能有几个原因。

第一,路由 API 可能把请求打散到不同上游。

同样一个模型名称,背后可能接的是不同供应商、不同区域、不同账号、不同实例。Prompt Cache 往往依赖同模型、同服务环境、相同前缀以及稳定路由。请求如果每次随机落到不同上游,缓存就很难连续命中。

第二,路由层可能没有透传缓存参数。

有些厂商支持专门的缓存控制字段,比如 Anthropic 的 prompt caching 机制。如果中转服务为了统一接口,把这些字段丢掉或改写,缓存收益就可能下降。

第三,路由层可能改写你的 prompt。

比如它在请求前面加自己的系统提示、时间戳、安全规则、供应商标识或兼容层包装。只要这些内容出现在前缀位置,就可能破坏原本的精确前缀匹配。

第四,即使上游命中了缓存,折扣也不一定返还给你。

第三方服务商可能拿到了上游缓存优惠,但仍然按自己的统一价格向用户收费。所以判断一个路由 API 是否真的省钱,不能只看模型单价,还要看它是否透明展示:

cache creation tokens

cache read tokens

cached tokens

缓存命中率

是否支持固定 provider

是否支持透传 cache_control / prompt_cache_key

如果一个平台只告诉你总 token 和总费用,却不展示缓存命中,那么它的缓存收益就是不可控的。

八、最后总结

大模型 API 的缓存命中价格之所以低,是因为模型厂商确实减少了重复计算。

KV Cache 解决的是一次生成内部的重复计算:历史 token 的 K/V 算过后不会再变,后面生成新 token 时可以直接复用。

Prompt Cache 解决的是多次请求之间的重复前缀:如果 system prompt、工具说明、项目背景等静态内容完全一致,就可以复用之前 prefill 的结果,从而降低输入成本和首 token 延迟。

对开发者来说,不需要手动管理底层 KV Cache,真正应该做的是优化 prompt 结构:

稳定内容放前面

  • 动态内容放后面
  • 系统提示词少改
  • 工具 schema 保持一致
  • RAG 和工具结果不要污染前缀
  • 连续任务中保持会话结构稳定
  • 优先选择官方 API 或支持缓存透明统计的路由 API

一句话总结:

缓存命中不是玄学,而是大模型推理成本结构的直接体现。谁能让重复前缀更稳定,谁就更容易拿到低价缓存命中;谁的请求被路由、改写、打散,谁就更容易按普通输入价格付费。

本文由 @爱研究的乐声 原创发布于人人都是产品经理。未经作者许可,禁止转载

题图来自Unsplash,基于CC0协议

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
评论
评论请登录
  1. 目前还没评论,等你发挥!