为什么大模型时代人人都需要懂 KV Cache

你跟 AI 助手聊到第 50 轮,它依然几秒钟就能回你。你大概隐约知道这背后有个东西叫 “KV cache”。这篇文章想用尽量少的术语,把这件事从头讲清楚——它是什么、为什么存在、为什么对账单影响这么大、以及为什么它在"agent 时代"忽然变成了一个工程话题。


一、先回答最朴素的问题:它到底缓存了什么?

KV 是两个矩阵的简写:K(Key,键)V(Value,值)

为什么是这两个?因为大语言模型的核心机制叫自注意力(self-attention)。我们不展开数学,只看它做的事:模型在生成每一个新 token 时,都要"环顾一下"它前面所有 token,问每一个:“你跟我有多相关?” 然后按相关度把这些 token 的信息加权汇总过来。

为了完成这个"环顾",每个已经出现的 token 都会被模型预先算出两个向量:

  • K:用来回答"我是谁、我代表什么含义"——给后面的 token 比对用的。
  • V:用来回答"如果你确实要用我,从我这里能取走什么信息"。

注意,每生成一个新 token,它会算出自己的 K 和 V,留给更后面的 token 用。

比喻:可以把每个 token 想成图书馆里的一本书。K 是这本书贴在书脊上的索引卡(告诉别人这本书讲什么),V 是这本书的正文摘要(要用时翻出来的内容)。模型每写下一个新词,就要在整个书架前站一会儿,先看每张索引卡(K),决定挑哪几本,再把挑中的那几本的摘要(V)拿出来揉成自己的下一句话。

KV cache 就是把每本书的索引卡和摘要存下来不重算。下一句话来了,直接从架子上拿。

二、为什么必须缓存?因为不缓存,慢到不能用

不缓存的情况下,模型每生成一个新 token,都得把前面所有 token 的 K 和 V 重新算一遍

不缓存:
  生成第 1 个 token:算 1 个 token 的 K/V
  生成第 2 个 token:算 1+2 个
  生成第 3 个 token:算 1+2+3 个
  ...
  生成第 N 个 token:算 1+2+3+...+N 个
  总计算量 ≈ N²

序列稍微一长,N² 的代价会让等待时间膨胀到完全不能用——你输入一个 8k 上下文的问题,模型为了吐出 500 个字,可能要重复计算几千万次本来已经算过的东西。

缓存:
  生成第 1 个 token:算 1 个新的 K/V,存起来
  生成第 2 个 token:只算 1 个新的,前 1 个查表
  生成第 3 个 token:只算 1 个新的,前 2 个查表
  ...
  总计算量 ≈ N

KV cache 把 N² 拉回到 N。这就是为什么你能流畅地跟 AI 聊天——不是模型"想得快",是它不再重复想已经想过的事

这是 KV cache 的第一层价值:单次生成时的加速。

三、第二层价值:跨请求的"前缀缓存"——你的账单是从这里被改写的

故事到这里只讲完了一半。第一层缓存解决的是"一句话之内别重复算",但有一个更大的浪费还没被处理:

当你跟 AI 聊到第 20 轮的时候,第 20 次请求里大约 95% 的内容,是和第 19 次请求逐字节相同的。

每一轮都要重新发送:

[ 你的 system prompt(4000 token,每轮都一样) ]
[ 工具定义(每轮都一样) ]
[ 第 1 轮的提问 + 回答 ]
[ 第 2 轮的提问 + 回答 ]
...
[ 第 19 轮的提问 + 回答 ]
[ 第 20 轮的新提问 ]   ← 真正变化的就这一小段

如果推理服务每次都把前面那一大段从头预填一遍(这一步叫 prefill,是把 prompt 喂进模型、把每个 token 的 K/V 算出来),那就是在白烧钱。

于是出现了 KV cache 的第二层用法——前缀缓存(prefix cache):

推理服务把你上一次请求算出来的那批 K/V 留在显存里。当下一次请求过来,它先看这次的前缀和上次有没有一段完全相同的开头;如果有,就直接从那段的末尾继续算,前面的全部跳过。

对你的账单的影响是直接的。以 Anthropic 为例,命中前缀缓存的 input token 价格约为正常价的 10%。同一段 system prompt,第一次发送按全价,从第二次起按 10% 计费——只要你没动它。

这就是为什么你常听到 “cache hit / cache miss” 的说法。命中或没命中,差一个数量级。

四、为什么会"没命中"?——这是这件事最反直觉的地方

前缀缓存命中的条件极其严苛:

前缀必须逐字节稳定。

注意是字节级,不是"意思一样"。任何一点变化,无论看起来多无害,都会改变前缀的 hash 值,整段缓存当场作废,这一轮按全价重新预填整个前缀

下面是几个最常见的"无害变化",每一个都足以让缓存命中变 0:

① 时间戳混进了 prompt。

You are a helpful assistant.
Current time: 2026-05-27 14:32:07

每一轮,14:32:07 都变。整个 system prompt 的字节都跟着变。从第一个字开始就算 miss。

② JSON key 顺序变了。

# 第 1 轮序列化出来
{"tools": [...], "system": "...", "messages": [...]}

# 第 2 轮(你用了一个不保序的 dict)
{"system": "...", "tools": [...], "messages": [...]}

字面意思完全一样,但字节序列完全不同。Miss。

③ 工具数组顺序变了。
你定义了 10 个工具,每次发请求时它们的顺序都可能不一样。Miss。

④ 历史对话被原地改写。
你想"清理"一下历史里的某段 tool_result,把它替换成更短的摘要。听起来很合理。但这一改,从那段被改的位置开始,往后所有字节的 hash 全部变了。下一轮:miss。

