注意力的显存战争:从 MHA 到 MLA,KV Cache 是怎么被一步步压缩的
长上下文时代,推理显存的大头不是权重而是 KV Cache。本文从符号约定和维度出发,分步把四种注意力变体的缓存账算实:每 token 每层元素数、给定层数与头数下的字节数、压缩比与并发上限,并说明 DeepSeek MLA 的低秩压缩、矩阵吸收与解耦 RoPE 各自解决了什么问题、又留下了什么代价。
大模型推理的显存开销由三块构成:模型权重(加载一次,大小固定)、中间激活(与批大小相关但不随上下文线性累积)、以及 KV Cache(随上下文长度和并发数线性增长)。前两块在 2023 年前后基本被量化、分页和调度工程磨平了,第三块却随着上下文窗口从 4K 走到 128K 再到 1M,从”可以忍受”变成了”决定你能不能开这个服务”。
本文要回答的是一个很具体的问题:从 MHA 到 MQA、GQA、MLA,每 token 的缓存到底被压掉了多少,代价是什么,什么时候不值得压。 为了让讨论可复算,下面所有数字都从公开的一手维度配置出发,先给直觉,再约定符号,然后一步一步算,最后回到工程取舍与边界。
一、先给直觉:长上下文的第一性成本是”缓存”,不是”算力”
一句话的直觉:自回归解码时,每生成一个新 token,都要拿它的查询向量去和历史上所有 token 的键、值打交道;为了不每一步都重算历史,历史键值必须全部躺在显存里。
不严谨地说,注意力机制是一个”每次开会都要把过去所有会议的纪要重读一遍”的流程。你可以不重读(省显存),但那就得每次重新生成纪要(算力爆炸);你也可以每人只保存一份压缩摘要(省显存),但摘要写得越狠,读的时候丢的信息越多。
这里有一个容易被忽略的不对称:注意力的计算量只在前向过程中重复,而缓存是持久的。预填充(prefill)阶段把整段 prompt 的键值算完写进缓存,之后每一步解码(decode)只追加一行,但每一步都要把整份缓存从显存读回片上。所以长上下文服务的瓶颈通常先出现在显存容量与显存带宽上,而不是出现在矩阵乘的算力上。这也是为什么在 2024 年到 2026 年之间,主流开源模型架构的演进几乎全部集中在”怎么把这份缓存做小”,而不是”怎么把注意力算得更快”——后者直到 DeepSeek Sparse Attention 这类稀疏注意力出现才重新成为一等公民。
下面这张图给出单步解码的数据流,它是后面所有账目的基础。
flowchart LR
H["当前 token 的隐状态 h_t"] --> Q["投影得到 q_t"]
H --> KV["投影得到 k_t 与 v_t"]
KV --> CACHE["KV Cache 追加一行"]
CACHE --> ATT["注意力:q_t 与全部历史 K、V 打分"]
Q --> ATT
ATT --> OUT["输出进入下一层"]这张图说明了什么:每步只写入一行 k、v,却要读取整份历史缓存。写入是常数级,读取随上下文线性增长,因此显存容量和带宽都会随上下文线性恶化,而计算量的增长被缓存”接住”了。这就是压缩缓存的全部动机。
二、预填充与解码:两笔完全不同的账
在算缓存之前,必须先把推理的两个阶段分开,否则很容易把”省显存”和”省时间”混为一谈。
预填充(prefill) 处理整段输入:所有位置的查询、键、值可以并行算出,注意力矩阵一次成型,是算力密集的。解码(decode) 每步只处理一个新 token:它必须把整份历史缓存从显存读回片上,再用一个查询向量去打完整段历史,是显存带宽密集的。同一个模型,两个阶段的瓶颈完全不同,而 KV Cache 只属于第二个阶段。
把这件事算成算术强度(arithmetic intensity,每读一字节能顺带做多少次浮点运算)会更清楚。取单序列、单层、单步解码的注意力部分,分四步:
第 1 步,计算 的运算量,两个长度为 的向量点积是 次浮点运算,对 个头、 个历史位置全做一遍:
第 2 步,softmax 归一化之后用概率加权求和所有值向量,运算量同阶:
第 3 步,这一步真正需要从显存读回的缓存字节数( 层合计,键和值各一份,每元素 字节):
第 4 步,两者相除:
、、、 全部约掉了。bf16 下()算术强度恒为 1 FLOP/Byte,也就是每从显存搬 1 个字节,只够做 1 次浮点运算。而现代加速卡标称算力与显存带宽的比值通常在两个数量级以上,这意味着解码阶段这部分工作本质上是一条”搬字节”的流水线,算力闲置是常态。
这条推论的实用价值在于:压缩 KV Cache 不只是省显存,它按字节数等比例地缩短解码步的时间。缓存少一半,需要搬的字节少一半,在带宽瓶颈下延迟几乎同比例下降。反过来,前缀缓存这类”减少重复计算”的技术能大幅缩短预填充,对解码步的耗时几乎无影响——把这两类优化混着讲,是选型误判最常见的起点。
flowchart TD
P["整段 prompt 输入"] --> PF["预填充:全部位置并行计算"]
PF --> PW["把整段 K、V 写入缓存"]
PW --> D["解码:每步只算一个 token"]
D --> DR["从显存读回整份缓存"]
DR --> DB["带宽瓶颈:算术强度约 1 FLOP/Byte"]
DB --> D
PF --> CB["算力瓶颈:矩阵乘密集型"]这张图说明了什么:同一个模型的两个阶段,一条卡在算力、一条卡在显存带宽,而 KV Cache 只压在解码这条回路上——所以”缓存变小”最直接的回报是解码吞吐与并发,而不是首 token 延迟。
三、符号与术语表:先把记号约定清楚
后面每一节都建立在同一套记号上。表里的取值示例来自 DeepSeek-V2 / V3 技术报告与 Llama-2-70B 的公开配置,具体出处见文末来源段。
| 符号 | 含义 | 取值范围 / 示例 |
|---|---|---|
| 第 个 token 在某注意力层的输入隐状态,维度为 | 向量 | |
| 标准 MHA 中由 投影得到的查询 / 键 / 值向量(第二节数据流图中标出的那三个量,推理时每步只新算一行) | 各 维,按头切开后每头 维 | |
| 模型隐藏维度 | 5120(DeepSeek-V2)、7168(DeepSeek-V3)、8192(Llama-2-70B) | |
| 查询头数(注意力头数) | 128(DeepSeek-V2/V3)、64(Llama-2-70B) | |
| 每头维度 | 128 | |
| GQA 中键值被分成的组数(等价于键值头数) | 8(Llama-2-70B)、2.25(MLA 的等效口径) | |
| Transformer 层数 | 60(DeepSeek-V2)、61(DeepSeek-V3)、80(Llama-2-70B) | |
| MLA 的键值联合压缩维度 | 512 | |
| MLA 的查询压缩维度 | 1536 | |
| MLA 解耦 RoPE 的每头维度 | 64 | |
| 已缓存的序列长度(token 数) | 4K 到 1M | |
| 并发序列数(批大小) | 由预算倒推 | |
| 每个元素的字节数 | 2(bf16/fp16)、1(fp8) | |
| MLA 的键值联合潜在向量(图 3 中即被缓存的那一块) | 维 | |
| MLA 的查询压缩潜在向量(不进缓存,只影响训练期激活) | 维 | |
| MHA 的查询 / 键 / 值 / 输出投影矩阵 | 级 | |
| MLA 的键值下投影 / 键上投影 / 值上投影 / 解耦键投影 / 解耦查询投影 | 形状见第七、八节 |
三条术语约定,避免后文歧义:
- “每 token 每层元素数” 指的是单个序列、单层注意力、一个 token 需要驻留的标量个数,不含批维度。
- “缓存” 默认指解码期常驻显存的那份张量,不含预填充过程中临时的中间激活。
- “压缩比” 一律指同模型配置下该方案与 MHA 的每 token 元素数之比,跨模型比较会单独说明。
四、形式化:自回归解码里到底缓存了什么
标准多头注意力先把输入投影成查询、键、值:
其中 ,投影后按头切成 份,每份 维。第 个头的输出是
把 个头拼接后再过一个输出投影 。这里的关键是求和上限为 :每个新 token 都要和位置 到 的所有键值做点积。如果每一步都重新算 和 ,那么第 步的算力是 ,整段生成的总算力是 ,而其中绝大部分是重复劳动——位置 的键值在第 步算过一次,之后每步都要再算一次。
因此所有推理引擎都做同一件事:把每层每个 token 的键和值写进一块连续缓存,解码时只读不算。这份缓存的形状是
其中维度 表示键与值各一份。从这里出发,显存账就变成了一个纯乘法问题。
五、把 MHA 的账算实:五步推导
不要一步跳到结论。我们按”一个 token 需要什么 → 一层需要什么 → 整模型需要什么 → 序列需要什么 → 字节数”的顺序走五步,每一步都可以单独验证。
第 1 步:单个 token、单层的元素数。 键和值各要有 个头,每头 个标量:
代入 、,得到 个元素。也就是每层每 token 固定 32768 个标量,和上下文长度无关——这是增量。
第 2 步:乘层数。 每一层都有一份自己的缓存:
DeepSeek-V3 的 ,于是 个元素,约 ,即”200 万”量级。
第 3 步:乘序列长度。 缓存随上下文线性累积:
第 4 步:乘并发。 生产环境不是服务一个请求,而是同时服务 个:
这一步是很多预算估算出错的地方:单条 1M 上下文的账看起来”还能凑合”,乘上并发就直接超了。
第 5 步:乘元素字节数。 bf16 下 :
把数字代进去:
| 量级 | 元素数 | bf16 字节数 |
|---|---|---|
| 每 token 每层 | 32768 | 64 KiB |
| 每 token 全模型(61 层) | 1,998,848 | 3.8125 MiB |
| 单请求 1M token | 约 3.64 TiB(十进制约 4.00 TB) |
换句话说,在 DeepSeek-V3 的头数与维度配置下,如果它用 MHA,单条 1M token 上下文的缓存大约需要 50 张 80 GB 的加速卡,只为了存键和值。这不是”优化一下更好”,而是”这条路根本不通”。所以从 2023 年起,所有长上下文模型都必须回答同一个问题:这份缓存能不能少存一点。
六、MQA 与 GQA:拿”头的数量”开刀
最直观的想法:查询头各自保留,让键值的头共享。
MQA(Multi-Query Attention) 由 Shazeer 在 2019 年提出,做法是键和值只保留一份,所有查询头共用:
在同一个 的配置下是 256 个元素,相对 MHA 压缩 倍。代价也直接:GQA 论文明确指出,MQA 会导致质量退化,并且可能带来训练不稳定;论文附录里记录了从头训练的 MQA 模型在预训练中频繁出现损失尖峰,在长输入任务微调时直接发散。压缩太狠,模型没有足够的键值自由度来表达不同头关心的不同子空间。
GQA(Grouped-Query Attention) 是 Ainslie 等人在 2023 年提出的插值方案:把查询头分成 组,每组共享一份键和值,
这个族的两端正好是另外两个方案:GQA-1(一组)等价于 MQA,GQA- 等价于 MHA。所以 GQA 不是一个新机制,而是把 MHA 与 MQA 之间的连续插值参数化成了一个可调的旋钮 。
GQA 论文还给了两个工程上很重要的细节。第一,从已有的 MHA 检查点出发,可以把键值投影矩阵均值池化(mean-pooling)成更少的头,再用约 5% 的原始预训练算力做继续训练(uptraining),就能得到质量接近 MHA、速度接近 MQA 的模型——均值池化比”只挑一个头”或”重新随机初始化”效果都好。第二,模型越大,GQA 越划算:键值缓存随模型维度线性增长,而参数量与 FLOPs 随模型维度平方增长,所以大模型的注意力带宽开销占比相对更低,同时标准张量并行会按分片数复制那份唯一的键值头,GQA 把这种复制浪费也一起消掉了。
算一笔 Llama-2-70B 的实账。它的公开配置是 、键值头数 8、、(每头维度由隐藏维度 8192 除以 64 得到):
| 上下文 | 若用 MHA(64 个键值头) | 实际 GQA(8 个键值头) |
|---|---|---|
| 每 token 全模型 | 1,310,720 元素 / 2.5 MiB | 163,840 元素 / 320 KiB |
| 4096 token(原始窗口) | 10 GiB | 1.25 GiB |
| 128K token(外推后) | 320 GiB | 40 GiB |
8 倍的压缩,量级上很可观,但注意最后一行:即使有了 GQA,Llama-2-70B 在 128K 上下文下单条序列仍要 40 GiB 缓存。也就是说,如果给一份 40 GiB 的缓存预算,GQA 架构只能同时服务 1 条 128K 请求。GQA 把”不可能”变成了”勉强”,没有把它变成”便宜”。这解释了为什么 GQA 会成为 2023 年之后开源模型的事实标配,同时也解释了为什么 DeepSeek 认为这条路不够——组数不能太少,压一到两个数量级就到底了。
七、MLA:不砍头,换一个方向压缩”表示本身”
DeepSeek 在 V2 技术报告(2024 年 5 月)里提出的 MLA(Multi-head Latent Attention)换了一个方向:不减少头的数量,而是把键和值联合压缩到一个低秩潜在向量里,缓存这个潜在向量而不是缓存键值本身。
背后的假设是:不同头、不同维度的键值之间存在很强的相关性,跨头拼接之后的键值矩阵近似低秩,可以用一个远小于 的向量表示。形式上,先做下投影得到潜在向量,再做上投影还原:
其中 是键值联合潜在向量, 是下投影矩阵, 是键、值的上投影矩阵。DeepSeek-V2 把 设为 512,而 ,压缩了 32 倍。
这一改动的收益是立竿见影的:推理时只需要缓存 ,于是每 token 每层的增量从 32768 个元素降到 512 个元素。查询这边也做了同样的低秩压缩(),目的是降低训练时的激活显存,但它不进缓存,所以不影响本文的账。
不过到这里还有两个坑,是 MLA 真正难的地方,也是大多数文章一笔带过的地方:上投影矩阵能不能不算?位置编码怎么办?
八、矩阵吸收与解耦 RoPE:MLA 为什么在推理时不额外花钱
坑一:如果每步都要从潜在向量把键值”升维”还原出来,那不是白省了吗? 还好注意力分数可以改写。对未加位置编码的部分,
这一步只用了矩阵乘的结合律,但改变的东西很关键:等式右边把 单独拎了出来,而这个乘积与 token 无关,可以在加载权重时预先合并进查询侧。于是推理时查询直接和潜在向量做点积,上投影矩阵 根本不需要运行——这就是”矩阵吸收”(absorption)。同理,值侧的上投影 可以吸收进输出投影 。
坑二:RoPE 和吸收不兼容。 RoPE 是乘在键上的、随位置变化的旋转矩阵。如果键是压缩后再旋转的,那么
中的旋转矩阵夹在上投影和潜在向量之间;由于旋转矩阵和上投影矩阵不可交换, 没法再移过去。更糟的是,如果坚持先旋转再缓存,那么缓存的东西就依赖绝对位置,前缀复用时整段都要重算。
DeepSeek 的解法是解耦 RoPE:把位置信息从主通道里拆出来,用一条很小的旁路单独承载。
主通道(潜在向量)保持”位置无关”从而可以被吸收,旁路通道只有 维(DeepSeek 设为 64)且所有头共享同一个 。最终打分是”内容相似度 + 位置相似度”两部分的拼接:
于是真正需要缓存的只有两件东西:潜在向量和那条解耦键。每 token 每层的元素数变成
代入 DeepSeek 的配置:。DeepSeek-V2 报告里给出的等价说法是:MLA 的缓存量相当于只有 2.25 组的 GQA——验算一下,,对得上。
下面这张图把 MLA 的投影流水线和缓存边界画出来,注意”仅此被缓存”的那两个方块。
flowchart LR
H["隐状态 h_t"] --> DKV["下投影 W_DKV"]
DKV --> CKV["潜在向量 c_t^{KV}:仅此被缓存"]
CKV --> UK["上投影 W^{UK} 还原各头 K(逻辑路径)"]
CKV --> UV["上投影 W^{UV} 还原各头 V(逻辑路径)"]
H --> KRP["解耦键 W^{KR} 加 RoPE"]
KRP --> CKVR["解耦键 k_t^{R}:也被缓存"]
UK --> SCORE["注意力打分与加权求和"]
UV --> SCORE
CKVR --> SCORE这张图说明了什么:缓存被切成了”大而可压缩的内容通道”与”小而必须保留的位置通道”两部分,前者靠低秩压缩把 32768 降到 512,后者只有 64 维且不能压。两者相加 576,是 MLA 缓存成本的完整构成——只算 而漏掉 会让压缩比虚高。
图注(口径):图上把 / 画成指向注意力打分的实箭头,只是为了交代逻辑上的还原路径——它们说明了”潜在向量凭什么能顶替键值”,但推理时这两个上投影已被吸收( 并入查询侧、 并入输出投影),运行时并不实际执行,因此缓存里也没有它们还原出来的键值。本图的记号(、、 等)与第三、七、八节的 LaTeX 记号一致。
九、四种方案对照表
把前四节的公式并列,一张表就能看清四种方案的取舍。表内方案三列指的是”缓存技巧”,元素数列指的是每 token 每层的元素数,压缩比一律相对同配置 MHA。
| 方案 | 缓存技巧 | 每 token 每层元素数 | 相对 MHA | 质量证据(来自一手来源) | 代表模型 |
|---|---|---|---|---|---|
| MHA | 每头独立缓存完整 K、V | 1× | 论文中的强基线 | GPT-3 时代架构 | |
| MQA | 全部查询头共享一份 K、V | 约 1/128 | 官方口径为”会导致质量退化与训练不稳定” | Falcon、PaLM | |
| GQA | 查询头分 组,组内共享 K、V | ( 时 2048) | 随组数,典型 1/8 | uptraining 后”质量接近 MHA,速度接近 MQA” | Llama-2-70B、Llama 3、Qwen |
| MLA | 键值联合低秩压缩,只缓存潜在向量加解耦 RoPE 键 | 约 1/57 | 官方口径为”缓存量相当于 2.25 组 GQA,但性能强于 MHA” | DeepSeek V2 / V3 系列 |
把同一套账换成绝对字节数(以 DeepSeek-V3 的 61 层、bf16 计算),差异会更刺眼:
| 方案 | 每 token 全模型缓存 | 1M token 单请求 | 128K token 单请求 |
|---|---|---|---|
| MHA 形状 | 3.8125 MiB | 约 3.64 TiB | 约 488 GiB |
| GQA() | 244 KiB | 约 233 GiB | 约 30.5 GiB |
| MLA(bf16) | 68.625 KiB | 约 65.4 GiB | 约 8.58 GiB |
| MLA(fp8 缓存,实测布局;仅 MLA 主缓存,不含 DSA 索引器缓存) | 39.08 KiB | 约 37.3 GiB | 约 4.88 GiB |
表内上下文口径:1M 按 token 计,128K 按 token 计(两个数字都不是 ,全篇统一按此换算)。
最后一行的 39.08 KiB 不是笔算出来的,而是 vLLM 官方博客公开的实际缓存布局:DeepSeek-V3.2 的 MLA 每 token 是 656 字节,由三段构成——前 512 字节是 fp8 量化的”无位置编码”部分,接着 16 字节是 4 个 float32 缩放因子,最后 128 字节是 64 个 bf16 的解耦 RoPE 值(这一部分不做量化以保证精度)。乘上 61 层就是 40016 字节。
但这条口径有个缺口必须同时标出:那 656 字节只含 MLA 主缓存,不含 DeepSeek Sparse Attention(DSA)的索引器 key 缓存。vLLM 在实现 V3.2 时说得很明确——索引器 key 缓存”单独分配缓冲,与 MLA 的 K 缓存分开”(vLLM allocates separate buffers to save the indexer K cache, separate from the MLA K cache)。所以 39.08 KiB 不是这条栈的真实每 token 开销,只是它的一个组成部分。
另有一个值得留意的工程细节:官方对 RoPE 那 64 维拒绝了量化。原因不难推——位置相似度对数值扰动更敏感,压它换来的收益(128 字节降到 64 字节)不值得冒质量风险。
回到显存账本身:同样 1M token 上下文,MHA 形状要 3.64 TiB,MLA 加 fp8 缓存只要约 37 GiB(仅主缓存口径,见上)。从”一个集群都装不下”到”单机几张卡就能服务”,这就是过去两年注意力架构演进最直接的收益。
十、一个可复算的选型算例
把前面的公式串起来跑一遍,整个过程只有三次乘除。
问题设定: 上下文 128K token,KV Cache 预算 40 GiB,目标并发 8 条。
第 1 步,反推每条序列能用多少缓存:
第 2 步,反推每 token 允许的字节数:
第 3 步,拿候选架构对照这 40 KiB 的门槛:
| 候选 | 每 token 全模型缓存 | 相对 40 KiB 门槛 | 40 GiB 预算下 128K 可并发 |
|---|---|---|---|
| MHA 形状(61 层,bf16) | 3.8125 MiB | 超标约 98 倍 | 0 条,不可行 |
| GQA (bf16) | 244 KiB | 超标约 6 倍 | 0 条,不可行 |
| MLA(bf16) | 68.625 KiB | 超标约 1.7 倍 | 约 4.66 条 |
| MLA(fp8 缓存,656 B/token/层,仅主缓存) | 39.08 KiB | 达标(余量仅约 2.3%) | 约 8.2 条 |
这个”达标”必须加标注才成立。 39.08 KiB 距 40 KiB 门槛只低 ,而它没算 DSA 索引器缓存;vLLM 在 V4 适配博客的附录里把 V3.2 的账算全了(这一段是引擎侧的自述,不是笔算):MLA 主缓存 B/token/层,索引器 key 缓存 B/token/层,合计 1408 B/token/层,61 层、1,048,576 token 下约 83.9 GiB。把索引器一并计入后,门槛余量转负:
| 口径 | 每 token 全模型缓存(61 层) | 相对 40 KiB 门槛 | 40 GiB 预算下 128K 可并发 |
|---|---|---|---|
| 仅 MLA 主缓存(656 B/token/层) | 39.08 KiB | 达标,余量 2.3% | 约 8.2 条 |
| 计入索引器(bf16 索引器 256 B/token/层,即 vLLM 的 83.9 GiB 口径) | 54.33 KiB | 超标约 36% | 约 5.9 条 |
| 计入索引器(索引器同按 fp8 + 缩放计,约 144 B/token/层) | 47.66 KiB | 超标约 19% | 约 6.7 条 |
结论是:在”128K × 8 并发 × 40 GiB”这组约束下,只有 MLA 加量化缓存的组合有可能满足,而且只在”只算 MLA 主缓存”这个口径下勉强达标;把索引器缓存算进来,8 条并发就撑不住了(降到 6~7 条)。GQA 的结论不受这个口径影响,因为它本来就没有额外的索引器缓存:在同样预算下它只能把并发压到 1 条(单条约占 30.5 GiB,已经没有余量);要做到 8 条并发,缓存预算需要约 244 GiB。这就是”同样标称 128K 上下文,服务成本结构差一个数量级”的具体来源。
需要说明一个容易被过度解读的地方:表里的”可并发”是容量上限,不是吞吐上限。真实部署还要算每步读取全部缓存所需的带宽时间、专家并行的通信开销、调度器的分块与碎片策略。容量过关是必要条件,不是充分条件。
十一、不改架构也能省:量化、分页与前缀复用
前面几节都在改模型结构。工程上还有两条不需要碰架构的路线,值得单独说清边界。
KV 量化 不动元素个数,只动每个元素占几个字节,因此通用性最好。vLLM 官方博客给出的 DeepSeek-V3.2 缓存布局是一个可直接验证的例子(该实现对应的推荐起跑配置是张量并行 16×H100、8×H200 或 8×B200,三个档位都能跑同一份 656 字节布局):同样的 MLA 结构,每个 token 的缓存被拆成三段,
对照未量化的版本:
即 1.756 倍的额外压缩,而且是叠加在 MLA 那 1/57 之上的。注意最后那 128 字节:官方选择不对解耦 RoPE 的 64 个值做量化,理由写得直白——为了精度。位置相似度对数值扰动比内容相似度更敏感,多花这 64 字节是划算的。
分页(paged)与前缀复用 是另一条线,它既不减少元素个数也不减少字节数,而是减少浪费:KV Cache 按块分配、按块复用,避免为每条序列预留最大长度造成的碎片;前缀缓存让共享同一段前缀的请求直接复用已有块,跳过重复的预填充。vLLM 官方文档把这条边界写得很明确:自动前缀缓存只在预填充阶段带来收益,当请求之间没有共享前缀、或回答本身很长时,收益会消失。
两张表把几条线的关系说清楚。
| 技术 | 减少了什么 | 不减少什么 | 生效阶段 |
|---|---|---|---|
| GQA / MLA | 每 token 的元素个数 | 每元素的字节数 | 解码读取 |
| KV 量化 | 每元素的字节数 | 元素个数 | 解码读取 |
| 分页 / 前缀缓存 | 显存浪费与重复计算 | 元素个数与字节数 | 内存分配与预填充 |
| 稀疏注意力 | 每步参与打分的 token 数 | 缓存里驻留的元素个数 | 解码计算 |
| 显存占用项 | 是否随上下文线性增长 | 是否随并发增长 |
|---|---|---|
| 模型权重 | 否 | 否 |
| 中间激活 | 否(随批大小波动,不累积) | 是 |
| KV Cache | 是 | 是 |
| 量化后的 KV Cache | 是(斜率降为原来的 ) | 是 |
这两张表说明了什么:架构压缩、量化、调度复用是三条正交的线,收益相乘而不是相加,但每条线各自有清晰的适用边界;把它们混着谈,就会出现”我们做了量化所以不需要 GQA”这类错误结论。
flowchart LR
R["请求到达"] --> PC{"前缀缓存是否命中"}
PC -->|"命中"| REUSE["复用已有 KV 块,跳过预填充"]
PC -->|"未命中"| COMP["预填充计算并写缓存"]
REUSE --> DEC["逐 token 解码"]
COMP --> DEC
DEC --> RD["按块读取缓存"]
RD --> Q["量化:每元素字节更少"]
RD --> S["架构压缩:元素总数更少"]这张图说明了什么:前缀缓存决定”要不要重算”,量化和架构压缩决定”存多少、读多少”,三者在流水线的不同位置生效——可以同时叠加,但优化的是三个不同的量。
十二、工程含义:这套账怎么传导到你的部署参数
第一,把”每 token 缓存字节数”当成选型的一等指标。 它可以直接换算成并发上限。仍以 128K 上下文和 40 GiB 缓存预算为例:Llama-2-70B 形状的 GQA 只能跑 1 条并发;DeepSeek-V3 形状的 MLA(bf16)可以跑约 4.66 条,换 fp8 缓存后约 8.2 条——但这两个数都只算 MLA 主缓存,把 DSA 索引器缓存计入后,fp8 口径会掉到约 6.7 条。同一个”128K 上下文”的宣传数字,背后的服务成本结构可以差一个数量级,这会直接传导到 API 定价与吞吐承诺上。
第二,自部署的预算公式要按架构选对。 GQA 类模型按 估算,MLA 类模型按 估算。用错公式的典型症状是:按权重算好了显存,上线一压长文本就 OOM。
第三,缓存管理技术和注意力架构是正交的两条线,可以叠加。 vLLM 的自动前缀缓存(Automatic Prefix Caching)把相同前缀的缓存复用给新请求,官方文档明确它的收益主要在预填充阶段,不减少解码阶段的时间;HF Transformers 把缓存策略拆成 DynamicCache、StaticCache、QuantizedCache 等类别,官方的内存占用预期分别是中、高、低。前缀缓存解决”同一段内容不要重复算”,量化解决”每个元素占几个字节”,而 GQA/MLA 解决”要存多少个元素”——三者互不替代。
第四,MLA 与张量并行有结构性摩擦,这是它最容易被低估的代价。 vLLM 官方博客指出,张量并行部署对 MLA 不是最优的,因为潜在投影会在各个分片上被复制;在 DeepSeek-V3 的实测对比中,张量并行方案每张 H200 只剩 34 GB 可用显存。所以他们转而推荐专家并行(EP)加数据并行的 Wide-EP 部署,用共享专家和独立注意力副本来换有效 KV 容量。也就是说,MLA 省下的是”缓存元素数”,但它的权重布局并不天然适配最常见的并行切分方式——这是选型时必须一起考虑的。
第五,压缩到头之后,路线转向了稀疏,并且把 MLA 本身也换掉了。 DeepSeek-V3.2 引入的 DeepSeek Sparse Attention 不再只压缩缓存,而是让每个查询只关注被索引器挑出的 top-2048 个 token,直接砍掉注意力的计算量。到 DeepSeek-V4,注意力已经不再是 MLA:官方技术报告(arXiv:2606.19348,《DeepSeek-V4: Towards Highly Efficient Million-Token Context Intelligence》)给出的方案是 CSA(Compressed Sparse Attention)与 HCA(Heavily Compressed Attention)交错混用的混合注意力:
- CSA 先按压缩率 把每 4 个 token 的 KV 压成一个条目(重叠窗口,每个条目由 个原始 KV 加权得到),再用 DSA 让每个查询只看 top- 个压缩条目(Flash 默认 ,Pro 默认 )。
- HCA 按 做更狠的压缩,且不重叠、不做稀疏——所有压缩条目都参与稠密注意力。
- 两者的 core attention 都采用共享 K/V 的 MQA(
num_key_value_heads = 1,同一个张量既当 key 又当 value;也因此需要给注意力输出反向施加一次 RoPE 以抵消绝对位置),并各自附带一条窗口 的滑动窗口分支来补块内的局部信息。 - 层排布上,Flash 的前两层是纯滑动窗口,Pro 的前两层是 HCA,其后 CSA / HCA 交错。
官方给出的效率结论是可引的一手数字:在 1M 上下文下,V4-Pro 只需要 V3.2 的 27% 单 token 推理 FLOPs(等值 FP8 FLOPs)与 10% 的 KV cache(总参 1.6T / 激活 49B);V4-Flash 更进一步,是 10% / 7%(总参 284B / 激活 13B,注意 vLLM 博客写的是 285B,官方报告为 284B);相对 BF16 GQA8(head dim 128)这一常见基线,V4 系列的 KV cache 在 1M 上下文下可压到其约 2%;两个模型都预训练了 32T token 以上(Flash 32T、Pro 33T)。
在推理引擎这一侧,vLLM 把这套机制落地成 c4a(约 1/4 压缩)与 c128a(约 1/128 压缩)两类压缩层,逻辑块固定为 256 个原生位置,整个五类缓存栈归并成 3 种页大小;缓存精度上是索引器 fp4 + 注意力 fp8(官方报告对这一点另有更细的表述:索引器内部的注意力计算用 FP4,KV 条目则是 RoPE 维用 BF16、其余维用 FP8 的混合存储——所以”V4 的缓存精度”单独说成某一个精度都是不准确的)。这些是引擎侧的实现口径,用来把模型设计变成可调度的显存布局,不等于模型厂商的设计描述。这条路线说明:当低秩压缩把”存什么”压到极限之后,下一步的战场既是”算哪些”,也是”把 K 和 V 合并、把压缩写进注意力的定义里”。
十三、三则常见误解
误解一:GQA 是”MQA 的加强版”。 不止于此。GQA 论文自己给的定位是 MHA 与 MQA 之间的插值族:GQA-1 就是 MQA,GQA- 就是 MHA,中间的组数是连续可调的旋钮。理解成插值族的好处是,选型时问的就不是”要不要用 GQA”,而是” 设多少、省下的显存值不值得那点质量代价”。
误解二:MLA 每 token 每层的缓存是 512 个元素。 少算了那条解耦 RoPE 键。DeepSeek-V2 报告里的完整公式是 ,代进去是 。漏掉这 64 维会把压缩比从约 1/56.9 算成 1/64,看起来只差一点点,但这类误差会被层数、序列长度和并发数三级放大。
误解三:压缩 KV Cache 能降低首 token 延迟。 首 token 延迟由预填充决定,而预填充时缓存还没建起来,它的耗时取决于输入长度与算力。缓存压缩的收益落在解码阶段:按前面推的算术强度,它是按字节数等比例地缩短每一步的时间。要压预填充延迟,应该去找前缀缓存、分块预填充、稀疏注意力这类技术。
十四、边界与未解:什么时候压缩不值得
压缩从来不是免费的,下面几条是选型时最容易被忽略的边界。
短上下文场景下,压缩的收益可能小于它的复杂度。 回到 Llama-2-70B 那张表:在原始 4096 窗口下,GQA 缓存只有 1.25 GiB,MHA 也只有 10 GiB——两者都是”单卡装得下还很宽裕”,压缩在这里省下的不是可行性,只是余量。此时引入 MLA 那套低秩压缩 + 解耦 RoPE + 矩阵吸收的实现复杂度,换来的只是每序列几 GiB 的节省,而代价是训练要重新学出一套压缩矩阵、推理要实现非标准的注意力内核、调试难度显著上升。压缩比的收益随上下文长度线性放大,所以它本质上是一个长上下文工具。
低秩是有信息损失的,而”没有质量损失”这个结论有口径限制。 官方口径是 MLA 性能强于 MHA,但这是在特定训练配置与评测集上的结果,不能读成”任意低秩维度都无损”。 设多少、低秩约束是否起到了正则化作用、在更大规模上是否仍然成立,属于仍未完全定论的部分。同样地,DeepSeek-V2 报告里那个”KV cache 降低 93.3%“是相对 DeepSeek 67B(而 67B 本身已经使用了 GQA)的官方口径数字,报告正文没有展开这项对比的逐项推导;本文正文里的 1/57 是按报告同一张对照表给出的公式与维度自行计算的 MHA 对 MLA 压缩比。两个数字口径不同,混着引用会得出错误的量级判断。
官方的设计口径与引擎的落地口径要分开标。 DeepSeek-V4 的注意力细节现在有模型厂商的一手报告可查:arXiv:2606.19348《DeepSeek-V4: Towards Highly Efficient Million-Token Context Intelligence》。CSA / HCA 的两种压缩率(、)、窗口 128 的滑动窗口分支、共享 K/V 的 MQA、Flash 前两层纯滑动窗口 / Pro 前两层 HCA 的层排布,以及 27% / 10%(Pro)与 10% / 7%(Flash)这两组相对 V3.2 的效率数字,都出自该报告正文或其摘要;而 c4a / c128a 的命名、256 原生位置逻辑块、五类缓存归并为 3 种页大小、索引器 fp4 + 注意力 fp8,是 vLLM 官方博客给出的推理引擎侧实现口径,两者不应混为一谈。报告自称 “preview version”,完整设计动机、消融实验与其余超参并不都在正文里;官方没说的部分,本文照旧留白。
训练侧的代价被普遍低估。 MLA 的压缩矩阵必须从训练中”学出来”,报告里还提到需要在压缩潜在向量之后加 RMSNorm、并在宽度瓶颈处乘额外缩放因子来保证训练稳定。这是一笔训练时多花算力、推理时长期省钱的交易。如果你的模型只服务短上下文、或者推理量远小于训练量,这笔交易的回报期可能会很长。
并行与生态的成本是真实存在的。 前面提到 MLA 与张量并行的摩擦;类似地,稀疏注意力把”均匀的缓存”变成”每层压缩率不同、页大小不同的异构缓存”,vLLM 需要把五类缓存归并成三个页大小桶才能避免碎片。这些工程复杂度会沉淀成只有少数推理栈支持良好的现实,而架构选择的自由度往往受制于你实际能维护的推理栈。
最后是仍未解决的方向。 低秩压缩的”最优秩”是否有理论下界?解耦 RoPE 那条 64 维旁路是不是最小可行的设计,还是当年的工程折中?稀疏注意力选出的 top-k 是否会漏掉长距离依赖,官方是否给出了与全注意力等价的保证?这些问题在公开一手资料里都没有确定答案,属于开放问题。
十五、小结
回到开头的直觉:长上下文服务的第一性成本是那份随 token 线性增长的缓存,而不是算力。MHA 每 token 每层要 32768 个元素;MQA 把键值头砍到 1 份,压到 1/128,代价是官方承认的质量退化与训练不稳;GQA 用 把它变成连续旋钮,在大模型上是质量与显存的良好折中,但压一到两个数量级就到底;MLA 换了方向,把键值联合压进低秩潜在向量、用矩阵吸收免掉上投影、用解耦 RoPE 保住位置信息,最终每 token 每层只剩 576 个元素,也就是约 1/57。
工程上的结论可以压缩成三句话:先算账再选型(每 token 缓存字节数 × 上下文 × 并发 = 你的显存底线);压缩比是长上下文的函数(短上下文下不值得为它付出实现复杂度);架构压缩、KV 量化、前缀复用是三条正交的线,能叠就叠,但要知道每条线各自的边界在哪里。
📋 来源与核验记录
本文所有具体维度与倍数均来自下列一手来源(均于 2026-09-29 实际打开核对):
- DeepSeek-V2 技术报告(MLA 提出、低秩键值联合压缩、解耦 RoPE、矩阵吸收、“KV cache 降低 93.3%“与”相当于 2.25 组 GQA”、、、、、、60 层、隐藏维度 5120、236B 总参数 / 21B 激活、128K 上下文、8.1T token 语料、42.5% 训练成本节省、5.76 倍吞吐):https://arxiv.org/abs/2405.04434 与全文 https://arxiv.org/html/2405.04434v5
- DeepSeek-V3 技术报告(61 层、隐藏维度 7168、、、、、、671B 总参数 / 37B 激活、14.8T token、2.788M H800 GPU 小时、MLA 低秩联合压缩公式):https://arxiv.org/abs/2412.19437 与全文 https://arxiv.org/html/2412.19437v2
- GQA 论文(分组查询注意力的定义、GQA-1 等价 MQA、GQA- 等价 MHA、均值池化转换、约 5% 原始预训练算力的 uptraining、“质量接近 MHA 而速度接近 MQA”、MQA 的质量退化与训练不稳定附录):https://arxiv.org/abs/2305.13245 与全文 https://arxiv.org/html/2305.13245v3
- MQA 论文(Shazeer 2019,键值在所有头间共享以降低增量解码的内存带宽):https://arxiv.org/abs/1911.02150
- Llama 2 论文(70B 使用 GQA):https://arxiv.org/abs/2307.09288
- Llama-2-70B 公开模型配置(、键值头 8、 由隐藏维度 8192 / 64 推出、80 层、最大位置 4096):https://huggingface.co/NousResearch/Llama-2-70b-hf/raw/main/config.json
- RoPE 论文(旋转位置编码,及”键被位置相关旋转矩阵作用”这一与吸收技巧冲突的根源):https://arxiv.org/abs/2104.09864
- Hugging Face Transformers 缓存策略官方文档(DynamicCache / StaticCache / QuantizedCache 的分类与官方内存占用预期 中 / 高 / 低、前缀缓存用法):https://huggingface.co/docs/transformers/main/en/kv_cache
- vLLM 官方博客(DeepSeek-V3.2 在 vLLM 中的实现:MLA 每 token KV 缓存 656 字节的完整布局——512 字节 fp8 无位置编码部分 + 16 字节 float32 缩放因子 + 128 字节 bf16 解耦 RoPE 且不量化;block size 64;索引器 top-2048;索引器 key 缓存单独分配缓冲、按块存储且与 MLA 主缓存分开;推荐起跑配置 16×H100 / 8×H200 / 8×B200):https://vllm.ai/blog/2025-09-29-deepseek-v3-2
- vLLM 官方博客(大规模服务:张量并行对 MLA 不优、潜在投影在各分片复制、DeepSeek-V3 张量并行下每张 H200 剩 34GB 显存、Wide-EP 与专家并行):https://vllm.ai/blog/2025-12-17-large-scale-serving
- DeepSeek-V4 官方技术报告(CSA + HCA 交错混合注意力、CSA 压缩率 与 HCA 压缩率 、DSA top-k(Flash 512 / Pro 1024)、共享 K/V 的 MQA、窗口 的滑动窗口分支、Flash 前两层纯滑动窗口 / Pro 前两层 HCA、共享 K/V 每条目 512 维、索引头维 128、1M 上下文下 V4-Pro 27% 单 token 推理 FLOPs(等值 FP8)与 10% KV cache、V4-Flash 10% 与 7%、KV cache 约为 BF16 GQA8(head dim 128)基线的约 2%、V4-Pro 1.6T 总参 / 49B 激活、V4-Flash 284B 总参 / 13B 激活、预训练 Flash 32T / Pro 33T token):https://arxiv.org/abs/2606.19348 与全文 https://arxiv.org/html/2606.19348v1
- vLLM 官方博客(DeepSeek V4 适配: 约 1/4、 约 1/128 的跨 token 压缩、256 原生位置逻辑块、五类缓存归并为 3 种页大小、1M 上下文、V4-Pro 1.6T 与 V4-Flash 285B(官方报告为 284B)、索引器缓存 fp4 + 注意力缓存 fp8;附录给出 V3.2 的 bf16 账:MLA 1152 B + 索引器 256 B = 1408 B/token/层、61 层约 83.9 GiB):https://vllm.ai/blog/2026-04-24-deepseek-v4
- DeepSeek-V3.2 官方技术报告(DeepSeek Sparse Attention,稀疏注意力作为降低长上下文计算复杂度的路线):https://arxiv.org/abs/2512.02556
- vLLM 官方文档(自动前缀缓存:复用相同前缀的 KV,收益主要在预填充阶段,不减少解码时间):https://docs.vllm.ai/en/latest/features/automatic_prefix_caching/
数字核验口径说明:
- 表中”每 token 每层元素数”直接取自 DeepSeek-V2 报告的注意力机制缓存对照表(MHA 为 、GQA 为 、MQA 为 、MLA 为 ),换算到单层即为表内数值。
- 本文出现过的比例数字及其出处,按”出现在哪一层”分开列:
- 正文里引用的官方比例:93.3% 出现在第十四节(原文已注明是相对 DeepSeek 67B 的官方口径,且 67B 本身已用 GQA)。
- 只在来源清单里、正文并未使用:42.5% 与 5.76 倍(DeepSeek-V2 条目)。
- 正文自行计算的比例:1/57、1/128(含 n_g=8 的 1/8)、约 2.3% 余量等,均由官方维度直接算出,算式见本节其余各条。
- 上一稿的本说明曾把 98.2%(按 DeepSeek-V3 官方维度自行计算的 MHA→MLA 降幅)标成”正文出现的比例数字”,这与事实不符——该数字正文从未使用,已删除;正文用的是 1/57 与 576 两个可直接复算的口径。
- 其余字节数与并发数均为上述官方维度下的直接乘法结果,计算过程:每 token 每层元素数 × 层数 × 序列长度 × 每元素字节数(bf16 取 2)。压缩比 56.9 ≈ 1998848 / 35136,四舍五入为 1/57。上下文口径上,1M 一律按 token、128K 一律按 token 换算。
- 第十节 40 KiB 门槛的余量核验:仅 MLA 主缓存时 ,余量 2.3%;计入 DSA 索引器缓存后,按 vLLM 自述的 1408 B/token/层(bf16)为 54.33 KiB、超门槛约 36%,按索引器与主缓存同类的 fp8 + 缩放方案(约 144 B/token/层)为 47.66 KiB、超门槛约 19%。这两个数是本刊按 vLLM 公开的缓存构成自行换算的标注口径,不是官方给出的数字。
- DeepSeek-V4 的注意力设计细节以官方技术报告 arXiv:2606.19348 为准(CSA / HCA、、、、共享 K/V 的 MQA、前两层排布、27%/10% 与 10%/7%、约 2%、32T token、1.6T/49B 与 284B/13B),出处见上方来源清单;
c4a/c128a、256 原生位置逻辑块、三种页大小、索引器 fp4 + 注意力 fp8 属于 vLLM 官方博客的推理引擎侧实现口径,正文已分别标注来源层级。 - 核验日期:2026-09-29。