想象一下,你是某大型演唱会的主办方。从前,你的场馆里每位明星(query)都配了一个专属速记员(key/value),一字不落地记录着全程影像。速记员人数与明星相同,成本高昂到难以承受。于是,你尝试砍掉所有速记员,只留一人负责全部记录——省钱了,可这位速记员要同时服务上百位明星,忙得焦头烂额,记录质量急剧下降。怎么办?聪明的你灵机一动:把明星分成几个小组,每组配一位速记员。组内共享记录,组间各司其职。既省了人力,又保留了服务质量。这个巧妙的折中方案,正是我们今天的主角——GQA(分组查询注意力)。它让 Llama 2 等大模型的推理成本大幅降低,而性能几乎无损。

岗位关联

GQA 是解码阶段释放内存带宽的钥匙。掌握它,你就能在模型架构设计、推理性能优化岗位上,用最少的 KV Cache 撬动最大的吞吐量提升。

目录
  1. 可视化演示
  2. MHA内存瓶颈
  3. MQA激进简化
  4. GQA折中智慧
  5. KV Cache机制
  6. 分组比例设计
  7. 解码阶段提速
  8. Llama工程实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示MHA/GQA/MQA三种注意力头结构的KV共享机制,以及KV Cache与推理效率的对比。

本节配有交互动效(进入视口后自动加载,约 3KB)。

MHA/GQA/MQA头结构对比 MHA:每个Q头独立KV头,质量最高开销最大 GQA:分组共享KV头,质量与效率平衡 MQA:所有Q头共享一个KV头,效率最高 KV heads: MHA=8, GQA=4, MQA=1 GQA在质量与效率间取得最优平衡

MHA内存瓶颈

先回到我们的演唱会现场。多头注意力(MHA)是这个场馆的“全明星速记员制”:每位明星(query 头)都配备一名专属速记员(对应的 key/value 头)。这种“一人一记”的豪华配置确实让记录质量无懈可击,可问题也随之而来——当明星越来越多,速记员团队的规模就变得极其庞大,场馆的后台(显存)根本堆不下这么多人手。

这就是 MHA 在推理阶段遭遇的尴尬。Transformer 解码时,需要把历史上所有 token 的键值对(Key 和 Value)缓存下来——这就是 KV Cache。在 MHA 中,每个查询头都有一份独立的键值缓存,假设模型有 32 个注意力头,每层就要存 32 份键值对。乘以 32 层、乘以序列长度、乘以批大小,显存瞬间就被塞满了。

更致命的是内存带宽瓶颈。解码阶段每一步只生成一个 token,却要把缓存中的所有键值从头到尾读取一遍。哪怕计算本身只需要几毫秒,读数据的时间却要几十毫秒——就像演唱会观众只有一位,却要把整个速记员团队叫醒来翻阅所有档案。这种“算的少、读的多”的模式,让内存带宽成了真正的短板。MHA 对显存和带宽的挥霍,正是我们要解决的第一道难题。

回到演唱会:MHA 就像给每位明星配一个速记员,显存就是后台的休息室。明星越多,休息室越拥挤,最终走廊上都站满了人,进出都困难。

KV Cache 内存=2×层数×头数×维度×序列长度×批大小×2(字节)\text{KV Cache 内存} = 2 \times \text{层数} \times \text{头数} \times \text{维度} \times \text{序列长度} \times \text{批大小} \times 2 \text{(字节)}
这个公式说明:MHA 的 KV 缓存占用与注意力头数成正比,头数越多,缓存越大,内存带宽压力越高。
  1. 缓存数量每层每个注意力头的 KV 都要缓存一份,所以先乘上“层数 $\times$ 头数”。
    2×层数×头数2 \times \text{层数} \times \text{头数}
  2. 单头大小每个头的 K 和 V 向量各有一段长度,由维度决定。
    ×维度×序列长度\times \text{维度} \times \text{序列长度}
  3. 总占用最后乘上批大小(同时处理几条样本)和字节数(每维度占用内存)。因子 2 是因为 K 和 V 都要存。
    ×批大小×2字节\times \text{批大小} \times 2 \text{字节}

MQA激进简化

既然“一人一记”这么贵,为什么不试试“全场只用一个速记员”呢?这就是多查询注意力(MQA)的思路——所有查询头共享同一组键值头。相当于演唱会雇了一位金牌速记员,同时为台上所有的明星服务。

