news 2026/9/8 17:52:35

KV Cache优化全解析:从GQA/MLA到PagedAttention与KV量化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
KV Cache优化全解析:从GQA/MLA到PagedAttention与KV量化

做LLM服务化部署的人,几乎都会遇到同一个怪现象:一个小模型本身没多大,可一旦并发和上下文长度上来,GPU显存就像漏了一样。我自己最早部署7B模型时也踩过这个坑。FP16权重大概14GB,结果输入输出写长一点,加上几路并发,显存一下子就飙到20多GB。多出来的这部分开销,几乎全是KV Cache。

KV Cache可以说是LLM推理优化里最核心的一环。只要你在用vLLM、SGLang、TensorRT-LLM这类推理框架,或者只是自己在研究大模型推理性能,最终都会回到一个问题上:怎么把KV Cache的显存压下去。这篇文章就把KV Cache的来龙去脉,以及目前主流的内存优化方法——GQA/MQA、MLA、PagedAttention、KV量化、滑窗注意力、Prefix Cache——串起来讲一遍,重点说清楚它们各自解决什么问题,实际落地时怎么选。

1. KV Cache 到底存了什么

1.1 自回归生成里的重复计算

主流LLM都是decoder-only架构,生成时走的是自回归流程:给定prompt,模型先整体算一遍,生成下一个token,然后把这个token拼回原始序列,再预测下一个token。问题在于attention层里,每个位置都要和之前所有位置做交互。如果不做缓存的朴素实现,那么在生成第t+1个token时,模型会重新把前t个位置从头到尾完整算一遍,重新得到它们的K和V,再计算注意力分数。这相当于每一次解码都要重复之前算过的全部attention计算,复杂度随序列长度呈平方级上升,GPU利用率会掉到没法看。

KV Cache的本质就是拿显存换算力:把前t个位置已经算好的K和V矩阵缓存下来,下一次解码直接取出来用,只增量计算新token的K和V。这样做之后,decode阶段每一步的计算量只跟单token相关,虽然仍是串行生成,但整体吞吐比朴素实现高出一个数量级。

这里有个值得新手注意的点:KV Cache并不是一个可选开关,而是自回归解码的必然产物。只要你想避免重复计算,就必须把历史K/V留给后面的解码步用。差别只在于你用什么数据结构存、存成什么样、能复用多少。

1.2 KV Cache 体积怎么算

一次推理需要缓存多少?有个很直白的公式:

KV Cache Bytes = 2 × 层数 × 头数 × 每头维度 × 当前序列长度 × Batch Size × 每元素字节数

这里的2代表K和V两个矩阵。以常见的7B模型为例,32层、32个注意力头、每头128维、FP16存储,每个token产生的KV Cache大约是:

32层 × 32头 × 128维 × 2矩阵 × 2字节 = 512KB。

也就是说,一个token就要吃掉512KB显存。如果上下文长度是4096,单条请求就是2GB;8条并发,就是16GB。而7B模型权重在FP16下也不过14GB左右。KV Cache一多,直接反超权重,成为显存天花板。

扩展一下:

上下文长度单条请求KV8条并发KV
20481GB8GB
40962GB16GB
81924GB32GB
3276816GB128GB

所以,显存里的变量其实有两个:一个是模型权重,数量固定;另一个是KV Cache,会随着序列长度、并发数、量化位宽不停变化。这也是为什么部署大上下文模型时,经常出现权重明明放得下,并发一上来直接OOM的原因。

1.3 为什么KV Cache会成为推理吞吐瓶颈

推理过程可以分成两个阶段。prefill阶段把prompt一次性并行计算,这个阶段主要耗在计算上,KV Cache在建好后就已经完整落入显存;decode阶段则是每步只生成一个token,新增的KV少,但每一步都要从显存里把历史KV读出来做注意力计算。

随着序列加长,每个新token需要读出的KV字节数也线性增长,模型就会卡在显存带宽上,GPU算力再高也吃不满。这就是为什么很多长上下文场景里,decode速度明显变慢,而不是因为模型算力不够。

很多时候我们看显存,只看到CUDA进程占用,没有拆解它的构成。一次完整的推理显存大致是:权重 + 激活 + KV Cache + 临时buffer。对于长上下文和并发高的场景,KV Cache往往是最大项。KV Cache优化本质上有两个方向:一是从结构上减少需要缓存的字节数,二是从分配和调度上提高显存利用效率。讲完这些基础概念,下面几种主流方法就都好理解了。

2. 从模型结构上让它变小:MQA/GQA/MLA

2.1 MHA 的存储瓶颈在哪

