Skip to content

大模型缓存机制与 Codex 缓存命中率说明

你可能已经注意到,使用 Codex、Claude Code 这类 AI 编程智能体时,同样的 prompt 第二次发出去往往比第一次快很多,成本也低不少。这不是巧合——背后是一整套缓存机制在起作用。

但这些缓存机制并不是凭空出现的。它们深深根植于 Transformer 架构的推理方式。要真正理解 KV Cache 和 Prompt Cache 为什么有效、什么条件下会失效,我们需要从神经网络处理序列的历史讲起。

本文面向工程实践,沿着一条完整的技术演进线展开:

text
RNN → CNN → 注意力机制 → 自注意力 → Transformer 架构 → Prefill/Decode → KV Cache → Prompt Cache → Codex 缓存命中率

每个节点重点回答三个问题:

  1. 它是什么?
  2. 为什么要这么做?
  3. 有什么用,如何计算?

适合读者:后端工程师、AI 应用开发者、对大模型推理成本感兴趣的技术人。不需要深度学习背景,有编程基础即可阅读。

一、先从 RNN、CNN、Attention 说起

在 Transformer 出现之前,神经网络处理序列数据主要靠 RNN 和 CNN。它们各有优势,但也各有瓶颈。理解这些瓶颈,才能理解为什么注意力机制和 Transformer 会被发明出来。

1.1 RNN 是什么

RNN,全称 Recurrent Neural Network,循环神经网络。它的核心思想是:按时间顺序一个 token 一个 token 地处理序列,并把前面的信息压缩进一个隐藏状态。

对于输入序列:

text
x1, x2, x3, ..., xt

RNN 每一步都会更新隐藏状态:

text
h_t = f(W_x x_t + W_h h_{t-1} + b)

其中:

text
x_t:当前输入
h_t:当前隐藏状态
h_{t-1}:上一步隐藏状态
W_x、W_h、b:模型参数
f:非线性函数,例如 tanh、ReLU

你可以把 RNN 理解成一个边读边记笔记的人:

text
读第 1 个词 -> 更新笔记
读第 2 个词 -> 基于旧笔记更新新笔记
读第 3 个词 -> 继续更新

RNN 的优点是天然适合序列。它的问题是:

text
1. 难并行:必须先算 h_{t-1},才能算 h_t。
2. 长距离依赖弱:很早的信息被反复压缩,容易丢失。
3. 训练不稳定:长序列上容易梯度消失或梯度爆炸。

虽然 LSTM、GRU 对 RNN 做了改进,但序列处理仍然强依赖时间步,难以充分利用 GPU 的并行能力。

1.2 CNN 是什么

CNN,全称 Convolutional Neural Network,卷积神经网络。它最早在图像领域非常成功,也可以用于文本。

CNN 的核心思想是:用一个小窗口扫描局部区域,提取局部模式。

在图像里,卷积核可能识别:

text
边缘
纹理
角点
局部形状

在文本里,卷积核可以识别:

text
短语
n-gram 模式
局部语法结构

一维文本卷积可以粗略表示为:

text
y_i = f(W · x_{i:i+k-1} + b)

其中:

text
x_{i:i+k-1}:从第 i 个 token 开始的 k 个 token 窗口
W:卷积核参数
y_i:当前位置提取出的局部特征

CNN 的优点是:

text
1. 并行性好。
2. 局部特征提取强。
3. 计算稳定。

CNN 的问题是:

text
1. 默认只看局部窗口。
2. 想看远距离依赖,需要堆很多层或使用膨胀卷积。
3. 对"任意两个 token 的关系"表达不如注意力直接。

1.3 注意力机制是什么

注意力机制的核心思想是:处理当前 token 时,不是只依赖一个压缩隐藏状态,也不是只看固定窗口,而是让当前 token 主动决定应该关注上下文里的哪些 token。

例如:

text
Order order = orderService.getById(orderId);
return order.getStatus();

当模型处理 getStatus 时,它应该重点关注:

text
order
Order 类型
orderService.getById(orderId)

而不是平均看所有 token。

注意力机制通过 Q、K、V 三组向量完成这件事:

text
Q = Query:当前 token 想找什么
K = Key:每个 token 可以被怎样检索
V = Value:每个 token 真正提供什么信息

标准缩放点积注意力公式是:

text
Attention(Q, K, V) = softmax(QK^T / sqrt(d_k)) V

其中:

