提示缓存:10倍更便宜的 LLM Token,原理是什么?
- Published on
目录
- 引言
- LLM 架构速览
- 分词:从文本到 Token
- 嵌入:从 Token 到向量
- 注意力机制:缓存发生的地方
- KV 缓存:真正的魔法
- 提示缓存:跨请求的 KV 缓存
- OpenAI vs Anthropic:不同的缓存策略
- 总结
- 参考资料
引言
使用大型语言模型 (LLM) 的 API 价格通常按 token 收费。每次你向 Claude、GPT-4 或其他模型发送请求时,都需要为输入 token(发送给模型的内容)和输出 token(模型返回的内容)付费。
但事情有个微妙之处。使用 API 时,你经常会发送包含大量重复内容的消息。也许你每次请求都附带了一个大型系统提示,或者你在对话进行中不断发送整个对话历史。无论哪种情况,你都在为多次处理相同的数据付费。
为了解决这个问题,LLM 提供商开发了缓存功能。OpenAI 提供提示缓存,Anthropic 则提供扩展缓存。两者都承诺为缓存的 token 提供高达 90% 的折扣以及更低的延迟。
这就引出了一个问题:LLM 究竟是如何利用缓存的?什么数据被缓存了?为什么它能让处理更便宜?
本文将探讨这些问题,并让你深入理解这个机制的工作原理。
LLM 架构速览
要理解提示缓存如何让 LLM 推理更快、更便宜,我们首先需要对 LLM 进行端到端的概览,考察 transformer 模型的各个步骤及其作用。我们可以将 LLM 分为四个阶段:
- 分词器 (Tokenizer):将输入文本(字符串)转换为 token ID(整数)列表
- 嵌入 (Embedding):将 token ID(整数)转换为向量(n 维空间中的浮点数列表)
- 变换器 (Transformer):接收嵌入向量列表,输出另一个嵌入向量列表
- 输出 (Output):将嵌入转回词汇表上的 token 概率
让我们逐一探索这些阶段,以便了解缓存实际发生的位置以及它如何提高性能。
分词:从文本到 Token
大语言模型并不直接处理我们读写的原始文本。相反,它们处理的是 token。token 可以是单个字符、词素或整个单词,这取决于模型所使用的分词器。
所有 token 被编译成一个 词汇表——模型知道的所有 token 的完整列表。词汇表通常有数万个条目,每个条目都有唯一的整数 ID。这个概念并不特别新奇:就像你从电子表格中导出数据时,可能会将类别列转换为类别 ID。
为了让你直观感受这个概念:
| Token | Token ID |
|---|---|
| The | 464 |
| cat | 2857 |
| sat | 8714 |
| on | 319 |
| the | 279 |
| mat | 5765 |
当句子 "The cat sat on the mat" 经过分词器处理后:
"The cat sat on the mat" → [464, 2857, 8714, 319, 279, 5765]
嵌入:从 Token 到向量
一旦我们有了 token ID 序列,下一步就是将每个 ID 转换为 嵌入向量。
嵌入是表示一段数据(在这里是一个 token)的实数向量。你可以把它想象成 token 语义含义的数学表示,捕获了模型所理解的关于这个 token 在语言中如何使用的一切。
更直白地说:嵌入只是一个数字列表。每个 token 都有自己的嵌入,嵌入包含相同数量的数字(即维度)。现代 LLM 通常使用大约 1536 到 8192 个维度。
例如,一个非常简化的 3 维嵌入可能是:
| Token | 嵌入 (3维示例) |
|---|---|
| cat | [0.12, -0.45, 0.78] |
| dog | [0.11, -0.42, 0.80] |
| mat | [0.85, 0.23, -0.12] |
注意 "cat" 和 "dog" 的嵌入相似——因为它们在语义上相关(都是动物)。"mat" 则完全不同。实际的嵌入要复杂得多,但原理相同。
到这个阶段,我们输入到 LLM 的句子从一个字符串变成了一个 嵌入矩阵:每行代表一个 token,列代表嵌入维度。
注意力机制:缓存发生的地方
这就是精华所在。Transformer 的核心是 自注意力机制 (Self-Attention),它允许模型中的每个 token 关注序列中的其他 token 并混合信息。这是 LLM 真正进行"思考"和"推理"的地方。
Q、K、V 矩阵
注意力机制使用三个关键概念:Query (查询)、Key (键) 和 Value (值)。
可以这样理解:
- Query (Q):代表"我在寻找什么信息?"
- Key (K):代表"我有什么信息可以提供?"
- Value (V):代表"如果你问我,这是我实际提供的信息"
对于输入中的每个 token 嵌入,我们通过与三个权重矩阵相乘来生成 Q、K、V 向量:
其中 是输入嵌入矩阵,、、 是模型在训练过程中学到的权重矩阵。
注意力分数计算
计算出 Q、K、V 后,注意力机制执行以下计算:
这个公式做了什么?
- :计算每个 query 与每个 key 的点积,得到注意力分数
- :缩放因子,防止分数过大
- softmax:将分数转换为概率分布(和为 1)
- 乘以 :用注意力权重加权求和所有 value
因果掩码
对于生成式 LLM(如 GPT、Claude),还有一个关键细节:因果掩码 (Causal Mask)。
在生成文本时,模型一次生成一个 token。关键规则是:每个 token 只能看到它之前的 token,不能"偷看"未来的 token。
为了实现这一点,在计算 后、应用 softmax 之前,我们添加一个下三角掩码,将未来位置的分数设为负无穷。负无穷经过 softmax 后变成 0,有效地阻止了信息从未来流向过去。
KV 缓存:真正的魔法
现在我们终于可以理解提示缓存的核心机制了。
为什么需要 KV 缓存?
考虑这样一个场景:你正在与 LLM 进行对话,每次发送新消息时都需要包含之前的对话历史。
假设对话历史有 1000 个 token,你新增了 10 个 token。传统方式下,模型需要:
- 对所有 1010 个 token 重新计算 Q、K、V 矩阵
- 计算完整的注意力分数
- 生成输出
但这里有个洞察:之前 1000 个 token 的 K 和 V 矩阵不会改变!
因为计算 K 和 V 只依赖于 token 本身和固定的权重矩阵。只要输入 token 相同,输出的 K、V 就相同。
KV 缓存如何工作
有了这个洞察,KV 缓存的思路就很清晰了:
- 首次处理提示时:计算并存储所有 token 的 K 和 V 矩阵
- 生成新 token 时:只为新 token 计算 K 和 V,然后将其追加到缓存中
- 计算注意力时:新 token 的 Q 与所有缓存的 K 计算注意力分数,然后用这些分数加权所有缓存的 V
这带来了巨大的计算节省:
- 无缓存:每生成一个新 token,都要重新处理所有之前的 token
- 有缓存:每生成一个新 token,只需处理那一个 token
复杂度从 降低到了 ,其中 是序列长度。
实际数据量
让我们计算一下缓存了多少数据。假设:
- 模型维度 (常见于 GPT-4 级别模型)
- 32 层
- 32 个注意力头
- 每个头维度
对于 1024 个 token 的缓存:
- 每个 token:K 和 V 各 4096 个浮点数 × 32 层 = 262,144 个浮点数
- 1024 个 token:约 2.68 亿个浮点数
- 以 FP16 存储:约 512 MB
这就是为什么 LLM 推理需要大量 GPU 显存——缓存本身就占用了相当大的空间。
提示缓存:跨请求的 KV 缓存
标准的 KV 缓存只在单次请求内有效。但 LLM 提供商更进一步:跨请求持久化缓存。
这就是 OpenAI 和 Anthropic 的"提示缓存"功能。
工作原理
- 缓存键:系统使用提示内容的哈希值作为缓存键
- 缓存匹配:新请求到来时,检查提示前缀是否与缓存匹配
- 缓存命中:如果匹配,直接使用缓存的 K、V 矩阵,跳过计算
- 缓存失效:经过一段时间后缓存自动失效(通常 5-10 分钟)
为什么能省钱?
计算 K、V 矩阵是 LLM 推理中最昂贵的部分之一。当你的请求命中缓存时:
- 减少计算:GPU 不需要为缓存的 token 进行矩阵运算
- 降低延迟:直接使用预计算的结果
- 节省成本:提供商将节省的成本以折扣形式传递给用户
这就是为什么缓存的 token 可以便宜 50-90%。
OpenAI vs Anthropic:不同的缓存策略
虽然底层原理相同,但两家公司的实现策略有显著差异。
OpenAI:自动缓存
OpenAI 的提示缓存是 完全自动 的:
- 自动检测可缓存的提示前缀
- 对开发者完全透明
- 缓存持续约 5-10 分钟
- 最小缓存单位:1024 tokens
- 缓存命中时享受 50% 折扣
# OpenAI 无需任何改动,缓存自动工作
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": very_long_system_prompt},
{"role": "user", "content": user_message}
]
)
Anthropic:显式控制
Anthropic 让开发者 显式标记 要缓存的内容:
- 使用
cache_control参数指定缓存点 - 最多可设置 4 个缓存断点
- 更细粒度的控制
- 缓存命中时享受 90% 折扣
- 写入缓存时有额外费用(约 25%)
# Anthropic 需要显式标记缓存
response = client.messages.create(
model="claude-sonnet-4-20250514",
system=[
{
"type": "text",
"text": very_long_system_prompt,
"cache_control": {"type": "ephemeral"} # 显式缓存
}
],
messages=[{"role": "user", "content": user_message}]
)
如何选择?
| 场景 | OpenAI 方案 | Anthropic 方案 |
|---|---|---|
| 简单用例 | 零配置,自动生效 | 需要手动添加缓存标记 |
| 大型系统提示 | 自动缓存前缀 | 可精确控制缓存边界 |
| 成本优化 | 50% 折扣,较简单 | 90% 折扣,但有写入成本 |
| 复杂工作流 | 控制较少 | 更灵活的缓存策略 |
总结
提示缓存的核心原理可以总结为:
- LLM 推理的瓶颈:计算注意力机制中的 K、V 矩阵非常昂贵
- KV 缓存的洞察:对于相同的输入 token,K、V 矩阵的计算结果相同
- 跨请求持久化:将 KV 缓存扩展到多个 API 请求之间共享
- 成本节省:跳过重复计算 = 更少的 GPU 时间 = 更低的价格
理解这个机制后,你可以:
- 优化提示结构:将静态内容放在前面,动态内容放在后面
- 合理使用系统提示:大型系统提示是缓存的理想候选
- 选择合适的提供商:根据你的需求选择自动或手动缓存策略
下次当你看到 API 账单上的"缓存 token"折扣时,你就知道背后发生了什么了。
参考资料
- Prompt caching: 10x cheaper LLM tokens, but how? - Sam Rose, ngrok
- OpenAI Prompt Caching Guide
- Anthropic Prompt Caching Documentation
- Attention Is All You Need - Vaswani et al.