LLM 上下文缓存:概念、原理与实战

Published on July 9, 2026
154 views

从一个常见误区说起

不少开发者在第一次直接调用 LLM API 时,都会踩一个坑:把超长内容一股脑塞进 Prompt。例如模型上下文窗口限制 100k,却传入了 150k 的内容,期望模型"自己想办法"。

实际情况是,主流 LLM API 的标准行为是直接报错拒绝:

  • OpenAI:返回 400,错误码 context_length_exceeded
  • Anthropic Claude:返回 400,invalid_request_error,提示 "prompt is too long"
  • Google Gemini:返回 400,INVALID_ARGUMENT

少数自建网关支持 truncation 参数,会从末尾强行裁掉多余内容,但这部分仍然计费,且模型对被裁内容完全无感知,输出往往答非所问。本地私有化部署(vLLM、TGI)则更危险——未做长度校验时,巨大的 KV Cache 会直接耗尽显存,进程被 OOM Killer 杀掉。

直接报错,其实是在保护你的钱包和系统稳定性。真正要处理超长文本,靠的是 RAG、上下文压缩、长窗口模型、Map-Reduce 拆分合并这些工程手段;而在这些手段背后,让长上下文应用真正"跑得动、跑得起"的关键底座,就是缓存。

这篇文章要讲透的,就是 LLM 中"缓存"这件事——它到底是什么、为什么重要、在工程中如何用。


一、LLM 里的"缓存"到底指什么

这是最容易混淆的一点。当一家模型厂商在介绍里提到"缓存",它未必指同一个东西。根据作用层次不同,LLM 中的缓存可以分为三类,它们解决的问题、所处的位置、给开发者带来的收益都完全不同。

缓存类型存的是什么主要目的典型场景
KV Cache(KV 缓存)模型内部计算产生的 Key/Value 向量加速单次推理,避免生成每个新词时重算整个上文所有自回归 LLM 生成文本的底层机制,对用户透明
Prompt Caching(提示词缓存)模型处理过的输入前缀(系统指令、工具定义、长文档)省钱、降低首 Token 延迟,命中部分按折扣价计费频繁调用相同系统指令或长文档的 API 请求
Semantic Cache(语义缓存)完整的历史问答对(问题+答案)完全跳过模型计算,对相似问题直接返回缓存答案客服机器人等高频重复性问题场景

三者分别处于模型推理层、API 服务层、应用层,互不替代,常常组合使用。下面逐一拆解。


二、KV Cache:保证生成流畅的"隐形助手"

2.1 为什么需要它

Transformer 是自回归模型——生成第 N 个词时,需要"关注"它前面的所有词。如果没有任何缓存,每生成一个新词,模型都要把整个对话历史从头重新算一遍,计算量会随长度呈平方级增长。一段 1 万 Token 的对话,生成到末尾时单步计算量会大到无法接受。

KV Cache 的作用就是:把前面已经算好的"注意力中间结果"(Key 和 Value 向量)存下来,让生成新词时的计算量从平方级降为线性增长。

2.2 它的代价:显存

KV Cache 是用空间换时间的典型。它的开销和上下文长度成正比,且非常吃显存。一个直观的数字:处理 1 万 Token 的输入,其 KV Cache 大小就可能达到 5GB。

这对长文本场景是个硬约束。为了在有限显存里塞下更长的上下文,业界发展出了 KV Cache 量化技术——例如把 16 位浮点数压缩成 4 位整数(INT4),以微小的精度损失换取数倍的上下文容量提升。Hugging Face、vLLM 等推理框架都提供了这类选项。

2.3 对开发者的意义

KV Cache 对开发者基本是透明的,但理解它有两点实际价值:

  1. 理解为什么长上下文贵:长输入不仅消耗 Token 额度,还消耗大量显存资源,这是 API 按输入长度计费的根本原因之一。
  2. 部署时的容量规划:私有化部署长窗口模型时,必须把 KV Cache 的显存占用算进去,否则会出现"明明模型加载成功了,一跑长文本就 OOM"的诡异问题。

三、Prompt Caching:API 调用的"省钱加速器"

这是目前主流大模型 API(OpenAI、Anthropic 等)宣传的核心功能,也是日常开发中"利用缓存"最常指代的那一类。

3.1 工作原理

当你发送一个很长的 Prompt(包含上千字的系统指令、工具定义、长文档),模型的推理引擎会先花时间对这些输入做"预填充"(prefill)计算。如果下一次请求的开头部分和上次一模一样,API 就会直接复用之前算好的结果,只对新增的部分重新计算。