text
QK^T:计算 Query 和 Key 的相似度
sqrt(d_k):缩放因子,避免分数过大导致 softmax 过尖锐
softmax:把相似度转成权重分布
V:按权重汇总 Value 信息

直观理解:

text
当前 token 拿着自己的 Q 去问:
上下文里哪些 K 和我最相关?
相关度越高,对应 V 的贡献越大。

1.4 自注意力是什么

自注意力,Self-Attention,指的是同一段输入内部的 token 互相关注。

例如一句话:

text
小明把书放进书包,因为它太重了。

模型处理"它"时,需要判断"它"指的是"书"还是"书包"。自注意力允许"它"去关注前面的 token,并通过训练学会哪些关系更重要。

自注意力和 RNN 的关键区别是:

text
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)结构:

text
输入 tokens


┌─────────────────────┐
│     Encoder × N 层   │  ← 每层:Self-Attention + Feed-Forward
└─────────────────────┘


┌─────────────────────┐
│     Decoder × N 层   │  ← 每层:Masked Self-Attention + Cross-Attention + Feed-Forward
└─────────────────────┘


输出 tokens(一个一个生成)

编码器负责理解输入:把一串 token 编码成一组富含上下文信息的向量。

解码器负责生成输出:逐 token 生成,每一步都关注编码器的输出和已生成的历史。

后来的大语言模型对这个结构做了各种取舍:

模型类型结构代表模型
仅编码器只用 EncoderBERT、RoBERTa
仅解码器只用 DecoderGPT 系列、LLaMA、Claude、Codex
编码器-解码器完整结构T5、BART

当前主流大语言模型(包括驱动 Codex 的模型)基本都是仅解码器结构。这意味着自注意力只需要关注当前 token 及其之前的历史(因果注意力),不需要看未来 token。

每一层做了什么

以一个 Decoder 层为例,数据流是这样的:

text
输入向量


┌─────────────────────────┐
│  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 上:

text
PE(pos, 2i)   = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))

现代大模型更多使用旋转位置编码(RoPE)可学习位置编码,但核心目的不变:让模型知道每个 token 在序列中的位置。

没有位置编码,Transformer 会把"猫吃鱼"和"鱼吃猫"当作同样的意思。

多头注意力

上一小节提到的多头注意力,在 Transformer 中是标准配置。原始论文使用 8 个头:

text
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 的结构,它的优势就很清楚了:

text
1. 并行性好:训练时可以同时处理整个序列,不需要像 RNN 那样逐步递归。
2. 长距离依赖强:token 之间可以直接建立注意力关系,不需要像 CNN 那样堆层。
3. 可扩展性好:扩大层数、宽度、数据量后效果持续提升(Scaling Law)。
4. 架构通用:文本、代码、图像、多模态都可以转成 token 序列处理。

但 Transformer 也有代价:注意力计算会随着上下文长度增长而变贵。

对于长度为 n 的序列,全量自注意力需要计算每个 token 和每个 token 的关系,复杂度近似:

text
O(n²)

这就是为什么长上下文很贵,也解释了为什么 KV Cache 和 Prompt Cache 很重要——它们都是为了对抗这个 O(n²) 的代价。

二、Transformer 推理分成 Prefill 和 Decode

理解了 Transformer 的架构,接下来看它在推理时是怎么工作的。大模型生成答案并不是一步到位,而是分成两个阶段,每个阶段有不同的计算特征,也对应不同的缓存策略。

大模型生成答案通常可以分成两个阶段:

text
1. Prefill:读取输入 prompt。
2. Decode:一个 token 一个 token 生成输出。

2.1 Prefill 阶段

Prefill 阶段处理完整输入,例如:

text
系统规则
开发者规则
AGENTS.md
工具定义
用户问题
文件内容

如果输入长度为 n,模型需要对这些 token 计算隐藏状态、Q/K/V、注意力关系等。全量注意力的计算量和 n 的平方相关:

text
Prefill attention cost ≈ O(n^2)

例如 100k token 的 prompt,比 10k token 的 prompt 贵得多,不只是线性增加。

2.2 Decode 阶段

Decode 阶段逐个生成新 token。

例如模型已经读完 prompt,并生成到第 t 个 token:

text
已存在 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:

text
Q = X W_Q
K = X W_K
V = X W_V

KV Cache 保存的是历史 token 的:

text
K_1, K_2, ..., K_t
V_1, V_2, ..., V_t

对于多层、多头注意力,真实缓存结构一般类似:

text
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:

text
token_1, token_2, ..., token_999

模型已经缓存了:

