GQA 完全拆解:分组查询注意力,用 1/8 的 KV 缓存保住 MHA 的质量

7751 字
39 分钟
GQA 完全拆解:分组查询注意力,用 1/8 的 KV 缓存保住 MHA 的质量

从”每头一套键值”说起:解码推理的内存账本#

标准的 Transformer 使用多头注意力(multi-head attention,MHA)。模型维度 dmodeld_{model} 被切成 hh 个注意力头,每个头有自己的查询(Query)、键(Key)、值(Value)投影矩阵,各自在维度 dk=dmodel/hd_k = d_{model}/h 的子空间里做注意力:

headi=softmax(QiKidk)Vi\text{head}_i = \text{softmax}\left(\frac{Q_i K_i^{\top}}{\sqrt{d_k}}\right) V_i

其中 QiQ_iKiK_iViV_i 分别是第 ii 个头投影出的查询、键、值矩阵,dk\sqrt{d_k} 是缩放因子。注意力的输出再拼回完整维度,经过输出投影 WOW_O 得到这一层的输出。

预填充(prefill)阶段可以并行处理整条序列,但自回归解码(decode)阶段每次只生成一个 token。生成新 token 时,注意力需要让新 token 的查询去和历史上所有 token 的键和值做点积。如果每次现算历史 token 的 K、V,整个前缀的输入都要重新过一遍网络,代价是随序列长度平方级增长的。所以实际工程里都会把已算出的 K、V 存进显存,称为键值缓存(KV cache):解码第 tt 步时,新 token 的 QtQ_t 与缓存的全部 K1..tK_{1..t}V1..tV_{1..t} 做注意力,算完把这一行新 K、V 追加进缓存。

KV cache 的内存占用因此是每层、每 token、每 KV 头都存一份,精确地说:

SKV=2×L×G×dk×BS_{\text{KV}} = 2 \times L \times G \times d_k \times B

LL 是层数,GG 是 KV 头数量,dkd_k 是每个头的维度,BB 是每个元素占的字节数(FP16/BF16 下为 2),因子 2 来自 K 和 V 各一份。以 LLaMA-2-7B 为例:32 层、32 个头、头维度 128,BF16 下每个 token 的 KV 缓存是 2×32×32×128×2=5242882 \times 32 \times 32 \times 128 \times 2 = 524288 字节,即 512 KiB/token。4K 上下文就是 2 GiB,8K 就是 4 GiB——单条请求的 KV 缓存已经可以和模型权重(约 13 GB)相提并论。

为什么 KV cache 的大小这么重要?因为解码阶段是内存带宽受限(memory-bandwidth-bound)的。每一步生成,GPU 都需要:

  1. 把全部模型权重从 HBM 读一遍(自回归无法批量计算,权重只能复用);
  2. 把整条序列的 KV cache 从 HBM 读一遍,供注意力点积使用。

HBM 带宽决定了每一步能搬多少数据。Shazeer 在 2019 年的论文里给过一个干净的复杂度分析:自回归生成 nn 步时,增量式 MHA 的总计算量是 Θ(bnd2)\Theta(b n d^2)bb 为批大小),而总内存访问量是 Θ(bn2d+nd2)\Theta(b n^2 d + n d^2)——第一项来自每步都要重新读一遍大小随序列长度增长的 K、V 张量,第二项来自每步都要重新读一遍投影权重。两者相除,得到内存访问与计算的比值:

R=Θ(nd+1b)R = \Theta\left(\frac{n}{d} + \frac{1}{b}\right)

当序列长度 nn 接近模型维度 dd(几千 token 的对话在 4096 维模型上很容易达到),或者批大小 bb 接近 1 时,比值接近 1:每做一单位浮点运算就要搬一单位数据,而现代 GPU 上计算吞吐与内存带宽的比值通常在 100 以上(例如 A100 的 FP16 算力 312 TFLOPS 对 2 TB/s 带宽)。于是搬运数据成了解码的主宰,GPU 的计算单元大量空闲。

公式里的 nd\frac{n}{d} 项,展开来看正是 KV cache 的带宽开销:KV 张量每步要完整读一遍,读的量 ∝ 序列长度 × KV 头数。这一项不随硬件换代而消失——HBM 带宽的增长远慢于算力的增长,所以 KV 的搬运开销在每一代 GPU 上都占解码延迟的大头。优化 KV cache 的大小,就是优化解码的延迟与吞吐。GQA 的故事从这里开始。