3.2 核心机制:精确前缀匹配

这是理解提示词缓存最关键的一点,也是最容易踩坑的地方。

提示词缓存不是把整个 Prompt 存成一份文件做整体比对,而是基于 Token 序列的前缀树(Trie Tree)——它缓存的是"最长的、连续匹配的 Token 前缀"。

这意味着:

只要前缀中任意一个 Token 发生变化,从这个位置起、直到 Prompt 末尾的所有内容,都会被视为全新的、未命中的后缀,必须重新计算。

举例,假设你的 Prompt 结构是:

[System Prompt] + [固定文档A] + [用户问题] + [RAG结果]

如果 [固定文档A] 的第 100 个 Token 变了,那么从第 100 个 Token 往后的所有内容——包括后面的 [用户问题] 和 [RAG结果]——全部重新处理。这就是为什么 API 提供商会反复强调一句话:

请将变化最频繁的内容放在 Prompt 的最后面。

3.3 收益

缓存命中部分的输入 Token,价格通常只有原价的 10%–50%,且首个 Token 的延迟(TTFT)能降低 80% 以上。对于一个系统指令动辄几千 Token 的应用,这是实打实的成本和体验优化。

3.4 缓存友好的 Prompt 结构设计

基于前缀匹配原理,可以总结出一条黄金法则:按"变化频率"从低到高排列 Prompt 内容。

一个理想的对缓存友好的结构:

[顶层] 系统角色设定 (System Prompt)        ← 几乎永远不变,缓存命中率最高
[中层] 固定的工具/函数定义 (Tool Defs)     ← 很少变化
[中层] 固定的长文档/知识库背景             ← 可能会变,但尽量做成前缀的一部分
[底层] 对话历史摘要 (Summary)              ← 每次都会变
[底层] 本次用户问题 (User Query)            ← 每次都不同
[底层] 本次 RAG 检索到的临时文档            ← 每次都不同,且往往最长

收益是按命中 Token 长度计算的。即使 [RAG 结果] 有 5000 Token 每次都变、无法被缓存,只要它前面有 8000 Token 是固定的,那这 8000 Token 的算力每次都能复用,你只需为它们支付 10%–50% 的折扣价。

3.5 一个典型的"缓存杀手"

实战中有一个极易犯的错误,会让整个缓存策略瞬间失效:在 Prompt 最前面动态插入时间戳或随机 ID。

# ❌ 错误:破坏缓存
# 时间戳每秒在变,前缀树从根节点就匹配失败,整个 Prompt 都算作全新内容
prompt = f"[Current Time: {datetime.now()}]" + system_prompt + user_query
# ✅ 正确:保留缓存
# 把动态信息放到最后,或作为元数据字段传给 API
prompt = system_prompt + user_query + f"\n[Current Time: {datetime.now()}]"

记住这条:前缀一旦被破坏,后面命中再多也没用。一切动态内容,要么放到 Prompt 末尾,要么走 API 的 metadata 通道。


四、Semantic Cache:绕过模型的"快捷回复"

语义缓存是应用层的优化,与 LLM API 本身无关,需要开发者自行搭建。

4.1 工作原理

它不要求问题完全一致。当用户提问时,系统先去向量数据库里查找,看历史上有没有语义上非常相似的问题;如果有,直接把当时模型给出的答案返回,完全跳过调用 LLM 的步骤。

4.2 收益与代价

  • 收益:成本几乎为零,延迟极低。
  • 代价:需要自己维护一套缓存系统(通常用 Redis + 向量数据库),且要处理"相似但不等价"问题带来的误命中风险。

4.3 适用场景

它和提示词缓存的适用场景是互补的:

  • 提示词缓存:适用于长输入、每次只变一点点的场景(如 AI 编程助手,系统指令和代码库摘要很长,每次只变用户问题)。
  • 语义缓存:适用于短输入、重复度极高的场景(如客服机器人,"怎么退货""退款流程是什么"这类语义相近的高频问题)。

在同一个应用里完全可以把两者结合:用语义缓存拦截高频简单问题,用提示词缓存为复杂请求降本提速。


五、实战场景:IDE 是怎么"抹平"上下文限制的

理解了缓存,就能看懂一个有意思的现象:我们在 IDE(Cursor、GitHub Copilot)里用 AI 时,几乎从没遇到过"上下文超长"的报错,但这并不是因为 IDE 绕过了限制,而是它在后台做了一整套上下文工程(Context Engineering)。

