
搞大模型的人应该都绕不开这四个缩写MHA、MQA、GQA以及KV Cache。我见过不少朋友模型跑得挺溜但一聊到这几个概念就有点含糊——有人以为MQA只是某个模型的名字也有人把KV Cache理解成把整张注意力矩阵存下来。这篇就把它们一次性讲透每个方案解决什么问题、结构上到底差在哪、推理时如何真正影响显存和速度最后附上我自己部署和调优时的一些实测经验。这篇东西适合几类人看刚开始学大模型原理、准备面试的开发者已经在用推理框架但想搞清楚显存为什么不够用的工程师还有想做模型量化、长上下文适配的研究者。放心我尽量不堆公式用图和类比把计算逻辑讲明白但该给的计算量也会给方便你直接抄去估算自己的场景。1. 注意力机制的起源与MHA的设计逻辑1.1 从自注意力到多头注意力为什么要把注意力拆成多个头先回到最基础的地方。Transformer里最核心的自注意力做的事情概括成一句话根据当前的输入去序列里找到和自己相关的部分再把相关信息加权汇总。具体操作是先算三个矩阵——QQuery查询、KKey键、VValue值然后通过Q和K的点积计算相关性分数再用这个分数去加权V。你可以这样理解Q是你脑子里的问题K是每本书的目录索引V是书里的正文。你先拿问题和所有目录做匹配找出最相关的几页再把这几页内容按权重拼成最终答案。这就是注意力机制的本质。那多头注意力Multi-Head AttentionMHA是什么呢它不满足于只用一个“问题”而是同时用好几个不同侧重点的问题去检索。每个头拥有自己独立的一组Q、K、V投影矩阵可以关注不同维度的模式——有些头关注语法关系有些头关注长距离指代有些头关注局部语义。最后把多个头的结果拼接起来再过一层输出投影得到最终输出。多头带来的直接好处是模型表达能力更强可以在不同特征子空间里并行捕捉信息。这也是为什么从Transformer开始几乎所有顶尖模型都默认用MHA结构一直沿用到现在。1.2 MHA在训练阶段的组成结构与计算流程把MHA的计算拆开结构其实很清晰。假设输入序列长度是L隐藏维度是d_model头数是h每个头的维度是d_head d_model / h。输入的X先经过三个线性投影得到Q、K、V维度分别是L×d_model然后把d_model这个维度切成h段每段长度d_head就得到了h组独立的Q、K、V。接下来每一组独立做缩放点积注意力公式是 softmax(Q_i K_i^T / sqrt(d_head)) V_i。所有头的结果拼接起来仍然是L×d_model最后经过一层输出投影通常是另一个线性层得到多头注意力的最终输出。训练阶段这个结构没什么问题因为训练时所有token是一次性全部进来的可以高度并行。但到了推理阶段情况就变了模型是逐个token生成的每生成一个新token都要重新对整个序列计算一遍注意力。如果不做任何缓存你会发现前面算过的历史token的Q、K、V全部要重算复杂度随生成步数线性增长这在实际部署中是完全不可接受的。这就是后面的MQA、GQA和KV Cache要解决的核心痛点推理阶段如何避免重复计算、减少显存占用、提升生成速度。2. MQA与GQA减少KV开销的两种解法2.1 MQA所有头共享同一份K和VMQAMulti-Query Attention的提出时间其实很早2019年由Noam Shazeer在《Fast Transformer Decoding: One Write-Head is All You Need》里提出。它的思路非常极端保留多个Q头但所有Q头共享同一个K和同一份V。什么意思呢原来MHA里每个头都有自己的K、V比如32个头就有32份K和32份V。MQA直接把K、V压成一份32个头全部复用这份K和V。这样一来需要缓存的KV数据量直接降到原来的1/32显存占用大幅下降带宽压力也骤减。论文里的测试结论很吸引人在decoder-only模型上MQA能把解码速度提升数倍甚至接近一个数量级同时效果损失在可接受范围内。听起来很美好但实际落地时有个头疼的问题——训练稳定性差。因为所有Q头共享K、V等于严重限制了模型的表达能力还要在训练时花更多心思调节学习率搞不好就训飞了。所以一开始MQA并没有立刻被大规模用到训练中更多是被看作一种推理加速的取巧手段。2.2 GQA分组共享在性能和效果之间取平衡GQAGrouped-Query Attention是Google在2023年的论文《GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints》里提出的。它把MHA和MQA做了一次折中先把Q头分成若干组每组内部共享一份K和V但组与组之间是不共享的。举个例子假设一个模型有32个Q头GQA设置成8组那么每组里有4个Q头共享一份K和V整层就只需要8份K和8份VKV占用降为MHA的1/4。如果GQA的组数设成1就退化成了MQA如果组数设成和Q头数一致就是标准MHA。可以说MHA和MQA都是GQA的特例。为什么GQA能做到效果接近MHA、速度接近MQA核心原因是K、V的信息冗余度比Q高得多模型的性能瓶颈主要出在Q的表达能力上。多个Q头共享K、V后每个Q头仍然能保持相对独立的查询偏好只是从同一份记忆里读取信息这对表达力的损伤远小于直接把K、V抹掉。我在实际训练里也验证过这个结论同样规模的模型MHA和GQA在预训练loss上差距很小但推理时显存和延迟的差距非常明显。所以现在新发布的模型特别是追求推理效率的开源模型几乎清一色选择GQA。2.3 为什么这个优化方向能火起来推理墙的倒逼你可能会问为什么以前大家不关注KV占用现在却人人都在提MQA和GQA答案很简单模型越来越大显存却越卡越紧。大模型训练完之后真正要面对的问题是部署成本。一个70B模型光权重用FP16存储就要140GB需要好几张A100才能塞下。而推理时除了权重KV Cache也要占显存——长上下文场景下KV Cache甚至会超过权重本身的体积。显存不够要么砍并发、要么减少上下文长度、要么换更大的卡哪一种都意味着成本上升。另一方面推理是带宽瓶颈型任务。每个token生成时需要读取权重和KV Cache到计算单元里做矩阵乘而访存速度远低于计算速度。MQA和GQA把KV Cache砍到原来的几分之一甚至几十分之一等于把带宽压力也成比例降下来了解码延迟自然显著下滑。所以在“模型效果不能太伤、推理成本必须降低”的双重约束下GQA成了目前最合理的工程解。它不像MQA那么极端效果可控又能实打实地省下KV占用属于那种“一听就觉得对啊”的设计。3. KV Cache大模型推理提速的关键缓存机制3.1 KV Cache解决了什么问题为什么只缓存K和VKV Cache是大模型自回归推理时的一项基础优化现在的推理框架基本都默认开启。它的思路非常简单把已经算过的历史token的K和V缓存下来避免重复计算。自回归生成是个逐token的过程生成第n个token时注意力层需要计算这第n个token和前面n-1个token之间的注意力分数。如果不做缓存每生成一个新token都要把整个序列的K、V全部重算一遍包括那些历史token的K、V。这些计算在数学上是完全重复的——因为K、V只和token本身以及模型权重有关和当前要生成的新token没有关系。那为什么缓存K和V而不缓存Q呢因为Q是当前这个token的查询向量每生成一个新tokenQ都会变化而历史token的K、V是稳定不变的。所以只需要缓存K、V然后把当前token的K、V追加到缓存里让注意力计算只关注当前Q与全部K的匹配再从全部V里取值。用前面的图书馆类比来说K是书的目录V是书的内容。历史token的目录和内容已经定死了没必要每次都重新翻一遍书Q是当前的新问题只需要拿新问题去匹配旧目录再按相关度取内容就行。3.2 KV Cache的显存占用与计算细节KV Cache虽然减少了计算量但它不是免费的午餐——它变成了显存里的一大块常驻开销。具体占用可以用一个公式算清楚KV Cache大小 2 × 层数 × 序列长度 × KV头数 × 每头维度 × 每个元素字节数那个2是因为K和V各占一份。我举个实际例子以LLaMA 3 8B为例它的结构是32层、8个KV头、每头128维如果用FP16每个元素2字节存储单条序列在8192长度下的KV Cache为2 × 32 × 8192 × 8 × 128 × 2 1,073,741,824字节 ≈ 1GB听起来还能接受对吧但注意这是单个请求。如果并发32个请求光KV Cache就要吃掉32GB显存直接爆掉一张4090。反观如果用MHA结构KV头数从8变成32同样条件下KV Cache直接膨胀到4GB并发压力更大。这就是为什么GQA在部署场景里如此重要的原因。还有一个容易踩的坑KV Cache 和上下文窗口是强绑定关系。模型宣称支持128K上下文但如果你把整条序列拉满KV Cache的显存占用会随序列长度线性增长最后极可能卡死在显存上。这也是为什么有些框架会把KV Cache量化成INT8甚至INT4来节省空间——用一点精度换更长的上下文或更高的并发。3.3 缓存与上下文窗口的关系注意别把KV Cache和“模型看得到多远”搞混。模型的上下文窗口由训练时的位置编码方案决定比如RoPE、ALiBi等而KV Cache只是把当前已生成序列的K、V缓存起来保证模型在窗口内能做完整的注意力计算。当序列长度接近或超过训练窗口时位置编码的外推能力就成了限制因素这个跟KV Cache本身的显存占用是两个不同维度的问题。一个是“能不能算得对”一个是“显存放不放得下”。部署时要两手抓既要考虑模型最大上下文限制也要考虑KV Cache显存占用。工程上更常见的做法是限制实际运行时的最大序列长度。比如模型宣称128K但我一般会在推理框架里把max_model_len设成32K或更低这样能显著降低KV Cache预分配避免显存碎片化和OOM。对你来说一个可以抄的估算法则是允许的最大并发数 ≈ 总显存 - 权重显存 - 冗余预留÷ 单序列KV Cache大小。把这行公式刻在脑子里部署时能省掉一大半试错时间。4. 从理论到实践在真实部署中该关注什么4.1 主流的GQA配置到底怎么设置了解了GQA的原理后下一步就是看各大模型具体怎么配置的。我把常见的开源模型参数整理了一下对你参考会很有帮助模型总层数Q头数KV头数GQA组大小等效KV占用LLaMA 2 7B323232MHA1高LLaMA 2 70B806488低Mistral 7B323284低LLaMA 3 8B323284低LLaMA 3 70B806488低Qwen2.5 7B282847很低从上表能看出一条趋势小模型通常把GQA组大小设在4左右大模型设在8左右极少数追求极致省显存的模型会设到7甚至更高。这里有一个值得说的点LLaMA 2 7B是纯MHA所以推理时的KV占用明显偏大这也是早期部署LLaMA 2 7B时显存容易吃紧的原因之一。到了LLaMA 3这一代连7B/8B这种小尺寸版本都统一改用GQA了说明行业对推理效率的重视已经成了标配。如果你自己训练模型我建议优先从GQA开始组数可以从4或8起步而不是一上来就无脑MHA。4.2 推理框架中的KV Cache优化PagedAttention、缓存量化等除了模型结构本身的GQA优化推理框架也在KV Cache管理上做了大量工程工作这里必须提几个关键点。第一个是vLLM提出的PagedAttention。它的思路是借鉴操作系统的虚拟内存分页机制把KV Cache切成固定大小的块block按需分配、按需释放而不是一开始就为整条序列预分配一大块连续显存。这样做的好处有两个一是显存碎片大幅减少二是可以支持多个请求共享相同前缀的KV Cache比如多轮对话里系统提示词部分完全不用重复计算。我实测过在长上下文多并发场景下vLLM的显存利用率比传统实现高不少。第二个是KV Cache量化。把缓存从FP16降到INT8甚至INT4能直接减少一半到四分之三的显存占用。代价是精度损失但对很多任务来说量化的KV Cache和全精度之间的质量差距并不明显。像llama.cpp、MLC-LLM这类面向边缘设备的推理引擎都把KV Cache量化做成了一等公民功能CPU上都能明显感受到加速。第三个是Prefix Caching前缀缓存。如果多个请求共享相同的开头比如同一个系统提示词、同一段文档正文框架可以直接复用第一次计算好的KV Cache省掉后续请求对这部分的重复计算。这个优化在RAG场景里特别有用常见问题在于命中率取决于请求顺序和前缀长度需要合理调整调度策略。4.3 实际部署中的估值多少显存能做多长的上下文把理论和框架都理清后我来分享一个实战估算案例。假设你手里有一张24GB显存的RTX 4090想部署LLaMA 3 8BFP16权重约16GB剩下约8GB可用作KV Cache和运行时开销。LLaMA 3 8B在GQA配置下单条序列的KV Cache上文已经算过8192长度大约1GB。这意味着理论上一张卡能同时处理约6~8个并发请求且上下文达到8K如果你把max_model_len压到4K并发能力还能再翻一倍。反过来如果序列长度拉到32K单条序列的KV Cache涨到约4GB并发数就得降到1~2个。如果你想在同样的显存里跑更长的上下文一个常用组合是权重做AWQ或GPTQ 4bit量化KV Cache用INT8这样权重降到约5~6GBKV Cache降到原来一半24GB的卡跑一个7B/8B模型的32K上下文是完全可行的。我自己的经验是量化到4bit后的模型效果损失在多数业务场景下基本感知不到但显存余量会宽裕非常多。5. 容易混淆的几个概念与常见误区5.1 KV Cache、MQA、GQA的关系到底是什么先理清一条主线MHA、MQA、GQA是注意力机制的结构设计决定了模型在计算时会产生多少K、VKV Cache则是一种推理时的缓存优化技术解决的是生成阶段如何复用历史计算结果的问题。两者不冲突反而是配合关系。MHA的KV Cache占用大但因为MHA效果好一些场景还是坚持用它GQA/MQA结构把KV数量降下来再叠加KV Cache就能显著压缩显存和带宽开销。所以正确的理解是GQA改造了模型本身让KV Cache变得更“便宜”KV Cache则把“便宜”这个优势落实到推理速度上。还有一个常见误解是把GQA和FlashAttention放在一起比。FlashAttention是注意力计算时的IO优化算法它解决的是访存效率问题主要作用于训练和长序列预填充阶段而GQA解决的是KV缓存体量问题主要作用于解码阶段。两者完全可以共存实际效果是乘法关系不是二选一。5.2 常见误区纠正误区一以为KV Cache缓存的是完整注意力矩阵。注意力矩阵是Q和K的点积结果尺寸随着序列长度平方增长缓存它毫无意义。真正缓存的是K和V矩阵大小只随序列长度线性增长。误区二以为GQA效果一定比MHA差很多。实际差距取决于模型规模、训练数据和训练时长。大模型在足够数据下训练后GQA和MHA的差距会进一步缩小而小模型因为容量有限GQA带来的效果损失会更明显一点。误区三以为KV Cache越大越好。缓存越长显存占用越高内存带宽压力越大生成延迟反而可能上升。合理设定max_model_len、及时清理无效缓存往往比盲目拉长上下文更重要。误区四忽略多轮对话中的历史累计。很多人只算单轮输入的长度忘了多轮对话中每轮回复都会作为历史token继续累积KV Cache几轮下来序列长度涨得飞快OOM多半就是这么来的。5.3 选择策略什么时候用MHA、MQA、GQA如果团队资源充足、且追求极致的模型效果训练阶段还是可以用MHA然后在部署前通过一些转换方法把MHA权重转成GQAGoogle论文里提到的uptraining方法就是这个思路。但这种操作有一定技术门槛不是所有团队都有条件做。如果是从零预训练一个开源模型我的建议是直接上GQA。组数选择上7B~13B量级用8组以内比较稳妥70B量级用8组已经很成熟。MQA除非你明确知道自己在做什么否则不建议用于常规大模型训练它更适合一些极端的边缘部署场景。最后强调一点无论选哪种结构KV Cache的管理都是绕不开的工程主题。模型结构定了之后框架选型、量化方案、并发策略才是决定你部署效果上限的真正变量。我个人在实际部署中最大的体会是大模型推理优化的核心矛盾始终在显存和带宽而不在算力。学习这些概念时别只背定义建议花一个下午把自己显卡跑起来分别测一下不同上下文长度下的生成速度和显存占用比看十篇资料都有用。工具就选vLLM或者llama.cpp配好参数后你会发现原来很多“模型跑不动”的问题本质上只是KV Cache规划没做好。