把上面的复杂度分析落到具体数字上,能更直观地感受”内存账本”的分量。以 A100-80GB 为例:HBM 带宽约 2 TB/s,FP16 算力 312 TFLOPS。解码一个 token 需要把全部权重从 HBM 读一遍——LLaMA-2-7B 的权重约 13.4 GB,单这一步的延迟下限就是 13.4 GB/2 TB/s6.7 ms13.4\ \text{GB} / 2\ \text{TB/s} \approx 6.7\ \text{ms};如果上下文已有 4K token,还要再读 2 GiB 的 KV cache(MHA 配置下 512 KiB/token × 4096),又加约 1 ms。也就是说,一个 token 的解码延迟里,搬运权重与搬运 KV 是两大固定成本,与计算本身几乎无关

这里有一个 serving 场景的关键细节:权重是”每步读一次、服务整个批次”的,批大小 bb 越大,权重的搬运成本被 bb 个请求摊得越薄;而 KV cache 是”每步读 bb 份”的,批越大 KV 搬运占比越高。所以在高并发服务中,KV cache 的带宽与容量往往比权重更早成为瓶颈——这正是 GQA 这类 KV 压缩技术价值最大的场景。

MQA:一次性把 KV 头砍到只剩一个(2019)#

2019 年,Google 的 Noam Shazeer 发表了一篇只有三页的小论文《Fast Transformer Decoding: One Write-Head is All You Need》(arXiv:1911.02150),提出多查询注意力(multi-query attention,MQA):保留多个查询头,但所有查询头共享同一个键头和同一个值头

实现上极其简单——把 MHA 代码里 K、V 及其投影矩阵 PkP_kPvP_v 的”头”维直接删掉即可。原来 K、V 的形状是 [b,h,n,dk][b, h, n, d_k],MQA 里变成 [b,n,dk][b, n, d_k]。论文用 einsum 记号给出了这个改动的全部内容:MHA 的键投影是 einsum("bd,hdkbhk",X,Pk)\text{einsum}("bd, hdk \to bhk", X, P_k),MQA 把它改成 einsum("bd,dkbk",X,Pk)\text{einsum}("bd, dk \to bk", X, P_k)——字母 hh 从下标里消失,其余一字不动,查询侧的 einsum("bd,hdkbhk",X,Pq)\text{einsum}("bd, hdk \to bhk", X, P_q) 保留。训练的代码改动只有几个 einsum 字符串,这是 MQA 能在 2019 年迅速被采用的重要原因。

KV cache 的规模随之缩小到原来的 1/h1/h。Shazeer 把这种设计称为”one write-head”(单写入头):解码每步只需向缓存写入一行键和一行值,而不是 hh 行——写入量缩小 hh 倍,读取量(每步都要读全部 KV)同样缩小 hh。论文给出的带宽分析里,nd\frac{n}{d} 这一项被除以了 hh

RMQA=Θ(ndh+1b)R_{\text{MQA}} = \Theta\left(\frac{n}{d h} + \frac{1}{b}\right)

实验数据非常惊人。在 TPUv2 上、批大小 1024 的增量解码评测中,T5-XXL 基线模型的解码器每步耗时 47 ms,MQA 版本只有 3.9 ms,快了约 12 倍(编码器部分 222 ms → 195 ms,只快了 14%——编码器本来就是并行计算、不受带宽制约,印证了前一节的分析)。代价是质量的小幅下降:WMT14 英德翻译的 BLEU 从 26.7 降到 26.5,对数困惑度(ln perplexity)从 1.424 升到 1.439;论文还发现,把前馈层维度从 4096 增大到 5440 后,MQA 模型能在翻译任务上追平 MHA——用一点额外参数换带宽。质量损失在翻译任务上很小,在摘要类任务上更明显,这符合直觉:摘要需要模型从长上下文中提取并组织大量细节,对 KV 表示的表达能力更敏感

MQA 的思路随后被 PaLM、Falcon 等模型采用(Falcon-7B 有 71 个查询头,却只配 1 个 KV 头)。但它有两个明显缺陷:

  • 质量天花板低。所有查询头强制共享同一份 K、V 表示,注意力内容上的多样性被压缩到只剩查询侧。头数越多,砍掉的信息越多——而大模型恰恰头数更多(参数量以 dmodel2d_{model}^2 增长,头数通常线性增长)。
  • 训练不稳定。GQA 论文报告,MQA 在一些任务上出现质量退化甚至训练不稳定(training instability)的问题。

一句话概括:MQA 把带宽省到了极致,但砍掉了太多模型容量。能不能只砍一部分?

GQA:插值在 MHA 与 MQA 之间的分组共享(2023)#

2023 年,Google 团队的 Joshua Ainslie 等人提出分组查询注意力(grouped-query attention,GQA),论文《GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints》发表在 EMNLP 2023(arXiv:2305.13245)。核心思想一句话:hh 个查询头分成 GG 组,每组共享一个键头和值头。GQA-GG 表示组数为 GG 的 GQA;G=1G=1 时退化为 MQA,G=hG=h 时就是 MHA,GQA 是两者的插值:

G=1MQAG=hMHAG = 1 \Longleftrightarrow \text{MQA} \qquad \qquad G = h \Longleftrightarrow \text{MHA}

此时 KV 头数量从 hh 降到 GG,每个 KV 头服务 h/Gh/G 个查询头。KV cache 的大小公式 SKV=2LGdkBS_{\text{KV}} = 2 L G d_k BGG 取代了原来的 hh——KV cache 和每步 KV 的读取量都精确地缩小为原来的 G/hG/h

用符号写清楚:把查询头按下标 i=1,,hi = 1, \dots, h 编号,第 gg 组包含连续的第 ((g1)hG+1)\left(\frac{(g-1)h}{G}+1\right) 到第 ghG\frac{gh}{G} 个头。组 gg 内任意查询头 ii 的注意力计算为:

headi=softmax(QiKgdk)Vg,igroup g\text{head}_i = \text{softmax}\left(\frac{Q_i K_g^{\top}}{\sqrt{d_k}}\right) V_g, \qquad i \in \text{group } g

KgK_gVgV_g 是组 gg 共享的键、值头。可见查询侧的投影 WQ,iW_{Q,i} 依然是每个头独立的,共享的只有键与值——这是 GQA 质量损失小的结构性原因:注意力机制里”问什么”(查询)的多样性被完整保留,“记住什么”(键值)的表达被按组压缩。对于一组内的几个查询头,它们共享同一份上下文记忆,但各自用不同的查询投影去解读这份记忆,输出仍然各不相同。

论文的架构对比图把三种注意力画在同一张图上:

GQA 论文图 2:MHA(上)每个查询头有独立的 K、V 头;MQA(中)所有查询头共享同一个 K、V 头;GQA(下)每 G 个查询头共享一组 K、V 头,介于两者之间(来源:arXiv:2305.13245 图 2)
GQA 论文图 2:MHA(上)每个查询头有独立的 K、V 头;MQA(中)所有查询头共享同一个 K、V 头;GQA(下)每 G 个查询头共享一组 K、V 头,介于两者之间(来源:arXiv:2305.13245 图 2)

从上到下看:MHA 里三组方块——查询、键、值——数量完全一致,一一对应;MQA 的键、值各自只剩一个小方块,所有查询头共用;GQA 位于中间,键、值方块的数量比查询方块少、但不止一个,形成”几根查询线汇入一根 KV 线”的分组结构。图中特意用颜色区分了分组——同一组的查询头共享同一根 K/V,组与组之间的 KV 互不干扰。

为什么中间值(GQA)比两个端点都更好? 三个原因,论文里都有直接论证:

第一,带宽收益几乎全部保留。 论文图 6 测量了 T5-XXL 上 GQA 组数 GG 与推理时间的关系(输入 2048 token、输出 512 token,TPUv4):

GQA 论文图 6:T5-XXL 的每样本推理时间随 GQA 组数的变化。横轴为组数(1、4、8、16、32、64),纵轴为时间;从 1 组(MQA)增加到 8 组只带来少量开销,越接近 64 组(MHA)开销增长越快(来源:arXiv:2305.13245 图 6)
GQA 论文图 6:T5-XXL 的每样本推理时间随 GQA 组数的变化。横轴为组数(1、4、8、16、32、64),纵轴为时间;从 1 组(MQA)增加到 8 组只带来少量开销,越接近 64 组(MHA)开销增长越快(来源:arXiv:2305.13245 图 6)

图中曲线在 G=1G=1G=8G=8 之间非常平缓——KV cache 从 64 头缩到 8 头已经拿到了带宽收益的大头,再往 8 头上加组,每步多读的 KV 量仍然只是总 KV 的零头;而 GG 从 8 涨到 64(即回到 MHA)时,曲线陡峭上升,多出来的全是带宽负担。论文据此把 8 组选为”性价比最好的中间值”。

第二,质量接近 MHA。 主实验表(下节详述)里 GQA-8 的平均分 47.1,与 MHA-XXL 的 47.2 几乎持平,比 MQA 的 46.6 高出一截。从信息的角度看也不难理解:KV 表示在 Transformer 里承担的是”上下文记忆”的角色,一组查询头共享同一份记忆,损失的只是”对同一段记忆用不同投影去读取”的细粒度——下文会看到,均值池化后的共享表示依然覆盖了绝大多数信息,这正是消融实验中池化转换几乎不掉点的原因。