这个方案的效果立竿见影。KV Cache 的占用直接从“头数份”缩减到“1 份”。如果原本有 32 个查询头,那么 KV 缓存的内存直接降为原来的 1/32!内存带宽压力也同步大减,解码速度飙升。理论上,这是效率的极致。

然而,代价同样明显。想象这位速记员要同时记录 32 位明星的每句话、每个动作,他能记住谁说了什么?注意力头本应从不同角度(语法、语义、指代)捕捉信息,现在所有头共用一份键值对,信息多样性大打折扣。实验表明,MQA 与 MHA 相比,在质量上确实有所下滑,尤其是在长文本任务上。

更麻烦的是,MQA 的模型训练收敛速度偏慢,且很难通过事后微调从 MHA 直接转换。它像一个“一步到位”的激进改革:省了成本,但伤了元气。MQA 把“省”做到了极致,却也把“好”牺牲了太多,这不是我们想要的终点。

MQA 就像把速记员团队裁到只剩一人,省钱是省到了极致,可这位速记员记不过来。我们需要一个既省钱又不降质的方案。

KV Cache 内存MQA=2×层数×1×维度×序列长度×批大小×2\text{KV Cache 内存}_{\text{MQA}} = 2 \times \text{层数} \times 1 \times \text{维度} \times \text{序列长度} \times \text{批大小} \times 2
与 MHA 相比,MQA 的 KV 缓存占用与头数无关,恒定等于 1 组键值头的开销,节省了近 32 倍显存。
  1. 共享头数所有查询头复用 1 组 KV,所以头数位置直接为 1。
    2×层数×12 \times \text{层数} \times 1
  2. 体积锐减与 MHA 对比,此处少了“$\times$ 头数”这一项,显存与带宽压力大降。
    ×维度×序列长度×批大小×2\times \text{维度} \times \text{序列长度} \times \text{批大小} \times 2

GQA折中智慧

回到演唱会后台,主办方想通了一个道理:我们不需要给每位明星配速记员,也不需要全场合用一个,而是可以把明星分成 4 组,每组配一名速记员。这就是分组查询注意力(GQA)的核心思想。

GQA 的做法非常优雅:假设模型有 32 个查询头,我们设定分组数为 8,那么每组包含 4 个查询头,共享一组键值对。这样一来,KV Cache 的占用就是 MHA 的 8/32 = 1/4,也就是减少了 75% 的显存。而由于 4 个查询头共享一组 KV,它们捕捉信息的多样性虽然比 MHA 略逊,但远比 MQA 丰富。

为什么 GQA 能保持较好的质量?因为注意力头的多样性需求主要集中在 Query 侧——查询角度可以丰富多彩,而 Key/Value 侧记录的事实信息没必要重复太多。比如一段话里的“苹果”,既可以指水果,也可以指公司,但“苹果”这个名词本身对应的键值信息是确定的,不同头只是从不同角度查询它而已。因此,共享 KV 并不会造成严重的信息丢失。

GQA 还具备一个极佳的工程特性:分组数是一个可调节的超参数。设为 1 时退化为 MQA,设为与头数相同时退化为 MHA。它像一个可伸缩的旋钮,让我们在质量和效率之间自由滑动。Llama 2 之所以选择 GQA,正是看中了这种灵活性。

GQA 的分组共享,就像是给演唱会配了 8 位速记员,每位负责 4 位明星。成本降了,记录质量还基本保留——两全其美。

KV Cache 内存GQA=2×层数×G×维度×序列长度×批大小×2\text{KV Cache 内存}_{\text{GQA}} = 2 \times \text{层数} \times G \times \text{维度} \times \text{序列长度} \times \text{批大小} \times 2
其中 $G$ 是分组数(KV 头数)。相比 MHA,显存下降到原来的 $G / H$($H$ 为查询头总数),既不像 MQA 那么极端,又远比 MHA 省资源。
  1. 分组数 $G$KV 头数从 $H$ 降为 $G$,$G$ 通常在 4 到 16 之间,如 Llama 2 用 8。
    KV 头数=G\text{KV 头数} = G
  2. 压缩比例显存降至 MHA 的 $G/H$,假设 $H=32$、$G=8$,则只需原来的四分之一。
    GH=832=14\frac{G}{H} = \frac{8}{32} = \frac{1}{4}
  3. 内存公式整体公式结构与 MHA 相同,差异只在于头数位置由 $G$ 取代了 $H$。
    内存GQA=GH×内存MHA\text{内存}_{\text{GQA}} = \frac{G}{H} \times \text{内存}_{\text{MHA}}

