KV Cache、GQA 与 MLA KV Cache, GQA & MLA
所属:01-fundamentals / 01-llm-core
面试权重:★★★
来源:通用领域知识;未做一手考证
一句话#
KV Cache 把已生成 token 的 K/V 缓存下来,让自回归解码每步从 降到 ;代价是显存随长度线性增长,于是有了 MQA → GQA → MLA 这条"让多个 query 头共享/压缩 KV"的演进路线。
为什么重要(面试角度)#
这是推理优化与显存账的核心,也是面试里最容易靠"算一笔账"拉开差距的题。面试官想听的不是定义,而是:
- 能当场写出 KV Cache 显存公式并代入具体模型算数
- 说得清 GQA 到底省的是什么(省 KV 头数,不省 query 头数)
- 知道 MLA 的两个关键点:低维潜空间缓存 + decoupled RoPE
- 区分解码阶段的瓶颈是显存带宽而不是算力
- 知道这条线与 PagedAttention、连续批处理、前缀缓存是怎么配合的
你在 QueWI 做过 vLLM 推理服务与网关,这题应该答到能推导、能算数、能讲取舍。
核心内容#
为什么需要 KV Cache#
自回归生成第 个 token 时,需要与全部历史位置做注意力。若不缓存,每步都要重算历史 token 的 K/V,总计算量是 量级;缓存后每步只算新 token 的 Q/K/V,与历史 K/V 做点积:
- 单步计算:(对历史做注意力)
- 总生成 个 token:,相比重算的 是数量级差异
KV Cache 是拿显存换计算,这是它唯一的本质。
显存公式(必须能背)#
每个 token 的 KV Cache 字节数:
其中 是 K 和 V 两份, 是层数, 是 KV 头数(不是 query 头数), 是每头维度。整批的总占用再乘 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 头 | 质量最好,KV Cache 最大 | |
| MQA | 所有 query 头共享一组 K/V | 1 | KV Cache 最小(1/H 倍),但质量下降、训练不够稳 |
| GQA | 把 query 头分成 组,每组共享一组 K/V | 介于两者之间,质量接近 MHA,KV Cache 缩到 |
关键记忆点:GQA 省的是 KV 头的数量,query 头数量不变——它同时降低了 KV Cache 显存和 decode 阶段需要读取的字节数(decode 是带宽受限,所以这直接提升吞吐)。Mistral 7B、Llama 2 的 34B/70B、Llama 3 全系都采用 GQA。实践经验是把 GQA 训练放在已有 MHA 检查点上"uptrain"(少量继续训练)比从头训更省,效果也接近从头训。
换算关系:KV Cache 体积与 成正比。Llama-2-7B()到 Llama-3-8B()是 4 倍压缩。
MLA(Multi-head Latent Attention)#
DeepSeek 系列提出并在 DeepSeek-V2/V3 中使用。思路不是"共享头",而是把 KV 压到一个低维潜向量再缓存:
- 不缓存完整的 K/V,而是缓存一个压缩后的潜向量 (低秩压缩,公开配置中
kv_lora_rank为 512 量级);需要时用上投影矩阵还原出各头的 K/V。 - 因为 RoPE 是位置相关的,无法被折进位置无关的潜空间,所以额外单独缓存一小段 decoupled RoPE key(公开配置中
qk_rope_head_dim为 64 量级)。 - 每 token 每层的缓存量从 降到 。以公开配置(、、、)代入,就是从 32768 个元素降到 576 个元素。
两个必须说清的点:
- 为什么要 decoupled RoPE key:低秩压缩要求变换是位置无关的线性映射,而 RoPE 的旋转依赖位置,二者不能合并,只好把带位置信息的那部分单独留出来。
- 推理时的"吸收"技巧:由于上投影是线性的,可以把 K 的上投影矩阵折进 、V 的上投影折进 ,于是推理时直接在潜空间算注意力,不必显式还原完整 K/V。这是 MLA 能在工程上真正省显存而不是"省了缓存、贵了计算"的关键。
MLA 的代价:结构更复杂、训练实现更重(很多训练框架需要专门支持)、以及低秩压缩本身带来的表达能力损失(论文报告仍优于同规模 MHA/GQA,具体结论请核对原文)。论文报告的压缩比与对比对象强相关,引用数字前务必回原文核对。
Prefill vs Decode:为什么说 decode 是带宽瓶颈#
- Prefill:一次并行处理整个 prompt,是大矩阵乘,算术强度高,计算受限。注意此时还没有 KV Cache 的收益,开销是 。
- 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 并与缓存做注意力,避免每步重算整个前缀,把生成整段的总计算从 降到 量级。代价是显存占用随序列长度和 batch 线性增长,且成为长上下文与高并发的主要显存瓶颈。
⚠️ 被追问「缓存到底省的是哪一项」时,这段话要说得更准:KV Cache 省掉的是历史 token 的 K/V 投影重算;但每个新 token 的注意力仍要遍历全部缓存的 K/V,所以单步 decode 的注意力计算量是随上下文长度线性增长的(这也是长上下文 decode 越来越慢的原因)。 说的是生成 n 个 token 的累计量,不是单步量 —— 笼统说「省了 n 倍计算」会被问住。
Q:写出 KV Cache 的显存公式,Llama-2-7B 在 4k 上下文下占多少? A:。Llama-2-7B:,4k token 时约 2 GiB(batch=1)。注意这是 MHA;换 GQA()就是 1/4。
Q:GQA 和 MQA 的区别?为什么现在都用 GQA? A:MQA 所有 query 头共享一组 K/V(),压缩最狠但质量损失明显、训练不稳;GQA 折中,把 query 头分组,每组共享一组 K/V,。GQA 在压缩比和模型质量之间取得更好平衡,且可以通过对已有 MHA 检查点做短程 uptrain 得到,工程成本低,因此成为主流。
Q:MLA 相比 GQA 强在哪? A:GQA 是"共享头",压缩上限受 限制;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 头数 代替 。
- ❌ 说 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 下占多少?
答案
公式:。代入: 字节 = 128 KiB/token;×8192 = 1 GiB。若换成 MHA()则是 4 GiB。
2. GQA 到底省了什么?为什么它能同时提升吞吐?
答案
省的是 KV 头数量(每个 KV 头被多个 query 头共享),因此每 token 的 KV Cache 体积与 decode 时需读取的 KV 字节数都按 比例下降。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 的前缀缓存可以"。