老张是位连载小说作家,每天都要顺着剧情一个字一个字地写下去。他的铁律是:每写一个新字,都必须和前面写过的所有字保持连贯。可老张记性不好——早先他写新字前,总要把整部手稿从头到尾重读一遍,才能想起「之前到底写了什么」。写到第 100 个字就重读 99 个字,写到第 1000 个字就重读 999 个字,小说越长,他越寸步难行。 后来老张学聪明了:每写下一个字,他就把它的「主题标签」和「具体内容」抄在一张卡片上,存进随身笔记本。写新字时,他只需要看一眼当前最想表达什么,再翻开笔记本匹配卡片,就能顺畅地写下去。笔记本虽然越来越厚,但他再也不用重读整部手稿。再后来,出版社建议他把人物按家族分组、每组共用一张卡片,书包又轻了一大截。 老张的「笔记本」,正是大模型推理加速最核心的机制——KV Cache。大模型生成文本时同样是一个字一个字地「接龙」,每写一个新 Token 都要参考前文。KV Cache 把历史 Token 的 Key 和 Value 缓存下来,避免每一步重复计算,把生成的总耗时从「立方级」拉回「平方级」;而「分组共享卡片」的思路,对应着 GQA / MQA 这些让 KV 显存成倍缩小的架构设计。理解 KV Cache,你就拿到了解读大模型推理引擎的第一把钥匙。

岗位关联

掌握 KV Cache,你就能看懂 LLM 推理「第一个字慢、后面逐字快」的现象,学会估算显存预算,并理解 GQA 等架构为何成为主流——这是大模型推理与部署工程师的必修课。

目录
  1. 可视化演示
  2. 自回归生成原理:一个字一个字地接龙
  3. 注意力计算回顾:老张怎么翻笔记
  4. KV Cache核心思想:把卡片永久收进笔记本
  5. 推理加速效果:从重读手稿到翻笔记本
  6. 显存占用规律:笔记本越来越沉
  7. MHA与GQA/MQA:按家族合并卡片
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示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 要解决的核心矛盾。

P(xt∣x<t)=Softmax(Wo⋅ht−1)\displaystyle P(x_t \mid x_{<t}) = \mathrm{Softmax}(W_o \cdot h_{t-1})
公式解读:模型先读完前文,压缩成一个隐藏状态 h,再把它映射到「词表大小」的打分空间,最后用 Softmax 变成概率分布,从中选出下一个字。
  1. 前文编码把已生成的前 t-1 个 Token 完整送入 Transformer,经过多层自注意力,得到序列末尾位置的隐藏状态。
    ht−1=LLM(x1,x2,…,xt−1)\displaystyle h_{t-1} = \mathrm{LLM}(x_1, x_2, \ldots, x_{t-1})
  2. 映射打分用输出权重矩阵 W_o 把隐藏状态投影成词表大小的分数向量,分数越高表示越可能是下一个词。
    logits=Wo⋅ht−1\displaystyle \mathrm{logits} = W_o \cdot h_{t-1}
  3. 概率归一Softmax 把所有分数变成非负且总和为 1 的概率,模型按概率采样或取最大值得到 x_t。
    P(xt∣x<t)=exp⁡(logitsxt)∑v∈Vexp⁡(logitsv)\displaystyle P(x_t \mid x_{<t}) = \frac{\exp(\mathrm{logits}_{x_t})}{\sum_{v \in V} \exp(\mathrm{logits}_v)}

注意力计算回顾:老张怎么翻笔记

老张翻笔记本时,并不是把每张卡片都一字不差地读完。他会先明确当前这句想写什么——比如「今天天气很热」,现在要写「天气」后面接什么。这个「当前的写作需求」,就是Query(查询,简称 Q)。笔记本里每张卡片上有两个要点:Key(标签),说明这张卡片讲的是「人物」「天气」还是「地点」;Value(内容),记录这张卡片真正的细节。老张拿 Q 去和每张卡片的 Key 逐一比对,匹配度越高,Value 读得越仔细。