第三,容量随模型规模成比例。 论文专门指出:模型越大、头数越多,MQA 那种”砍到 1 个头”就越激进——KV 容量与模型规模不成比例。GQA 的组数则可以让 KV 容量随模型一起增长(例如头数翻倍时组数也翻倍),保持带宽收益与容量损失的比例不变。论文还提到一个张量并行(tensor parallelism,TP)场景下的细节:大模型按头切分到多张卡时,MQA 唯一的 KV 头会被复制到每个分区,造成冗余;GQA 的多个 KV 头可以分散到各分区,消除了这种浪费。

另外注意论文的应用范围:GQA/MQA 只用于解码器自注意力与交叉注意力(cross-attention),不用于编码器自注意力。原因是编码器是并行计算的,KV 只在内存里存一份、读一次,不是带宽瓶颈;而解码器(以及交叉注意力)每步都要重读全部 KV,才是带宽开销的来源。

Uptraining:把现成 MHA 检查点改造成 GQA#

GQA 论文的第二个贡献是”升级训练”(uptraining)配方——把一个已经训练好的 MHA 检查点,用很少的算力改造成 MQA/GQA 模型。这在当时是刚需:T5、LLaMA 等公开模型都是 MHA 的,推理方想用上 MQA/GQA 的速度,又不愿(也没有算力)从头训练一个模型。论文提出的流程分两步:

第一步:均值池化(mean pooling)转换。 把原来 hh 个 K 头(和 V 头)的投影矩阵,按目标分组做平均,合并成 GG 个。第 gg 组的新键投影矩阵为:

WKg=1gigWKiW^{g}_K = \frac{1}{|g|} \sum_{i \in g} W^{i}_K

WKiW^{i}_K 是原模型第 ii 个头的键投影矩阵,g|g| 是该组的头数。值投影同理。下图即论文图 1 的转换流程示意:

GQA 论文图 1:从 MHA 到 MQA 的检查点转换流程。上方是转换前每个头独立的 K、V 投影矩阵,中间箭头表示把所有头的 K(V)投影矩阵均值池化成一份,下方是转换后单一 K、V 头的模型,随后用原训练配方继续预训练 α 比例的训练步数(来源:arXiv:2305.13245 图 1)
GQA 论文图 1:从 MHA 到 MQA 的检查点转换流程。上方是转换前每个头独立的 K、V 投影矩阵,中间箭头表示把所有头的 K(V)投影矩阵均值池化成一份,下方是转换后单一 K、V 头的模型,随后用原训练配方继续预训练 α 比例的训练步数(来源:arXiv:2305.13245 图 1)

为什么是均值池化而不是其他方法? 论文做了消融(ablation),对比三种转换方式——均值池化、“取第一个头”、随机初始化:

GQA 论文图 4:T5-Large 转 MQA(α=0.05)时三种检查点转换方式的性能对比,横轴为性能。均值池化(Mean)最好,取第一个头(First)次之,随机初始化(Random)最差(来源:arXiv:2305.13245 图 4)
GQA 论文图 4:T5-Large 转 MQA(α=0.05)时三种检查点转换方式的性能对比,横轴为性能。均值池化(Mean)最好,取第一个头(First)次之,随机初始化(Random)最差(来源:arXiv:2305.13245 图 4)

结果与”信息保留程度”的顺序完全一致:均值池化保留了所有头的线性组合信息,最好;取一个现成头保留了该头的信息,次之;随机初始化等于把这块知识全部丢弃,最差。这个结果直观得近乎平庸——但它说明了一个关键设计原则:转换的目标不是创造新权重,而是尽量不破坏已有权重里的信息

第二步:用小比例算力继续预训练。 转换后的模型用原训练配方继续预训练 α\alpha 比例的训练步数。论文取 α=0.05\alpha = 0.05——T5-XXL 的预训练算力约 5%,换算成硬件时间约为 600 个 TPUv3 芯片日。作为参照,T5-XXL 的完整预训练约需上万 TPUv3 芯片日,升级训练把”获得一个 MQA/GQA 模型”的成本压到了原始预训练的 5%,而且用的是现成数据管线,不需要重新采集、清洗数据。下图是 α\alpha 的扫描结果:

GQA 论文图 5:T5-XXL 上 MQA 与 GQA-8 的性能随升级训练比例 α 的变化。GQA 在 α=0(纯转换、零训练)时已有合理性能,MQA 则需要升级训练才能追上;两者在 5% 处增益最大,10% 之后收益递减(来源:arXiv:2305.13245 图 5)
GQA 论文图 5:T5-XXL 上 MQA 与 GQA-8 的性能随升级训练比例 α 的变化。GQA 在 α=0(纯转换、零训练)时已有合理性能,MQA 则需要升级训练才能追上;两者在 5% 处增益最大,10% 之后收益递减(来源:arXiv:2305.13245 图 5)

