LLM 面试备考
首页/LLM 核心

KV Cache、GQA 与 MLA KV Cache, GQA & MLA

所属:01-fundamentals / 01-llm-core
面试权重:★★★
来源:通用领域知识;未做一手考证

一句话#

KV Cache 把已生成 token 的 K/V 缓存下来,让自回归解码每步从 O(n2)O(n^2) 降到 O(n)O(n);代价是显存随长度线性增长,于是有了 MQA → GQA → MLA 这条"让多个 query 头共享/压缩 KV"的演进路线。

为什么重要(面试角度)#

这是推理优化与显存账的核心,也是面试里最容易靠"算一笔账"拉开差距的题。面试官想听的不是定义,而是:

  • 能当场写出 KV Cache 显存公式并代入具体模型算数
  • 说得清 GQA 到底省的是什么(省 KV 头数,不省 query 头数)
  • 知道 MLA 的两个关键点:低维潜空间缓存 + decoupled RoPE
  • 区分解码阶段的瓶颈是显存带宽而不是算力
  • 知道这条线与 PagedAttention、连续批处理、前缀缓存是怎么配合的

你在 QueWI 做过 vLLM 推理服务与网关,这题应该答到能推导、能算数、能讲取舍。

核心内容#

为什么需要 KV Cache#

自回归生成第 tt 个 token 时,需要与全部历史位置做注意力。若不缓存,每步都要重算历史 token 的 K/V,总计算量是 O(n3)O(n^3) 量级;缓存后每步只算新 token 的 Q/K/V,与历史 K/V 做点积:

  • 单步计算:O(nd)O(n d)(对历史做注意力)
  • 总生成 nn 个 token:O(n2d)O(n^2 d),相比重算的 O(n3d)O(n^3 d) 是数量级差异

KV Cache 是拿显存换计算,这是它唯一的本质。

显存公式(必须能背)#

每个 token 的 KV Cache 字节数:

bytes/token=2×L×Hkv×dhead×bytes_per_element\text{bytes/token} = 2 \times L \times H_{kv} \times d_{head} \times \text{bytes\_per\_element}

其中 22 是 K 和 V 两份,LL 是层数,HkvH_{kv} 是 KV 头数(不是 query 头数),dheadd_{head} 是每头维度。整批的总占用再乘 seq_len × batch_size。

代入公开配置算两个例子(数值来自各模型公开 config,请以对应版本为准):

模型 层数 KV 头 head_dim 每 token KV(FP16) 4k 上下文(batch=1)
Llama-2-7B(MHA) 32 32 128 512 KiB ≈ 2 GiB
Llama-3-8B(GQA) 32 8 128 128 KiB ≈ 0.5 GiB(8k 时约 1 GiB)

可以看到:同样的层数与 head_dim,KV 头数从 32 降到 8,KV Cache 直接变成 1/4。这就是 GQA 的全部意义。

MHA → MQA → GQA#

方案 结构 KV 头数 特点
MHA 每个 query 头有独立的 K/V 头 HqH_q 质量最好,KV Cache 最大
MQA 所有 query 头共享一组 K/V 1 KV Cache 最小(1/H 倍),但质量下降、训练不够稳
GQA 把 query 头分成 GG 组,每组共享一组 K/V GG 介于两者之间,质量接近 MHA,KV Cache 缩到 G/HqG/H_q

关键记忆点:GQA 省的是 KV 头的数量,query 头数量不变——它同时降低了 KV Cache 显存和 decode 阶段需要读取的字节数(decode 是带宽受限,所以这直接提升吞吐)。Mistral 7B、Llama 2 的 34B/70B、Llama 3 全系都采用 GQA。实践经验是把 GQA 训练放在已有 MHA 检查点上"uptrain"(少量继续训练)比从头训更省,效果也接近从头训。

换算关系:KV Cache 体积与 HkvH_{kv} 成正比。Llama-2-7B(Hkv=32H_{kv}=32)到 Llama-3-8B(Hkv=8H_{kv}=8)是 4 倍压缩。

MLA(Multi-head Latent Attention)#

DeepSeek 系列提出并在 DeepSeek-V2/V3 中使用。思路不是"共享头",而是把 KV 压到一个低维潜向量再缓存:

  • 不缓存完整的 K/V,而是缓存一个压缩后的潜向量 ctc_t(低秩压缩,公开配置中 kv_lora_rank 为 512 量级);需要时用上投影矩阵还原出各头的 K/V。
  • 因为 RoPE 是位置相关的,无法被折进位置无关的潜空间,所以额外单独缓存一小段 decoupled RoPE key(公开配置中 qk_rope_head_dim 为 64 量级)。
  • 每 token 每层的缓存量从 2×Hq×dhead2 \times H_q \times d_{head} 降到 dc+droped_c + d_{rope}。以公开配置(H=128H=128、dhead=128d_{head}=128、dc=512d_c=512、drope=64d_{rope}=64)代入,就是从 32768 个元素降到 576 个元素。