KV Cache机制

前面反复提到 KV Cache,它是理解 GQA 价值的基石。你可以把 KV Cache 想象成演唱会的“舞台提词器”:当明星(模型)需要回忆之前说过什么、观众反应如何时,不用重新翻看整场录影,而是直接看提词器上的摘要。

在 Transformer 解码过程中,每生成一个新 token,都需要与之前所有 token 计算注意力。如果不缓存,就得把之前的 token 全部重新计算一遍键值——这相当于每次说一句话都要重新读完整个剧本,计算量是灾难性的。KV Cache 的聪明之处在于:把历史 token 的 Key 和 Value 矩阵保存下来,新 token 只需计算自己的 Query 与缓存的 Key/Value 做注意力运算。

然而,KV Cache 也是有代价的——它占显存。我们以 Llama 2-7B 为例:该模型有 32 层,每层有 32 个注意力头,每头维度 128。当序列长度为 2048 时,如果采用 MHA,KV Cache 的占用约为 32 × 32 × 128 × 2048 × 2 字节 ≈ 512MB。这还只是一个请求,如果批处理 8 个请求,就是 4GB!显存瞬间被缓存吞没。

而 KV Cache 的减半机制,正是通过减少 KV 头数实现的。在 MHA 中,32 个查询头对应 32 组 KV;在 GQA 中,这 32 个查询头只对应 8 组 KV,缓存体积直接缩到原来的 1/4。缓存小了,内存带宽压力也同步下降,解码速度自然提升。

这个机制就像演唱会后台不断增加的道具箱。MV Cache 就是为了避免每次演出都重搬道具,但道具箱本身也占地方。GQA 的妙处在于:减少道具箱的数量,但不影响演出效果。

KV Cache 大小=2×nlayers×nKV heads×dhead×L×b×2bytes\text{KV Cache 大小} = 2 \times n_{\text{layers}} \times n_{\text{KV heads}} \times d_{\text{head}} \times L \times b \times 2 \text{bytes}
其中 $n_{\text{KV heads}}$ 是 KV 头数,$L$ 是序列长度,$b$ 是批大小。KV 头数越少,缓存越小,推理越快。
  1. 层数与头数每个 Transformer 层都有独立的 KV 缓存,所以乘上 $n_{\text{layers}} \times n_{\text{KV heads}}$。
    nlayers×nKV headsn_{\text{layers}} \times n_{\text{KV heads}}
  2. 向量维度每个 KV 头包含一个维度为 $d_{\text{head}}$ 的向量。
    ×dhead\times d_{\text{head}}
  3. 序列与批处理$L$ 是当前序列长度(已生成的 token 数),$b$ 是批大小,最后乘 2 字节(半精度)。
    ×L×b×2bytes\times L \times b \times 2 \text{bytes}

分组比例设计

既然 GQA 的分组数 $G$ 可以自由调节,那到底选多少合适?是做事的另一大关键。如果 $G$ 选太小(比如 1),就成了 MQA,质量受损;如果 $G$ 选太大(比如 32),就等于 MHA,显存压力又回来了。

实践中,最常见的配置是 $G=8$。Llama 2 全系列模型都采用“32 个查询头 + 8 个 KV 头”的配置。为什么是 8?这背后有硬件适配的考量:现代 GPU 的并行计算单元通常以 8 或 16 为粒度,选择 8 组 KV 头可以让每组查询头数量(32/8=4)在硬件上实现高效的内存访问模式。

不同分组比例对性能的影响也呈现鲜明的权衡曲线。把 $G$ 从 32 降到 8,KV Cache 内存下降 75%,而质量损失几乎可以忽略(约 0.1% 的困惑度增加)。继续降到 4,内存再降一半,但质量损失开始显性化。到 2 或 1 时,质量下降已经能让用户感知了。这就是所谓的“边际收益递减”——前期的压缩几乎无痛,后面的压缩则开始伤害效果。

此外,分组比例的选择还要考虑任务性质。对于短文本的对话场景(序列长度几百 token),KV Cache 的绝对占用不大,$G$ 大一点没关系;但对于长文本生成(如论文摘要、代码生成),序列长度动辄几万 token,KV Cache 每压缩一点都能省出大量显存,此时选择较小的 $G$ 更划算。