⑤ 把 <env>cwd=/repo, dirty=3 files</env> 这种瞬时信息放进了 system 段前部。
cwd 变、dirty 数量变,缓存就跟着死。

注意这些都不是模型的"脾气",也不是推理引擎的 bug。是你自己的 prompt 字节在抖。引擎只是诚实地按 hash 查表——hash 不一样,它就不可能找到对应的 K/V。

这就是为什么很多人盯着 dashboard 上的 cache_read: 0 一头雾水。模型没有"心情不好",是 prompt 本身没给它机会。

五、agent 时代为什么忽然变重要了

KV cache 不是什么新概念,2020 年以来一直在用。但它在最近一两年成为工程热点,是因为 agent 这种用法把"重复前缀"做到了极致

普通 chatbot 一来一回,前缀变化没那么剧烈。agent 不一样:

  • system prompt 几千 token;
  • 工具定义动辄十几个,几千 token;
  • 一次任务跑下来轻松 20–50 轮,每轮带 tool_use + tool_result;
  • 每轮都把前面所有内容全部重发

到第 30 轮时,你这一轮发送出去的几万 token 里,真正变化的可能不到 5%。如果前缀缓存能命中,账单就只算那 5%;如果命不中,全部按全价。

这就是为什么有人能把同一段 agent 工作流的费用从 $362 干到 $26——不是技术魔法,是把缓存命中率从 0 拉到 90%+。差出来的就是这一个数量级。

六、缓存命中是"运气",还是"结构"?

很长一段时间,业界默认前缀缓存是推理引擎"愿意给你就给你"的运行时礼物。命中了就赚到,没命中也没办法。

但近一两年大家逐渐意识到一件事:

缓存能不能命中,完全是 prompt 字节稳不稳定的函数。如果你把 prompt 当作一个会被每轮重写的东西,缓存就永远是运气;如果你把 prompt 写成只允许往尾部追加、绝不回写已有字节的样子,缓存命中率就会变成会话长度的单调不下降函数——会话越长,复用越多,永远不会回退。

这是一个挺哲学的转折:KV cache 的命中率,不是引擎的属性,是你 prompt 的结构属性。

落到工程上,大致就是三件事:

  1. 把"瞬时信息"挪到尾巴。 时间戳、cwd、git status、<system-reminder> envelope 这类每轮必变的东西,全部塞到 prompt 的最末尾,不要污染前面的稳定段。
  2. prompt 用只追加方式演化。 新一轮只往尾巴加新 block,绝不回头改写早期内容。所谓"修改"通过新 block 表达——摘要、脱敏、压缩都是追加新内容而不是回写旧内容。
  3. 保证序列化字节稳定。 工具数组排序固定、JSON key 排序固定、字段顺序固定。不要让序列化库的"心情"影响你的 hash。

做到这三条,前缀缓存基本就稳了。

七、不同厂商给你的"控制面"长得不一样

如果你是开发者,可能会注意到不同推理服务暴露 KV cache 的方式不一样:

  • Anthropic 给你显式断点cache_control breakpoint)。你可以告诉它:“这一块及之前,请帮我缓存。” 控制权最大,但要你自己想清楚断点放在哪里。
  • OpenAI 走另一条路:没有显式断点,但有 prompt_cache_key,让带同一个 key 的请求倾向于路由到同一台已经预热过的实例上——本质是"让相同前缀的请求打到同一份 KV cache 上"。
  • DeepSeek 既没有断点也没有 key,但前缀匹配是字节稳定且确定性的——只要你前缀不抖,它就会命中。
  • vLLM / SGLang 这类自托管推理框架能力更强:除了前缀匹配,还能做显式锚点、预热、缓存探测/驱逐、甚至"fork-and-replace"(在已有 KV 上分叉一份再替换尾部,不重算前缀)。

差异虽然不少,但底层那条不变量是一致的:前缀不抖,缓存就命中。 控制面只是给你不同粒度的把柄。

八、写给三种读者的收尾

如果你是普通用户: 你不需要操心 KV cache 这件事,但下次你听到"AI 用了 prefix cache,价格便宜了一个数量级",你知道它在说什么——它在说:你这一轮的对话有 95% 和上一轮一样,模型懒得重新读了一遍。

如果你是产品/运营: KV cache 是你账单上最容易被忽视、但收益最大的优化项。短期可能不显眼,长期能差出 5–10 倍的服务器成本。它不是"模型更聪明",是"prompt 写得更稳"。

如果你是开发者: 把 prompt 看作一个 append-only 的字节流——这是过去几年 agent 工程里最值得记住的一条原则。把瞬时信息推到尾巴;把序列化字节钉死;不要原地改写历史。如果你正在写自己的 agent 主循环、又懒得自己造一遍这套结构,可以看看像 TELOS 这样的协议——它把"前缀稳定"这件事从一个靠人自觉的运行时属性,变成了一个编译期就能检查的结构约束。但即便不用任何 framework,理解了 KV cache 是怎么工作的,你也已经能把自己的账单砍掉大半。

收尾的一句话

KV cache 本质上是一个非常朴素的工程妥协:已经算过的东西,别再算一遍。 这一句话从底层硬件一直贯穿到你和 AI 的对话体验,再贯穿到云服务商账单上的那几位数字。

它不是魔法。它只是一份"账单和延迟联合起来逼出来的"工程纪律。理解了它,你就理解了为什么 AI 应用的世界里,"prompt 怎么写"这件事,越来越像在写一份可复用的代码,而不是在和模型聊天

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