#P5613. 第3题-分组查询注意力
-
1000ms
Tried: 1051
Accepted: 152
Difficulty: 5
所属公司 :
华为
时间 :2026年10月10日-AI方向
第3题-分组查询注意力
解题思路
核心思路:分组查询注意力就是带掩码的缩放点积注意力,再让多个 Query 头共用较少的 KV 头。把 Q 按连续列切成 num_q_heads 个形状为 (n,dk) 的头,把 K,V 也按连续列切成 num_kv_heads 个头。题面保证 num_q_heads 能被 num_kv_heads 整除,组大小为
g=num_kv_headsnum_q_heads第 i 个 Query 头只和第 ⌊i/g⌋ 个 Key 头、Value 头做注意力,再把所有 Query 头的结果按原顺序横向拼接。
每个头的公式与标准缩放点积注意力相同:
题目描述
分组查询注意力(GQA)是标准多头注意力的变体,核心思想是让多个 Query 头共享一组 KV 头,在保证模型效果的同时降低计算开销。请基于缩放点积注意力实现分组查询注意力。
核心数学逻辑
GQA 由基础缩放点积注意力扩展而来,关键差别是头的分组共享:
- 将 Query 矩阵拆分为 num_q_heads 个独立的 Query 头子矩阵,记为 Q0,Q1,…,Qnum_q_heads−1。
- 将 Key、Value 矩阵拆分为 num_kv_heads 个独立的 KV 头子矩阵,记为 K0,K1,…,Knum_kv_heads−1 与 V0,V1,…,Vnum_kv_heads−1。
- num_q_heads 必须是 num_kv_heads 的整数倍。每组包含 g=num_q_heads/num_kv_heads 个 Query 头,共享 1 组 KV 头:
- 第 0 组:Q0,Q1,…,Qg−1 共享 K0,V0。
- 第 1 组:Qg,Qg+1,…,Q2g−1 共享 K1,V1。
- 第 num_kv_heads−1 组:Q(num_kv_heads−1)g,…,Qnum_q_heads−1 共享 Knum_kv_heads−1,Vnum_kv_heads−1。
- 对每组内的每个 Query 头,与共享的 KV 头做基础缩放点积注意力,得到该 Query 头的输出。
- 将全部 Query 头的输出按顺序拼接,得到最终的 GQA 输出矩阵。
基础缩放点积注意力要求
单组 Q,K,V 按下列规则计算。
输入输出
输入:Q(n,dk)、K(n,dk)、V(n,dv)、mask(n,n)。
输出:Output(n,dv)。
核心公式
Attention(Q,K,V,mask)=Softmax(dkQKT+M)V
当 mask[i][j]=1 时 M[i][j]=−∞,否则 M[i][j]=0。实现中 −∞ 可取 −1030。
Softmax(逐行)
A[i][j]=∑t=0n−1exp(S[i][t]−maxtS[i][t])exp(S[i][j]−maxtS[i][t])
计算步骤
- 转置:对 K 转置得到 KT。
- 手动矩阵乘法:S[i][j]=∑t=0dk−1Q[i][t]⋅K[j][t]。
- 缩放:S[i][j]=S[i][j]/dk。
- 掩码:若 mask[i][j]=1,则 S[i][j]=−∞。
- Softmax:按上述公式逐行计算注意力权重 A。
- 加权求和:Output[i][k]=∑t=0n−1A[i][t]⋅V[t][k]。
算法实现流程
步骤1:输入矩阵拆分
- 原始 Q 的形状为 (n,dtotal_q),其中 dtotal_q=num_q_heads×dk。
- 原始 K 的形状为 (n,dtotal_k),其中 dtotal_k=num_kv_heads×dk。
- 原始 V 的形状为 (n,dtotal_v),其中 dtotal_v=num_kv_heads×dv。
拆分规则:
- Q 拆成 num_q_heads 个子矩阵,每个形状为 (n,dk)。
- K 拆成 num_kv_heads 个子矩阵,每个形状为 (n,dk)。
- V 拆成 num_kv_heads 个子矩阵,每个形状为 (n,dv)。
步骤2:分组计算注意力
- 每组 Query 头数为 g=⌊num_q_heads/num_kv_heads⌋,即 num_q_heads 除以 num_kv_heads 后向下取整。
- 对第 m 组(0≤m<num_kv_heads):
- 取该组 KV 头 Km,Vm。
- 取该组 g 个 Query 头 Qm⋅g,Qm⋅g+1,…,Q(m+1)⋅g−1。
- 对每个 Query 头 Qi 计算 Outputi=Attention(Qi,Km,Vm,mask)。
步骤3:拼接输出
将 Output0,Output1,…,Outputnum_q_heads−1 按列拼接,得到最终输出矩阵,形状为 (n,num_q_heads×dv),即序列长度 ×(Query 头数 × 单个 Value 头维度)。
输入描述
第一行:n num_q_heads num_kv_heads dk dv。
字段含义依次为:序列长度、Query 头总数、Key/Value 头总数、单个 Query/Key 头维度、单个 Value 头维度。
- num_q_heads:Query 头总数,必须是 num_kv_heads 的整数倍。
- num_kv_heads:Key 与 Value 的头总数,二者始终相等。
- dk:单个 Query 头与单个 Key 头的特征维度,二者相同。
- dv:单个 Value 头的特征维度。
接下来 n 行:原始 Query 矩阵 Q,形状为 (n,num_q_heads×dk),每行 num_q_heads×dk 个浮点数。
接下来 n 行:原始 Key 矩阵 K,形状为 (n,num_kv_heads×dk),每行 num_kv_heads×dk 个浮点数。
接下来 n 行:原始 Value 矩阵 V,形状为 (n,num_kv_heads×dv),每行 num_kv_heads×dv 个浮点数。
接下来 n 行:掩码矩阵,形状为 (n,n)。0 表示可见,1 表示屏蔽;若全为 0,则表示无掩码。
输入约束:
- 1≤n≤100
- 1≤num_q_heads≤16,1≤num_kv_heads≤8,且 num_q_headsmodnum_kv_heads=0
- 1≤dk,dv≤10
- 矩阵元素取值范围:[−10.0,10.0]
输出描述
输出一个二维浮点数矩阵,表示分组查询注意力的计算结果。
形状:(n,num_q_heads×dv)(序列长度 ×(Query 头数 × 单个 Value 头维度))。
输出格式:共 n 行,每行 num_q_heads×dv 个浮点数,每个数保留 2 位小数。
样例
样例1
输入
2 4 2 1 1
1.0 0.0 0.0 1.0
0.0 1.0 1.0 0.0
0.0 1.0
1.0 0.0
2.0 4.0
8.0 6.0
0 0
0 0
输出
6.39 5.00 5.00 4.54
5.00 6.39 4.54 5.00
说明
n=2,num_q_heads=4,num_kv_heads=2,dk=1,dv=1,故 g=2。掩码全为 0,dk=1。
第 0 组 Q0,Q1 共享 K0,V0;第 1 组 Q2,Q3 共享 K1,V1。按列拆开后:
- Q0 两行为 1,0,Q1 两行为 0,1,K0 两行为 0,1,V0 两行为 2,8。
- Q2 两行为 0,1,Q3 两行为 1,0,K1 两行为 1,0,V1 两行为 4,6。
得分相差 1 时,较大位置的 Softmax 权重为 e/(1+e)≈0.731059,较小位置为 1/(1+e)≈0.268941。两行得分相同时,权重各为 0.5。
- Q0:第 0 行得分为 0,1,输出 0.268941×2+0.731059×8=6.386354,保留两位小数为 6.39;第 1 行得分为 0,0,输出 0.5×2+0.5×8=5.00。
- Q1:第 0 行得分为 0,0,输出 5.00;第 1 行得分为 0,1,输出 6.39。
- Q2:第 0 行得分为 0,0,输出 0.5×4+0.5×6=5.00;第 1 行得分为 1,0,输出 0.731059×4+0.268941×6=4.537882,保留两位小数为 4.54。
- Q3:第 0 行得分为 1,0,输出 4.54;第 1 行得分为 0,0,输出 5.00。
四个头按列拼接,即得到输出。
样例2
输入
2 2 2 2 1
1.0 0.0 0.0 1.0
0.0 1.0 1.0 0.0
1.0 0.0 0.0 1.0
0.0 1.0 1.0 0.0
2.0 6.0
4.0 8.0
0 1
0 0
输出
2.00 6.00
3.34 7.34
说明
n=2,num_q_heads=2,num_kv_heads=2,dk=2,dv=1,故 g=1。Q0 只使用 K0,V0,Q1 只使用 K1,V1。dk=2≈1.414214。掩码第 0 行第 1 列为 1,该位置在缩放之后改为 −∞。
Q0 两行是 (1,0) 与 (0,1),K0 两行是 (1,0) 与 (0,1),V0 两行是 2,4。点积矩阵两行分别为 1,0 与 0,1,缩放后第 0 行约为 0.707107,0;掩码使第二项为 −∞,权重为 1,0,输出 2.00。第 1 行缩放后约为 0,0.707107,权重约为 0.330238,0.669762,输出 0.330238×2+0.669762×4=3.339524,保留两位小数为 3.34。
Q1 两行是 (0,1) 与 (1,0),K1 两行是 (0,1) 与 (1,0),V1 两行是 6,8。缩放后的得分与 Q0 相同,掩码也相同,因此第 0 行输出 6.00,第 1 行输出 0.330238×6+0.669762×8=7.339524,保留两位小数为 7.34。
两头按列拼接,即得到输出。