老张是位连载小说作家,每天都要顺着剧情一个字一个字地写下去。他的铁律是:每写一个新字,都必须和前面写过的所有字保持连贯。可老张记性不好——早先他写新字前,总要把整部手稿从头到尾重读一遍,才能想起「之前到底写了什么」。写到第 100 个字就重读 99 个字,写到第 1000 个字就重读 999 个字,小说越长,他越寸步难行。 后来老张学聪明了:每写下一个字,他就把它的「主题标签」和「具体内容」抄在一张卡片上,存进随身笔记本。写新字时,他只需要看一眼当前最想表达什么,再翻开笔记本匹配卡片,就能顺畅地写下去。笔记本虽然越来越厚,但他再也不用重读整部手稿。再后来,出版社建议他把人物按家族分组、每组共用一张卡片,书包又轻了一大截。 老张的「笔记本」,正是大模型推理加速最核心的机制——KV Cache。大模型生成文本时同样是一个字一个字地「接龙」,每写一个新 Token 都要参考前文。KV Cache 把历史 Token 的 Key 和 Value 缓存下来,避免每一步重复计算,把生成的总耗时从「立方级」拉回「平方级」;而「分组共享卡片」的思路,对应着 GQA / MQA 这些让 KV 显存成倍缩小的架构设计。理解 KV Cache,你就拿到了解读大模型推理引擎的第一把钥匙。
掌握 KV Cache,你就能看懂 LLM 推理「第一个字慢、后面逐字快」的现象,学会估算显存预算,并理解 GQA 等架构为何成为主流——这是大模型推理与部署工程师的必修课。
目录
可视化演示
展示KV Cache在自回归生成中缓存历史Token的K/V避免重复计算、显存随序列长度线性增长规律及MHA与GQA/MQA缓存结构差异,切换模式理解大模型推理优化核心。
本节配有交互动效(进入视口后自动加载,约 3KB)。
KV Cache自回归推理加速 自回归生成时缓存已计算Token的Key和Value矩阵,新Token仅需计算Q与缓存K/V做注意力 避免对历史Token重复计算K/V投影,推理从O(n²)降为O(n)每步,总加速约N倍 step_t: Q_t × [K_1..K_t]^T → cache K_t,V_t | O(n) per step KV Cache是大模型推理加速的核心机制,将重复计算转化为缓存查找
自回归生成原理:一个字一个字地接龙
老张写连载有个铁律:必须顺着情节,一个字一个字地往下写。他先写了「今、天、天、气、很、好」,才能接着写「我」——每写一个新字,他都要确认这个字与前面所有字都能对上。换句话说,第 t 个字的选择,取决于前 t-1 个字组成的全部上下文。这种「写一个字,参考全部前文」的生成方式,专业名称叫自回归生成(Autoregressive Generation)。
大模型做文本生成时,走的正是老张这条铁律。它一次只预测一个 Token(可以理解为一个子词或字),把这个 Token 拼到序列末尾,再以「更新后的完整序列」为输入,预测下一个 Token。如此循环,直到模型输出代表结束的 Token。
这个过程本质上是不断计算「下一个词的条件概率」:给定前文 x₁ 到 xₜ₋₁,预测 x_t 是什么。每一步,模型都要把全部前文重新编码一遍,才能给出答案。
麻烦的是:前文越长,这一遍「完整重读」就越昂贵。老张写第 t 个字要重读 t-1 个字,大模型在第 t 步也要重新计算前 t 个位置两两之间的注意力——单个步骤是 O(t²),写完整部 T 个字的连载,累计工作量高达 O(T³)。这就是「重复计算」的根源,也是 KV Cache 要解决的核心矛盾。
- 前文编码把已生成的前 t-1 个 Token 完整送入 Transformer,经过多层自注意力,得到序列末尾位置的隐藏状态。
- 映射打分用输出权重矩阵 W_o 把隐藏状态投影成词表大小的分数向量,分数越高表示越可能是下一个词。
- 概率归一Softmax 把所有分数变成非负且总和为 1 的概率,模型按概率采样或取最大值得到 x_t。
注意力计算回顾:老张怎么翻笔记
老张翻笔记本时,并不是把每张卡片都一字不差地读完。他会先明确当前这句想写什么——比如「今天天气很热」,现在要写「天气」后面接什么。这个「当前的写作需求」,就是Query(查询,简称 Q)。笔记本里每张卡片上有两个要点:Key(标签),说明这张卡片讲的是「人物」「天气」还是「地点」;Value(内容),记录这张卡片真正的细节。老张拿 Q 去和每张卡片的 Key 逐一比对,匹配度越高,Value 读得越仔细。
Transformer 的自注意力(Self-Attention)做的就是这件事。每个 Token 经过三个不同的线性投影,分别生成 Q、K、V 三个向量;注意力层负责「用 Q 去查所有 K,再按匹配度聚合 V」。整个过程可以用一个公式概括:
关键观察来了:在整个生成过程中,一个 Token 一旦被写出来,它的内容和主题就固定了——K 和 V 不会改变。可如果没有缓存,每生成一个新 Token,前文所有 Token 的 K、V 都要从头再算一遍。这就像一个演员台词已定,却每次对戏都要重新背台词,荒唐又浪费。
- 匹配打分把 Q 与每个 K 做点积。点积越大,说明这个历史 Token 与当前需求越相关——像老张看到标签「天气」时眼前一亮。
- 尺度缩放除以向量维度 d_k 的平方根,避免点积数值随维度变大而膨胀,让 Softmax 梯度更稳定。
- 权重归一对缩放后的分数做 Softmax,得到一组非负且和为 1 的注意力权重 α_i。
- 内容聚合按权重对全部 V 加权求和,得到当前 Token 的新表示——权重越大的历史内容,对输出影响越大。
KV Cache核心思想:把卡片永久收进笔记本
老张很快悟出一个道理:既然卡片写好了就不会变,为什么每次写新字都要重抄一遍?他把所有卡片永久收进笔记本,每写一个新字,只需现写新字的卡片,再把它插进笔记本。从此他再也不重读手稿——只翻笔记本。
KV Cache 的原理完全一致:在自回归生成过程中,把已经计算好的历史 Token 的 K、V 张量缓存在显存里。生成第 t 个 Token 时,历史 K、V 直接读取缓存,模型只需为当前 Token 计算一组新的 K、V,并算出一个 Q 用来查询。
把每一步累加起来:无缓存生成 T 个 Token 的总复杂度是 O(T³),有了 KV Cache 则降为 O(T²)。看似只降了一阶,但 T 是序列长度——当上下文从 1K 涨到 32K,省下的是 32 倍的重复计算量级。老张的笔记本,就是这么值钱。
- 缓存读取从显存中直接读入前 t-1 个 Token 的 K、V,代替从头重算。
- 新词计算只对当前 Token x_t 做投影,得到它自己的 Q_t、K_t、V_t。
- 缓存更新把新算出的 K_t、V_t 追加到缓存末尾,供后续所有生成步使用。
- 注意力查询用 Q_t 与缓存中的全部 K、V 做一次注意力计算,产出当前 Token 的表示。
推理加速效果:从重读手稿到翻笔记本
不背笔记本时,老张写 1000 字的小说,总阅读量高达约 50 万个字;带上笔记本后,总阅读量只有约 1000 次翻页。省下的不是一点点,而是随篇幅线性放大的红利。大模型推理中的收益同样分成两条时间线。
首先是两阶段的时间线。Prefill(预填充)阶段一次性并行读完整个 Prompt,此时缓存是空的——因为 Prompt 是第一次见,K/V 本来就没人算过,所以 Prefill 不受益;但正是它把 Prompt 的 K/V 写进了缓存。Decode(解码)阶段逐个生成新 Token,从第二个 Token 开始,每走一步 KV Cache 就多发挥一分威力。
其次是每一步的时间账。第 t 步时,无缓存是 O(t²) 的计算量,有缓存是 O(t),两者之比就是单步加速比:
这就是你问 AI 聊天时「第一个字要等片刻,后面字却一个接一个蹦出来」的根本原因:第一个字属于 Prefill,要读完整个 Prompt;后面的字属于 Decode,每个字都只需增量计算加读缓存。配合持续批处理(Continuous Batching)等工程手段,KV Cache 能让 Decode 阶段的吞吐提升数倍甚至一个数量级。
- 无缓存成本第 t 步要重算全部前文的注意力,计算量约 t²。
- 有缓存成本第 t 步只做一次注意力查询,计算量约 2t。
- 两者相除得到单步加速比约 t/2,随序列长度线性增长。
显存占用规律:笔记本越来越沉
笔记本带来速度,也带来负担——书包越来越沉。每张卡片都占地方,故事越长、人物越多,书包越重。KV Cache 同样如此:它没有无限容量,显存占用是有明确规律的。
代入一组真实数字:某 7B 参数模型,L=32、H=32、d_h=128、FP16 精度、序列长度 2048、批大小 1,则 M_KV = 2 × 32 × 32 × 128 × 2048 × 1 × 2 = 1,073,741,824 字节 ≈ 1 GB。如果批大小增加到 8,就是 8 GB——而 7B 模型的权重本身也才 14 GB。可见 KV Cache 不是「可忽略的边角料」,而是推理显存规划里的大头,工程上必须精打细算「批大小 × 序列长度」的预算。
- K 与 V 双份每个 Token 都要缓存 Key 和 Value 两组向量,先乘 2。
- 模型结构乘积Transformer 有 L 层,每层 H 个注意力头,每个头的向量维度是 d_h。
- 上下文与并发序列长度 T 与批大小 B 同时放大缓存——并发请求越多、上下文越长,卡片越多。
- 精度决定字节FP16 每个数值占 2 字节,FP8 占 1 字节,量化可直接减半显存。
MHA与GQA/MQA:按家族合并卡片
出版社给老张出了个减负主意:与其每个角色一张卡片,不如把戏份相近的角色按家族分组合并,每组共用一张「家族卡」。写新字时,先定位到家族卡,再结合具体情节展开——卡片数量骤减,故事依然连贯。这个「分组共享」的智慧,在模型架构上催生了 GQA 与 MQA。
先看最完整也最奢侈的 MHA(多头注意力):每一层有 H 个 Query 头,每个 Query 头都配一组独立的 K、V 头。KV Cache 需要存下全部 H 组——就像每个角色人手一张专属卡。GQA(分组查询注意力)把 H 个 Query 头分成 g 组,每组 Query 头共享一组 K、V 头,KV 头数从 H 降到 H/g——同一家族共用一张卡。MQA(多查询注意力)更极端:所有 Query 头共享唯一一组 K、V 头,KV 头数变成 1——整本小说只有一张人物总表。
三者对 KV 显存的压缩比:
现实中,Llama 2 70B、Llama 3、Mistral、Qwen 等主流模型普遍采用 GQA(如 g=8),KV Cache 相比 MHA 缩小 8 倍,而模型质量几乎持平。老张的家族卡,让长篇连载的书包,轻了一大截。
- MHA 基准Query 头数与 KV 头数相等,压缩比 α=1。
- GQA 分组g 个 Query 头共享一组 KV 头,KV 头数降为原来的 1/g。
- MQA 全共享全部 Query 头共用一组 KV 头,压缩比等于头数 H。
AI 应用场景
LLM 推理框架的请求调度(vLLM 与 PagedAttention)
阶段:模型推理与服务化(Decode 阶段)
解决的问题:多轮对话中,历史 K/V 如果不缓存,每轮新问题都要重算整个对话历史,延迟随轮数急剧上升。vLLM 的 PagedAttention 把 KV Cache 分页管理,像操作系统虚拟内存一样按需分配——就像老张的笔记本按卡片分页存放,翻到哪页读哪页,绝不重抄。
具体案例:在 vLLM 部署 Llama 3 的推理服务中,多轮对话到第 10 轮时,每轮只做少量增量计算;若无 KV Cache,第 10 轮需要重算前 9 轮的所有 K/V,响应时间从「秒回」退化到「几十秒思考」。
流式输出与长文档追问
阶段:生成阶段(Decode 与 Prefix Cache 复用)
解决的问题:流式打字效果要求每个 Token 尽快产出,逐字重算会击穿延迟预算。利用 prefix caching,在同一 Prompt 前缀的重复请求之间共享 KV,把「已读过的内容」变成永远有效的卡片。
具体案例:文档摘要服务接收 10K tokens 的长文档,首次生成 Prefill 耗时较长;但用户针对同一文档连续追问时,后续请求直接复用已缓存的 KV,第二次响应近乎实时,体验从「等待几十秒」变为「即刻回答」。
硬件显存规划与批量推理
阶段:推理资源配置(容量规划)
解决的问题:服务端必须对最大并发与上下文长度做显存预算。用 M_KV 公式估算每路请求的 KV Cache 大小,决定批大小上限避免 OOM;选用 GQA 架构模型可把缓存预算压缩数倍。
具体案例:在 24GB 显卡上部署 7B 模型(FP16 权重约 14GB),单条 2048 上下文的 KV 约 1GB,可留出约 9 路并发余量;若换用 MHA 且上下文拉长到 8192,单路 KV 涨到 4GB,并发路数骤降——这就是为什么长上下文服务普遍选 GQA 模型。
Python 演示
import numpy as np def attention_with_cache(q, K_cache, V_cache, d_k=8): # single-head attention: query all cached K/V once scores = np.dot(q, K_cache.T) / np.sqrt(d_k) exp_scores = np.exp(scores - np.max(scores)) weights = exp_scores / np.sum(exp_scores) return np.dot(weights, V_cache) def kv_compute_count(with_cache=True, T=64, d_k=8): # count how many K/V pairs are computed in total K_cache, V_cache = [], [] total = 0 for t in range(1, T + 1): if with_cache: # only compute K/V for the new token K_cache.append(np.random.randn(d_k)) V_cache.append(np.random.randn(d_k)) total += 1 else: # recompute all previous tokens' K/V from scratch K_cache = [np.random.randn(d_k) for _ in range(t)] V_cache = [np.random.randn(d_k) for _ in range(t)] total += t q = np.random.randn(d_k) attention_with_cache(q, np.array(K_cache), np.array(V_cache)) return total if __name__ == "__main__": T = 64 c_no = kv_compute_count(with_cache=False, T=T) c_yes = kv_compute_count(with_cache=True, T=T) print("KV computes without cache:", c_no) print("KV computes with cache:", c_yes) print("speedup ratio: %.1fx" % (c_no / c_yes))
scores = np.dot(q, K_cache.T) / np.sqrt(d_k)当前 Query 与缓存中所有 Key 做点积并缩放,对应注意力公式的「打分 + 缩放」。weights = exp_scores / np.sum(exp_scores)Softmax 归一化,把分数变成和为 1 的注意力权重。K_cache.append(np.random.randn(d_k))有缓存时每步只新增一组 K/V,历史 K/V 只计算一次。K_cache = [np.random.randn(d_k) for _ in range(t)]无缓存时每步把全部历史 K/V 重新算一遍,代价随 t 线性膨胀。total += t无缓存模式累计计算量是 1+2+…+T,这正是 O(T²) 的来源。c_yes = kv_compute_count(with_cache=True, T=T)统计有缓存的 KV 计算总组数,与无缓存对比得到加速比。
常见误区
误区KV Cache 名字里带 Cache,我一开始以为它是类似预训练权重一样的参数文件,模型加载时一起读进显存,属于模型的一部分。
正解KV Cache 不是参数,而是推理时针对「某一条具体输入」算出的中间激活值。它由当前序列各 Token 的 K、V 张量组成,请求结束就被释放。把模型比作老张的写作风格,KV Cache 则是他这部小说的专属笔记本——换一部小说,笔记必须重新写。
为何常见:因为「Cache」和模型权重都常被说成「占显存」,初学者容易把两者混为一谈;且推理框架确实会把它们一起放在 GPU 显存里。
误区以为加上 KV Cache 后,生成第一个字也会明显变快,毕竟缓存听起来是「全面加速」的组件。
正解生成第一个 Token 时,缓存还是空的,没有历史 K/V 可复用;此时大部分耗时在并行处理整个 Prompt(Prefill)上。KV Cache 的价值从第二个 Token 开始显现——每个新 Token 只需向缓存追加一组 KV,越往后省得越多。
为何常见:只看到整段生成的总延迟下降,误以为所有阶段平均受益,忽略了 Prefill 与 Decode 的本质区别。
误区KV Cache 只是临时算出来的中间数据,用完就丢,应该可以忽略不计,重点盯住模型权重就够了。
正解恰恰相反,KV Cache 是大模型推理显存的最大开销之一。长序列加大批量时,KV Cache 可达数 GB 甚至数十 GB,超过模型权重本身。它不写入模型文件,却在运行时实打实占据 GPU 显存,是容量规划的第一主角。
为何常见:对比模型文件大小与训练显存时,容易忽略推理阶段动态分配的激活值缓存。
误区既然缓存能减少重复计算,那就把缓存无限做大,反正显存不用白不用。
正解KV Cache 减少的是「重复计算」,但注意力每步仍要读取缓存中的全部 K、V,读显存本身有带宽成本;缓存过大会挤占显存,导致批大小被迫缩小甚至 OOM。理想的工程实践是在计算量与显存占用之间做平衡,而不是一味贪大。
为何常见:把「减少计算」误等同于「无代价」,忽略了访存带宽与显存容量这两条硬约束。
误区KV 头被共享后,直觉上觉得模型表达能力断崖式下跌,质量应该会严重缩水。
正解GQA 共享的是 K、V 头,Q 头仍然各自独立,表达能力损失远小于直觉;主流大模型(Llama 2/3、Mistral、Qwen 等)大量使用 GQA,质量基本持平,却能砍掉数倍 KV 显存。就像老张按家族合卡,故事照样讲得精彩。
为何常见:用「头数变少」的朴素直觉推断能力缩减,忽略了注意力表达中存在大量冗余。
面试题
Q1. KV Cache 在自回归推理中缓存的是?
- A.缓存模型的全部权重
- B.缓存历史 Token 的 KV← 正确答案
- C.缓存历史 Token 的 QK
- D.缓存优化器的梯度值
答案:B。KV Cache 缓存的是历史 Token 的 K(Key)和 V(Value),Q(Query)只属于当前生成步,用完即弃。权重参数是训练得到的模型参数,与 KV Cache 无关。
Q2. 为什么 KV Cache 不缓存 Q 向量?
- A.Q 只服务当前词,用完即弃← 正确答案
- B.Q 向量数值不稳定易出错
- C.Q 占用的显存空间太大啦
- D.Q 会影响注意力收敛速度
答案:A。每个新 Token 只产生一次 Q,用来查询当前步该关注哪里,生成完当前 Token 后 Q 不再被任何后续步骤使用,因此没有缓存价值。K 和 V 会被后续所有 Token 反复查询,才值得缓存。
Q3. 没有 KV Cache 时,生成 T 个 Token 的总复杂度是?
- A.线性复杂度 O(T)
- B.平方复杂度 O(T²)
- C.立方复杂度 O(T³)← 正确答案
- D.对数复杂度 O(logT)
答案:C。无缓存时,第 t 步要重算全部历史位置的注意力,单步 O(t²),累计 Σt² ≈ T³/3,总复杂度为 O(T³)。这与老张每写一个字就重读整部手稿的累计阅读量一个道理。
Q4. 使用 KV Cache 后,decode 每生成一个新 Token 的注意力计算量是?
- A.约 O(t²),随序列平方增长
- B.约 O(t),随序列线性增长← 正确答案
- C.严格 O(1),与序列长度无关
- D.严格 O(T³),与词表大小相关
答案:B。有缓存后 K/V 不再重算,但注意力仍要对缓存中全部 t 个 Key 做点积查询,因此单步计算量是 O(t)。O(1) 是错的——序列越长,单步查询的读取量仍然线性变大。
Q5. KV Cache 显存大小与哪些因素成正比?
- A.词表大小与采样温度高低
- B.序列长度与批处理大小← 正确答案
- C.隐藏维度与学习率峰值
- D.Dropout 概率与层间残差
答案:B。M_KV = 2 × L × H × d_h × T × B × 字节数,其中序列长度 T 与批大小 B 是运行时放大缓存的两大因素。词表大小、学习率、Dropout 都不进入 KV Cache 公式。
Q6. 某 7B 模型 L=32、H=32、d_h=128、FP16、batch=1、序列长 2048,KV Cache 约多大?
- A.约 0.5 GB
- B.约 1 GB← 正确答案
- C.约 2 GB
- D.约 8 GB
答案:B。代入公式 2 × 32 × 32 × 128 × 2048 × 1 × 2 = 1,073,741,824 字节 ≈ 1 GB。若 batch 变为 8 则约 8 GB。这一题检验对公式各项是否真正理解,而不是只看表面数字。
Q7. GQA(分组查询注意力)的核心思想是?
- A.每个查询头独享一组 KV 头
- B.若干查询头共享一组 KV 头← 正确答案
- C.全部查询头共享同一组 KV
- D.按词频动态分配 KV 显存
答案:B。GQA 把 H 个 Query 头分成 g 组,每组共享一组 KV 头,KV 头数降为 H/g,KV Cache 随之压缩 g 倍。MQA 才是「全部查询头共享一组 KV」,A 是 MHA,D 是无关选项。
Q8. KV Cache 给推理带来的收益包括?
- A.避免历史 KV 的重复计算← 正确答案
- B.降低单步解码计算复杂度← 正确答案
- C.直接扩大模型上下文窗口
- D.减少每步注意力读取总量
答案:AB。KV Cache 避免历史 K/V 重算,使单步复杂度从 O(t²) 降到 O(t),A、B 正确。它本身不扩大上下文窗口(那是 RoPE 等位置编码的职责);每步注意力仍需读取全部缓存 K/V,读取总量并未减少,C、D 错误。
Q9. 关于 KV Cache 的显存占用,正确的说法有?
- A.与 Transformer 层数成正比← 正确答案
- B.与注意力头数及其维度成正比← 正确答案
- C.与模型词表大小成正比
- D.与序列长度和批大小成正比← 正确答案
答案:ABD。M_KV = 2 × L × H × d_h × T × B × 字节数,层数 L、头数 H、头维度 d_h、序列长度 T、批大小 B 全部成正比。词表大小决定的是 Softmax 输出层的显存,不属于 KV Cache,C 错误。
Q10. 相比 MHA,使用 GQA 的模型具备哪些特点?
- A.KV Cache 显存成倍压缩← 正确答案
- B.模型质量基本持平略降← 正确答案
- C.每个 Q 头独享更多参数
- D.长上下文推理吞吐提升← 正确答案
答案:ABD。GQA 共享 KV 头后,KV 头数降为 H/g,缓存显存压缩 g 倍(A 对);Q 头仍各自独立,质量损失通常很小(B 对);同等显存下可支撑更长上下文或更大并发,吞吐提升(D 对)。GQA 并非让每个 Q 头独享更多参数,C 错误。