(四)KV Cache 放不下?MLA 把它压成 512 维

ClawLihai · · 共 3,192 字 · 约 11 分钟读完

MLA 低秩压缩封面

地基打完,开始拆第一道墙:放不下

上一篇我们把标准注意力从零拆了一遍,最后落在三块地基上:注意力怎么算、为什么是 N²、KV Cache 是个什么东西。地基篇留的钩子很具体——凭什么 GLM-5.2 敢把每个 token 的 K/V 压到只存一个 512 维的潜在表示?

这篇就来兑现这个承诺。它破的是墙 2:放不下

进度感:本篇破墙 2(放不下 KV Cache)
墙 1(算不动 N²)→ 已铺垫,第 5 篇破
墙 2(放不下)→ 本篇 MLA 在破 ★
墙 3(推不动)→ 第 6 篇 MoE
墙 4(蹦得慢)→ 第 7 篇 MTP

先把墙 2 量化:KV Cache 到底多大

上一篇讲了 KV Cache 的设计:模型生成时,每个历史 token 在每一层都要存一份 K 和 V,供后面的 token 来查,免得每蹦一个字都把前面全重算一遍。聪明的设计,代价是占地方。

占多大?拆 config 算一笔账。GLM-5.2 有 64 个注意力头,每个头的 K/V 维度不小:

qk_head_dim: 256      # 每个头的 K 总维度(含位置编码分量)
v_head_dim: 256        # 每个头的 V 维度
num_attention_heads: 64

单个 token 在单层的 K 和 V 合起来:64 头 × 256 维 × 2(K 和 V)≈ 3.3 万个数。再乘上层数和上下文长度:

每 token 每层: 64 头 × 256 维 × 2(K,V) ≈ 3.3 万个数
× 78 层 × 100 万 token(1M)
→ 约 2.5 万亿个数
→ bf16 下约 5 TB

5 TB。 这里按每头完整 256 维算(nope 192 + rope 64);地基篇算的 3-4TB 用的是每头 192 的纯内容维度,没算位置分量。两种都是真实账,差异只在算不算那 64 维位置编码。这还只是 KV Cache,没算模型权重本身——一块 H100 只有 80GB 显存,5TB 要 60 多块卡才装得下光是”记忆历史”这件事。这就是墙 2,算得动是一回事,存得下是另一回事

KV Cache 爆炸:1M × 78 层 × 64 头 → 约 5TB

这对你用 AI 意味着什么: 为什么长对话聊到后面,AI 开始”忘掉”前面说过的话?很多产品不是不想记住,是 KV Cache 把显存吃满了,只能截断或清缓存。你感受到的”健忘”,本质是显存账本上的取舍。 懂了这点,你就明白为什么压缩 KV Cache 直接等于”AI 能记住更久”。

GLM-5.2 破这道墙的武器叫 MLA(Multi-head Latent Attention,多头潜在注意力)。它能把这 5TB 压到能在普通多卡上跑。怎么压?答案藏在一个你其实早就熟悉的直觉里。

压缩的底牌:数据有冗余

先建立直觉——凭什么数据能被压缩?因为它有冗余。

例子一:JPEG 图片

一张 4096×4096 的照片有 1600 万个像素值。但相邻像素的颜色通常很接近——天空一片蓝,皮肤一片黄。这种”相邻相似”就是冗余。JPEG 利用它,把 1600 万个数字压成几百 KB;解压时重建图片,不是 100% 相同,但肉眼看几乎一样。

例子二:身高体重表

假设你有 1000 个人的身高和体重,表面上是 1000 行 × 2 列 = 2000 个数字。但身高和体重高度相关(高的人通常更重),所以这 2000 个数字可以这么压:

压缩版:
  1000 个人的身高(1000 个数字)
  + 一个公式: 体重 ≈ 0.9 × 身高 − 88
→ 1001 个数字,能恢复出近似的 2000 个

2000 个压成 1001 个,靠的就是”体重和身高相关”这层内在结构。

这就是”低秩”的直觉:数据有内在结构(相关性),所以能用更少的信息近似恢复。

压缩的底牌是冗余:JPEG 丢高频、身高体重用公式

例子三(最关键):4 维压成 2 维

这个例子直接对应 MLA 的原理。假设一个 4 维的向量 K,其实是由两个”基础方向”组合出来的:

基础方向 A = [1, 1, 1, 1]
基础方向 B = [1, 2, 3, 4]

K = 3 × A + 1 × B = [4, 5, 6, 7]

K 是 4 维,但**只需要存 2 个系数(3 和 1)**就能完整恢复它——因为 A 和 B 是预先知道的(训练学出来的):

压缩: K = [4, 5, 6, 7]  →  只存系数 [3, 1]   (4 维 → 2 维)
恢复: [3, 1] × {A, B}    →  [4, 5, 6, 7]      (2 维 → 4 维)

4 维压成 2 维:两个基础方向 + 两个系数

这就是 MLA 压缩的数学本质:把高维向量,分解成”少数基础方向的线性组合”。 存系数(低维),用时再恢复(高维)。4 维能压成 2 维,是因为 K 本来就躺在一个 2 维的子空间里——它”看起来”是 4 维,“实际上”的有效自由度只有 2。

把原理套到 GLM-5.2:MLA 怎么压

现在把这个原理套到真实参数上。

要压的东西有多大

上面算过,每个 token 在每层要存的 K,展开是 64 头 × 256 维 = 16384 维。这个 16384 怎么来的,拆 config 看得很清楚:

qk_nope_head_dim: 192   # 不带旋转位置编码的部分
qk_rope_head_dim: 64    # 带旋转位置编码的部分
# 两者相加 = 256,就是每头的 K 总维度(qk_head_dim: 256)
64 头 × 256 = 16384

顺便说一句,地基篇里我提过 head_dim: 192——那是标准多头注意力里”每头纯内容”的维度(即这里的 nope 部分)。GLM 每头的 K 实际还多带 64 维的 RoPE 位置分量,合起来才是 256。所以 16384 要用 64×256 来算,不是 64×192。

MLA 的两步:压、再还原

MLA 发现:这 16384 维的 K,其实可以用一个 512 维的”潜在向量”近似恢复出来。512 这个数同样是 config 写死的:

kv_lora_rank: 512

具体分两步走。

压缩(存进 Cache 前):

上一层的输出 x(6144 维)

    │  × 压缩矩阵 W_DKV(6144 → 512)

潜在向量 c(512 维)   ← 这才是真正存进 KV Cache 的东西

恢复(要用 K 时):

潜在向量 c(512 维)

    │  × 恢复矩阵 W_UK(512 → 12288)   ← 只还原内容部分(64 头 × 192 nope)

恢复出的 K'(近似 K 的内容部分,nope)

V 用对应的恢复矩阵 W_UV 同理还原。注意这里只还原了 nope 内容部分——那 64 维的 rope 位置分量走的是另一条路(不压缩、单独存),不进这条压缩链路。这正是下面”解耦设计”要讲的事,也解释了为什么前面说每头完整是 256 维、却只压 192 维的内容。

关键:压缩和恢复矩阵是训练学出来的

W_DKVW_UK 不是人手设计的,是训练学出来的。训练给它们的目标很明确:让恢复出的 K’ 和原始 K 尽量接近,同时潜在向量 c 尽量小。

打个比方,JPEG 算法也是”学”出来的——它发现”丢掉人眼不敏感的高频信息损失最小”。MLA 的两个矩阵是训练自己摸索出”用 512 维怎么最优地压缩 16384 维”,模型自己学会了哪些信息该留、哪些可以丢。

顺带一提:MLA 对 Q 也做了类似的低秩压缩(config 里 q_lora_rank: 2048),但 Q 用完即弃、不进 KV Cache,所以不影响显存账。本篇聚焦影响存储的 K/V 侧。

MLA 压缩-恢复流程:6144 → 512 潜在向量 → 恢复 12288(只还原 nope)

这对你用 AI 意味着什么: MLA 的压缩矩阵是训练学出来的,意味着这套压缩是”为 GLM-5.2 自己量身定做”的——它在自己的数据分布上学会了最优的低秩结构。所以 MLA 压缩的收益不能简单外推到别的模型:同样标 1M 上下文,用 GQA 的模型没有这种压缩,显存账完全不一样。这也是为什么”标称长度”背后藏着完全不同的成本结构。

收益算账:5TB 压到能装下

把压缩前后的账算清楚。压缩后,每个 token 在每层不再存完整的 K/V,只存那个 512 维的潜在向量:

标准注意力(不压缩)MLA
每 token 每层存什么K(16384) + V(16384)潜在向量 c(512)
1M × 78 层 总量5 TB88 GB
压缩比57 倍

(这里的 5TB、88GB、57 倍是我按 config 维度估算的,非官方数字;第 2 篇算路线时用的也是同一套口径。)

KV Cache 从 5TB 降到约 88GB。 这个量级意味着——配合权重的合理切分,普通的多卡集群就能扛住整个 1M 的历史,不再需要 Google 级别的专用集群。这就是 MLA 这道墙被拆掉后的样子。

压缩前后:5TB → 88GB,约 57 倍

这对你用 AI 意味着什么: 为什么同样是”1M 上下文”,有的只能按 API 调用、有的能自己买卡部署?压缩 KV Cache 直接决定了部署门槛。MLA 把记忆从”需要 TPU 海洋”压到”几张消费级/企业级卡能扛”,这是开源模型能让人人跑起来的前提之一。

压成这样,质量不会崩吗

你一定会问:16384 维压成 512 维,信息丢了这么多,模型不就傻了吗?

理论上确实有损。 512 维不可能完美编码 16384 维的全部信息,恢复出的 K’ 只是近似。这一点 MLA 从不回避。

但实际损失很小,原因有三层:

  1. 训练让矩阵学会”丢最不重要的信息”。 就像 JPEG 丢的是人眼不敏感的高频,MLA 丢的是对注意力影响最小的那些维度——不是平均地丢,是挑着丢。
  2. MLA 从预训练就嵌进去了。 模型从第一天训练就在适应这种压缩,而不是训完再硬塞一个压缩层。整个模型是”知道自己在被压缩”的前提下长出来的。
  3. 它已在多代模型上跑通。 MLA 从 DeepSeek-V2 起就在生产里验证,被 DeepSeek、GLM 多代模型沿用至今,效果稳定在第一梯队——如果压缩真的伤筋动骨,这些模型不可能长期保持竞争力。

核心权衡一句话:用一点点精度,换 KV Cache 压缩约 57 倍。 在 1M 上下文这个场景里,这笔账绝对划算——光靠后面 DSA 省计算不够,还得 MLA 省存储,两者联手才能让 1M 从”装不下”变成”装得下”。

MLA vs GQA:开源阵营的内部分化

KV 压缩不是只有 MLA 一条路。这条路上,开源阵营自己就分成了两派,做法完全不同。

GQA:让多个头共享一组 K/V

Llama 3、Qwen3 走的是这条。把 64 个查询头分成几组(比如 8 组),每组共享同一组 K/V:

MHA(标准): 64 个 Q 头,64 组 K/V      → 不省
GQA:        Q 头分组,每组共享一组 K/V   → 压约 4-8 倍(视头数配置)
MQA:        所有 Q 头共享 1 组 K/V      → 压约 64 倍(质量差)

GQA 是”共享头”的思路——不改变每份 K/V 的内容,只是让多份共用,实现简单,压缩比约 4-8 倍(视头数配置)。

MLA:用低秩矩阵压扁 K/V

DeepSeek、GLM-5.2 走的是这条。不共享头,而是把每个 K/V 用矩阵压成 512 维的潜在向量,用的时候再恢复。压缩比约 57 倍,但实现复杂——要存压缩矩阵、恢复矩阵,推理时还要解压。

为什么 GLM-5.2 不选更简单的 GQA

一句话:冲 1M,GQA 的几倍不够,必须 MLA 的约 57 倍。 表里各方案的压缩比按各自头数计,跨模型不可直接横比,只看量级。

MHAMQAGQAMLA
做法每头独立 K/V全共享 1 组分组共享低秩压缩
压缩比~64×~4-8×~57×
质量最好良好接近 MHA
代表早期模型较少用Llama 3 / Qwen 3DeepSeek / GLM-5.2

GQA 适合中等上下文(128K 量级)——简单够用。但要做 1M,KV Cache 的基数太大,几倍压缩后还是装不下,只有 MLA 这种”把每份 K/V 真正瘦身”的狠招才行。

MHA / MQA / GQA / MLA 四种注意力对比

这对你用 AI 意味着什么: 选模型时,“用 GQA 还是 MLA”是个隐形的分水岭。用 GQA 的模型(Llama、Qwen)在中等长度上性价比高,但往 1M 冲会吃力;用 MLA 的模型(GLM、DeepSeek)为超长上下文而生,代价是实现更复杂。你想跑多长的上下文,决定了哪一派更适合你。

一个关键细节:MLA 和 RoPE 的解耦

讲一个略深、但挺关键的细节。

上一篇提过,GLM-5.2 用 **RoPE(旋转位置编码)**给 token 注入位置信息。问题来了:RoPE 是”旋转”,属于非线性变换,没法被低秩压缩——硬把 RoPE 部分也塞进潜在向量,旋转信息就丢了。

GLM-5.2 的解法是把 K 拆成两半,各走各的路:

K 向量 = [nope 部分 192 维(可压缩)]  +  [rope 部分 64 维(单独存,不压缩)]
  • nope 部分(192 维):不带位置编码的纯内容,交给 MLA 的低秩压缩。
  • rope 部分(64 维):带旋转位置编码,单独存储,不参与压缩。

这就是 config 里那两个参数的来历:

qk_nope_head_dim: 192   # 可压缩的内容部分
qk_rope_head_dim: 64    # 不可压缩、单独存的位置部分

这个”解耦设计”让 MLA 和 RoPE 能协同工作——既吃到了 MLA 的压缩红利,又没丢掉 RoPE 编码位置的能力。两个本来打架的设计,靠”分家”和平共处。这是 GLM-5.2 架构里值得多看一眼的一处取舍。

解耦设计:nope(192) 可压缩 + rope(64) 单独存

墙 2 拆完了,但 N² 还在

MLA 把墙 2(放不下)拆了:KV Cache 从 5TB 压到约 88GB,普通多卡装得下了。

但墙 1(算不动)纹丝没动。 MLA 让 Cache 小了,可每次算注意力时,该做的点积一个也没少——1M 上下文还是一万亿次点积。压缩省的是”存”,没省”算”。

这就是下一篇的主题。GLM-5.2 怎么把这一万亿次点积,砍成每个 token 只算 2048 次?答案是它最核心的架构创新——DSA(一种稀疏注意力方案)+ IndexShare。关键是一个叫 **indexer(索引器)**的东西,像一个”机场安检的粗筛台”,先用低成本快速筛出每个 token 该关注哪 2048 个,再用完整注意力只对这 2048 个精算。

更妙的是,GLM-5.2 还让每 4 层共享一个 indexer,把粗筛成本也省掉 3/4。这套组合拳是整个连载的重头戏。

下篇预告:《注意力 N² 太贵怎么办?DSA + IndexShare》


本篇涉及的 config 字段——qk_head_dim: 256qk_nope_head_dim: 192qk_rope_head_dim: 64v_head_dim: 256kv_lora_rank: 512q_lora_rank: 2048num_attention_heads: 64num_hidden_layers: 78hidden_size: 6144dtype: bfloat16——均来自 GLM-5.2 公开的 config.json。KV Cache 的 5TB / 88GB / 57 倍为笔者按 config 维度估算(bf16;57 倍 ≈ 5TB ÷ 88GB),非官方数字,第 2 篇路线对比用的是同一套口径。MLA 的低秩分解、W_DKV/W_UK/W_UV 压缩-恢复机制、解耦 RoPE 设计引自 DeepSeek 论文公开的 MLA 方案,GLM-5.2 直接沿用;MLA 自 DeepSeek-V2 起被 DeepSeek、GLM 多代模型沿用。GQA/MQA/MHA 为业界通用方案,Llama 3 / Qwen 3 用 GQA 据其公开资料,实际压缩比视头数配置(Qwen3 系列约 4 倍、Llama 3 70B 约 8 倍)。无任何闭源或未公开信息。

这个话题还有

🖼 图文卡片版 · 点击放大轮播
双击或滚轮缩放 · 拖动平移 · Esc 关闭