分组比例的设计就像演唱会速记员人数的取舍:速记员太多,后台拥挤(显存高);太少,记录质量堪忧(效果差)。8 人组是一个经过实践检验的“黄金人数”。

压缩比=1−GH\text{压缩比} = 1 - \frac{G}{H}
当 $H=32$、$G=8$ 时,压缩比为 75%。$G$ 越小压缩越大,但质量损失也越多。
  1. 压缩比公式与原始 MHA 相比,GQA 将 KV 头数从 $H$ 压缩到 $G$,节省的显存比例。
    压缩比=1−GH\text{压缩比} = 1 - \frac{G}{H}
  2. 数值实践$H=32, G=8$ 时压缩 75%,质量几乎无损;$G=2$ 时压缩 93.75%,但质量明显下降。
    1−832=75%1 - \frac{8}{32} = 75\%

解码阶段提速

解码阶段,才是 GQA 真正发光的舞台。理解这一点,要先搞懂 Transformer 生成文本的方式:它是逐 token 生成的,就像作家一个字一个字地写文章。每写一个字,都要回顾之前已经写下的所有内容。

在每一步解码中,模型都要执行这样的操作:将当前 token 的 Query 与 KV Cache 中所有的 Key 计算注意力分数,再与对应的 Value 加权求和。关键在于——KV Cache 中存储的数据量通常远大于计算量。一个 7B 模型,每一步解码的浮点运算量只有几百万次,但要读取的 KV 数据却有几十 MB。GPU 的算力再强,也追不上内存搬运的速度,这就是“内存带宽瓶颈”。

GQA 怎么解决这个问题?减少 KV 头数 = 减少需要搬运的数据量。假设原本每步要读取 32 组 KV(MHA),现在只需读取 8 组(GQA),数据搬运量直接减少 75%。每一步省下 75% 的带宽,累积到几千步的解码过程,生成速度的提升是肉眼可见的。

这就是为什么 GQA 能显著提升推理吞吐量的原因。它不仅省显存(能塞下更大的批大小),还省带宽(每步解码更快)。两者叠加,推理吞吐量自然水涨船高。这也是 Llama 2 选择 GQA 的底层逻辑——它不是为了让模型训练得更快,而是为了让部署后的推理成本更低、响应更快。

解码的提速原理,相当于让速记员只把“小组摘要”递给明星,而不是把整场演唱会的所有记录都抱过来。小组摘要够用,还更省时间。

每步读取数据量=2×nlayers×nKV heads×dhead×L\text{每步读取数据量} = 2 \times n_{\text{layers}} \times n_{\text{KV heads}} \times d_{\text{head}} \times L
解码时每个新 token 都要读取这个数据量,KV 头数越少,读取量越小,生成越快。
  1. 每步读取量每生成一个 token,需要把 KV Cache 中所有数据读一遍。
    2×nlayers×nKV heads×dhead×L2 \times n_{\text{layers}} \times n_{\text{KV heads}} \times d_{\text{head}} \times L
  2. 关键变量$n_{\text{KV heads}}$ 从 32(MHA)降到 8(GQA),读取量同步减少 75%。
    832=14\frac{8}{32} = \frac{1}{4}

Llama工程实践

理论说得再好,最终要看工程实践。Meta 的 Llama 2 系列模型,是 GQA 大规模落地的最佳注脚。Llama 2-7B 和 13B 模型采用 MHA,而到了 34B 和 70B 的大规模版本,则切换到了 GQA。

为什么大模型更需要 GQA?因为 KV Cache 的开销与模型规模成正比——层数越多、头数越多、维度越大,KV Cache 的膨胀就越恐怖。70B 模型如果用 MHA,KV Cache 能撑爆几乎所有的显存,根本没法做长文本推理。Llama 2-70B 采用 GQA 后,KV Cache 显存直接除以 4,让 70B 模型可以在有限的 GPU 上流畅运行。

实测数据显示,GQA 带来的提升非常可观。在相同的硬件环境下,Llama 2-70B(GQA)的推理吞吐量比同规模的 MHA 模型提升了大约 2-3 倍。同时,长文本生成的中间态显存峰值显著下降,这使得 Llama 2 能够支持更长的上下文窗口。在工程部署中,这种优势意味着更低的成本、更高的并发。

