一篇读懂 KV Cache:推理显存的大头是怎么省下来的

生成一个字,为什么要背一整本账

大模型生成文本是逐 token 进行的:写出第 1000 个词,要用它去「关注」前 999 个词。注意力机制里,这种关注靠的是每个历史 token 的两组中间结果——K(键)与 V(值)。朴素做法是每生成一个新 token,就把前文全部重新算一遍 K 和 V,但自回归生成的每一步都依赖几乎相同的历史,重算意味着巨大的浪费,计算量还会随序列长度平方级增长。

KV cache 的思路因此非常直接:把每个历史 token 的 K、V 存下来,生成新 token 时只算新 token 自己的,历史部分直接查表。这是所有推理引擎的标配优化,效果是决定性的——没有它,长对话和长文档处理在工程上根本不可行。

代价也很直接:显存。KV cache 的大小有一个简单公式:

KV cache 字节数 = 2 × 层数 × KV 头数 × head_dim × 序列长度 × batch × 每元素字节数

系数 2 对应 K 和 V 两份。拿 Llama-2-7B 代入(32 层、32 个注意力头、head_dim 128、FP16 两字节):每生成一个 token 就要新增约 0.5 MB 的缓存;上下文 4096、batch 1 时约 2 GB,batch 4 就是 8 GB。这还只是 7B 的小模型——vLLM 论文实测 OPT-13B 单个 token 的 KV cache 约 800 KB,单个请求最大可占到 1.6 GB。

显存账本:长上下文时代,KV cache 反超权重

2 GB 听起来还好,坏消息在长上下文。用上面公式算 Llama-2-13B:上下文拉到约 27K token 时,光 KV cache 就要 13 GB,与模型权重持平;继续拉长,缓存反超权重。Llama 3 70B 在 128K 上下文、batch 1 的场景下,仅 KV cache 的估算需求就到了 40 GB 量级,而它 FP16 权重约 140 GB——在「一本 200 页文档 + 一次提问」这种场景里,KV cache 常常才是显存的真正大头。更隐蔽的是带宽账:每生成一个 token,完整 KV cache 都要从显存(HBM)读一遍,序列越长,每个字的生成成本越高。

所以过去几年,模型架构层面的演进有一条清晰的主线:砍 KV 头数。多头注意力(MHA)里每个查询头都配一组独立的 K/V;多查询注意力(MQA)让所有头共享一组 KV;分组查询注意力(GQA,Ainslie et al. 2023)取中间——若干查询头共享一组。MQA 压得最狠但质量损失明显,GQA 在两者之间找到了甜点位。Llama-2-70B 用 GQA:64 个查询头只配 8 组 KV,KV cache 直接缩到 1/8。这 8 还有工程上的巧思——恰与 8 卡张量并行的拓扑匹配,每张卡正好分到 1 组 KV 头。4096 上下文下 70B 的 KV cache 约 2.5 GB,同时省下的还有每 token 生成的 HBM 读取带宽。今天的主流开源模型几乎全是 GQA 或其变体。

KV cache 与吞吐的关系可以算得更直白。一张 A100 40GB 服务 13B 模型,权重吃掉约 65%,留给 KV cache 的空间按论文口径只占约 30%——再除以上面那个「单请求最大 1.6 GB」,极限并发不过个位数。并发上不去,GPU 算力就在空转:推理是访存密集型负载,batch 越大越能把算力喂饱。省显存就是提吞吐,这是理解后面所有优化的钥匙。

架构之外,推理引擎层还留着一个巨大的优化空间,这就是 PagedAttention 登场的地方。

PagedAttention:把操作系统的老智慧搬进推理引擎

在算这笔优化账之前,先补一个推理过程的背景。一次 LLM 请求分两个阶段:预填充(prefill)把整段提示词一次性喂进模型算好所有 KV,这一步计算密集、可以大批并行;解码(decode)逐 token 生成,每步把新 token 的 KV 追加进缓存,并读取全部历史 KV——这一步访存密集,GPU 的算力大量空闲。推理引擎的吞吐艺术,本质上是「让尽可能多的请求把两阶段的空闲资源互相填满」,而决定能塞进多少请求的,正是 KV cache 的显存占用。

vLLM 团队在 SOSP 2023 的论文《Efficient Memory Management for LLM Serving with PagedAttention》(Kwon et al.)开头给出了一个惊人的测量:现有系统(FasterTransformer、Orca)为 KV cache 预留的显存里,只有 20.4%-38.2% 存了真实数据——换言之 60%-80% 被白白浪费。

浪费来自三种碎片:预留浪费,系统按「最大可能长度」为每个请求预留连续显存(比如按 2048 token 预留,实际只用到 300);内部碎片,预留块里没用满的部分;外部碎片,多个不连续的小空闲块拼不出一段连续空间。传统方案必须连续存储 KV,就只能靠「按最大预留」来保证不溢出,于是碎片不可避免。

