logo

提示缓存:10倍更便宜的 LLM Token,原理是什么?

Published on

目录

引言

使用大型语言模型 (LLM) 的 API 价格通常按 token 收费。每次你向 Claude、GPT-4 或其他模型发送请求时,都需要为输入 token(发送给模型的内容)和输出 token(模型返回的内容)付费。

但事情有个微妙之处。使用 API 时,你经常会发送包含大量重复内容的消息。也许你每次请求都附带了一个大型系统提示,或者你在对话进行中不断发送整个对话历史。无论哪种情况,你都在为多次处理相同的数据付费。

为了解决这个问题,LLM 提供商开发了缓存功能。OpenAI 提供提示缓存,Anthropic 则提供扩展缓存。两者都承诺为缓存的 token 提供高达 90% 的折扣以及更低的延迟。

这就引出了一个问题:LLM 究竟是如何利用缓存的?什么数据被缓存了?为什么它能让处理更便宜?

本文将探讨这些问题,并让你深入理解这个机制的工作原理。

LLM 架构速览

要理解提示缓存如何让 LLM 推理更快、更便宜,我们首先需要对 LLM 进行端到端的概览,考察 transformer 模型的各个步骤及其作用。我们可以将 LLM 分为四个阶段:

  1. 分词器 (Tokenizer):将输入文本(字符串)转换为 token ID(整数)列表
  2. 嵌入 (Embedding):将 token ID(整数)转换为向量(n 维空间中的浮点数列表)
  3. 变换器 (Transformer):接收嵌入向量列表,输出另一个嵌入向量列表
  4. 输出 (Output):将嵌入转回词汇表上的 token 概率

让我们逐一探索这些阶段,以便了解缓存实际发生的位置以及它如何提高性能。

分词:从文本到 Token

大语言模型并不直接处理我们读写的原始文本。相反,它们处理的是 token。token 可以是单个字符、词素或整个单词,这取决于模型所使用的分词器。

所有 token 被编译成一个 词汇表——模型知道的所有 token 的完整列表。词汇表通常有数万个条目,每个条目都有唯一的整数 ID。这个概念并不特别新奇:就像你从电子表格中导出数据时,可能会将类别列转换为类别 ID。

为了让你直观感受这个概念:

TokenToken ID
The464
cat2857
sat8714
on319
the279
mat5765

当句子 "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=XWQQ = X \cdot W_Q K=XWKK = X \cdot W_K V=XWVV = X \cdot W_V

其中 XX 是输入嵌入矩阵,WQW_QWKW_KWVW_V 是模型在训练过程中学到的权重矩阵。

注意力分数计算

计算出 Q、K、V 后,注意力机制执行以下计算:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right) V

这个公式做了什么?

  1. QKTQK^T:计算每个 query 与每个 key 的点积,得到注意力分数
  2. dk\sqrt{d_k}:缩放因子,防止分数过大
  3. softmax:将分数转换为概率分布(和为 1)
  4. 乘以 VV:用注意力权重加权求和所有 value

因果掩码

对于生成式 LLM(如 GPT、Claude),还有一个关键细节:因果掩码 (Causal Mask)

在生成文本时,模型一次生成一个 token。关键规则是:每个 token 只能看到它之前的 token,不能"偷看"未来的 token。

为了实现这一点,在计算 QKTQK^T 后、应用 softmax 之前,我们添加一个下三角掩码,将未来位置的分数设为负无穷。负无穷经过 softmax 后变成 0,有效地阻止了信息从未来流向过去。

KV 缓存:真正的魔法

现在我们终于可以理解提示缓存的核心机制了。

为什么需要 KV 缓存?

考虑这样一个场景:你正在与 LLM 进行对话,每次发送新消息时都需要包含之前的对话历史。

假设对话历史有 1000 个 token,你新增了 10 个 token。传统方式下,模型需要:

  1. 对所有 1010 个 token 重新计算 Q、K、V 矩阵
  2. 计算完整的注意力分数
  3. 生成输出

但这里有个洞察:之前 1000 个 token 的 K 和 V 矩阵不会改变

因为计算 K 和 V 只依赖于 token 本身和固定的权重矩阵。只要输入 token 相同,输出的 K、V 就相同。

KV 缓存如何工作

有了这个洞察,KV 缓存的思路就很清晰了:

  1. 首次处理提示时:计算并存储所有 token 的 K 和 V 矩阵
  2. 生成新 token 时:只为新 token 计算 K 和 V,然后将其追加到缓存中
  3. 计算注意力时:新 token 的 Q 与所有缓存的 K 计算注意力分数,然后用这些分数加权所有缓存的 V

这带来了巨大的计算节省:

  • 无缓存:每生成一个新 token,都要重新处理所有之前的 token
  • 有缓存:每生成一个新 token,只需处理那一个 token

复杂度从 O(n2)O(n^2) 降低到了 O(n)O(n),其中 nn 是序列长度。

实际数据量

让我们计算一下缓存了多少数据。假设:

  • 模型维度 d=4096d = 4096(常见于 GPT-4 级别模型)
  • 32 层
  • 32 个注意力头
  • 每个头维度 dk=128d_k = 128

对于 1024 个 token 的缓存:

  • 每个 token:K 和 V 各 4096 个浮点数 × 32 层 = 262,144 个浮点数
  • 1024 个 token:约 2.68 亿个浮点数
  • 以 FP16 存储:约 512 MB

这就是为什么 LLM 推理需要大量 GPU 显存——缓存本身就占用了相当大的空间。

提示缓存:跨请求的 KV 缓存

标准的 KV 缓存只在单次请求内有效。但 LLM 提供商更进一步:跨请求持久化缓存

这就是 OpenAI 和 Anthropic 的"提示缓存"功能。

工作原理

  1. 缓存键:系统使用提示内容的哈希值作为缓存键
  2. 缓存匹配:新请求到来时,检查提示前缀是否与缓存匹配
  3. 缓存命中:如果匹配,直接使用缓存的 K、V 矩阵,跳过计算
  4. 缓存失效:经过一段时间后缓存自动失效(通常 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% 折扣,但有写入成本
复杂工作流控制较少更灵活的缓存策略

总结

提示缓存的核心原理可以总结为:

  1. LLM 推理的瓶颈:计算注意力机制中的 K、V 矩阵非常昂贵
  2. KV 缓存的洞察:对于相同的输入 token,K、V 矩阵的计算结果相同
  3. 跨请求持久化:将 KV 缓存扩展到多个 API 请求之间共享
  4. 成本节省:跳过重复计算 = 更少的 GPU 时间 = 更低的价格

理解这个机制后,你可以:

  • 优化提示结构:将静态内容放在前面,动态内容放在后面
  • 合理使用系统提示:大型系统提示是缓存的理想候选
  • 选择合适的提供商:根据你的需求选择自动或手动缓存策略

下次当你看到 API 账单上的"缓存 token"折扣时,你就知道背后发生了什么了。


参考资料