Llama 之外,Falcon 系列、Mistral 系列也纷纷采用 GQA 或变体。这说明 GQA 已经从学术研究走向工业标准,成为大模型推理优化的事实标配。对于 AI 从业者来说,GQA 不是可学可不学的知识,而是打开高性能推理大门的一把钥匙。

Llama 2 的实践表明:在真正的工业场景中,GQA 不是纸上谈兵的优化技巧,而是撑起大规模模型高效推理的基石工程。

吞吐量提升=原带宽需求新带宽需求≈HG\text{吞吐量提升} = \frac{\text{原带宽需求}}{\text{新带宽需求}} \approx \frac{H}{G}
理论上吞吐量提升接近 $H/G$ 倍,$H=32, G=8$ 时约 4 倍,实际受其他瓶颈制约约 2-3 倍。
  1. 带宽需求比解码阶段的吞吐量受内存带宽制约,带宽需求与 KV 头数 $H$ 成正比。
    吞吐量提升≈HG\text{吞吐量提升} \approx \frac{H}{G}
  2. 实际效果MHA 的 $H=32$ 变为 GQA 的 $G=8$,理论提升 4 倍,实测约 2-3 倍。
    328=4≈2∼3(实测)\frac{32}{8} = 4 \approx 2\sim3 \text{(实测)}

AI 应用场景

大语言模型推理服务部署

阶段:模型推理优化

解决的问题:在 GPU 上部署 70B 以上的大模型时,KV Cache 会占据大量显存,导致批大小受限、并发能力差。传统 MHA 架构下,一个请求就要占 1-2GB 显存,根本没法支撑高并发服务。GQA 将 KV 头数压缩 4 倍,显存占用同步降低,批大小提升 2-3 倍,让推理服务成本大幅降低。

具体案例:某 AI 公司基于 Llama 2-70B 搭建企业内部知识库问答服务。部署时采用 GQA 架构(32 个查询头、8 个 KV 头),在相同 A100 显卡配置下,单卡同时处理的请求数从 4 个提升到 16 个,生成速度提升了约 2 倍。用户的服务响应时间从 6 秒降到 2 秒。

长文本生成场景优化

阶段:序列生成加速

解决的问题:撰写论文、生成代码、分析长文档时,序列长度可达数万 token。MHA 架构下 KV Cache 随序列长度线性增长,很容易 OOM(显存溢出)。GQA 将缓存占用缩小 4 倍,让长文本生成成为可能。

具体案例:用于论文写作的 AI 助手需要一次生成 5000 token 以上的草稿。使用 GQA 优化后的模型,KV Cache 从 3.2GB 降到 0.8GB,多轮编辑对话的响应速度提升了近 3 倍,且生成质量与 MHA 版本基本一致。

边缘设备部署

阶段:端侧推理压缩

解决的问题:手机、嵌入式设备上的内存资源极其有限,无法承载大模型的 KV Cache 开销。GQA 通过减少 KV 头数,让模型的显存需求适配边缘设备的紧缺资源。

具体案例:某智能硬件公司尝试在手机端部署 7B 对话模型,采用 GQA 配置后,KV Cache 占用从 1.4GB 降至 350MB,首次实现了手机端的流畅对话体验。生成速度提升了约 1.8 倍。

Python 演示

