MLP 把固定窗口的向量拼接起来;注意力则为每个位置计算一组信息权重。对自回归语言模型来说,它还有一个约束:预测下一个 token 时,不能看到未来位置。
Q、K、V 分别做什么
把输入向量 X 乘上三组可训练矩阵,得到 Q、K、V。可以把 Q 理解为当前位置提出的查询,K 是各位置提供的匹配信息,V 是按权重汇总的内容。这是理解计算的比喻,不意味着模型里存在自然语言问答。
若 X 的形状是 [B,T,C],单头 Q/K/V 都可以是 [B,T,D]。Q×Kᵀ 得到 [B,T,T],其中每一行代表某个查询位置对所有键位置的分数。
缩放、遮罩与 softmax
点积通常除以 √D,然后把未来位置的分数置为负无穷,再沿最后一个维度做 softmax。这样每行合法位置的权重和为 1,未来位置的权重为 0。加权乘 V 后得到 [B,T,D]。
三个位置的合法关系是:第一个位置只能看自己,第二个可以看前两个,第三个可以看前三个。因此保留对角线和下三角,而不是只保留对角线以下。
import math
import torch
def causal_attention(q, k, v):
length = q.shape[-2]
scores = q @ k.transpose(-2, -1)
scores = scores / math.sqrt(q.shape[-1])
allowed = torch.ones(length, length, dtype=torch.bool,
device=q.device).tril()
scores = scores.masked_fill(~allowed, float("-inf"))
weights = scores.softmax(dim=-1)
return weights @ v, weights
这是无 dropout 的教学版本,假设查询和键有相同序列长度。生产实现还需要处理 padding、多头拆分、缓存等条件,不能把这个函数直接当成所有场景的完整实现。
一个能发现泄漏的测试
固定前三个输入,先做一次前向。只修改最后一个位置,再前向一次。若模型没有随机 dropout,前面位置的输出应在数值容差内保持不变。否则先检查遮罩方向、目标对齐和位置索引。
还要检查注意力权重每行求和是否接近 1、未来位置是否为 0。仅看输出形状正确,无法证明没有信息泄漏。
下一步是把注意力放进 block
这一课以 Raschka 的注意力实现章节和 Transformer 原论文为延伸资料。多头注意力会并行计算多个子空间的权重,再组合输出;它并不取消因果约束。
下一课把注意力、前馈网络和残差连接放进完整 GPT。保留你这次的因果测试,随后让整个模型也通过同样的检查。