标准Transformer用的是MHA(Multi-Head Attention),每条注意力查询由多个头并行计算,每个头有自己独立的K/V投影。多头设计给了模型很强的表达能力,但代价是每个token、每一层都要为每个头单独存一份K、V。这就是1.2节那个512KB/token的主要来源。

MHA是大多数经典LLM的默认结构,比如早期GPT系列、很多开源小模型都用它。它的计算特点是灵活,但内存特性不好。我们经常看到“同样的模型,为什么新版本显存省这么多”的对比,本质往往是新版本把MHA换成了GQA或MLA,而不是权重变小了。

2.2 MQA:让所有头共享一组KV

MQA(Multi-Query Attention)的思路非常激进:所有注意力头共用一个K投影和一个V投影。这项技术最早可以追溯到2019年Shazeer的工作,当时的出发点就是降低机器翻译场景里的显存和带宽压力。后来不少对吞吐要求极高的在线服务模型也沿用了这个设计。

原本每个头单独存一份KV,现在整个层只存一份,KV Cache体积直接缩小到头数分之一。以32头的7B模型为例,每token的KV从512KB降到16KB,40万个token的KV也才一个多GB,内存压力骤减,解码带宽需求也随之下降。

但MQA不是没有代价。所有头共享同一份KV,意味着模型对上下文信息的拆解方式被限制住了,在需要细粒度关注多样内容的场景下,精度会有损失。因此MQA更多出现在对吞吐要求极高、对质量不是最敏感的任务里,比如一部分机器翻译、对话场景,以及早期的PaLM等模型。

2.3 GQA:分组共享,实际落地最广

MHA太费内存,MQA又太省表达,GQA(Grouped-Query Attention)取了一个中间值:把注意力头分成若干组,每组内部共享一份K/V。组数设成1,退化成MQA;组数等于头数,退化成MHA。实际最常用的是8组、4组这类配置。

很多近两年的主流模型都采用了GQA。Llama 2 70B、Llama 3系列都默认使用GQA,常见KV组数是8。以32个Q头为例,KV Cache能降到原来的8/32,也就是四分之一。表达能力损失比MQA小得多,显存收益又很明显,所以GQA几乎成了新一代模型的默认选择。

为什么8组这么常见?因为8这个数字在表达能力和存储之间平衡得比较好。组太少,KV Cache省得多,但对上下文表达的细粒度会下降;组太多,省得有限,结构优势就体现不出来。在Llama这个规模上,8组基本能保持接近MHA的效果。

这里需要提醒一点:GQA/MQA是模型结构层面的事,不是推理参数。你要是拿到一个MHA模型,想通过配置把它变成GQA是不行的。不过这也不是完全不能改,GQA论文里提过一种做法:从MHA checkpoint出发,把同一组Q头对应的K/V投影取平均,得到一个GQA初始化,再做短期的继续训练。这个叫uptraining,属于改结构的范畴,不是部署阶段能简单做到的。所以做推理选型时,直接看模型config里的num_key_value_heads字段,这个数越小,KV Cache越省。

下面做个直观对比:

方案每层KV组数相对MHA存储表达能力特点典型代表
MHAh1倍最强,内存最费早期GPT、不少开源小模型
MQA11/h吞吐优先,精度略损PaLM、部分翻译模型
GQAgg/h接近MHA,内存省Llama 2/3、Qwen2等
MLA低秩压缩明显缩小接近MHADeepSeek-V2/V3

2.4 MLA:把KV再压进低秩空间

MLA(Multi-head Latent Attention)是DeepSeek-V2开始使用的方法。它的核心思路是:不再把每个头完整的K/V直接存下来,而是先用低秩矩阵把KV压缩成一个小得多的latent向量,推理时缓存这个压缩后的向量,等计算注意力时再通过升维投影把K/V还原出来。

这个手段和量化完全不同。量化是把已有数值用更低精度表示;MLA是从数学结构上减少了要存的信息量。以DeepSeek-V2为例,每层每token的KV呈现为一个较低维度的latent表示,相比传统MHA要存h×d的完整K/V矩阵,存储压力明显小一个量级,长上下文场景尤其受益。

MLA还有一个容易被忽略的设计点:它一般会额外保留一个低维的RoPE位置编码头,把位置信息从压缩KV里分离出来。否则低秩表示容易削弱模型对位置的感知,长距离依赖会受影响。这也是为什么MLA模型在配置上和传统MHA不太一样。

从部署角度说,MLA模型的KV Cache天然占比更低,同显存能跑的并发更多。如果你正在选新模型,同等参数规模和精度的前提下,优先考虑GQA或MLA结构的型号。它们在KV Cache上的先天优势,会直接转化成部署成本优势。