python76 行
import mathimport torchimport torch.nn as nnimport torch.nn.functional as F class MHA(nn.Module):    """多头注意力:每个查询头配一组独立的KV头"""    def __init__(self, d_model, n_heads):        super().__init__()        self.n_heads = n_heads        self.head_dim = d_model // n_heads        self.wq = nn.Linear(d_model, d_model)        self.wk = nn.Linear(d_model, d_model)        self.wv = nn.Linear(d_model, d_model)        self.wo = nn.Linear(d_model, d_model)     def forward(self, x):        B, L, _ = x.shape        q = self.wq(x).view(B, L, self.n_heads, self.head_dim).transpose(1, 2)        k = self.wk(x).view(B, L, self.n_heads, self.head_dim).transpose(1, 2)        v = self.wv(x).view(B, L, self.n_heads, self.head_dim).transpose(1, 2)        attn = F.scaled_dot_product_attention(q, k, v)        return self.wo(attn.transpose(1, 2).reshape(B, L, -1))  class GQA(nn.Module):    """分组查询注意力:n_heads个查询头共享n_kv_heads组KV头"""    def __init__(self, d_model, n_heads, n_kv_heads):        super().__init__()        self.n_heads = n_heads        self.n_kv_heads = n_kv_heads        self.head_dim = d_model // n_heads        self.wq = nn.Linear(d_model, d_model)        self.wk = nn.Linear(d_model, n_kv_heads * self.head_dim)        self.wv = nn.Linear(d_model, n_kv_heads * self.head_dim)        self.wo = nn.Linear(d_model, d_model)     def forward(self, x):        B, L, _ = x.shape        q = self.wq(x).view(B, L, self.n_heads, self.head_dim).transpose(1, 2)        k = self.wk(x).view(B, L, self.n_kv_heads, self.head_dim).transpose(1, 2)        v = self.wv(x).view(B, L, self.n_kv_heads, self.head_dim).transpose(1, 2)        # 分组扩展:将KV头复制到对应的查询组        reps = self.n_heads // self.n_kv_heads        k = k.repeat_interleave(reps, dim=1)        v = v.repeat_interleave(reps, dim=1)        attn = F.scaled_dot_product_attention(q, k, v)        return self.wo(attn.transpose(1, 2).reshape(B, L, -1))  def count_kv_cache(seq_len, batch_size, d_model, n_heads, n_kv_heads):    """计算KV Cache占用(半精度字节数)"""    head_dim = d_model // n_heads    mha_cache = 2 * seq_len * batch_size * n_heads * head_dim * 2    gqa_cache = 2 * seq_len * batch_size * n_kv_heads * head_dim * 2    return mha_cache, gqa_cache  if __name__ == "__main__":    torch.manual_seed(42)    d_model, seq_len, batch_size = 512, 128, 4    n_heads, n_kv_heads = 32, 8     model_mha = MHA(d_model, n_heads)    model_gqa = GQA(d_model, n_heads, n_kv_heads)     x = torch.randn(batch_size, seq_len, d_model)    out_mha = model_mha(x)    out_gqa = model_gqa(x)     mha_cache, gqa_cache = count_kv_cache(seq_len, batch_size, d_model, n_heads, n_kv_heads)    print(f"MHA 输出形状: {out_mha.shape}")    print(f"MHA KV Cache 占用: {mha_cache / 1024:.2f} KB")    print(f"GQA 输出形状: {out_gqa.shape}")    print(f"GQA KV Cache 占用: {gqa_cache / 1024:.2f} KB")    print(f"显存节省比例: {(1 - gqa_cache / mha_cache) * 100:.1f}%")
目的:分别实现 MHA 和 GQA 的前向传播,并对比两者的 KV Cache 内存占用,直观展示 GQA 的显存压缩效果。
  1. self.wq = nn.Linear(d_model, d_model)查询头保持全量,每个头都有独立的查询变换
  2. self.wk = nn.Linear(d_model, n_kv_heads * head_dim)键投影压缩到 n_kv_heads 组,这是 GQA 的核心
  3. self.wv = nn.Linear(d_model, n_kv_heads * head_dim)值投影同样压缩到 n_kv_heads 组
  4. k.repeat_interleave(reps, dim=1)将 KV 头复制到对应的查询组,完成分组共享
  5. F.scaled_dot_product_attention(q, k, v)PyTorch 内置的高效缩放点积注意力
  6. count_kv_cache(...)验证 KV Cache 的显存节省比例,MHA 与 GQA 对比一目了然

常见误区

误区KV 头数不是越少越好吗?减少到 1 个不就是最省显存、最快速度?那为什么还要用 GQA 而不是直接上 MQA?

正解KV 头数减少到 1 确实显存最优,但模型质量会明显下降。MQA 在最省资源的同时,也牺牲了注意力头捕捉多样信息的能力。GQA 的意义在于平衡——8 组 KV 头比 32 组省了 75% 显存,但质量损失几乎为零;而继续压缩到 1 组,质量下降就会让用户明显感知。就像演唱会速记员从 32 人减到 8 人无损,但减到 1 人就真的记不过来了。

为何常见:看到 MQA 显存更小就认为更好,忽略了模型生成质量这个核心指标。优化是显存、速度、质量三方的权衡,不是单指标最大化。

误区GQA 看起来和 MHA 差不多,只是把“头”改叫“组”而已。应该不用改变代码,直接调参就行吧?