两个必须说清的点:

  1. 为什么要 decoupled RoPE key:低秩压缩要求变换是位置无关的线性映射,而 RoPE 的旋转依赖位置,二者不能合并,只好把带位置信息的那部分单独留出来。
  2. 推理时的"吸收"技巧:由于上投影是线性的,可以把 K 的上投影矩阵折进 WQW_Q、V 的上投影折进 WOW_O,于是推理时直接在潜空间算注意力,不必显式还原完整 K/V。这是 MLA 能在工程上真正省显存而不是"省了缓存、贵了计算"的关键。

MLA 的代价:结构更复杂、训练实现更重(很多训练框架需要专门支持)、以及低秩压缩本身带来的表达能力损失(论文报告仍优于同规模 MHA/GQA,具体结论请核对原文)。论文报告的压缩比与对比对象强相关,引用数字前务必回原文核对。

Prefill vs Decode:为什么说 decode 是带宽瓶颈#

  • Prefill:一次并行处理整个 prompt,是大矩阵乘,算术强度高,计算受限。注意此时还没有 KV Cache 的收益,开销是 O(n2)O(n^2)。
  • Decode:每步只算 1 个 token 的 Q/K/V,计算量极小,但必须把全部权重 + 整个 KV Cache 从 HBM 读一遍,显存带宽受限,GPU 算力利用率极低。

这解释了一系列工程现象:为什么增大 batch 能提升吞吐(摊薄每步的权重读取)、为什么 GQA/MLA/量化 KV 直接提升吞吐、为什么 PagedAttention 有价值(→ 见 06-agent-infra/vllm-pagedattention.md)。

配套优化#

手段 作用
PagedAttention 把 KV Cache 按固定大小的 block 分页管理,消除碎片、支持前缀共享
前缀缓存 / Prompt Caching 相同前缀(如长 system prompt)复用已有 KV,省 prefill
KV Cache 量化 ⚠️ 别记成「省一半以上」 —— 量化要按组存 scale,省不到一半。实测口径:把同一组 32 个 BF16 值(64 bytes)换成 q8_0(32B 码值 + 2B scale = 34/64),约省 47%;换成 q4_0(16B + 2B = 18/64),约省 72%。计上 scale 后平均每值分别占 8.5 bit / 4.5 bit。代价是精度
KV 驱逐/滑窗 只保留最近/重要的 KV(如 attention sink + 滑窗),用于超长生成
连续批处理 每步动态进出请求,避免等最长序列

常见面试问法#

Q:KV Cache 是什么?为什么能加速,代价是什么? A:缓存历史 token 的 K/V,decode 时新 token 只需算自己的 Q/K/V 并与缓存做注意力,避免每步重算整个前缀,把生成整段的总计算从 O(n3)O(n^3) 降到 O(n2)O(n^2) 量级。代价是显存占用随序列长度和 batch 线性增长,且成为长上下文与高并发的主要显存瓶颈。

⚠️ 被追问「缓存到底省的是哪一项」时,这段话要说得更准:KV Cache 省掉的是历史 token 的 K/V 投影重算;但每个新 token 的注意力仍要遍历全部缓存的 K/V,所以单步 decode 的注意力计算量是随上下文长度线性增长的(这也是长上下文 decode 越来越慢的原因)。O(n3)→O(n2)O(n^3)\to O(n^2) 说的是生成 n 个 token 的累计量,不是单步量 —— 笼统说「省了 n 倍计算」会被问住。

Q:写出 KV Cache 的显存公式,Llama-2-7B 在 4k 上下文下占多少? A:2×L×Hkv×dhead×bytes×seq×batch2 \times L \times H_{kv} \times d_{head} \times \text{bytes} \times \text{seq} \times \text{batch}。Llama-2-7B:2×32×32×128×2B=512 KiB/token2 \times 32 \times 32 \times 128 \times 2\text{B} = 512\text{ KiB/token},4k token 时约 2 GiB(batch=1)。注意这是 MHA;换 GQA(Hkv=8H_{kv}=8)就是 1/4。

Q:GQA 和 MQA 的区别?为什么现在都用 GQA? A:MQA 所有 query 头共享一组 K/V(Hkv=1H_{kv}=1),压缩最狠但质量损失明显、训练不稳;GQA 折中,把 query 头分组,每组共享一组 K/V,Hkv=GH_{kv}=G。GQA 在压缩比和模型质量之间取得更好平衡,且可以通过对已有 MHA 检查点做短程 uptrain 得到,工程成本低,因此成为主流。

Q:MLA 相比 GQA 强在哪? A:GQA 是"共享头",压缩上限受 Hkv≥1H_{kv}\ge1 限制;MLA 是"低秩压缩",把 KV 压到低维潜向量缓存,压缩率可以远超 GQA。另外 MLA 通过上投影的线性性做权重吸收,推理时能在潜空间直接算注意力,不显式还原 K/V。代价是结构复杂、实现与训练成本高,且需要额外的 decoupled RoPE key 来保留位置信息。