图中两条曲线的起点很能说明问题:GQA-8 在 α=0\alpha=0(只做均值池化、一步都不训练)时就有相当的性能,而 MQA 几乎必须经过升级训练才可用。原因不难理解——组内共享让 GQA 转换后的注意力分布与原来的 MHA 很接近,查询头与 KV 头的配对关系大体保留;MQA 则是把所有 KV 头的信息硬压成一个,原有配对关系被彻底打散,必须靠训练重新学习。这也再次印证:GQA 的容量损失远小于 MQA,因此对训练的依赖也更小

论文的相关工作一节还提到,Rabe(2023)独立地开发了 GQA 及公开实现——这个方向在当时已是多方共识,只是由 Google 团队率先给出系统化的定义与实验。

质量与速度的实测权衡#

GQA 论文的主实验在 T5-Large 与 T5-XXL 上进行(T5-XXL 有 64 个注意力头、dmodel=4096d_{model}=4096),对比 MHA、MQA 与 GQA-8(升级训练比例 α=0.05\alpha=0.05),评测覆盖摘要(CNN/Daily Mail、arXiv、PubMed、MediaSum、Multi-News)、翻译(WMT14 英德)与问答(TriviaQA)。推理时间按每样本、每 TPUv4 芯片测量。完整数据如下:

模型推理时间(s/样本)平均分CNN R1arXiv R1PubMed R1MediaSum R1MultiNews R1WMT BLEUTriviaQA F1
MHA-Large0.3746.042.944.646.235.546.627.778.2
MHA-XXL1.5147.243.845.647.536.446.928.481.9
MQA-XXL0.2446.643.045.046.936.146.528.581.3
GQA-8-XXL0.2847.143.545.447.736.347.228.481.6

数据来源:arXiv:2305.13245 表 1(推理时间测量自 TPUv4,8 张 TPU、每卡最大批 32)。

逐数据集看,GQA-8-XXL 相对 MHA-XXL 的质量几乎无损,有些任务还反超:arXiv 摘要 R1 45.4 vs 45.6(-0.2)、PubMed 45.4 vs 45.6(-0.2)、MediaSum 47.7 vs 47.5(+0.2)、MultiNews 47.2 vs 46.9(+0.3)、WMT BLEU 28.4 持平、CNN/Daily Mail 43.5 vs 43.8(-0.3)、TriviaQA F1 81.6 vs 81.9(-0.3)。反超出现在长文摘要任务(MediaSum、MultiNews)上——这也间接说明 GQA-8 的 KV 表示在这些任务上保留了足够的信息,甚至因为升级训练多训了几步而小幅受益。

把时间与质量画在同一张图上(论文图 3),权衡一目了然:

GQA 论文图 3:平均性能(纵轴)与每样本推理时间(横轴)的散点图。MHA-Large 在左下(慢且质量低),MHA-XXL 在最右(最慢),MQA-XXL 在左上(最快但质量略低),GQA-XXL 位于左上、贴近上边缘——质量接近 MHA-XXL,时间接近 MQA(来源:arXiv:2305.13245 图 3)
GQA 论文图 3:平均性能(纵轴)与每样本推理时间(横轴)的散点图。MHA-Large 在左下(慢且质量低),MHA-XXL 在最右(最慢),MQA-XXL 在左上(最快但质量略低),GQA-XXL 位于左上、贴近上边缘——质量接近 MHA-XXL,时间接近 MQA(来源:arXiv:2305.13245 图 3)

四个散点的相对位置讲述了这个设计的全部逻辑:

  • GQA-8-XXL 比 MHA-XXL 快 5.4 倍(1.51 s → 0.28 s),平均质量只差 0.1 分(47.1 vs 47.2),在 MediaSum、MultiNews 两个数据集上甚至反超;
  • GQA-8-XXL 比 MQA-XXL 只慢 1.17 倍(0.28 s vs 0.24 s),平均质量高 0.5 分
  • 更大尺度的 MHA-Large(0.37 s,46.0 分)在速度和质量上被 GQA-8-XXL 全面压制——同样的算力预算,升级训练的 GQA-XXL 比更大的 MHA 模型更好用。

组合起来就是论文的结论:升级训练的 GQA 用接近 MQA 的速度拿到了接近 MHA 的质量。注意一个诚实的前提:GQA 省的是 KV 的内存与带宽,注意力本身的浮点运算量(FLOPs)没有减少——KV 头要广播回 hh 个查询头参与点积,logits 计算量不变。所以 GQA 的加速比例不会超过带宽占比,长序列、大批量下收益最明显,短序列下则主要由其他开销主导。

