一、一句话总览
- 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(书的实际内容):每本书里真正的内容
流程:
- 用你的 Q 去和所有书的 K 做匹配(计算相似度)
- 找到最相关的几本书
- 把这些书的 V 按相关程度加权汇总,得到最终信息
3. 在 Transformer 中具体怎么算
① 投影生成 Q、K、V
输入 embedding 向量 x,经过三个不同 的可学习权重矩阵 W_Q、W_K、W_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 待命。