text
K_1...K_999
V_1...V_999

现在生成第 1000 个 token 时:

text
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:

text
每生成一个新 token,都重新计算完整历史序列的 K/V。

有 KV Cache:

text
历史 token 的 K/V 已经算过并保存。
新 token 只需要计算自己的 Q/K/V。

所以 KV Cache 主要加速 Decode 阶段。

没有 KV Cache 时,生成 m 个 token,可能反复处理越来越长的上下文:

text
第 1 步:处理 n
第 2 步:处理 n + 1
第 3 步:处理 n + 2
...

有 KV Cache 后,每步主要新增当前 token 的计算,并对历史 K/V 做查询:

text
第 1 步:新增 1 个 token 的 Q/K/V
第 2 步:新增 1 个 token 的 Q/K/V
第 3 步:新增 1 个 token 的 Q/K/V
...

3.4 KV Cache 复杂度怎么理解

对单次全量注意力,长度为 n 时:

text
Attention cost ≈ O(n^2)

对 decode 阶段的每一步,如果使用 KV Cache,当前 token 的 Q 需要和历史 n 个 K 做匹配:

text
Per-token decode attention cost ≈ O(n)

如果生成 m 个 token,总 decode 注意力成本近似:

text
O(mn)

其中 n 会随着生成增长,但不会像"每一步重新跑完整历史"那样重复计算历史 K/V。

3.5 KV Cache 是否节省显存

通常不是。

KV Cache 的典型代价是:

text
用显存换速度。

因为缓存需要保存每层、每个头、每个历史 token 的 K/V。上下文越长,KV Cache 越大。

一个粗略显存估算公式:

text
KV Cache bytes
≈ 2 × num_layers × batch_size × seq_len × num_kv_heads × head_dim × bytes_per_element

其中:

text
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

示例:

text
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 本身,不包括模型权重、激活、运行时开销。因此长上下文推理对显存压力很大。

工程上常见优化包括:

text
Paged Attention
KV Cache 量化
Sliding Window Attention
Grouped Query Attention / Multi Query Attention
KV Cache Offload
Prefix Cache
上下文压缩

3.6 KV Cache 有什么用

KV Cache 的主要作用:

text
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 缓存的是一段输入前缀被模型处理后的内部计算结果。服务商通常不会暴露具体张量,但可以理解为复用了这段前缀对应的注意力状态或等价计算。

例如:

text
请求 1:
[系统规则 + 工具定义 + 代码仓库内容] + 问题 A

请求 2:
[系统规则 + 工具定义 + 代码仓库内容] + 问题 B

如果前面的公共前缀完全一致,第二次请求可以复用第一次已经处理过的前缀计算。

4.2 Prompt Cache 和 KV Cache 的区别

对比项KV CachePrompt 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 复用的是前缀计算,因此最容易命中的形式是:

text
请求 1:
A B C D E + X

请求 2:
A B C D E + Y

其中 A B C D E 是完全相同的前缀。

如果中间插入或修改了内容:

text
请求 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 的主要作用:

text
1. 降低输入 token 成本。
2. 降低长 prompt 的处理延迟。
3. 提高 Agent 多轮工具调用的效率。
4. 让长代码库、长文档、多轮对话场景更经济。

它解决的是"多个请求反复发送同一大段上下文"的问题。

4.5 OpenAI Prompt Caching 的关键点

根据 OpenAI 官方 Prompt Caching 文档和 API 公告:

text
1. Prompt Caching 自动启用,不需要额外代码打开。
2. 缓存命中依赖 prompt 内的精确前缀匹配。
3. 静态内容应该放在 prompt 开头,变量内容放在结尾。
4. API 响应中可以通过 usage.prompt_tokens_details.cached_tokens 查看命中 token 数。
5. 缓存通常会在短时间不活跃后清理,并且有最长保留时间限制。
6. 缓存不改变模型输出语义,只影响延迟和成本。

一个典型 usage 结构类似:

json
{
  "usage": {
    "prompt_tokens": 2006,
    "completion_tokens": 300,
    "total_tokens": 2306,
    "prompt_tokens_details": {
      "cached_tokens": 1920
    }
  }
}

这里表示:

text
输入 token 总数:2006
其中命中缓存:1920
未命中输入:86
输出 token:300

4.6 阿里云 Context Cache 的关键点

阿里云 Model Studio 文档把 Context Cache 分为两类:

text
1. 显式缓存:开发者主动标记 cache_control,确定性更强。
2. 隐式缓存:系统自动识别公共前缀,无需配置,但命中率不确定。