由此可以推出 GQA 在推理两个阶段的差异化收益,这是部署选型时容易踩坑的地方:

  • 解码阶段是主战场。每步重读 KV 是纯带宽开销,KV 缩小 h/Gh/G 倍直接等比例缩减这部分延迟;同时 KV 显存占用缩小,批容量扩大。论文表 1 测的正是解码端到端时间。
  • 预填充阶段收益有限。prefill 是计算密集的(一次性并行计算整条前缀的注意力),KV 只写入一次、不需要反复读取,GQA 省下的带宽占比很小。实测中 prefill 的提速远不如 decode 显著,这也是 MQA 论文里编码器部分只快 14% 的原因——同一个道理。
  • 与 FlashAttention 是互补关系。FlashAttention 优化的是”计算与访存的比值”(IO 感知的分块调度),GQA 优化的是”KV 的总量”——一个解决”每块数据怎么搬更高效”,一个解决”要搬的数据能不能少一些”,作用在不同层面,同时使用效果叠加。

主流模型里的 GQA:KV 缓存账本#

GQA 发表后迅速成为大模型的事实标准。LLaMA-2 论文(2023 年 7 月)在 34B 与 70B 上采用 GQA(70B 配置为 64 个查询头配 8 个 KV 头),明确说明动机是提升推理可扩展性;LLaMA-3 全系列、Mistral-7B、Qwen2、Gemma 等模型也都采用了 GQA,Falcon 系列则沿用更激进的 MQA。各家配置与 KV 缓存账本如下(BF16,头维度 128 除特别注明):

模型层数查询头KV 头每 token KV相对 MHA
LLaMA-2-7B323232(MHA)512 KiB
LLaMA-2-70B80648(GQA)320 KiB1/8
LLaMA-3-8B32328(GQA)128 KiB1/4
Mistral-7B32328(GQA)128 KiB1/4
Qwen2-7B28284(GQA)56 KiB1/7
Falcon-7B32711(MQA)8 KiB1/71

这些数字用前面的公式 SKV=2LGdkBS_{\text{KV}} = 2 L G d_k B 直接算出。以 LLaMA-3-8B 为例:32 层 × 8 个 KV 头 × 128 维 × 2 字节 × 2(K、V)= 131072 字节 = 128 KiB/token——只有同尺寸 MHA 模型的四分之一。“GQA 用 1/8 的 KV 缓存”的说法对应 GQA-8(64 头→8 头)的模型,如 LLaMA-2-70B;组数不同,压缩比不同。

有个容易被忽略的细节:LLaMA-2 的 7B/13B 版本用的是 MHA,34B/70B 才用 GQA。这背后的权衡正是 GQA 论文的论证——KV cache 随模型维度线性增长(LLGG 都与 dmodeld_{model} 同阶),而参数量与计算量随模型维度平方增长。模型越大,KV 的带宽与容量占比越高,压缩 KV 的收益越大;小模型上 KV 占比本就低,GQA 省下的带宽有限,反而可能损失质量,所以小模型保持 MHA 更划算。到了 LLaMA-3 时代,8B 也采用了 GQA-8——长上下文支持让 KV 的绝对量急剧上升,压缩收益重新压过了质量损失。

KV 缓存账本在长上下文下尤其触目惊心。LLaMA-3.1-8B 支持 128K 上下文,若真把上下文用满,KV 缓存是 128 KiB × 131072 ≈ 16 GiB——和模型权重(约 16 GB)相当,单条请求就吃掉 A100-80GB 近四分之一的显存。

对推理服务系统来说,KV cache 的大小直接决定能并发的请求数量:权重占用是固定的,剩余显存除以单请求的 KV 占用就是批大小上限。用一个具体的容量账目说明 GQA 的价值:假设在 A100-80GB 上部署 LLaMA-3-8B(权重约 16 GB,BF16),剩余约 64 GB 留给 KV cache 与激活。4K 上下文、MHA 配置(32 个 KV 头)下单请求 KV 为 512 KiB × 4096 = 2 GiB,64 GB 只够约 30 个并发请求;换成 GQA-8 后单请求 KV 降到 512 MiB,并发上限变成约 120 个——同样的显存,4 倍的批容量。而批大小又是解码吞吐的关键:批量越大,每步读一次的权重被越多请求分摊,GPU 越接近”算得过来”的状态。这也是为什么连续批处理(continuous batching)与 KV 管理技术(如 vLLM 的 PagedAttention,本站已有完全拆解)能与之叠加:GQA 在”结构”上压缩 KV,分页在”分配”上消除 KV 的碎片浪费,两者正交。

工程实现:权重布局、repeat 与 Kernel 融合#

从代码层面看,GQA 的实现改动集中在两个地方:投影矩阵的输出维度注意力计算前的头对齐