3. 从内存分配与精度上优化:PagedAttention 与 KV 量化

3.1 PagedAttention 解决什么问题

结构优化决定了每个token的KV有多大,但显存怎么用、能不能用满,是另一个问题。早期推理框架会为每条请求申请连续显存,而且通常按最大序列长度预留。比如配置支持4096长度,就算某条请求实际只生成200个token,预留空间也按4096算。并发一多,预留和实际使用的差距就非常可观,显存碎片也很严重。

PagedAttention是vLLM论文提出的方案,思路和操作系统的虚拟内存几乎一模一样:把KV Cache切成固定大小的块,比如每块16个token,再用一张block table记录每个逻辑块落在显存的哪个物理位置。需要多少块就分配多少块,不要求物理连续。这样显存利用率能接近100%,碎片也被摊平了。

block size是PagedAttention里的一个核心参数。太小,管理开销变大;太大,内部碎片又浪费多。vLLM默认块大小是16个token,多数场景下够用。如果你发现并发很高但大部分请求偏短,可以适当把block size调小一点;反过来,超长上下文为主的场景可以调大。

3.2 实际收益与框架采纳

这个机制落地后,最直观的变化就是吞吐。vLLM相比当时非分页方案,在同样的显存和模型下,吞吐能提升数倍。原因不是单次计算变快了,而是同等显存条件下能塞进的并发请求显著增多。后来这种设计也被其他框架吸收,今天你看到的TensorRT-LLM、SGLang里的paged KV cache,基本是同一个思想。

这也解释了为什么vLLM部署时--gpu-memory-utilization不建议设成1.0。KV Cache是动态分配的,一旦显存不够,解码就可能OOM,或者调度器会把新请求排队。经验值是给权重、激活和KV Cache整体预留10%上下的余量,0.85到0.95之间比较常见,具体取决于模型规模、上下文长度和并发预期。

3.3 KV Cache 量化的基本思路

再往下压就是精度维度。KV Cache量化,就是把K、V矩阵从FP16存成更低精度的格式,比如FP8、INT8甚至INT4。以FP8为例,单元素从2字节变成1字节,KV Cache体积直接减半;INT8同理。这个方法简单有效,但对量化粒度和outlier要格外小心。

这里说的outlier,是LLM里一个绕不开的现象:注意力机制的少数维度会出现极大数值,如果不单独处理,统一缩放之后,那些小数值会被压缩得几乎没有分辨率。大规模实验还会发现,K和V对量化的敏感度不一样。K矩阵往往存在明显的通道级outlier,少数channel数值特别大,如果按整块统一scale,误差会被放大;V矩阵的分布相对均匀,更适合细粒度量化。

所以主流做法是混合粒度:对K做per-head加per-channel量化,对V做per-token加per-channel量化。更精细的方法还会专门处理outlier channel,或按少量校准数据为每层选量化参数。下面把常见粒度整理一下:

量化粒度scale计算范围适用位置特点
per-tensor整层一个scale不推荐单独用实现最简单,outlier影响大
per-channel每个输出通道独立scaleK矩阵缓解通道outlier,内存开销较低
per-token每个token独立scaleV矩阵跟V的分布更契合
per-head每个注意力头独立scaleK矩阵增强对异常头/位置的鲁棒性

3.4 框架里已经有现成开关

好消息是这些方法不用自己实现。vLLM、TensorRT-LLM、SGLang基本都内置了KV Cache量化选项。以vLLM为例,一行参数就能开启FP8 KV Cache:

python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen2.5-7B-Instruct \ --kv-cache-dtype fp8 \ --max-model-len 32768 \ --gpu-memory-utilization 0.9

实测下来,FP8或INT8的KV量化在绝大多数任务上损失很小,尤其配合GQA结构,通常可以无感使用。INT4要看模型和任务,做长上下文或者对质量要求高的场景,建议先在测试集上验证困惑度和下游指标再上,不要因为省显存就盲目把位宽拉满。

4. 从复用与上下文管理上优化:Prefix Cache 与滑窗注意力

4.1 Prefix Cache 解决什么问题

真实业务里,大量请求共享同一个开头。典型场景是对话机器人的system prompt,几千字固定不动的功能定义、few-shot示例轮流出现;RAG场景里,知识库同一段文档会被不同用户反复查询。每次来一个新请求,框架都要重新计算这部分KV。如果能把公共前缀的KV缓存下来,后续请求直接复用,首token延迟(TTFT)可以大幅下降。