文档中的典型设计是:

text
系统人设:高度稳定
外部知识:半稳定
对话历史:动态增长
当前问题:每次不同

如果把整段 prompt 作为一个整体缓存,任何中间变化都可能导致缓存失效。因此显式缓存允许使用多个缓存标记,把不同稳定性的片段分开管理。

这和 Agent 设计高度相关:稳定内容应该尽量固定在前面,动态内容追加到后面。

五、Codex 这种智能体如何组织上下文

有了 KV Cache 和 Prompt Cache 的概念基础,我们可以看一个具体场景:Codex 这类编程智能体是怎么利用这些缓存机制的。理解它的上下文组织方式,才能看懂缓存命中率的数字背后到底在说什么。

Codex 这类软件工程 Agent 的工作方式不是"一问一答"那么简单。它会循环执行:

text
用户输入
模型推理
工具调用
读取文件或执行命令
把工具结果追加进上下文
再次调用模型
继续工具调用或输出最终答复

OpenAI 的 Codex agent loop 文章说明,Codex 会把系统/开发者指令、工具定义、AGENTS.md、环境信息、用户消息、工具调用结果等组织成请求发送给 Responses API。工具结果会追加回 input,下一次请求继续使用。

5.1 Codex 请求里通常有什么

一个 Codex 请求可以粗略拆成:

text
系统消息:
  模型基础行为、平台级规则

开发者消息:
  Codex 行为规范、工具使用规范、沙箱权限说明

项目规则:
  AGENTS.md、仓库约定、用户配置

工具定义:
  shell、apply_patch、MCP 工具、web 工具等 schema

环境信息:
  cwd、shell、sandbox、approval mode

会话历史:
  用户消息、助手消息、工具调用、工具输出

当前任务:
  用户最新输入

这些内容里,有些很稳定,有些频繁变化。

稳定内容:

text
系统规则
开发者规则
AGENTS.md
工具定义
项目约定
沙箱配置
当前工作目录

变化内容:

text
最新用户问题
工具输出
测试日志
文件 diff
错误堆栈
任务计划变化

Prompt Cache 的命中率,主要取决于稳定内容是否形成完全相同的前缀。

5.2 Codex 为什么喜欢 append-only

在 Agent 循环中,一个关键设计是:尽量追加新消息,而不是修改旧消息。

例如:

text
请求 1:
[固定前缀][用户任务][工具调用 A]

请求 2:
[固定前缀][用户任务][工具调用 A][工具结果 A][工具调用 B]

请求 1 的全部内容是请求 2 的前缀,因此请求 2 有机会大量命中缓存。

如果中途修改旧内容:

text
请求 1:
[固定前缀][用户任务][工具调用 A]

请求 2:
[固定前缀被改写][用户任务][工具调用 A][工具结果 A]

那么从改写位置开始,缓存就会被破坏。

所以 Codex 类 Agent 的高效上下文管理原则是:

text
能追加就追加。
不要频繁改写历史。
不要改变工具顺序。
不要中途改变模型、sandbox、cwd、工具定义。
变量内容尽量靠后。

OpenAI Codex agent loop 文章也指出,旧 prompt 成为新 prompt 的精确前缀是有意为之,因为这能利用 prompt caching 提升效率。

5.3 Codex 的缓存命中率是什么意思

Codex 的缓存命中率可以理解为:

text
本次请求输入 token 中,有多少 token 命中了服务端 Prompt Cache。

公式:

text
cache_hit_rate = cached_input_tokens / total_input_tokens

如果使用 OpenAI API 的 usage 字段:

text
cache_hit_rate
= usage.prompt_tokens_details.cached_tokens / usage.prompt_tokens

如果是 Responses API,字段名可能表现为 input token details,但含义类似:

text
cached_tokens / input_tokens

注意:

text
缓存命中率不是答案复用率。
缓存命中率不是正确率。
缓存命中率不是模型记忆能力。
缓存命中率只是输入计算复用比例。

5.4 Codex 缓存命中率案例

假设某次 Codex 请求输入如下:

text
系统/开发者规则:8,000 tokens
AGENTS.md 和项目规则:4,000 tokens
工具定义:10,000 tokens
历史工具结果和文件片段:60,000 tokens
用户最新问题:1,000 tokens

总输入:

text
83,000 tokens

如果其中前 72,000 tokens 与上一轮请求前缀完全一致:

text
cached_input_tokens = 72,000
total_input_tokens = 83,000

