大模型KVCache和Prompt Cache

一、一句话总览

  • KVCache :推理引擎内部机制,让生成新 token 时不重算历史,加速单次请求
  • Prompt Cache :把每次请求都要带的那段固定开头(最典型就是系统提示词 )的处理结果缓存起来,下次同样的开头来了直接复用,省钱 + 加速
  • Q、K、V :注意力机制的三个向量------Q 是 Query(查询,当前 token 的"问题")K 是 Key(键,每个 token 的"标签/索引")V 是 Value(值,每个 token 的"内容/答案")。KVCache 缓存的正是 K 和 V,要理解它就必须先理解 QKV。

二、KVCache

1. 是什么

Transformer 自回归推理过程中,缓存已生成 token 的 Key 和 Value 矩阵,避免重复计算。

2. 为什么需要

生成第 n 个 token 时,需要让它关注前面 n-1 个 token:

  • 没有 KVCache:每步重算所有历史的 K、V → 复杂度 O(n²)
  • 有 KVCache:历史的 K、V 只算一次存起来反复用 → 复杂度 O(n)

推理速度提升数倍到数十倍,是 vLLM、TensorRT-LLM、TGI 等框架的默认标配


三、Prompt Cache(提示词缓存)

1. 一个具体场景:AI 客服的痛点

假设你做一个 AI 客服,每次调用 API 都要带上这样一段 system prompt:

text 复制代码
你是 XX 商城客服,负责回答退换货、运费、优惠等问题。
[产品介绍 1000 字]
[售后政策 1500 字]
[回复风格要求 500 字]
[示例对话 2000 字]
(合计 5000 字,每次一字不差)

用户每次问的问题都不同:

第几次 用户问 模型要"读"的内容
1 "怎么退货?" 5000 字 system + 5 字问题
2 "运费多少?" 5000 字 system + 5 字问题
3 "能开发票吗?" 5000 字 system + 6 字问题

问题 :5000 字 system prompt 一字不变,但每次都要让模型"重新读、重新消化一遍"。这 5000 字占了每次请求 99% 的处理量,是巨大的浪费。

Prompt Cache 做的事 :服务端把"模型第一次读完这 5000 字之后的状态"缓存 5~60 分钟。下次用户再发同样的 5000 字时,模型不用再从头读这 5000 字一遍,直接接着处理用户的新问题。

带来两个收益:

  • 省钱:这部分按"缓存价"计费,比正常价便宜很多(Anthropic 是正常价的 1/10)
  • 加速:跳过重新处理,首字响应延迟明显降低(长 system prompt 时效果最明显)

2. 一句话定义

Prompt Cache = 把每次都重复出现的那段 prompt(最常见就是 system prompt)的处理结果缓存起来,避免重复处理,跨多个请求都能命中。

3. 谁在做这件事?(不是模型能力)

很多人会误以为"大模型有了缓存能力",其实不是

  • 模型本身:完全不知道"缓存"这件事。每次调用都是按顺序处理 token,没有"我读过这个 prompt 了"的记忆
  • API 服务商 :在推理基础设施 里做的工程优化
    • 维护一套缓存系统
    • 每个请求来时检查 prompt 前缀是否匹配缓存
    • 匹配上就在内部跳过那部分的前向计算
  • 本质 :仍然是 KVCache 的复用,只是从"一次请求内"扩展到"跨请求",加上了 TTL、命中率统计、按缓存价计费这些工程层

开发者怎么用

  • Anthropic :API 加 cache_control 字段显式标记哪些段要缓存
  • OpenAI :自动开启,开发者不用管
  • 自部署开源模型(vLLM 等) :推理引擎也支持 prefix caching,但默认只对单次请求内有效;想跨请求需要额外配置

4. 还有什么类似的场景?

只要是"prompt 前缀固定不变 + 后缀每次变化"的形态都能受益:

  • 系统提示词(System Prompt):最典型,几乎所有应用都有
  • RAG 中的检索文档:同一批文档被反复检索出来
  • Agent 工具描述:工具列表通常几百到几千字
  • Few-shot 示例:固定的示例对话
  • 多轮对话中已经说过、不会改的前文