PagedAttention 的解法几乎是照搬操作系统虚拟内存的思想:把每个请求的 KV cache 切成固定大小的块(默认 16 个 token 一块),块与块在物理显存上不要求连续,由一张块表负责映射。显存从此按需分配,碎片被压到每请求最后一块的一点点;逻辑上「相邻」的块物理上散在显存各处也无所谓。

分页还顺手解锁了另一个重要优化——前缀共享。多个请求共用同一段前缀(同一个系统提示词、few-shot 示例,或一次请求并行采样 N 个候选)时,共享部分的物理块只需要存一份,引用计数管理,只在分叉处写时复制(copy-on-write)。论文实测 beam search 场景下内存节省 37.6%-66.3%。你每个请求都要挂的那段 2000 token 系统提示词,从此只占一份显存、只算一次。

块大小的选择也有讲究,论文 7.2 节给过实测:默认 16 个 token 一块在 ShareGPT 这类真实对话负载下表现稳健,16-128 区间差距不大;但在 Alpaca 这类短序列负载上,块设得过大反而明显变差——块越大,平均每请求「最后一块没填满」的内部碎片就越多。这个「没有银弹、看负载特征」的结论,本身就是一份很好的工程提醒。

吞吐的提升来自「省下来的显存装下了更多并发请求」:论文摘要的说法是比当时的 SOTA 系统(FasterTransformer、Orca)提升 2-4 倍,正文实测比 FasterTransformer 最高 22 倍、比 Orca 高 2.7-8 倍(更早流传的「比 HF Transformers 最高 24 倍」出自 vLLM 官方博客的对比,口径不同,引用时别混)。这套分页机制后来成了行业事实标准,vLLM 之外的多数主流推理引擎都以不同形式实现了它。

前缀共享的价值还可以直接用公式算给你看。还用 Llama-2-7B 那个每 token 约 0.5 MB 的数字:一个 2000 token 的系统提示词,KV 占 1 GB;100 个并发用户各自维护一份,就是 100 GB——超过了这张卡的全部显存。而前缀共享把它变成全局只存一份:100 GB 的账单变成 1 GB。这就是为什么企业级部署里「统一的系统提示词 + 前缀缓存」几乎是标配配置,省下的不只是显存,还有每请求重复预填充前缀的计算时间。

再往后:压 KV 的三条路线

分页解决了「存得下」,接下来的问题是「存得小」。三条路线并行推进:

一是量化 KV cache。 K/V 的数值精度从 FP16 降到 FP8,显存直接减半。vLLM 的实测显示精度影响有限(至多约 0.7 个点,AIME25 这类评测恢复约 99% 水平),而配合按通道校准的 scaling 因子还能进一步收窄误差——这是当前服务端最实惠的一项,成本几乎只有配置一行,因此也是生产环境里最先被打开的开关。

二是限制注意力范围。 滑动窗口注意力(Mistral、Mixtral 采用,窗口 4K)只让每个 token 关注最近的一小段,KV cache 上限从「正比于总上下文」变成「正比于窗口 × 层数」,与对话拖多长无关——缓存的上限从此可预算、可保证。代价是窗口之外的历史只能靠层级传递的方式「摘要」保留,适合多数对话任务、不适合需要全文精读的场景。

三是改架构压 KV。 DeepSeek-V2 引入的多头潜在注意力(MLA)把每层的 KV 压成一个低秩共享的潜在向量(约 512 维,外加 64 维解耦的 RoPE 键),KV cache 压缩幅度按不同基准有 93%-98% 多种口径——引用数字时要先问对比基准是什么,但「数量级级别的压缩」这一点各口径一致。代价是实现复杂、与部分推理优化(如某些 KV 量化方案)的兼容需要额外工程。MLA 代表了一个更激进的方向:与其在存量架构上省,不如让架构生来就少存。

三条路线不互斥:GQA 在架构层砍头数,分页在系统层消灭碎片,FP8 在数值层压字节数——生产系统里常常是叠加使用。

小结

KV cache 是那类「不出现就活不下去、出现了又撑爆显存」的必要之恶:它把平方级的重算换成了线性级的存储,然后逼着整个行业在存储上继续创新。这条进化链很值得回味——架构层用 GQA 把 KV 砍到 1/8,系统层用 PagedAttention 把 60%-80% 的碎片浪费收回来,数值层用 FP8 再省一半,MLA 这样的新架构则干脆把 KV 压成一个小的潜在向量。每一层单看都不性感,叠起来才让「百万上下文、每秒千请求」成为可能。理解了这条链,你就理解了大模型推理成本下降的一半来源;而另一半,藏在下一篇文章要讲的权重量化里。

参考资料

← 返回资讯列表

读者留言

COMMENTS 暂无
仅本站原创文章开放留言 · 请勿留下手机号、邮箱等个人信息

还没有留言,来说第一句?