缓存命中率:

text
cache_hit_rate = 72,000 / 83,000 ≈ 86.75%

这表示:

text
86.75% 的输入 token 对应的前缀计算可以复用。

不表示:

text
86.75% 的答案是旧答案。
86.75% 的推理不用做。
86.75% 的上下文被模型永久记住。

5.5 Codex 成本如何估算

如果模型价格为:

text
普通输入单价:P_input
缓存输入单价:P_cached
输出单价:P_output

一次请求:

text
total_input_tokens = T_in
cached_input_tokens = T_cached
output_tokens = T_out

未缓存输入:

text
T_uncached = T_in - T_cached

成本估算:

text
cost
= (T_uncached / 1,000,000) × P_input
+ (T_cached / 1,000,000) × P_cached
+ (T_out / 1,000,000) × P_output

示例:

text
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

则:

text
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

如果没有缓存:

text
input_cost = 100,000 / 1,000,000 × 5 = $0.50
output_cost = $0.15
total_cost = $0.65

在这个例子里,Prompt Cache 把单次请求成本从:

text
$0.65 降到 $0.29

节省比例:

text
($0.65 - $0.29) / $0.65 ≈ 55.38%

实际节省比例取决于:

text
1. 输入 token 占总成本的比例。
2. cached input 的折扣幅度。
3. 缓存命中率。
4. 输出 token 是否很多。

如果输出非常长,缓存只能节省输入部分,整体节省比例会下降。

六、缓存命中率如何计算和观察

6.1 通用公式

最常用的缓存命中率公式:

text
缓存命中率 = 命中缓存的输入 token 数 / 总输入 token 数

英文:

text
cache hit rate = cached input tokens / total input tokens

如果 API 返回:

json
{
  "prompt_tokens": 50000,
  "prompt_tokens_details": {
    "cached_tokens": 42000
  }
}

则:

text
cache_hit_rate = 42000 / 50000 = 84%

6.2 更细的拆分

输入 token 可以拆成:

text
total_input_tokens
= cached_input_tokens
+ uncached_input_tokens

所以:

text
uncached_input_tokens = total_input_tokens - cached_input_tokens

未命中率:

text
cache_miss_rate = 1 - cache_hit_rate

或者:

text
cache_miss_rate = uncached_input_tokens / total_input_tokens

6.3 为什么同样的内容有时不命中

常见原因:

text
1. 前缀不是完全一致。
2. 内容位置变了。
3. 工具定义顺序变了。
4. 模型变了。
5. system/developer 指令变了。
6. 沙箱、cwd、环境上下文变了。
7. 缓存过期。
8. 请求被路由到无法复用缓存的后端。
9. 低于服务商缓存阈值。
10. 图片、工具、结构化内容虽然看似一样,但序列化后不一致。

6.4 为什么"摘要压缩"可能降低缓存命中

摘要压缩可以节省上下文窗口,但会破坏已有前缀。

例如:

text
压缩前:
[A][B][C][D][E][最新问题]

压缩后:
[Summary(A-E)][最新问题]

压缩后的 prompt 更短,但它不再以原来的 [A][B][C][D][E] 开头。因此旧缓存不能直接复用。

这就是 Agent 上下文管理的核心权衡:

text
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 结果否或部分应用层减少检索和向量计算

不要把它们混为一谈。

例如:

text
Prompt Cache 命中:
模型仍然会生成新答案,只是输入前缀处理更便宜更快。

HTTP 缓存命中:
可能直接返回旧答案,模型甚至不运行。

八、工程实践:如何提高 Codex / Agent 的缓存命中率

理论讲完了,回到工程层面:作为 Agent 框架的开发者或使用者,有哪些具体手段可以提高缓存命中率?以下是最实用的几条原则。

8.1 稳定内容放前面

推荐顺序:

text
系统规则
开发者规则
工具定义
项目规则
AGENTS.md
长期任务约束
稳定参考资料
会话历史
工具结果
当前用户问题

不推荐:

text
当前时间
随机 ID
最新用户问题
动态检索结果
系统规则
工具定义
项目规则

因为动态内容放在前面会破坏后面所有缓存。

8.2 保持工具定义稳定

工具定义通常很长,适合缓存。但它们也很容易因为顺序变化导致缓存 miss。

建议:

text
1. 工具列表排序稳定。
2. 不要在会话中频繁增删工具。
3. 工具 schema 不要插入动态描述。
4. MCP 工具变化时,尽量追加说明,而不是改写前面的工具上下文。