Q:为什么推理时 GPU 利用率很低? A:decode 阶段每步只算一个 token,算术强度极低,大部分时间花在把权重和 KV Cache 从显存搬到计算单元上,属于显存带宽受限而非计算受限。提升手段是增大 batch(摊薄权重读取)、压缩 KV(GQA/MLA/量化)、前缀缓存、以及连续批处理。

Q:KV Cache 能无限增长吗?超长生成怎么办? A:不能,受显存限制。常见处理:KV 量化、按 block 分页(PagedAttention)、滑窗 + attention sink 只保留局部与开头若干 token、按重要性驱逐(如 H2O 一类方法)、以及把历史摘要化。这些手段都会带来信息损失,需要按任务权衡。

易错点#

  • ❌ 混淆 GQA 省的是哪一部分:它省 KV 头数,query 头数不变。说"GQA 减少了注意力头的数量"是错的。
  • ❌ 忘记公式里的因子 2(K 和 V 两份),或误用 query 头数 HqH_q 代替 HkvH_{kv}。
  • ❌ 说 KV Cache 也能加速 prefill——prefill 是并行的一次前向,没有 KV 复用收益(除非命中前缀缓存)。
  • ❌ 认为 MLA 只是"更激进的 GQA"。它换的是机制(低秩潜空间),并需要 decoupled RoPE key 与权重吸收。
  • ❌ 引用 DeepSeek 论文的压缩比时不说对比对象——比率随基线(MHA/GQA、模型规模、层数)变化很大,务必注明。
  • ❌ 说"decode 是计算受限"——恰恰相反,它是显存带宽受限。
  • ❌ 忽略精度:KV Cache 用 FP16/BF16 还是 INT8 差一倍显存,量化会掉精度,不能只谈压缩率。

自测#

1. 写出 KV Cache 显存公式。Llama-3-8B(32 层、8 个 KV 头、head_dim 128、GQA)在 8k 上下文、batch=1、FP16 下占多少?

答案

公式:2×L×Hkv×dhead×bytes×seq×batch2 \times L \times H_{kv} \times d_{head} \times \text{bytes} \times \text{seq} \times \text{batch}。代入:2×32×8×128×2=1310722 \times 32 \times 8 \times 128 \times 2 = 131072 字节 = 128 KiB/token;×8192 = 1 GiB。若换成 MHA(Hkv=32H_{kv}=32)则是 4 GiB。

2. GQA 到底省了什么?为什么它能同时提升吞吐?

答案

省的是 KV 头数量(每个 KV 头被多个 query 头共享),因此每 token 的 KV Cache 体积与 decode 时需读取的 KV 字节数都按 G/HqG/H_q 比例下降。decode 是显存带宽受限,KV 读取是每步固定开销,读得少就直接提升每步速度与可容纳的并发数,所以吞吐上升。query 头数不变,因此注意力的"表示多样性"基本保留。

3. MLA 为什么需要额外缓存一个 decoupled RoPE key?

答案

MLA 把 KV 压成低秩潜向量,依赖的是位置无关的线性上投影。但 RoPE 是位置相关的旋转,无法被折进一个位置无关的潜空间(不同位置的旋转不同,合并会把位置信息抹掉或引入错误)。因此把携带位置信息的部分单独保留为一段小维度(公开配置中为 64 量级)的 decoupled RoPE key,与潜向量一起缓存,其余不带位置的部分才做低秩压缩。

4. 上线一个 128k 上下文的模型,KV Cache 会成为什么问题?请给出三个缓解方向。

答案

KV Cache 随长度线性增长,128k 时单请求的占用取决于模型档位:按本文上面的 Llama-3-8B GQA 口径(约 128 KiB/token)约 16 GiB;若是 MHA 的大模型(每 token 的 KV 头数不压缩)才会达到数十 GB 量级。说之前先报口径 —— 直接说「数十 GB」而没说明是 MHA 大模型,会被追问时露怯。即便 16 GiB 也已经严重限制并发。三个方向:① 结构层面——用 GQA/MLA 减少每 token 的 KV 量,或对 KV 做 INT8/INT4 量化;② 调度层面——PagedAttention 分页消除碎片、前缀缓存复用公共 system prompt、连续批处理提高 GPU 利用率、请求级限流与长度配额;③ 信息层面——滑窗 + attention sink、按重要性驱逐、历史摘要化(都会损失信息,需按任务评估)。

5. 有人提议"用 KV Cache 把 prefill 也加速一下"。这个说法对吗?

答案

笼统说不对。prefill 是把整个 prompt 一次并行输入,本身没有"历史"需要复用,所以传统的 KV Cache 不带来 prefill 加速。但存在两个相关优化:① 前缀缓存(prefix caching / prompt caching)——当多个请求共享同一段前缀(长 system prompt、few-shot 示例、多轮对话的公共部分)时,可复用该前缀已算好的 KV,这时确实省的是 prefill;② PagedAttention 的 block 共享机制让这种复用可行。所以准确说法是"KV Cache 本身不加速 prefill,但基于 KV 的前缀缓存可以"。