权重布局。 MHA 的 WKW_KWVW_V 形状是 [h,dk,dmodel][h, d_k, d_{model}];GQA 直接把它们改成 [G,dk,dmodel][G, d_k, d_{model}],总参数量除以 h/Gh/G。Hugging Face Transformers 里对应 num_key_value_heads 配置项——k_projv_proj 的输出维度从 h×dkh \times d_k 变成 G×dkG \times d_k,查询侧不变。推理框架通常更进一步,把 K、V 的投影合并成单块权重 WKV:[G,2dk,dmodel]W_{KV}: [G, 2 d_k, d_{model}],一次 GEMM 同时产出 K、V,减少矩阵乘次数与权重搬运(这正是 vLLM 与 llama.cpp 的常见做法)。

头对齐。 注意力计算时,查询有 hh 个(batch 维上分头),键/值只有 GG 个,两者的 batch 维不匹配,无法直接做 batched GEMM。Transformers 的做法是把 KV 头沿头维”重复”(repeat)h/Gh/G 次,展开成 hh 个后照常计算,对应代码即建模文件里的 repeat_kv

def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
batch, num_key_value_heads, slen, head_dim = hidden_states.shape
if n_rep == 1:
return hidden_states
hidden_states = hidden_states[:, :, None, :, :].expand(
batch, num_key_value_heads, n_rep, slen, head_dim
)
return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)

调用处:

key_states = repeat_kv(key_states, self.num_key_value_groups)
value_states = repeat_kv(value_states, self.num_key_value_groups)

num_key_value_groups = num_heads // num_key_value_heads 即每组查询头数 h/Gh/Gexpand 只复制元数据不复制数据,reshape 后同一组 KV 头的副本共享底层内存——注意这只是”视图”层面的重复,数据本身没有复制

显式 repeat 的代价在哪里? 展开后的 K、V 张量虽然不占额外 HBM,但在 CUDA kernel 里,头副本最终要进入寄存器/共享内存参与计算,等效于把 KV 数据的读取量放大了 h/Gh/G 倍——正好抵消 GQA 省下的带宽。所以生产级实现不会真的”展开”,而是让 kernel 直接感知分组:FlashAttention-2 论文明确声明其实现推广到了 MQA 与 GQA(arXiv:2307.08691),做法是把”头”拆成”组下标 + 组内下标”,共享的 KV 头在共享内存中只加载一次,多个查询头复用同一份数据;vLLM 的 PagedAttention kernel(arXiv:2309.06180)同样原生支持 GQA 的头组共享——它在分页块(block)的布局里按 KV 头组织数据,kernel 内部做组内广播,GQA 的模型权重在 vLLM 的配置里就是 num_kv_heads 字段,与 num_attention_heads 一起驱动显存分配与 kernel 选型。这就是”结构压缩”与”kernel 感知”缺一不可的原因:GQA 在模型结构上省下的带宽,只有底层 kernel 不重新把数据复制回来,才算真正落地。

训练侧的实现则绕开 repeat:PyTorch 的 scaled_dot_product_attention(SDPA)直接支持查询头数与 KV 头数不等的输入——传入形状为 [b,h,n,dk][b, h, n, d_k] 的 Q 和 [b,G,n,dk][b, G, n, d_k] 的 K、V,CUDA 后端自动走 FlashAttention 的高效广播路径,训练与推理共用同一套 kernel。框架层面,Hugging Face 的模型配置里只需声明 num_key_value_headsattention_mask、位置编码、RoPE 等其余逻辑全部复用——这也是 GQA 在 2023 年后能迅速铺开的原因之一:从模型架构到推理框架,改动都被收敛到了一个配置字段和几个 kernel 特化上。

还有一个容易被忽略的工程点是缓存的读取模式。GQA 的 KV cache 在显存里按”层 × KV 头 × 序列位置”组织,解码时每个 KV 头被同一组的多个查询头反复读取。如果 kernel 把每个查询头都单独发起一次 KV 加载,带宽收益照样会打折扣;正确的做法是让同一组的查询头在同一个线程块(thread block)里处理,KV 块加载一次、组内所有查询头复用。FlashAttention 与 vLLM 的实现都遵循这个”组内共驻”的调度原则,CPU 推理框架(如 llama.cpp)的 QK 融合路径也按同样的思路处理。

更进一步的 KV 压缩#

GQA 打开了”压缩 KV 头”的思路。把这条线上几类方案放在一起对比,可以看清各自的压缩对象与代价:

方案压缩对象KV 压缩比质量影响代表
MHA基线早期 GPT、LLaMA-2-7B/13B
MQAKV 头数压到 1h×h\times明显下降PaLM、Falcon-7B
GQAKV 头数压到 GGh/G×h/G\times轻微LLaMA-2/3、Mistral、Qwen2
MLA低秩潜在向量压缩数倍以上轻微DeepSeek-V2/V3
KV 量化每元素位数2–8×轻微(需校准)QEvict 等