Prefix Cache就是把“已经算过的KV”按前缀路径缓存起来,命中时跳过重复prefill。命中率取决于前缀是否完全一致。vLLM里叫Automatic Prefix Caching,SGLang里叫RadixAttention,底层通常是一棵前缀树:一个请求结束后,它的KV块不会立刻销毁,而是挂在树上,供下一个相同前缀的请求复用。

4.2 工程上怎么让命中率更高

想让Prefix Cache充分发挥作用,prompt结构要设计得对缓存友好:固定内容尽量放在最前面,动态内容放到尾部。比如system prompt保持不变、few-shot固定、用户输入拼在最后。如果每次都把用户当前输入重新拼接在整个模板的前端,前缀就会频繁变化,缓存基本失效。

在RAG知识库这种场景,前缀缓存的收益尤其明显。同一份文档被多个用户查询时,文档内容部分的KV可以被复用,每个新问题只需要计算问题部分和回答部分的KV。命中率好的时候,TTFT可以压到原来的几分之一,这个优化在超长知识库场景里比单纯增加推理并行度更划算。

框架层面,vLLM有--enable-prefix-caching这样的开关,SGLang的RadixAttention也会默认参与路由和缓存。不过具体到各个版本,默认行为可能不同,上线前最好确认一下文档,别把开关写上去就以为生效了。

还需要留意一个坑:Prefix Cache匹配的是完全一致的token序列,不是语义相似。只要系统提示词里多了一个空格、改了一个词,前面那些块的缓存就作废,只能从头prefill。所以线上如果想用前缀缓存吃满命中率,prompt模板必须严格固定,版本间变更也要谨慎处理。

4.3 滑窗注意力控制上下文记忆边界

另一个方向是限定到底要不要对所有历史token保留KV。滑窗注意力让每个位置只与最近W个token做注意力交互,第t个token不会再去看第t-W之前的token。这样在内存管理上,可以只保留一个窗口长度的KV,旧token的KV块会被新token覆盖。W固定后,KV Cache大小就和上下文总长度解耦了,不管用户输多长,KV都能维持在一个相对恒定的规模。

从计算量上看,传统注意力每个token要跟整个历史做交互,复杂度接近序列长度的平方;滑窗注意力把范围限制在W以内,复杂度降成跟n×W同一量级,这也是它能明显降低KV带宽占用的原因。

Mistral 7B的模型结构里就用到了滑窗注意力,窗口配置为4096。实际推理时往往配合滚动缓存实现:新的token不断写入,旧的token KV块被覆盖,显存占用保持恒定。代价也很明确,窗口外的上下文信息模型完全看不到,长程依赖能力被削弱。所以滑窗适合对内存友好、又不太需要超长记忆的场景;如果要支持64K+上下文并保留全局依赖,一般不会只靠滑窗。

4.4 这些方案和前面的组合性

需要强调一点,滑窗通常是模型结构预先决定的,不是部署参数。选了带滑窗的模型,就要接受它的记忆边界;选了长上下文模型,就得靠GQA/MLA先把KV压小,再用分页和量化提升显存利用率,最后用Prefix Cache减少重复计算。每一层解决不同环节的问题,这就是为什么现代推理框架会把它们全部集成在一起,而不是只挑一个用。

5. 工程落地:组合式优化与实战参数

5.1 不同部署场景怎么组合

不同业务的显存瓶颈往往不一样,组合策略要跟着场景走。我按常见场景做了个组合参考:

场景推荐组合理由
高并发短对话GQA/MLA + PagedAttention + Prefix Cache并发多,显存利用率优先,前缀固定可复用
长文档问答GQA/MLA + FP8/INT8量化 + PagedAttention上下文长,KV占大头,量化收益显著
流式低延迟GQA/MLA + Prefix Cache + 适度量化减少首包延迟优先,decode带宽要控
本机显存有限滑窗模型或量化 + PagedAttention显存有限,接受一定精度取舍

实际动手前,我建议先做一个“显存快照”。找个压测脚本,把并发从1加到8,分别记录每档下的KV Cache占用量、每请求首token延迟和吞吐。这样能快速看出瓶颈到底在KV Cache总量,还是在运行时调度,再决定优先做结构级优化还是部署参数优化。

压测时别只看首包,要看稳态吞吐和P99延迟。KV Cache在压力测试中往往要到中后段才显示威力,因为并发上来了、上下文也累积了。很多问题在低并发下完全看不出来,一上生产就暴露。

5.2 常见误区与排查技巧