Transformer 的自注意力(Self-Attention)做的就是这件事。每个 Token 经过三个不同的线性投影,分别生成 Q、K、V 三个向量;注意力层负责「用 Q 去查所有 K,再按匹配度聚合 V」。整个过程可以用一个公式概括:

关键观察来了:在整个生成过程中,一个 Token 一旦被写出来,它的内容和主题就固定了——K 和 V 不会改变。可如果没有缓存,每生成一个新 Token,前文所有 Token 的 K、V 都要从头再算一遍。这就像一个演员台词已定,却每次对戏都要重新背台词,荒唐又浪费。

Attention(Q,K,V)=Softmax(QK⊤dk)V\displaystyle \mathrm{Attention}(Q, K, V) = \mathrm{Softmax}\left(\frac{Q K^{\top}}{\sqrt{d_k}}\right) V
公式解读:Q 与所有 K 做点积得到「匹配分数」,除以 √d_k 防止分数过大,Softmax 把分数变成权重,最后用权重对 V 加权求和,得到融合了上下文的新向量。
  1. 匹配打分把 Q 与每个 K 做点积。点积越大,说明这个历史 Token 与当前需求越相关——像老张看到标签「天气」时眼前一亮。
    scores=QK⊤\displaystyle \mathrm{scores} = Q K^{\top}
  2. 尺度缩放除以向量维度 d_k 的平方根,避免点积数值随维度变大而膨胀,让 Softmax 梯度更稳定。
    scoresscaled=QK⊤dk\displaystyle \mathrm{scores}_{\text{scaled}} = \frac{Q K^{\top}}{\sqrt{d_k}}
  3. 权重归一对缩放后的分数做 Softmax,得到一组非负且和为 1 的注意力权重 α_i。
    αi=exp⁡(si)∑j=1texp⁡(sj)\displaystyle \alpha_i = \frac{\exp(s_i)}{\sum_{j=1}^{t} \exp(s_j)}
  4. 内容聚合按权重对全部 V 加权求和,得到当前 Token 的新表示——权重越大的历史内容,对输出影响越大。
    outt=∑i=1tαiVi\displaystyle \mathrm{out}_t = \sum_{i=1}^{t} \alpha_i V_i

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 倍的重复计算量级。老张的笔记本,就是这么值钱。