正解GQA 在实现上有本质区别:MHA 的每个查询头都有独立的 KV 投影矩阵,而 GQA 的 KV 投影矩阵只有 n_kv_heads 组。这意味着模型参数量本身也减少了(KV 侧投影的参数量降为 1/4),前向计算时的 K、V 张量形状完全不同,需要做 repeat_interleave 展开。这不仅是超参数改变,更是架构层的修改。

为何常见:从结构图上看 MHA 和 GQA 很像,容易产生“只是参数不同”的错觉。实际上 GQA 是模型类级别的改动,不是配置文件里改个数就行。

误区我已经有一个训练好的 MHA 模型了,能不能直接把它的注意力头合并成几组,变成 GQA 模型直接使用?

正解不能直接转换。MHA 中每个 KV 头是独立训练的,直接合并会破坏模型的注意力模式,导致性能骤降。正确的做法是:从零训练一个 GQA 架构的模型,或者用 MHA 模型的权重初始化后做持续预训练(upcycling)。Llama 2 的 GQA 模型就是从零训练的。

为何常见:看到 GQA 和 MHA 结构相似,就联想到“剪枝”这类可以直接压缩的技术。但注意力头的共享涉及权重矩阵的重新组织,不是简单的参数删除。

误区GQA 是 70B 这种超大模型才需要用的技术,我现在做的小模型根本用不上。

正解GQA 的价值与模型规模相关,但并非只适用于大模型。即使是 1B 规模的模型,在长序列场景下 KV Cache 也可能占用过半的显存。边缘设备上部署小模型时,GQA 同样能带来显著的显存节省和速度提升。只是在模型很小时,KV Cache 的占比较小,优化收益相对有限。

为何常见:看到 Llama 2 只有 34B/70B 版本才用 GQA,就误以为这是“大模型专属”。实际上 GQA 对小模型、长序列、低资源设备同样有价值。

误区把 32 组 KV 合并成 8 组,信息丢失这么多,模型一定变笨了。所以 GQA 只能在要求不高的场景用。

正解实验表明,GQA 在 $G=8$ 时与 MHA 的性能差异极小(困惑度增加约 0.1%),几乎可以忽略。原因是 Transformer 底层中,多个头共享 KV 并不等于丢失信息——查询头仍然可以从共享的 KV 中提取不同的信息组合。真正的质量下降要到 $G \leq 4$ 时才逐渐显著。GQA 是一个巧妙利用了注意力机制冗余性的高效方案,不是牺牲质量的妥协之举。

为何常见:直觉上“共享=丢失”,忽略了注意力头的分工不是独立存储信息,而是并行从同一信息源提取不同特征这一本质。

面试题

Q1. GQA 中“分组”指的是什么?

单选题 · easy

  • A.将序列中的 token 分组成几个部分
  • B.将多个查询头分成几组,每组共享一组键值头← 正确答案
  • C.将权重矩阵按行分块处理
  • D.将隐层维度划分到不同设备上

答案:B。GQA 的分组是指查询头层面的分组:原本每个查询头配一组KV,现在把 $H$ 个查询头分成 $G$ 组,每组共享 $1$ 组KV。A 是对序列的分组,C 是矩阵并行分块,D 是张量并行,均与 GQA 无关,故选 B。

Q2. 假设模型有 32 个查询头、8 个 KV 头,KV Cache 相对 MHA 节省多少显存?

单选题 · easy

  • A.50%
  • B.65%
  • C.75%← 正确答案
  • D.90%

答案:C。MHA 的 KV 头数等于查询头数 32,GQA 为 8。KV Cache 大小与 KV 头数成正比,因此节省比例为 $1 - 8/32 = 75%$,故选 C。A 对应 $G=16$,B 映射无直接对应,D 对应 $G \approx 3$,均与 8/32 不符。

Q3. 为什么 MQA 的质量往往不如 GQA?

单选题 · mid

  • A.MQA 使用的训练数据更少
  • B.MQA 仅剩一组 KV,注意力头无法捕捉多样信息← 正确答案
  • C.MQA 的计算量更小所以精度更低
  • D.MQA 不适用于自回归模型

答案:B。MQA 将所有查询头共用一组 KV,注意力头从不同角度查询信息的特性被抹杀,信息多样性下降,导致质量受损。A 与架构无关,C 计算量小不必然低质,D 自回归场景同样可用 MQA,只是效果不如 GQA,故选 B。