部署踩坑的案例我见过太多了,把反复出现的问题汇总一下:

  • 只估权重不看KV。很多新手估显存只算权重,结果并发一起来直接爆显存。用7B那个例子就能提前算出KV大概占多少,提前预留。
  • max-model-len开得太大。上下文长度设到最大没有错,但它会直接影响KV Cache总量。如果业务里大部分请求都用不到那么长,建议按90分位长度去配置,而不是按极限长度硬扛。
  • 量化直接上INT4。KV量化一提,就有人直接上INT4。除非任务对精度极其宽容,否则先从FP8/INT8起步,用测试集验证后再考虑更低精度。
  • 并发过高导致排队/拒绝,先怀疑模型坏了。经常看到服务明明有显存余量,却不断拒掉新请求,其实是KV Cache动态分配触顶,调度器在保护已有请求。这类问题要看框架指标,别急着重启。
  • nvidia-smi看不到KV Cache占用。GPU进程显存是整体的,vLLM、SGLang内部会单独管理KV块,要用框架自己的指标看,比如vLLM的Prometheus接口里就有vllm:num_requests_runningvllm:cache_usage等。

做成速查表更直观:

现象常见原因排查方式
显存充足但OOMKV Cache动态分配触顶查看框架cache_usage指标,调低并发或max-model-len
TTFT时高时低Prefix Cache命中率不稳检查prompt前缀是否完全一致
decode速度变慢KV过大,显存带宽不够启用KV量化,或换GQA/MLA结构模型
并发上不去权重+KV+激活总和超限用压测脚本做显存快照,拆解瓶颈

5.3 我再分享几个实操经验

第一,优化顺序很重要。我个人的习惯是:先看模型结构,GQA/MLA选型带来的收益最大,部署参数只是锦上添花;再看调度和分配,PagedAttention能释放的显存红利比想象中多;最后才上量化,因为量化要跟模型精度一起验证,成本最高。

第二,KV量化最好跟着模型精度测试一起做。只测一个指标永远不够,比如长文本任务要同时看困惑度、摘要任务ROUGE、问答命中率。量化的收益是显存减半,但如果给业务带来1%的精度损失,可能比多买一张卡更贵。

第三,Prefix Cache在高命中率场景下是优先级很高的优化。同样的显存下,它不减KV总量,但能把大量重复计算直接消掉。对话机器人和RAG这类固定前缀设计良好的业务,TTFT能成倍下降,这个体验提升用户感知很明显。

于我而言,KV Cache优化的收益顺序永远是:先看模型结构,再调分配调度,最后才上量化。结构上省下来的内存是乘法级的,后面做的所有优化也都会放大这份收益。希望这篇梳理能让你配置推理服务时少些试错,多些底牌。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 17:52:08

易语言前端+PHP后端:MuX云切片转码系统源码剖析与实战部署

简介:MuX云切片转码系统源码是2023年的完整项目,前端为易语言配合EXUI插件实现的全新界面,后端为PHP代码且全开源、无加密,便于二次开发。系统主要面向需要搭建视频切片转码平台、实现TS图床加密播放以及对接支付宝当面付的开发者…

作者头像 李华
网站建设 2026/9/8 17:52:00

Kotlin + 无障碍服务打造老人极简桌面:一键视频通话实战

去年国庆回老家,我发现爷爷的手机桌面乱得可怕。壁纸是他孙女的照片,但上面叠了一层又一层购物推送、直播入口和“清理加速”的小红点。他不敢乱点,又总误触,每次想把屏幕调回拨号界面都要找半天。更扎心的是,他儿子在…

作者头像 李华
网站建设 2026/9/8 17:50:28

爱享素材下载器傻瓜级指南:3步免费抓取视频号抖音视频

爱享素材下载器傻瓜级指南:3步免费抓取视频号抖音视频 【免费下载链接】res-downloader 视频号、小程序、抖音、快手、小红书、直播流、m3u8、酷狗、QQ音乐等常见网络资源下载! 项目地址: https://gitcode.com/GitHub_Trending/re/res-downloader 爱享素材下…

作者头像 李华
网站建设 2026/9/8 17:47:24

ARM官方MCU关键词识别项目ML-KWS-for-MCU源码深度评测

干嵌入式语音交互这一行的人,多多少少都会碰到一个绕不开的名字:ML-KWS-for-MCU。这是 ARM 官方放出来的一个面向微控制器的关键词识别(Keyword Spotting,KWS)参考实现,整个项目的训练、模型转换、量化、部…

作者头像 李华
网站建设 2026/9/8 17:45:37

LangChain杂记(python版本)

1.连接大模型先在项目根目录下面建".env"文件,用于存放一些环境变量连接大模型首先要配置好自己的api key和厂商的base url,将这些信息放在.env里面,便于统一管理,也便于后期上传代码到远程仓库时忽略这个文件&#xff…

作者头像 李华