5. 典型服务商

  • Anthropic :Prompt Caching(用 cache_control 字段标记,命中后价格降到 1/10 + 延迟降低
  • OpenAI :自动启用,无需配置,命中后延迟降低(价格不变)
  • DeepSeek / Gemini:prefix cache(价格更低 + 延迟降低)

四、Q、K、V 详解

要理解 KVCache 缓存的"为什么",必须先理解 Q、K、V。

1. 字面含义

字母 英文 含义
Q Query 问题
K Key 标签 / 索引
V Value 内容

2. 图书馆类比

想象你走进图书馆找资料:

  • Q(你想找什么):你带着问题去,例如"我想了解 Transformer"
  • K(每本书的索引 / 标签):书架上每本书的书名、关键词、摘要
  • V(书的实际内容):每本书里真正的内容

流程:

  1. 用你的 Q 去和所有书的 K 做匹配(计算相似度)
  2. 找到最相关的几本书
  3. 把这些书的 V 按相关程度加权汇总,得到最终信息

3. 在 Transformer 中具体怎么算

① 投影生成 Q、K、V

输入 embedding 向量 x,经过三个不同 的可学习权重矩阵 W_QW_KW_V

复制代码
Q = x · W_Q
K = x · W_K
V = x · W_V

同一输入 x,拆出三个"视角不同"的向量,分别承担"提问、被检索、承载信息"三种职责,所以叫"自注意力"。

注意区分两个"输入"

  • x投影层的输入(即将被 W 变换的原始向量)
  • Q、K、V注意力机制的输入(投影后参与 Q·K 计算)

x 像原料,Q/K/V 是原料经过不同模具压出来的三种零件。

② 计算注意力分数

当前 token(位置 i)的 Q 与所有历史 token 的 K 做点积:

复制代码
score_i,j = Q_i · K_j

点积越大 → Q 和 K 越相似 → j 位置对 i 位置越重要。

③ 归一化(Softmax)

把所有 score 一起 softmax,变成和为 1 的概率分布:

复制代码
α_i,j = softmax(score_i,j / √d_k)
④ 加权求和 V

用注意力权重对所有 V 加权求和,得到当前 token 的输出:

复制代码
output_i = Σ α_i,j · V_j
完整公式

Attention(Q,K,V)=softmax ⁣(QK⊤dk)V \text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^{\top}}{\sqrt{d_k}}\right) V Attention(Q,K,V)=softmax(dk QK⊤)V

4. 例子

句子:" 坐在 垫子 上"

模型处理到"垫子"时:

  • "垫子"的 Q:"我(垫子)被什么坐?在哪里?"
  • "猫"的 K:"动物 / 主语"
  • "坐在"的 K:"动作 / 谓语"
  • "上"的 K:"方位 / 介词"

匹配结果:

  • Q("垫子") · K("猫") → 高分
  • Q("垫子") · K("坐在") → 较高
  • Q("垫子") · K("上") → 中等

最终"垫子"的输出 = 这些 V 的加权和,重点融合了"猫"和"坐在"的语义

5. 为什么拆成三套而不是一套?

  • 表达能力更强:Q、K、V 各学各的,能区分"匹配模式"和"内容模式"
  • 多头机制可行:每头有独立的 W_Q、W_K、W_V,捕捉不同维度(语法、语义、位置等)
  • 符合搜索本质:搜索天然就需要"查询"和"文档索引"是不同表示,混在一起会降低检索质量

五、Q、K、V 与 KVCache 的关系

理解了 Q、K、V,KVCache 的意义就彻底清楚了:

  • Q 不需要缓存:每步的 Q 只属于当前新 token,旧的 Q 永远用不上
  • K 和 V 必须缓存:每个历史位置都要为未来新 token 的 Q 时刻"待命"

一句话收尾

KVCache 缓存的是 K 和 V,不是 Q

因为 Q 每步一换,K 和 V 才是"历史的索引"和"历史的答案",必须为后续所有 Q 待命。