它的核心策略是:从"全塞进去"变成"按需查找"。

5.1 动态上下文发现

不再把对话历史、长文本、工具返回结果一股脑塞进上下文,而是把它们先存成"文件"或"引用",只给 AI 一个"目录"和基础工具(grep、read)。需要细节时,AI 自己去"硬盘"里查找读取。这背后用到了几个关键手段:

策略做法效果
智能压缩与锚定对话接近窗口上限时,用另一个模型对历史做高密度总结,提炼任务目标、技术决策、待办,把"锚"钉在上下文里数万 Token 压到几千,保留 80%–90% 关键信息
卸载过时信息5 轮前的工具调用结果、已查看过的代码直接从 Prompt 卸掉,只保留文件路径大幅节省空间,并配合提示词缓存让重复读取成本降到 10%
长输出落地成文件命令返回几百行日志时,不把日志塞进去,而是存为文件,告诉 AI "可以用 tail 查看"MCP 任务 Token 用量平均减少 46.9%
上下文锚定仪表盘每次用户消息固定位置插入实时状态(时间、最近编辑文件、终端状态),替换历史中的旧仪表盘无效状态查询减少 50%+,效率提升 20%–30%
文件级摘要超大文件不全文加载,用 Tree-sitter 生成只含类型定义、函数签名的骨架摘要需要时再按行号读取关键代码段

5.2 缓存在其中的角色

这套机制之所以能跑得经济,提示词缓存功不可没。IDE 把"不变的"系统指令、工具定义、文件摘要放在 Prompt 前部稳定复用,把"每次都变的"用户问题、RAG 结果放在尾部,正好吃满了前缀匹配的红利。


六、自己搭建 Chat 工具时的落地

如果用 LangGraph 这类框架自己搭一套类似 IDE 的智能 Chat 工具,"内存管理"这件活必须自己干。LangGraph 提供的是工作流编排骨架,但缓存与上下文管理要靠你的代码填充。把三类缓存的思路落进去,有几个关键模块:

6.1 上下文提炼(对应"压缩")

用轻量模型(如 Qwen2.5-1.5B、Phi-3-mini)对超过阈值的旧对话做摘要,用摘要替换原始历史。压缩节点可以异步执行,不阻塞用户响应。

6.2 RAG 内容过滤(对应"按需查找")

向量检索粗排 + Cross-Encoder 重排序精排。纯向量检索召回率只有 60%–70%,加重排序可提升到 90%+。代码场景建议用专用 Embedding(如 voyage-code-2),并按函数/类为单位切分(Tree-sitter 解析),而非固定字符块。

6.3 工具的动态注入

所有工具都塞进 System Prompt 会导致上下文爆炸。做法是:常用工具常驻,稀有工具按需注入——用小模型判断当前任务需要哪些工具,只注入 Top-5 相关工具的定义。

6.4 提示词缓存优化

这是成本最低、收益最直接的一步:把固定的系统提示词、工具定义放在请求最前面,把用户问题、RAG 检索结果等动态内容放在最后。几乎不需要额外搭建系统,只需注意 Prompt 结构就能享受到红利。

6.5 语义缓存拦截

在工作流入口处先对语义缓存发起查询,高频简单问题(如"帮我写个排序函数")直接返回缓存结果,省下大模型调用开销。


七、关键要点回顾

  1. LLM 中的"缓存"不是单指一个东西:KV Cache(推理层)、Prompt Caching(API 层)、Semantic Cache(应用层)三者层次不同、各司其职。
  2. KV Cache 是自回归生成的底层机制,用显存换计算效率,理解它有助于成本估算和部署容量规划。
  3. Prompt Caching 是日常开发"利用缓存"最常指的那一类,基于精确前缀匹配——前缀中任一 Token 变化,其后全部重算。
  4. 黄金法则:按变化频率从低到高排列 Prompt 内容,固定在前、动态在后;绝对不要在最前面放时间戳或随机 ID。
  5. Semantic Cache 适用于高频重复的短问题,与 Prompt Caching 互补组合使用效果最佳。
  6. 真正的长上下文应用不是靠"硬塞",而是靠"检索—压缩—卸载"的上下文工程,缓存是让这套机制经济可运行的底座。

把"不变的东西放前面,把变化的东西放最后"这一条做到极致,就能榨干提示词缓存的每一分价值——这是最容易落地、也最容易被忽视的省钱小技巧。

©本文版权归作者所有,未经授权禁止转载。如需转载,请注明原文出处及链接。

Comments

Leave a Comment

No comments yet

Be the first to comment!