大模型缓存机制与 Codex 缓存命中率说明
你可能已经注意到,使用 Codex、Claude Code 这类 AI 编程智能体时,同样的 prompt 第二次发出去往往比第一次快很多,成本也低不少。这不是巧合——背后是一整套缓存机制在起作用。
但这些缓存机制并不是凭空出现的。它们深深根植于 Transformer 架构的推理方式。要真正理解 KV Cache 和 Prompt Cache 为什么有效、什么条件下会失效,我们需要从神经网络处理序列的历史讲起。
本文面向工程实践,沿着一条完整的技术演进线展开:
RNN → CNN → 注意力机制 → 自注意力 → Transformer 架构 → Prefill/Decode → KV Cache → Prompt Cache → Codex 缓存命中率每个节点重点回答三个问题:
- 它是什么?
- 为什么要这么做?
- 有什么用,如何计算?
适合读者:后端工程师、AI 应用开发者、对大模型推理成本感兴趣的技术人。不需要深度学习背景,有编程基础即可阅读。
一、先从 RNN、CNN、Attention 说起
在 Transformer 出现之前,神经网络处理序列数据主要靠 RNN 和 CNN。它们各有优势,但也各有瓶颈。理解这些瓶颈,才能理解为什么注意力机制和 Transformer 会被发明出来。
1.1 RNN 是什么
RNN,全称 Recurrent Neural Network,循环神经网络。它的核心思想是:按时间顺序一个 token 一个 token 地处理序列,并把前面的信息压缩进一个隐藏状态。
对于输入序列:
x1, x2, x3, ..., xtRNN 每一步都会更新隐藏状态:
h_t = f(W_x x_t + W_h h_{t-1} + b)其中:
x_t:当前输入
h_t:当前隐藏状态
h_{t-1}:上一步隐藏状态
W_x、W_h、b:模型参数
f:非线性函数,例如 tanh、ReLU你可以把 RNN 理解成一个边读边记笔记的人:
读第 1 个词 -> 更新笔记
读第 2 个词 -> 基于旧笔记更新新笔记
读第 3 个词 -> 继续更新RNN 的优点是天然适合序列。它的问题是:
1. 难并行:必须先算 h_{t-1},才能算 h_t。
2. 长距离依赖弱:很早的信息被反复压缩,容易丢失。
3. 训练不稳定:长序列上容易梯度消失或梯度爆炸。虽然 LSTM、GRU 对 RNN 做了改进,但序列处理仍然强依赖时间步,难以充分利用 GPU 的并行能力。
1.2 CNN 是什么
CNN,全称 Convolutional Neural Network,卷积神经网络。它最早在图像领域非常成功,也可以用于文本。
CNN 的核心思想是:用一个小窗口扫描局部区域,提取局部模式。
在图像里,卷积核可能识别:
边缘
纹理
角点
局部形状在文本里,卷积核可以识别:
短语
n-gram 模式
局部语法结构一维文本卷积可以粗略表示为:
y_i = f(W · x_{i:i+k-1} + b)其中:
x_{i:i+k-1}:从第 i 个 token 开始的 k 个 token 窗口
W:卷积核参数
y_i:当前位置提取出的局部特征CNN 的优点是:
1. 并行性好。
2. 局部特征提取强。
3. 计算稳定。CNN 的问题是:
1. 默认只看局部窗口。
2. 想看远距离依赖,需要堆很多层或使用膨胀卷积。
3. 对"任意两个 token 的关系"表达不如注意力直接。1.3 注意力机制是什么
注意力机制的核心思想是:处理当前 token 时,不是只依赖一个压缩隐藏状态,也不是只看固定窗口,而是让当前 token 主动决定应该关注上下文里的哪些 token。
例如:
Order order = orderService.getById(orderId);
return order.getStatus();当模型处理 getStatus 时,它应该重点关注:
order
Order 类型
orderService.getById(orderId)而不是平均看所有 token。
注意力机制通过 Q、K、V 三组向量完成这件事:
Q = Query:当前 token 想找什么
K = Key:每个 token 可以被怎样检索
V = Value:每个 token 真正提供什么信息标准缩放点积注意力公式是:
Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V其中:
QK^T:计算 Query 和 Key 的相似度
sqrt(d_k):缩放因子,避免分数过大导致 softmax 过尖锐
softmax:把相似度转成权重分布
V:按权重汇总 Value 信息直观理解:
当前 token 拿着自己的 Q 去问:
上下文里哪些 K 和我最相关?
相关度越高,对应 V 的贡献越大。1.4 自注意力是什么
自注意力,Self-Attention,指的是同一段输入内部的 token 互相关注。
例如一句话:
小明把书放进书包,因为它太重了。模型处理"它"时,需要判断"它"指的是"书"还是"书包"。自注意力允许"它"去关注前面的 token,并通过训练学会哪些关系更重要。
自注意力和 RNN 的关键区别是:
RNN:信息通过 h_1 -> h_2 -> h_3 逐步传递。
Self-Attention:任意两个 token 可以直接建立关联。这让 Transformer 更容易建模长距离依赖,也更适合 GPU 并行。
1.5 Transformer 是什么
有了自注意力,Transformer 的故事才真正开始。
Transformer 是 2017 年 Google 在论文 "Attention Is All You Need" 中提出的架构。它的核心主张很简单:不需要 RNN,也不需要 CNN,仅靠注意力机制就能处理序列。
整体结构
原始 Transformer 是一个编码器-解码器(Encoder-Decoder)结构:
输入 tokens
│
▼
┌─────────────────────┐
│ Encoder × N 层 │ ← 每层:Self-Attention + Feed-Forward
└─────────────────────┘
│
▼
┌─────────────────────┐
│ Decoder × N 层 │ ← 每层:Masked Self-Attention + Cross-Attention + Feed-Forward
└─────────────────────┘
│
▼
输出 tokens(一个一个生成)编码器负责理解输入:把一串 token 编码成一组富含上下文信息的向量。
解码器负责生成输出:逐 token 生成,每一步都关注编码器的输出和已生成的历史。
后来的大语言模型对这个结构做了各种取舍:
| 模型类型 | 结构 | 代表模型 |
|---|---|---|
| 仅编码器 | 只用 Encoder | BERT、RoBERTa |
| 仅解码器 | 只用 Decoder | GPT 系列、LLaMA、Claude、Codex |
| 编码器-解码器 | 完整结构 | T5、BART |
当前主流大语言模型(包括驱动 Codex 的模型)基本都是仅解码器结构。这意味着自注意力只需要关注当前 token 及其之前的历史(因果注意力),不需要看未来 token。
每一层做了什么
以一个 Decoder 层为例,数据流是这样的:
输入向量
│
▼
┌─────────────────────────┐
│ Masked Self-Attention │ ← 当前 token 关注历史 token
└─────────────────────────┘
│ + 残差连接
▼
┌─────────────────────────┐
│ Layer Norm │
└─────────────────────────┘
│
▼
┌─────────────────────────┐
│ Feed-Forward Network │ ← 逐 token 的非线性变换(通常是两层线性 + 激活函数)
└─────────────────────────┘
│ + 残差连接
▼
┌─────────────────────────┐
│ Layer Norm │
└─────────────────────────┘
│
▼
输出向量(传给下一层)两个关键组件:
- Self-Attention:让每个 token 能够"看到"上下文中的其他 token,建模全局依赖关系。
- Feed-Forward Network (FFN):对每个 token 做独立的非线性变换,可以理解为"消化"注意力收集到的信息。
位置编码:没有循环怎么知道顺序
RNN 天然按时间步处理,自带顺序感。但自注意力是同时看所有 token 的,它本身不知道"谁在前谁在后"。
解决方案:位置编码(Positional Encoding)。
原始论文用正弦/余弦函数生成位置向量,加到 token embedding 上:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))现代大模型更多使用旋转位置编码(RoPE) 或可学习位置编码,但核心目的不变:让模型知道每个 token 在序列中的位置。
没有位置编码,Transformer 会把"猫吃鱼"和"鱼吃猫"当作同样的意思。
多头注意力
上一小节提到的多头注意力,在 Transformer 中是标准配置。原始论文使用 8 个头:
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) W^O
其中 head_i = Attention(Q W_i^Q, K W_i^K, V W_i^V)每个头用不同的投影矩阵,关注不同的语义子空间。最后拼接起来,经过一个输出投影 W^O 融合多头信息。
直觉上:有的头关注语法结构,有的头关注指代关系,有的头关注语义相似性。
1.6 Transformer 为什么适合大模型
理解了 Transformer 的结构,它的优势就很清楚了:
1. 并行性好:训练时可以同时处理整个序列,不需要像 RNN 那样逐步递归。
2. 长距离依赖强:token 之间可以直接建立注意力关系,不需要像 CNN 那样堆层。
3. 可扩展性好:扩大层数、宽度、数据量后效果持续提升(Scaling Law)。
4. 架构通用:文本、代码、图像、多模态都可以转成 token 序列处理。但 Transformer 也有代价:注意力计算会随着上下文长度增长而变贵。
对于长度为 n 的序列,全量自注意力需要计算每个 token 和每个 token 的关系,复杂度近似:
O(n²)这就是为什么长上下文很贵,也解释了为什么 KV Cache 和 Prompt Cache 很重要——它们都是为了对抗这个 O(n²) 的代价。
二、Transformer 推理分成 Prefill 和 Decode
理解了 Transformer 的架构,接下来看它在推理时是怎么工作的。大模型生成答案并不是一步到位,而是分成两个阶段,每个阶段有不同的计算特征,也对应不同的缓存策略。
大模型生成答案通常可以分成两个阶段:
1. Prefill:读取输入 prompt。
2. Decode:一个 token 一个 token 生成输出。2.1 Prefill 阶段
Prefill 阶段处理完整输入,例如:
系统规则
开发者规则
AGENTS.md
工具定义
用户问题
文件内容如果输入长度为 n,模型需要对这些 token 计算隐藏状态、Q/K/V、注意力关系等。全量注意力的计算量和 n 的平方相关:
Prefill attention cost ≈ O(n^2)例如 100k token 的 prompt,比 10k token 的 prompt 贵得多,不只是线性增加。
2.2 Decode 阶段
Decode 阶段逐个生成新 token。
例如模型已经读完 prompt,并生成到第 t 个 token:
已存在 token:1...t-1
当前要生成 token:t第 t 个 token 需要关注前面的历史 token。如果每生成一个 token 都重新计算全部历史 token 的 K/V,代价会非常高。
KV Cache 就是为了解决这个问题。
三、KV Cache 是什么
Prefill 阶段已经算好了所有 token 的 K/V。但 Decode 阶段每生成一个新 token,都需要回头看历史——如果每次都重算,太浪费了。KV Cache 就是为了解决这个问题而生的。
KV Cache 是 Transformer 推理阶段的一种内部缓存机制。它缓存的是历史 token 在每一层注意力中的 Key 和 Value 张量。
它不是缓存最终答案,也不是缓存业务数据,而是缓存模型内部计算结果。
3.1 KV Cache 缓存了什么
在每一层 Transformer 里,每个 token 都会被线性投影成 Q、K、V:
Q = X W_Q
K = X W_K
V = X W_VKV Cache 保存的是历史 token 的:
K_1, K_2, ..., K_t
V_1, V_2, ..., V_t对于多层、多头注意力,真实缓存结构一般类似:
Key Cache:
(num_layers, batch_size, num_heads, seq_len, head_dim)
Value Cache:
(num_layers, batch_size, num_heads, seq_len, head_dim)不同框架会调整维度顺序以优化访存,但本质一样。
3.2 KV Cache 怎么工作
假设已经有历史 token:
token_1, token_2, ..., token_999模型已经缓存了:
K_1...K_999
V_1...V_999现在生成第 1000 个 token 时:
1. 为当前 token 计算 Q_1000、K_1000、V_1000。
2. 从 KV Cache 读取历史 K_1...K_999 和 V_1...V_999。
3. 拼接得到 K_1...K_1000、V_1...V_1000。
4. 用 Q_1000 attend 到所有历史 K。
5. 加权汇总所有 V。
6. 把 K_1000、V_1000 追加进 KV Cache。注意一个常见误区:新 token 不是只计算 Q。新 token 的 Q、K、V 都要计算。只是历史 token 的 K/V 不用重算。
3.3 为什么 KV Cache 能加速
没有 KV Cache:
每生成一个新 token,都重新计算完整历史序列的 K/V。有 KV Cache:
历史 token 的 K/V 已经算过并保存。
新 token 只需要计算自己的 Q/K/V。所以 KV Cache 主要加速 Decode 阶段。
没有 KV Cache 时,生成 m 个 token,可能反复处理越来越长的上下文:
第 1 步:处理 n
第 2 步:处理 n + 1
第 3 步:处理 n + 2
...有 KV Cache 后,每步主要新增当前 token 的计算,并对历史 K/V 做查询:
第 1 步:新增 1 个 token 的 Q/K/V
第 2 步:新增 1 个 token 的 Q/K/V
第 3 步:新增 1 个 token 的 Q/K/V
...3.4 KV Cache 复杂度怎么理解
对单次全量注意力,长度为 n 时:
Attention cost ≈ O(n^2)对 decode 阶段的每一步,如果使用 KV Cache,当前 token 的 Q 需要和历史 n 个 K 做匹配:
Per-token decode attention cost ≈ O(n)如果生成 m 个 token,总 decode 注意力成本近似:
O(mn)其中 n 会随着生成增长,但不会像"每一步重新跑完整历史"那样重复计算历史 K/V。
3.5 KV Cache 是否节省显存
通常不是。
KV Cache 的典型代价是:
用显存换速度。因为缓存需要保存每层、每个头、每个历史 token 的 K/V。上下文越长,KV Cache 越大。
一个粗略显存估算公式:
KV Cache bytes
≈ 2 × num_layers × batch_size × seq_len × num_kv_heads × head_dim × bytes_per_element其中:
2:K 和 V 两份
num_layers:Transformer 层数
batch_size:批大小
seq_len:上下文长度
num_kv_heads:KV 头数量,GQA/MQA 下可能小于 attention heads
head_dim:每个头的维度
bytes_per_element:FP16/BF16 通常为 2 bytes,FP32 为 4 bytes示例:
num_layers = 40
batch_size = 1
seq_len = 100000
num_kv_heads = 8
head_dim = 128
bytes_per_element = 2
KV Cache bytes
≈ 2 × 40 × 1 × 100000 × 8 × 128 × 2
≈ 16,384,000,000 bytes
≈ 15.26 GiB这只是 KV Cache 本身,不包括模型权重、激活、运行时开销。因此长上下文推理对显存压力很大。
工程上常见优化包括:
Paged Attention
KV Cache 量化
Sliding Window Attention
Grouped Query Attention / Multi Query Attention
KV Cache Offload
Prefix Cache
上下文压缩3.6 KV Cache 有什么用
KV Cache 的主要作用:
1. 降低每个输出 token 的生成延迟。
2. 提高吞吐,服务端可以支持更多并发。
3. 让长文本生成、多轮对话、代码智能体更可用。
4. 避免对历史 token 做重复 K/V 投影计算。它解决的是"同一次请求内部,生成阶段不要重复计算历史 token"的问题。
四、Prompt Cache / Context Cache 是什么
KV Cache 解决的是"同一次请求内部不要重复计算"的问题。但在实际应用中,还有一个更大的浪费:不同的请求之间,大量 prompt 前缀是重复的。Prompt Cache 正是为此而来。
Prompt Cache,也叫 Context Cache、提示词缓存、上下文缓存。它和 KV Cache 有关,但不是同一层概念。
KV Cache 通常指一次推理请求内部的历史 K/V 复用;Prompt Cache / Context Cache 通常指跨请求复用相同或公共 prompt 前缀的计算结果。
4.1 Prompt Cache 缓存了什么
Prompt Cache 缓存的是一段输入前缀被模型处理后的内部计算结果。服务商通常不会暴露具体张量,但可以理解为复用了这段前缀对应的注意力状态或等价计算。
例如:
请求 1:
[系统规则 + 工具定义 + 代码仓库内容] + 问题 A
请求 2:
[系统规则 + 工具定义 + 代码仓库内容] + 问题 B如果前面的公共前缀完全一致,第二次请求可以复用第一次已经处理过的前缀计算。
4.2 Prompt Cache 和 KV Cache 的区别
| 对比项 | KV Cache | Prompt Cache / Context Cache |
|---|---|---|
| 发生范围 | 一次请求内部 | 多次请求之间 |
| 主要阶段 | Decode 阶段 | Prefill 阶段和跨请求前缀复用 |
| 缓存对象 | 每层历史 token 的 K/V 张量 | 公共 prompt 前缀的计算结果 |
| 用户是否可见 | 通常不可见 | 常通过 cached_tokens / cache_read_tokens 可见 |
| 主要收益 | 加速生成 token | 降低重复输入成本和首 token 延迟 |
| 典型场景 | 单次长回复生成 | 多轮 Agent、代码库问答、长文档多次提问 |
4.3 为什么 Prompt Cache 要求"前缀匹配"
Transformer 的位置、顺序和 token 内容都会影响计算。Prompt Cache 复用的是前缀计算,因此最容易命中的形式是:
请求 1:
A B C D E + X
请求 2:
A B C D E + Y其中 A B C D E 是完全相同的前缀。
如果中间插入或修改了内容:
请求 1:
A B C D E + X
请求 2:
A B Z C D E + Y从 Z 开始,后面的 token 位置和上下文都变了,缓存很可能断掉。
这就是为什么 OpenAI Prompt Caching 文档强调:静态内容放前面,变量内容放后面;工具、图片等也需要保持一致才有利于命中。
4.4 Prompt Cache 有什么用
Prompt Cache 的主要作用:
1. 降低输入 token 成本。
2. 降低长 prompt 的处理延迟。
3. 提高 Agent 多轮工具调用的效率。
4. 让长代码库、长文档、多轮对话场景更经济。它解决的是"多个请求反复发送同一大段上下文"的问题。
4.5 OpenAI Prompt Caching 的关键点
根据 OpenAI 官方 Prompt Caching 文档和 API 公告:
1. Prompt Caching 自动启用,不需要额外代码打开。
2. 缓存命中依赖 prompt 内的精确前缀匹配。
3. 静态内容应该放在 prompt 开头,变量内容放在结尾。
4. API 响应中可以通过 usage.prompt_tokens_details.cached_tokens 查看命中 token 数。
5. 缓存通常会在短时间不活跃后清理,并且有最长保留时间限制。
6. 缓存不改变模型输出语义,只影响延迟和成本。一个典型 usage 结构类似:
{
"usage": {
"prompt_tokens": 2006,
"completion_tokens": 300,
"total_tokens": 2306,
"prompt_tokens_details": {
"cached_tokens": 1920
}
}
}这里表示:
输入 token 总数:2006
其中命中缓存:1920
未命中输入:86
输出 token:3004.6 阿里云 Context Cache 的关键点
阿里云 Model Studio 文档把 Context Cache 分为两类:
1. 显式缓存:开发者主动标记 cache_control,确定性更强。
2. 隐式缓存:系统自动识别公共前缀,无需配置,但命中率不确定。文档中的典型设计是:
系统人设:高度稳定
外部知识:半稳定
对话历史:动态增长
当前问题:每次不同如果把整段 prompt 作为一个整体缓存,任何中间变化都可能导致缓存失效。因此显式缓存允许使用多个缓存标记,把不同稳定性的片段分开管理。
这和 Agent 设计高度相关:稳定内容应该尽量固定在前面,动态内容追加到后面。
五、Codex 这种智能体如何组织上下文
有了 KV Cache 和 Prompt Cache 的概念基础,我们可以看一个具体场景:Codex 这类编程智能体是怎么利用这些缓存机制的。理解它的上下文组织方式,才能看懂缓存命中率的数字背后到底在说什么。
Codex 这类软件工程 Agent 的工作方式不是"一问一答"那么简单。它会循环执行:
用户输入
模型推理
工具调用
读取文件或执行命令
把工具结果追加进上下文
再次调用模型
继续工具调用或输出最终答复OpenAI 的 Codex agent loop 文章说明,Codex 会把系统/开发者指令、工具定义、AGENTS.md、环境信息、用户消息、工具调用结果等组织成请求发送给 Responses API。工具结果会追加回 input,下一次请求继续使用。
5.1 Codex 请求里通常有什么
一个 Codex 请求可以粗略拆成:
系统消息:
模型基础行为、平台级规则
开发者消息:
Codex 行为规范、工具使用规范、沙箱权限说明
项目规则:
AGENTS.md、仓库约定、用户配置
工具定义:
shell、apply_patch、MCP 工具、web 工具等 schema
环境信息:
cwd、shell、sandbox、approval mode
会话历史:
用户消息、助手消息、工具调用、工具输出
当前任务:
用户最新输入这些内容里,有些很稳定,有些频繁变化。
稳定内容:
系统规则
开发者规则
AGENTS.md
工具定义
项目约定
沙箱配置
当前工作目录变化内容:
最新用户问题
工具输出
测试日志
文件 diff
错误堆栈
任务计划变化Prompt Cache 的命中率,主要取决于稳定内容是否形成完全相同的前缀。
5.2 Codex 为什么喜欢 append-only
在 Agent 循环中,一个关键设计是:尽量追加新消息,而不是修改旧消息。
例如:
请求 1:
[固定前缀][用户任务][工具调用 A]
请求 2:
[固定前缀][用户任务][工具调用 A][工具结果 A][工具调用 B]请求 1 的全部内容是请求 2 的前缀,因此请求 2 有机会大量命中缓存。
如果中途修改旧内容:
请求 1:
[固定前缀][用户任务][工具调用 A]
请求 2:
[固定前缀被改写][用户任务][工具调用 A][工具结果 A]那么从改写位置开始,缓存就会被破坏。
所以 Codex 类 Agent 的高效上下文管理原则是:
能追加就追加。
不要频繁改写历史。
不要改变工具顺序。
不要中途改变模型、sandbox、cwd、工具定义。
变量内容尽量靠后。OpenAI Codex agent loop 文章也指出,旧 prompt 成为新 prompt 的精确前缀是有意为之,因为这能利用 prompt caching 提升效率。
5.3 Codex 的缓存命中率是什么意思
Codex 的缓存命中率可以理解为:
本次请求输入 token 中,有多少 token 命中了服务端 Prompt Cache。公式:
cache_hit_rate = cached_input_tokens / total_input_tokens如果使用 OpenAI API 的 usage 字段:
cache_hit_rate
= usage.prompt_tokens_details.cached_tokens / usage.prompt_tokens如果是 Responses API,字段名可能表现为 input token details,但含义类似:
cached_tokens / input_tokens注意:
缓存命中率不是答案复用率。
缓存命中率不是正确率。
缓存命中率不是模型记忆能力。
缓存命中率只是输入计算复用比例。5.4 Codex 缓存命中率案例
假设某次 Codex 请求输入如下:
系统/开发者规则:8,000 tokens
AGENTS.md 和项目规则:4,000 tokens
工具定义:10,000 tokens
历史工具结果和文件片段:60,000 tokens
用户最新问题:1,000 tokens总输入:
83,000 tokens如果其中前 72,000 tokens 与上一轮请求前缀完全一致:
cached_input_tokens = 72,000
total_input_tokens = 83,000缓存命中率:
cache_hit_rate = 72,000 / 83,000 ≈ 86.75%这表示:
86.75% 的输入 token 对应的前缀计算可以复用。不表示:
86.75% 的答案是旧答案。
86.75% 的推理不用做。
86.75% 的上下文被模型永久记住。5.5 Codex 成本如何估算
如果模型价格为:
普通输入单价:P_input
缓存输入单价:P_cached
输出单价:P_output一次请求:
total_input_tokens = T_in
cached_input_tokens = T_cached
output_tokens = T_out未缓存输入:
T_uncached = T_in - T_cached成本估算:
cost
= (T_uncached / 1,000,000) × P_input
+ (T_cached / 1,000,000) × P_cached
+ (T_out / 1,000,000) × P_output示例:
T_in = 100,000
T_cached = 80,000
T_uncached = 20,000
T_out = 5,000
P_input = $5 / 1M tokens
P_cached = $0.5 / 1M tokens
P_output = $30 / 1M tokens则:
input_cost = 20,000 / 1,000,000 × 5 = $0.10
cached_cost = 80,000 / 1,000,000 × 0.5 = $0.04
output_cost = 5,000 / 1,000,000 × 30 = $0.15
total_cost = $0.29如果没有缓存:
input_cost = 100,000 / 1,000,000 × 5 = $0.50
output_cost = $0.15
total_cost = $0.65在这个例子里,Prompt Cache 把单次请求成本从:
$0.65 降到 $0.29节省比例:
($0.65 - $0.29) / $0.65 ≈ 55.38%实际节省比例取决于:
1. 输入 token 占总成本的比例。
2. cached input 的折扣幅度。
3. 缓存命中率。
4. 输出 token 是否很多。如果输出非常长,缓存只能节省输入部分,整体节省比例会下降。
六、缓存命中率如何计算和观察
6.1 通用公式
最常用的缓存命中率公式:
缓存命中率 = 命中缓存的输入 token 数 / 总输入 token 数英文:
cache hit rate = cached input tokens / total input tokens如果 API 返回:
{
"prompt_tokens": 50000,
"prompt_tokens_details": {
"cached_tokens": 42000
}
}则:
cache_hit_rate = 42000 / 50000 = 84%6.2 更细的拆分
输入 token 可以拆成:
total_input_tokens
= cached_input_tokens
+ uncached_input_tokens所以:
uncached_input_tokens = total_input_tokens - cached_input_tokens未命中率:
cache_miss_rate = 1 - cache_hit_rate或者:
cache_miss_rate = uncached_input_tokens / total_input_tokens6.3 为什么同样的内容有时不命中
常见原因:
1. 前缀不是完全一致。
2. 内容位置变了。
3. 工具定义顺序变了。
4. 模型变了。
5. system/developer 指令变了。
6. 沙箱、cwd、环境上下文变了。
7. 缓存过期。
8. 请求被路由到无法复用缓存的后端。
9. 低于服务商缓存阈值。
10. 图片、工具、结构化内容虽然看似一样,但序列化后不一致。6.4 为什么"摘要压缩"可能降低缓存命中
摘要压缩可以节省上下文窗口,但会破坏已有前缀。
例如:
压缩前:
[A][B][C][D][E][最新问题]
压缩后:
[Summary(A-E)][最新问题]压缩后的 prompt 更短,但它不再以原来的 [A][B][C][D][E] 开头。因此旧缓存不能直接复用。
这就是 Agent 上下文管理的核心权衡:
append-only:缓存友好,但上下文越来越长。
compact:释放上下文窗口,但会牺牲部分缓存连续性。Codex 这类 Agent 通常需要在两者之间平衡:上下文还够用时尽量追加;接近窗口限制时再压缩。
七、Prompt Cache、KV Cache、应用层缓存的区别
| 类型 | 缓存内容 | 是否缓存答案 | 典型控制方 | 用途 |
|---|---|---|---|---|
| KV Cache | 历史 token 的 K/V 张量 | 否 | 模型推理引擎 | 加速同一次请求的 decode |
| Prompt Cache / Context Cache | 相同 prompt 前缀的计算结果 | 否 | 模型服务商 | 跨请求降低 prefill 成本和延迟 |
| 语义缓存 | 相似问题的答案 | 可能是 | 应用层 | 相似问题直接复用旧答案 |
| HTTP 缓存 | 完整响应 | 是 | 网关/CDN/应用 | 相同请求直接返回旧结果 |
| RAG 缓存 | 检索结果、embedding、rerank 结果 | 否或部分 | 应用层 | 减少检索和向量计算 |
不要把它们混为一谈。
例如:
Prompt Cache 命中:
模型仍然会生成新答案,只是输入前缀处理更便宜更快。
HTTP 缓存命中:
可能直接返回旧答案,模型甚至不运行。八、工程实践:如何提高 Codex / Agent 的缓存命中率
理论讲完了,回到工程层面:作为 Agent 框架的开发者或使用者,有哪些具体手段可以提高缓存命中率?以下是最实用的几条原则。
8.1 稳定内容放前面
推荐顺序:
系统规则
开发者规则
工具定义
项目规则
AGENTS.md
长期任务约束
稳定参考资料
会话历史
工具结果
当前用户问题不推荐:
当前时间
随机 ID
最新用户问题
动态检索结果
系统规则
工具定义
项目规则因为动态内容放在前面会破坏后面所有缓存。
8.2 保持工具定义稳定
工具定义通常很长,适合缓存。但它们也很容易因为顺序变化导致缓存 miss。
建议:
1. 工具列表排序稳定。
2. 不要在会话中频繁增删工具。
3. 工具 schema 不要插入动态描述。
4. MCP 工具变化时,尽量追加说明,而不是改写前面的工具上下文。OpenAI Codex agent loop 文章提到,MCP 工具列表顺序不稳定曾导致 cache miss,这说明"序列化稳定性"对 Agent 成本非常重要。
8.3 变量内容放后面
变量内容包括:
用户最新输入
当前错误日志
测试输出
时间戳
请求 ID
临时文件路径
最新 diff这些内容应该尽量放在 prompt 后部。
8.4 避免中途改写历史
缓存友好的方式:
[旧上下文][新增消息]缓存不友好的方式:
[重写后的旧上下文][新增消息]这也是为什么"append-only log"是 Agent 上下文设计中的重要原则。
8.5 大文件谨慎放入上下文
大文件可以提高模型理解能力,但也会增加输入成本和上下文压力。
建议:
1. 只放任务相关片段。
2. 多次复用的长文档放在稳定前缀。
3. 一次性临时日志放后面。
4. 不要每轮重新组织同一批文件内容顺序。8.6 不要为了缓存牺牲正确性
缓存优化是成本和延迟优化,不是第一目标。Agent 首先要正确完成任务。
如果为了保持缓存而不更新过期信息,反而会导致错误决策。
优先级应该是:
正确性 > 安全性 > 可维护性 > 延迟/成本九、常见误区
9.1 误区:KV Cache 会缓存答案
错误。
KV Cache 缓存的是历史 token 的 Key/Value 张量,不是最终答案。
9.2 误区:Prompt Cache 命中后模型不会重新推理
错误。
Prompt Cache 只是复用前缀计算。模型仍然会处理新增输入,并生成新的输出。
9.3 误区:KV Cache 一定节省显存
通常错误。
KV Cache 通常增加显存占用,用显存换速度。
9.4 误区:语义相似就能命中 Prompt Cache
通常错误。
主流 Prompt Cache 依赖精确前缀匹配,而不是语义相似。语义相似缓存属于应用层 semantic cache,是另一类系统。
9.5 误区:缓存命中率高代表回答质量高
错误。
缓存命中率只代表输入计算复用比例,不代表答案质量。
9.6 误区:只要 total tokens 一样就能命中
错误。
命中依赖内容和顺序,而不是总长度。
十、一个完整案例:代码智能体如何利用缓存
假设用户让 Codex 修改一个 Java 服务。
第一轮请求:
[系统规则 5k]
[开发者规则 8k]
[工具定义 12k]
[AGENTS.md 4k]
[环境信息 1k]
[用户任务 1k]总计:
31k tokens这一轮没有历史缓存,可能 cached_tokens = 0。
Codex 读取 README、pom.xml、Controller、Service 后,第二轮请求:
[系统规则 5k]
[开发者规则 8k]
[工具定义 12k]
[AGENTS.md 4k]
[环境信息 1k]
[用户任务 1k]
[工具调用和文件内容 40k]总计:
71k tokens如果前 31k 完全一致,第二轮可能:
cached_tokens = 31k
cache_hit_rate = 31 / 71 ≈ 43.66%第三轮,Codex 运行测试,追加测试结果:
[前一轮完整 71k]
[测试输出 5k]总计:
76k tokens如果前一轮 71k 是第三轮的精确前缀:
cached_tokens = 71k
cache_hit_rate = 71 / 76 ≈ 93.42%这就是 Agent 场景中缓存命中率可能越来越高的原因:每轮都在旧上下文后面追加新内容,旧上下文成为新请求的前缀。
但如果第三轮中途改变了工具定义顺序或改写了 AGENTS.md 位置,可能导致:
cached_tokens 大幅下降这就是为什么 Agent 框架会非常在意上下文序列化稳定性。
十一、一句话总结
RNN 顺序读、逐步压缩记忆;CNN 窗口扫描、提取局部模式;注意力机制让 token 直接选择该关注哪些上下文;自注意力让同一段输入内部的 token 互相关注。Transformer 架构由多层自注意力和前馈网络组成,通过位置编码补上顺序信息,靠自注意力获得强大的并行和长距离建模能力。但它付出的代价是 O(n²) 的注意力计算复杂度。
KV Cache 是一次请求内部的推理加速机制,缓存历史 token 的 K/V,减少 decode 阶段重复计算。
Prompt Cache / Context Cache 是跨请求的前缀计算复用机制,缓存相同 prompt 前缀的计算结果,降低输入成本和延迟。
Codex 的缓存命中率表示本次请求输入 token 中有多少命中了 Prompt Cache。它通常按 cached_input_tokens / total_input_tokens 计算。命中率高说明上下文前缀稳定、追加式组织良好、重复计算少;它不代表答案被缓存,也不代表回答一定正确。
十二、参考资料
以下资料用于交叉核对本文概念。
OpenAI 官方资料
OpenAI:Prompt caching https://platform.openai.com/docs/guides/prompt-caching
OpenAI:Prompt Caching in the API https://openai.com/index/api-prompt-caching/
OpenAI:Unrolling the Codex agent loop https://openai.com/index/unrolling-the-codex-agent-loop/
OpenAI Cookbook:Prompt Caching 101 https://cookbook.openai.com/examples/prompt_caching101
云厂商与工程文档
阿里云百炼 Model Studio:上下文缓存 Context Cache https://help.aliyun.com/zh/model-studio/context-cache
阿里云开发者社区:Transformer 的核心:自注意力机制 https://developer.aliyun.com/article/1688141
腾讯云开发者社区:Transformer 的自注意力机制详细解析 https://cloud.tencent.com/developer/article/2648495
Transformers 快速入门:注意力机制 https://transformers.run/c1/attention/
用户提供的社区资料
shareAI-lab / mini-claude-code:上下文缓存经济学 https://github.com/shareAI-lab/mini-claude-code/blob/main/articles/上下文缓存经济学.md
进一步阅读
Prompt Cache: Modular Attention Reuse for Low-Latency Inference https://arxiv.org/abs/2311.04934
Auditing Prompt Caching in Language Model APIs https://arxiv.org/abs/2502.07776
Don't Break the Cache: An Evaluation of Prompt Caching for Long-Horizon Agentic Tasks https://arxiv.org/abs/2601.06007