“压缩比”一栏以 KV cache 内存为口径:MQA/GQA 压缩的是头数维度,MLA 压缩的是表示本身的维度,量化压缩的是数值精度——三者作用在不同维度,可以任意组合。后续工作在这条线上继续推进:

  • MLA(Multi-head Latent Attention):DeepSeek-V2 提出的低秩(low-rank)KV 压缩——把 KV 压缩进一个低维潜在向量,比 GQA 的分组共享更激进、压缩比更高,本质上可以看作 GQA 向”连续分组”方向的推广。本站已有MLA 完全拆解,其与 GQA 的对比(1/8 vs 1/16 乃至更高的压缩比)在那篇文章里有详细讨论。
  • KV 缓存量化:把缓存的 K、V 从 FP16 压到 INT8/INT4(例如本站拆解过的 QEvict 的 INT2 档位),与 GQA 完全正交——GQA 减少”头数”,量化减少”每元素字节数”,两者相乘。
  • 注意力复用与淘汰:MAC-Attention 复用相似查询的注意力结果、QEvict 淘汰低价值 KV,处理的是”哪些 KV 值得存”的问题,与”KV 头怎么排布”互补。

边界也要说清楚:GQA 改变的是 KV cache 的系数,不改变它随序列长度线性增长的事实。在 128K、1M 级长上下文下,即使 KV 缩小 8 倍,总容量仍然可观——这也是长上下文推理里 KV 卸载(如 DualPath)、分层存储等技术依然必要的原因。GQA 解决的是”单位 token 的 KV 成本”,而不是”上下文长度”问题。

小结#

GQA 是注意力机制演进中一个简洁而关键的分水岭。它把 MHA 的”每头一套 KV”与 MQA 的”全部共享一套 KV”统一进一个可调参数 GG:查询头保留完整的多头多样性,键值头按组共享,KV cache 与解码带宽精确缩小 G/hG/h 倍。配合均值池化 + 5% 算力的升级训练配方,已有的 MHA 检查点可以低成本地改造为 GQA 模型——论文实测 T5-XXL 上获得 5.4 倍推理加速,平均质量仅下降 0.1 分。

三个值得记住的要点:

  1. 解码推理是内存带宽受限的,KV cache 的大小直接决定延迟、批容量与吞吐;GQA 压缩的是 KV 头数量(结构压缩),与量化(数值压缩)、分页(分配优化)正交叠加。
  2. GQA 的收益与代价都来自”组内共享”:省带宽、保容量,但注意力 FLOPs 不减;组数 GG 是一个可调旋钮,G=8G=8 是论文实测的性价比甜点,主流模型(LLaMA-2/3、Mistral、Qwen2)普遍采用 4–8 组。
  3. 工程落地要求 kernel 感知分组:显式 repeat 会把省下的带宽重新读回去,FlashAttention 与 PagedAttention 的原生 GQA 支持才是收益真正到手的环节。

如果要在”省 KV 内存”这条路上继续深入,下一站是 MLA 的低秩压缩与 KV 量化;如果想把 GQA 放在整个推理服务里看,连续批处理与 PagedAttention 的配合是完整的图景——本站均已单独拆解,可对照阅读。

参考资料#

  1. GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints(arXiv:2305.13245)
  2. GQA 论文 EMNLP 2023 正式版(ACL Anthology)
  3. Fast Transformer Decoding: One Write-Head is All You Need(arXiv:1911.02150)
  4. LLaMA 2: Open Foundation and Fine-Tuned Chat Models(arXiv:2307.09288)
  5. The Llama 3 Herd of Models(arXiv:2407.21783)
  6. Mistral 7B(arXiv:2310.06825)
  7. Qwen2 Technical Report(arXiv:2407.10671)
  8. FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning(arXiv:2307.08691)
  9. Efficient Memory Management for Large Language Model Serving with PagedAttention(arXiv:2309.06180)
  10. DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model(arXiv:2405.04434)
  11. Hugging Face Transformers LLaMA 建模实现(repeat_kv 源码位置)
  12. 深度解析 Group Query Attention (GQA) 为什么能给 LLM decoder 带来极大推理加速(知乎)
  13. 深度解析新型注意力机制 Group Query Attention (GQA)(CSDN)

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

GQA 完全拆解:分组查询注意力,用 1/8 的 KV 缓存保住 MHA 的质量
https://pinghaoyang.com.cn/aigc/posts/gqa/
作者
平昊阳
发布于
2026-08-27
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
平昊阳
乘长风,破巨浪, 展鸿图于未央!
--
总访问量
--
访客数
公告
欢迎来到我的个人博客!欢迎关注交流吖!
更多相关公告,见
社交-留言」。
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
站点统计
文章
84
分类
18
标签
108
总字数
701,808
运行时长
0
最后活动
0 天前

文章目录