Q4. 在解码阶段,GQA 提速的主要原理是?

单选题 · mid

  • A.减少模型参数量使计算更快
  • B.减少需要读取的 KV Cache 数据量← 正确答案
  • C.增加批大小以提高 GPU 利用率
  • D.跳过注意力计算直接输出

答案:B。解码阶段受内存带宽瓶颈制约,每步生成需读取全部 KV Cache。GQA 减少 KV 头数,使每步读取的数据量缩减(如 75%),从而显著提升生成速度。A 参数减少的收益有限,C 是 GQA 的间接结果而非原理,D 与注意力机制相悖,故选 B。

Q5. Llama 2-70B 使用 GQA 而非 MHA,最核心的工程动因是?

单选题 · hard

  • A.GQA 让训练过程更快收敛
  • B.GQA 让 70B 模型在有限显存下可推理长序列← 正确答案
  • C.GQA 大幅减少预训练所需的数据量
  • D.GQA 支持无监督学习

答案:B。70B 模型在 MHA 下 KV Cache 占用天文数字(数十 GB),单卡无法承载长序列推理。GQA 将 KV Cache 压缩为 1/4,使有限显存可用。训练速度和数据量在预训练阶段并非 GQA 的主要优势,故选 B。

Q6. 以下是 GQA 演进的基础机制是?

单选题 · easy

  • A.量化感知训练
  • B.KV Cache 机制← 正确答案
  • C.模型蒸馏
  • D.混合精度训练

答案:B。GQA 本质上是在减少 KV Cache 的体量,KV 头数与缓存大小直接相关。理解 KV Cache 的 I/O 开销规律是理解 GQA 的基石。量化、蒸馏、混合精度与 GQA 属于不同方向,故选 B。

Q7. GQA 中 KV 头数 $G$ 的选择受到哪些因素影响?

多选题 · mid

  • A.目标 GPU 的显存容量← 正确答案
  • B.序列长度预计范围← 正确答案
  • C.期望的推理吞吐量← 正确答案
  • D.嵌入维度是否为偶数

答案:ABC。$G$ 越小 KV Cache 占用越低,显存受限场景应选更小 $G$;序列越长、吞吐量要求越高,越倾向于小 $G$。嵌入维度奇偶不影响 $G$ 选择,因为 GQA 的维度分割本就要求整除关系,与偶数无关,故 D 错。

Q8. 关于 GQA 的分组数与模型质量的关系,下列说法正确的是?

多选题 · hard

  • A.$G$ 减小时质量持续线性下降
  • B.$G$ 从 32 降至 8 时质量损失几乎可忽略← 正确答案
  • C.$G$ 越小,KV Cache 压缩率越高但质量风险越大← 正确答案
  • D.$G$ 从 8 降至 2 时质量下降更显著← 正确答案

答案:BCD。质量与 $G$ 不是线性关系。32→8 约损失 0.1%,几乎无损;8→4 开始可感知;4→2 降幅更明显。因此 A 的“持续线性下降”错误,B、D 正确反映了分阶段退化的规律,C 描述了两者此消彼长的权衡关系,也正确。

Q9. GQA 中“重复 KV 头”操作(repeat_interleave)的目的是什么?

单选题 · mid

  • A.增加模型参数量
  • B.将 KV 头复制以匹配查询头的数量← 正确答案
  • C.提高注意力分数的数值稳定性
  • D.降低内存带宽压力

答案:B。GQA 中查询头数为 $H$、KV 头数为 $G$,二者数量不同。计算注意力时,需要将 $G$ 组 KV 按分组映射复制为 $H$ 份(每个查询头各一份),才能与 $H$ 个查询头对齐。复制不增加参数和带宽,也不用于数值稳定,故选 B。

Q10. 与 MHA 相比,GQA 在推理阶段带来哪些改进?

多选题 · mid

  • A.KV Cache 显存占用大幅降低← 正确答案
  • B.解码阶段内存带宽压力减小← 正确答案
  • C.批处理吞吐量得以提升← 正确答案
  • D.训练所需样本量减少

答案:ABC。GQA 主要改善推理性能:KV Cache 下降 → 显存下降、带宽压力减小 → 吞吐量提升。训练收敛速度不是 GQA 的核心优势,所需样本量也不因架构而显著改变,故 D 错误。