X→Q,K,V→QKT→Mask→Softmax→PV→Merge Heads
Multi-head Attention·
设 Transformer 某一层的输入为
X∈RB×S×H
这里:
- B:batch size,一次处理多少条序列;
- S:sequence length,每条序列多少个 token;
- H:hidden size,每个 token 的隐藏向量维度。
Attention 首先对 X 做三个线性变换:
Q=XWQ,K=XWK,V=XWV.
如果暂时不拆 head,它们通常都是
Q,K,V∈RB×S×H.
然后进入 Multi-Head Attention。
设 head 数量为 Nh,每个 head 的维度为 Dh。通常有
H=NhDh.
原始:
Q∈RB×S×H.
因为 H=NhDh,先 reshape:
[B,S,H]→[B,S,Nh,Dh].
然后为了方便矩阵乘法,把 head 维提前:
[B,S,Nh,Dh]→[B,Nh,S,Dh].
于是:
Q,K,V∈RB×Nh×S×Dh.
例如:
[2,32,1024,128].
可以理解为每个 batch 有 Nh 个独立的 Attention head,每个 head 都看到 S 个 token,每个 token 在这个 head 中是 Dh 维。
对于一个 head:
Qh∈RS×Dh
而
Kh∈RS×Dh.
为了做矩阵乘法,对 K 最后两个维度转置:
KhT∈RDh×S.
所以:
QhKhT:(S×Dh)(Dh×S)
得到:
S×S
的矩阵。
因此完整 batch + multi-head 情况下:
QKT∈RB×Nh×S×S.
为了避免和 sequence length S 混淆,可以把 score matrix 记为 A:
A=DhQKT∈RB×Nh×S×S.
假设一句话有 4 个 token:
那么 S=4。每个 token 都要询问:“我应该关注哪些 token?”
所以每一个 Query 都要和所有 Key 做点积。得到:
A=a11a21a31a41a12a22a32a42a13a23a33a43a14a24a34a44.
其中:
aij=DhqiTkj
表示第 i 个 token 的 Query,认为第 j 个 token 的 Key 有多相关。
因此:
- 行 i:当前 Query token;
- 列 j:它正在观察的 Key token。
例如 a34 就是:第 3 个 token 对第 4 个 token 的注意力匹配分数。
P,A∈RB×Nh×S×S.
这里出现了:
S2.
因此标准 Attention 的 score matrix 元素数量:
BNhS2.
当 sequence length 增大时,增长速度是平方级。这是长上下文昂贵的原因之一。
不过 Attention 不是直接计算 QKT,而是:
DhQKT.
原因和数值尺度有关。假设 Query 和 Key 每一个分量大致满足均值 0、方差 1。点积
qTk=r=1∑Dhqrkr
的方差会随着 Dh 增长。如果直接进入 Softmax,例如:
softmax(100,0,−100)
分布会非常尖锐,容易导致梯度不好训练。除以 Dh 可以把点积的典型尺度稳定下来:
DhqTk.
这就是 Scaled Dot-Product Attention 中 “Scaled” 的来源。
自回归语言模型有一个非常重要的限制:第 i 个 token 不能偷看未来 token。
例如:
预测“喜欢”时,不能提前看到后面的“深度 学习”。因此需要 causal mask。
对于 S=4,mask 可以理解成:
M=0000−∞000−∞−∞00−∞−∞−∞0.
然后计算:
A+M.
第一行:
(a11,−∞,−∞,−∞)
第二行:
(a21,a22,−∞,−∞)
因为后面马上做 Softmax:
P=softmax(A+M).
而 e−∞=0,所以一个被 mask 的位置最终概率就是 0。
例如:
softmax(3,1,−∞)
第三项概率:
e3+e1+e−∞e−∞=0.
因此 causal mask 最终实现:
未来 token 的 Attention 权重=0.
现在:
A∈RB×Nh×S×S.
对于每一个:
- batch b
- head h
- query token i
都有一整行:
Ab,h,i,:.
Softmax 对最后一个维度,也就是 Key/token 维度 进行:
Pb,h,i,:=softmax(Ab,h,i,:).
所以:
j=1∑SPb,h,i,j=1.
每一个 Query token 都得到一组对所有 Key token 的概率权重。例如:
Pi,:=(0.1,0.2,0.6,0.1).
表示 token i 把 10% 注意力放第 1 个 token,20% 放第 2 个,60% 放第 3 个,10% 放第 4 个。
得到 Attention probability:
P∈RB×Nh×S×S.
而:
V∈RB×Nh×S×Dh.
计算:
O=PV.
观察最后两个矩阵维度:
(S×S)(S×Dh)
得到:
S×Dh.
所以:
O∈RB×Nh×S×Dh.
跟原来的 Q,K,V shape 一样。
对于 token i:
oi=j=1∑SPijvj.
例如 Attention 权重:
Pi,:=(0.1,0.2,0.6,0.1).
那么:
oi=0.1v1+0.2v2+0.6v3+0.1v4.
所以 Attention 的真正含义是 根据 Query-Key 相似度,对不同 Value 做加权求和。
因此:
- Q:我在寻找什么?
- K:我有什么特征可供匹配?
- V:如果你关注我,你真正获得什么信息?
现在:
O∈RB×Nh×S×Dh.
先 transpose:
[B,Nh,S,Dh]→[B,S,Nh,Dh].
然后把最后两个维度拼起来:
NhDh=H.
所以:
[B,S,Nh,Dh]→[B,S,H].
最终:
O∈RB×S×H.
和原始输入
X∈RB×S×H
shape 完全一致。
Flash Attention·
Flash Attention 的核心不是改变 Attention 数学公式,而是不把完整 S×S 中间矩阵写回 HBM。
普通实现逻辑上可能类似:
Q,K→QKT.
把巨大的 S×S 矩阵写入 GPU HBM。然后再:
HBM→Softmax
再写回。然后:
P,V→PV.
大量时间浪费在:
HBM 读写.
而不仅仅是 FLOPs。Flash Attention 不一次生成整个:
S×S.
而是把 Q,K,V 切成小块。例如概念上:
1 2 3 4 5 6 7
| Q block ↓ K block → 算局部 QKᵀ ↓ 局部 Softmax ↓ 和 V block 累积
|
只在 GPU 更快但容量更小的片上存储中保留当前 block 所需数据,例如:
- registers;
- shared memory / SRAM。
算完这个块后立即把贡献累积到输出。因此不需要:
完整 materialize 一个 S×S score matrix.
普通 Softmax:
pj=∑kexkexj
通常需要知道整行:
x1,…,xS.
而数值稳定版本还需要:
m=jmaxxj.
然后:
pj=∑kexk−mexj−m.
看起来必须一次拿到所有 S 个元素。
Flash Attention 使用一种 online softmax 思想分块处理时持续维护当前行的最大值和归一化因子。
假设已经处理了一部分,保存:
mold
和:
lold=∑exi−mold.
新 block 出现新的最大值之后,可以重新缩放旧累积值,从而得到正确的新 normalization。因此无需保存整行 score,也能精确完成稳定 Softmax。事实上,Flash Attention 没有消除标准 Attention 的平方计算复杂度。Flash Attention 主要是 IO-aware exact attention,显著降低了 HBM IO 带宽从而降低显存压力。
标准 self-attention 仍然需要比较:
S×S
个 Query-Key 对。所以主要计算复杂度仍然近似:
O(S2Dh).
多头总体近似:
O(S2H).
讨论
评论