Cost(t)={O(t2),无缓存:重新计算全部前文O(t),有缓存:只算新 Token 并读缓存\displaystyle \mathrm{Cost}(t) = \begin{cases} \mathcal{O}(t^2), & \text{无缓存:重新计算全部前文} \\ \mathcal{O}(t), & \text{有缓存:只算新 Token 并读缓存} \end{cases}
公式解读:第 t 步的计算成本,无缓存时随序列长度平方增长(要重算所有历史位置的 K/V),有缓存时只随长度线性增长(注意力仍要扫过全部缓存,但省去了重算 K/V)。
  1. 缓存读取从显存中直接读入前 t-1 个 Token 的 K、V,代替从头重算。
    K<t,V<t←KV Cache\displaystyle K_{<t}, V_{<t} \gets \text{KV Cache}
  2. 新词计算只对当前 Token x_t 做投影,得到它自己的 Q_t、K_t、V_t。
    Qt,Kt,Vt=f(xt)\displaystyle Q_t, K_t, V_t = f(x_t)
  3. 缓存更新把新算出的 K_t、V_t 追加到缓存末尾,供后续所有生成步使用。
    Kcache←[Kcache;Kt],Vcache←[Vcache;Vt]\displaystyle K_{\text{cache}} \gets [K_{\text{cache}}; K_t], \quad V_{\text{cache}} \gets [V_{\text{cache}}; V_t]
  4. 注意力查询用 Q_t 与缓存中的全部 K、V 做一次注意力计算,产出当前 Token 的表示。
    outt=Attention(Qt,Kcache,Vcache)\displaystyle \mathrm{out}_t = \mathrm{Attention}(Q_t, K_{\text{cache}}, V_{\text{cache}})

推理加速效果:从重读手稿到翻笔记本

不背笔记本时,老张写 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 阶段的吞吐提升数倍甚至一个数量级。

单步加速比≈t22t=t2\displaystyle \text{单步加速比} \approx \frac{t^2}{2t} = \frac{t}{2}
公式解读:序列越长,单步加速比近乎线性放大。生成第 200 个字时,有缓存比无缓存省下约 100 倍计算量。
  1. 无缓存成本第 t 步要重算全部前文的注意力,计算量约 t²。
    Cost无缓存(t)≈t2\displaystyle \text{Cost}_{\text{无缓存}}(t) \approx t^2
  2. 有缓存成本第 t 步只做一次注意力查询,计算量约 2t。
    Cost有缓存(t)≈2t\displaystyle \text{Cost}_{\text{有缓存}}(t) \approx 2t
  3. 两者相除得到单步加速比约 t/2,随序列长度线性增长。
    加速比≈t22t=t2\displaystyle \text{加速比} \approx \frac{t^2}{2t} = \frac{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 不是「可忽略的边角料」,而是推理显存规划里的大头,工程上必须精打细算「批大小 × 序列长度」的预算。

MKV=2×L×H×dh×T×B×nbytes\displaystyle M_{\text{KV}} = 2 \times L \times H \times d_h \times T \times B \times n_{\text{bytes}}
公式解读:KV Cache 的显存 = 2(K、V 两组)× 层数 × 每层头数 × 头向量维度 × 序列长度 × 批大小 × 每个数值的字节数(FP16 是 2 字节)。
  1. K 与 V 双份每个 Token 都要缓存 Key 和 Value 两组向量,先乘 2。
    2 (K 一份、V 一份)\displaystyle 2 \ (\text{K 一份、V 一份})
  2. 模型结构乘积Transformer 有 L 层,每层 H 个注意力头,每个头的向量维度是 d_h。
    L×H×dh\displaystyle L \times H \times d_h
  3. 上下文与并发序列长度 T 与批大小 B 同时放大缓存——并发请求越多、上下文越长,卡片越多。
    T×B\displaystyle T \times B
  4. 精度决定字节FP16 每个数值占 2 字节,FP8 占 1 字节,量化可直接减半显存。
    nbytes=2 (FP16)或1 (FP8)\displaystyle n_{\text{bytes}} = 2\ (\text{FP16}) \quad \text{或} \quad 1\ (\text{FP8})

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 倍,而模型质量几乎持平。老张的家族卡,让长篇连载的书包,轻了一大截。

α=nq headsnkv heads={1,MHA 每头独立 KVg,GQA 分组共享 KVnheads,MQA 全头共享 KV\displaystyle \alpha = \frac{n_{\text{q heads}}}{n_{\text{kv heads}}} = \begin{cases} 1, & \text{MHA 每头独立 KV} \\ g, & \text{GQA 分组共享 KV} \\ n_{\text{heads}}, & \text{MQA 全头共享 KV} \end{cases}
公式解读:KV Cache 与 KV 头数成正比,KV 头数越少,缓存越小。MHA 不压缩,GQA 压缩 g 倍,MQA 压缩到原来的 1/H。
  1. MHA 基准Query 头数与 KV 头数相等,压缩比 α=1。
    nkv heads=nq heads,α=1\displaystyle n_{\text{kv heads}} = n_{\text{q heads}}, \quad \alpha = 1
  2. GQA 分组g 个 Query 头共享一组 KV 头,KV 头数降为原来的 1/g。
    nkv heads=nq heads/g,α=g\displaystyle n_{\text{kv heads}} = n_{\text{q heads}} / g, \quad \alpha = g
  3. MQA 全共享全部 Query 头共用一组 KV 头,压缩比等于头数 H。
    nkv heads=1,α=nq heads\displaystyle n_{\text{kv heads}} = 1, \quad \alpha = n_{\text{q heads}}

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 演示

python35 行
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))
目的:极简「单头注意力」模拟器,统计生成 T 个 Token 时有/无 KV Cache 各需计算多少组 K/V,直观对比加速比。
  1. scores = np.dot(q, K_cache.T) / np.sqrt(d_k)当前 Query 与缓存中所有 Key 做点积并缩放,对应注意力公式的「打分 + 缩放」。
  2. weights = exp_scores / np.sum(exp_scores)Softmax 归一化,把分数变成和为 1 的注意力权重。
  3. K_cache.append(np.random.randn(d_k))有缓存时每步只新增一组 K/V,历史 K/V 只计算一次。
  4. K_cache = [np.random.randn(d_k) for _ in range(t)]无缓存时每步把全部历史 K/V 重新算一遍,代价随 t 线性膨胀。
  5. total += t无缓存模式累计计算量是 1+2+…+T,这正是 O(T²) 的来源。
  6. 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 在自回归推理中缓存的是?

单选题 · easy

  • A.缓存模型的全部权重
  • B.缓存历史 Token 的 KV← 正确答案
  • C.缓存历史 Token 的 QK
  • D.缓存优化器的梯度值

答案:B。KV Cache 缓存的是历史 Token 的 K(Key)和 V(Value),Q(Query)只属于当前生成步,用完即弃。权重参数是训练得到的模型参数,与 KV Cache 无关。

Q2. 为什么 KV Cache 不缓存 Q 向量?

单选题 · easy

  • A.Q 只服务当前词,用完即弃← 正确答案
  • B.Q 向量数值不稳定易出错
  • C.Q 占用的显存空间太大啦
  • D.Q 会影响注意力收敛速度

答案:A。每个新 Token 只产生一次 Q,用来查询当前步该关注哪里,生成完当前 Token 后 Q 不再被任何后续步骤使用,因此没有缓存价值。K 和 V 会被后续所有 Token 反复查询,才值得缓存。

Q3. 没有 KV Cache 时,生成 T 个 Token 的总复杂度是?

单选题 · mid

  • 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 的注意力计算量是?

单选题 · mid

  • A.约 O(t²),随序列平方增长
  • B.约 O(t),随序列线性增长← 正确答案
  • C.严格 O(1),与序列长度无关
  • D.严格 O(T³),与词表大小相关

答案:B。有缓存后 K/V 不再重算,但注意力仍要对缓存中全部 t 个 Key 做点积查询,因此单步计算量是 O(t)。O(1) 是错的——序列越长,单步查询的读取量仍然线性变大。

Q5. KV Cache 显存大小与哪些因素成正比?

单选题 · easy

  • 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 约多大?

单选题 · hard

  • 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(分组查询注意力)的核心思想是?

单选题 · easy

  • 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 给推理带来的收益包括?

多选题 · mid

  • A.避免历史 KV 的重复计算← 正确答案
  • B.降低单步解码计算复杂度← 正确答案
  • C.直接扩大模型上下文窗口
  • D.减少每步注意力读取总量

答案:AB。KV Cache 避免历史 K/V 重算,使单步复杂度从 O(t²) 降到 O(t),A、B 正确。它本身不扩大上下文窗口(那是 RoPE 等位置编码的职责);每步注意力仍需读取全部缓存 K/V,读取总量并未减少,C、D 错误。

Q9. 关于 KV Cache 的显存占用,正确的说法有?

多选题 · extreme

  • 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 的模型具备哪些特点?

多选题 · mid

  • A.KV Cache 显存成倍压缩← 正确答案
  • B.模型质量基本持平略降← 正确答案
  • C.每个 Q 头独享更多参数
  • D.长上下文推理吞吐提升← 正确答案

答案:ABD。GQA 共享 KV 头后,KV 头数降为 H/g,缓存显存压缩 g 倍(A 对);Q 头仍各自独立,质量损失通常很小(B 对);同等显存下可支撑更长上下文或更大并发,吞吐提升(D 对)。GQA 并非让每个 Q 头独享更多参数,C 错误。