OpenAI Codex agent loop 文章提到,MCP 工具列表顺序不稳定曾导致 cache miss,这说明"序列化稳定性"对 Agent 成本非常重要。

8.3 变量内容放后面

变量内容包括:

text
用户最新输入
当前错误日志
测试输出
时间戳
请求 ID
临时文件路径
最新 diff

这些内容应该尽量放在 prompt 后部。

8.4 避免中途改写历史

缓存友好的方式:

text
[旧上下文][新增消息]

缓存不友好的方式:

text
[重写后的旧上下文][新增消息]

这也是为什么"append-only log"是 Agent 上下文设计中的重要原则。

8.5 大文件谨慎放入上下文

大文件可以提高模型理解能力,但也会增加输入成本和上下文压力。

建议:

text
1. 只放任务相关片段。
2. 多次复用的长文档放在稳定前缀。
3. 一次性临时日志放后面。
4. 不要每轮重新组织同一批文件内容顺序。

8.6 不要为了缓存牺牲正确性

缓存优化是成本和延迟优化,不是第一目标。Agent 首先要正确完成任务。

如果为了保持缓存而不更新过期信息,反而会导致错误决策。

优先级应该是:

text
正确性 > 安全性 > 可维护性 > 延迟/成本

九、常见误区

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 服务。

第一轮请求:

text
[系统规则 5k]
[开发者规则 8k]
[工具定义 12k]
[AGENTS.md 4k]
[环境信息 1k]
[用户任务 1k]

总计:

text
31k tokens

这一轮没有历史缓存,可能 cached_tokens = 0

Codex 读取 README、pom.xml、Controller、Service 后,第二轮请求:

text
[系统规则 5k]
[开发者规则 8k]
[工具定义 12k]
[AGENTS.md 4k]
[环境信息 1k]
[用户任务 1k]
[工具调用和文件内容 40k]

总计:

text
71k tokens

如果前 31k 完全一致,第二轮可能:

text
cached_tokens = 31k
cache_hit_rate = 31 / 71 ≈ 43.66%

第三轮,Codex 运行测试,追加测试结果:

text
[前一轮完整 71k]
[测试输出 5k]

总计:

text
76k tokens

如果前一轮 71k 是第三轮的精确前缀:

text
cached_tokens = 71k
cache_hit_rate = 71 / 76 ≈ 93.42%

这就是 Agent 场景中缓存命中率可能越来越高的原因:每轮都在旧上下文后面追加新内容,旧上下文成为新请求的前缀。

但如果第三轮中途改变了工具定义顺序或改写了 AGENTS.md 位置,可能导致:

text
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 官方资料

  1. OpenAI:Prompt caching https://platform.openai.com/docs/guides/prompt-caching

  2. OpenAI:Prompt Caching in the API https://openai.com/index/api-prompt-caching/

  3. OpenAI:Unrolling the Codex agent loop https://openai.com/index/unrolling-the-codex-agent-loop/

  4. OpenAI Cookbook:Prompt Caching 101 https://cookbook.openai.com/examples/prompt_caching101

云厂商与工程文档

  1. 阿里云百炼 Model Studio:上下文缓存 Context Cache https://help.aliyun.com/zh/model-studio/context-cache

  2. 阿里云开发者社区:Transformer 的核心:自注意力机制 https://developer.aliyun.com/article/1688141

  3. 腾讯云开发者社区:Transformer 的自注意力机制详细解析 https://cloud.tencent.com/developer/article/2648495

  4. Transformers 快速入门:注意力机制 https://transformers.run/c1/attention/

用户提供的社区资料

  1. shareAI-lab / mini-claude-code:上下文缓存经济学 https://github.com/shareAI-lab/mini-claude-code/blob/main/articles/上下文缓存经济学.md

  2. 知乎文章 https://zhuanlan.zhihu.com/p/705149429

  3. 知乎文章 https://zhuanlan.zhihu.com/p/17239625983

  4. 掘金文章 https://juejin.cn/post/7325130156237586458

  5. 掘金文章 https://juejin.cn/post/7616267492046946339

进一步阅读

  1. Prompt Cache: Modular Attention Reuse for Low-Latency Inference https://arxiv.org/abs/2311.04934

  2. Auditing Prompt Caching in Language Model APIs https://arxiv.org/abs/2502.07776

  3. Don't Break the Cache: An Evaluation of Prompt Caching for Long-Horizon Agentic Tasks https://arxiv.org/abs/2601.06007

Last updated: