LLM Infer Infra 学习资料库

复杂模型与 KV 存储:MLA、滑窗、混合状态、MoE 与量化

KV 管理主题 · KV 原理与容量推导 · KV 存储详解 · 互动容量计算器

前面的容量公式 2 × 层数 × KV head 数 × head dim × 字节数 只适用于普通 MHA/GQA。现在很多主流模型不是这样:DeepSeek 系列用 MLA,部分模型混用滑窗层和全局层,还有模型把注意力和 Mamba 类状态层混在一起。模型一变,“存什么、存多大、怎样才算命中”都会跟着变。这一页把这些差异讲清楚,KV 存储岗位面试经常追问。

一句话结论:先问清楚“这个模型每层到底缓存了什么状态”,再谈容量、切分、复用和存储格式。不要把普通 GQA 的公式套到所有模型上。

1. 总览:六类差异一张表

模型特征每层缓存什么容量怎么算TP 切分前缀复用条件对存储的影响
MHA / GQA / MQA每个 KV head 的 K 和 V2 × KV head × head dim × 字节 × tokenKV head 能整除 TP 时均分;不够分时常见实现会复制完整连续前缀标准情况,前面各章的算例都基于它
MLA(如 DeepSeek-V2/V3)一个压缩后的 latent 向量 + 一小段位置相关的 key(latent 维度 + RoPE 维度) × 字节 × token,没有单独的 Vlatent 被所有 head 共用,常见实现在每个 TP rank 各存一份完整副本完整连续前缀单 token 小很多;各 rank 的对象可能内容相同
滑窗注意力(SWA)只需最近 w 个 token 的 K、Vmin(token 数, w) × 每 token 大小同 GQA只需前缀末尾的 w 个 token长前缀下这些层几乎不占空间;命中规则与全局层不同
混合状态(Mamba/SSM、线性注意力)每条序列一个固定大小的状态,每步覆盖更新状态大小,与 token 数无关按实现切分必须有恰好在前缀边界的状态快照不能用分块拼接;只能存“检查点”
MoE注意力部分与对应的稠密模型相同按注意力结构计算,专家数量不影响 KV常与注意力 DP、专家 EP 组合同注意力结构KV 公式不变;部署方式改变 KV 落在哪个 rank
KV 量化(FP8 等)低精度 K/V + 缩放因子元素字节减半(或更少)+ 缩放元数据同原结构同原结构,但格式必须一致dtype 与缩放方案必须进格式身份

多模态、LoRA、投机解码等不改变“每层存什么”,但会改变“什么情况下两段 KV 相同”,放在第 7 节单独讲。

2. MLA:缓存一个压缩向量

2.1 白话原理

普通注意力为每个 head 分别缓存 K 和 V。MLA(Multi-head Latent Attention)的做法是:每个 token 只缓存一个低维的 latent 向量,计算时再从它“展开”出各个 head 需要的 K 和 V。为了让位置编码(RoPE)还能正常工作,另外单独缓存一小段带位置信息的 key。

所以 MLA 的缓存是“一个共享 latent + 一段 RoPE key”,不再是“很多个 head 的 K 和 V”。

2.2 算一笔账

以 DeepSeek-V3 的公开配置为例:61 层,latent 维度(kv_lora_rank)512,RoPE key 维度 64,BF16 每个元素 2 字节。

每层每 token = (512 + 64) × 2 B = 1,152 B
每 token(61 层)= 1,152 × 61 = 70,272 B ≈ 68.6 KiB
8K token 前缀 ≈ 68.6 KiB × 8192 ≈ 549 MiB

作为对比,如果同样的 128 个注意力头用普通 MHA 缓存(K 每头 192 维、V 每头 128 维),每层每 token 是 128 × (192 + 128) × 2 B = 81,920 B,约为 MLA 的 71 倍。这正是 MLA 能支持长上下文的关键原因之一。

注意:以上按 BF16 未量化计算;实际部署还可能用 FP8 KV,或有对齐填充。精确数字以所用引擎的 KV 规格为准。

2.3 TP 下的特殊之处

latent 被所有 head 共用,无法像普通 KV head 那样按 head 切给不同 rank。以 vLLM 学习版本为例,使用 MLA 时每个 GPU 的“KV head 数”按 1 处理,也就是每个 TP rank 都保存完整的 latent。

3. 滑窗注意力:只看最近一段

3.1 白话原理

滑窗层的每个 token 只关注最近 w 个 token。更早的 KV 对这一层已经没用,可以丢掉。不少模型把滑窗层和全局层交替排列:滑窗层省空间,少量全局层保留长距离信息。

3.2 算一笔账

假设一个 48 层模型中 8 层是全局层、40 层是滑窗层,窗口 w = 1024,前缀长 32K token。以“层 × token”作为 KV 占用的单位:

全部按全局层算:48 × 32,768 = 1,572,864
实际:8 × 32,768 + 40 × 1,024 = 262,144 + 40,960 = 303,104
约为全局版本的 19%

3.3 复用规则不同

要从长度为 p 的缓存前缀继续计算:全局层需要 [0, p) 全部 token 的 KV;滑窗层只需要 [p − w, p) 这一段。因此:

4. 混合状态模型:只能存检查点

4.1 白话原理

Mamba/SSM、线性注意力这类层不保存每个 token 的 K、V,而是维护一个固定大小的“状态”,每处理一个 token 就把它更新一次。好处是长度再长,状态大小也不变。

4.2 为什么不能分块拼接

普通 KV 可以按 chunk 存:chunk 0、chunk 1、chunk 2 拼起来就是前缀的 KV。状态层不行:位置 p 的状态已经“吸收”了之前所有 token,无法由几个 chunk 的部分结果拼出来。要从位置 p 继续,必须有恰好在 p 处保存的完整状态快照。

4.3 存储上怎么做

5. MoE:专家不改变 KV

MoE 的“专家”在前馈网络(FFN)里,注意力部分不变,所以每 token 的 KV 大小只取决于注意力结构,与专家数量无关。例如 DeepSeek-V3 是 MoE 模型,它的 KV 大小由 MLA 决定。

MoE 改变的是部署方式:常见做法是注意力用数据并行(DP)、专家用专家并行(EP)。注意力 DP 时,每个 DP rank 保存自己负责的请求的完整 KV,不再按 TP 切分。对缓存系统的影响是:

6. KV 量化:省一半,也多一层格式

把 KV 从 BF16 换成 FP8,元素大小减半,容量、带宽和存储都近似减半。代价有两点:

7. 还会改变“相同”的因素

因素为什么影响复用key 或格式里要体现什么
多模态输入图片、音频常用相同的占位 token 表示,但不同图片产生的 KV 完全不同媒体内容的 hash 必须进入前缀身份。例如 vLLM 学习版本在计算 block hash 时,把多模态内容 hash 作为额外 key 加入;自建缓存必须同样处理
LoRA 适配器同一基座、不同 LoRA,相同 token 的 KV 不同适配器 ID 与版本(vLLM 学习版本同样把 LoRA 名称写进 block hash 的额外 key)
投机解码草稿模型有自己的 KV,与目标模型不能混用按模型分开命名空间
位置编码配置RoPE 缩放等参数变化会改变 K模型配置版本
并行与布局TP/PP 改变每个 rank 保存的内容与形状rank、layout 描述

8. 面试怎么答

问:给你一个新模型,怎样估算 KV 容量并设计缓存?

  1. 先读模型配置和引擎的 KV 规格,按层列出每层缓存的是 KV、latent、窗口 KV 还是状态。
  2. 按层分别算容量再求和,标出哪些层随 token 数增长、哪些固定。
  3. 确认 TP/DP 下每个 rank 保存什么,是否存在复制。
  4. 分别写出每类层的命中条件,取共同可用范围。
  5. 把模型版本、dtype、量化方式、布局、适配器和多模态内容纳入身份与格式。
  6. 用一次正确性实验(对比开关缓存的输出)验证。

常见失分:把 GQA 公式套到 MLA;认为 TP 一定能降低每 rank KV;认为滑窗层缺早期 chunk 就不能复用;认为状态层也能按 chunk 拼接;认为 MoE 的专家数影响 KV 大小。

动手验证:在互动容量计算器里选择“MLA”,层数填 61、维度填 576,可以得到每 token 约 68.6 KiB,并看到 TP 增大时每 rank 占用不变。

输入主题、术语或问题