注意力就是加权平均
一句话总结
注意力是确定“现在在这个位置上看哪个位置多少”,然后计算出值的加权平均**的运算。仅此而已。
三个名字
从同样的输入中创建三个向量。
- Q(query) — 我正在寻找的东西
- K(key) — 每个位置展示的封面
- V(value) — 那个位置实际呈现的内容
图书馆的比喻很准确。拿着问题(Q)去匹配书等的标题(K),然后拿出与之匹配的程度的书籍的内容(V)。
score = Q · Kᵀ / √d ← 얼마나 맞는가
weight = softmax(score) ← 합이 1이 되게
out = weight · V ← 가중 평균
只有三行。其余的全部是重复做这三行,从不同的方向做。
为什么用√d除法——用数字
这是最常被忽略的部分,就是“就这样做”,理由很明确。
成分是独立的,分散度为1d维度向量的内积是方差d可以去。d=64面分数大致分布在±8范围内。
如果在Soft Max中加入±8分的分数会怎么样?e⁸ / e⁻⁸ ≈ 9백만是。**最大的一个几乎把1都拿走了,其余的就变成了0。**不是加权平均,而是直接“选择一个”。
那样就无法学习了。因为当softmax饱和时,斜率会接近0。
√d如果除以,分散率又恢复为1。在这个练习的第2阶段,直接测量这个——没有除以时的最大概率能上升到多高。
软麦克斯除以最大值后进行计算。
def softmax(xs):
m = max(xs) # 이 한 줄이 없으면
e = [exp(x - m) for x in xs] # exp(1000) 에서 터진다
s = sum(e)
return [v / s for v in e]
exp(x - max)虽然在数学上是相同的值,但**不会出现溢位。**在实际代码中遗漏这一行的话,大值输入的瞬间inf哇nan这个出来了。
口罩——看不到未来
语言模型学习如何正确识别以下的代币。但是注意力基本上是看所有位置。也就是说,看到答案后再回答。
所以分数矩阵的上方的三角形-inf变成。
k0 k1 k2
q0 0.3 -inf -inf
q1 0.1 0.5 -inf
q2 0.2 0.1 0.4
-inf是exp通过后正好变成0。不是乘以0,而是软最大值之前加上-inf才是关键——软最大值之后乘以0的话,剩下的加权数的和就不等于1了。
这就是“causal”或“decoder”掩码。BERT等编码器模型没有。所以BERT擅长看整个句子理解,GPT擅长连续写作。
多头——为什么拆分?
d=64与其一次性使用,不如分成8个d=8重复8次扎里注意力后连在一起。计算量几乎一样。
为什么?**一个softmax只能表达一种关系。**加权和为1,所以不能同时强烈地看多个地方。分头的话,哪一个头就是前面一个单词,哪一个头就是句子前的主语,哪一个头就是看引号对。
打开学习模型的头,实际上是那样分开的。
位置编码——注意力不知道顺序
这是第一次学习时最令人惊讶的部分。
**注意力完全没有顺序概念。**如果混合输入令牌,输出也会一样混合出来,但值本身不会改变(permutation equivariant)。“我喜欢你”和“我喜欢你”是无法区分的。
所以除了输入位置信息外还增加了位置信息。
- 正弦/余弦(原论文) — 无学习参数,通过长长度进行外插
- 学习型嵌入式(BERT, ViT) — 虽然简单,但学习时不能超过本长度
- RoPE(LLaMA, Qwen等最近大部分) — 旋转Q·K,自然地包含相对位置。因为长度扩展容易,所以增加语境的最近模型全部使用这个。
- ALiBi — 按距离加罚点数
**在第5步中亲自确认这个。**如果没有位置编码,如果混合输入的话,输出会按照原样混合,如果加起来的话就不一样了。
LayerNorm与残差连接
x = x + Attention(LayerNorm(x))
x = x + FFN(LayerNorm(x))
- 残差(residual) — 即使深度积累,斜率也会达到输入。如果没有这个,即使超过6层也无法学习。
- LayerNorm — 将每个令牌向量平均调整为0,分散为1。与BatchNorm不同的是,不是通过配置,而是通过特征轴进行归一化,所以不受批次大小或序列长度的影响。
LayerNorm把注意力放在前面还是放在后面(pre-LN vs post-LN)实际上决定了学习稳定性。虽然原论文是post-LN,但最近几乎都是pre-LN——因为即使没有热身也能学习。
费用从哪里来
分数矩阵是n × n是。通过序列长度的平方进行内存和计算。如果将上下文4k增加到8k,是4倍。
试图减少这个的尝试是最近研究的重大方向。
- FlashAttention — 保持数学不变,改变GPU内存访问顺序,实际速度提高几倍。不是近似值
- GQA / MQA — K·V头共享多个Q头,减少推理时KV缓存。最近大部分开放型模型都是GQA
- MoE — 每个层只打开一部分专家,参数大但计算小
- 滑动窗口/稀有注意力——完全不看远处的位置
整理
注意力本身只有三行。其余的内容是关于如何稳定、便宜、知道顺序地写出那三行的方法。在下次练习中直接写出那三行,如果没有缩放和位置编码,用数字看看会崩溃什么。
在现场
几乎没有直接做这个计算的事情。因为框架已经全部完成了。但是,应该记住的原因是遇到问题时应该看哪里在这里就分开了。
如果学习损失突然变成nan,通常是因为softmax前面值超出,如果戴错了面罩,就不会出现错误,安静地看着未来代币学习,只会有奇怪的好评价分数。如果改变了配置大小,结果变了,就应该怀疑正则化轴,如果将上下文增加两倍,内存增加四倍,这不是错误,而是结构上理所当然的事情。
如果运营推理服务的话,KV缓存很快就会成为内存预算。首先计算一下同时请求次数和最大上下文长度乘积的值是否进入GPU内存,如果进入不了,选择使用GQA的模型或降低上下文上限才是实际选择。