本文目录
先建立一个具体场景
上一章算出了一张注意力分数矩阵:行是 Query,列是 Key。如果任务是「根据已经出现的词预测下一个词」,当前位置就不能偷看还没生成出来的词。
用三个 Token 举例:我、爱、猫。当模型站在「我」这一行做查询时,它只应该看见「我」;站在「爱」时,可以看见「我」和「爱」;站在「猫」时,可以看见整句前三个位置。右边那些尚未合法出现的位置,就是未来 Token。
下面的实验把这件事做成可开关的矩阵:开启因果掩码(Causal Mask)后,右上角会变成 -∞,Softmax 后对应权重变成 0。
多头 × 因果掩码
开关掩码,看右上角怎样变成 −∞
头决定学什么关系;因果掩码决定每个位置允许读到谁。两者职责不同。
对角线附近分数更高:更偏向看自己和近邻。
可见(列 ≤ 行) 未来 Token(列 > 行)→ −∞ 当前挡住 3 格
读矩阵:谁在问,准备读谁
表格的行表示「谁在发起查询」,列表示「它准备读取谁」。
- 第 1 行第 2 列:Token「我」想读 Token「爱」
- 第 2 行第 1 列:Token「爱」想读 Token「我」
- 第 3 行第 3 列:Token「猫」读自己
关闭掩码时,九个格子都是普通分数,每一行都能给三个位置打分。这在双向理解任务里可以成立;但在自回归生成里,右上角等价于「用未来答案帮助当前预测」,必须挡掉。
开启因果掩码后,规则很短:
于是允许区域变成左下三角形(含对角线):第一行只留第一列,第二行留前两列,第三行留前三列。不需要死记「右上角」,按「列是否在当前行的未来」逐格检查即可。
−∞ 之后 Softmax 为什么会变成 0
掩码不是 QK^T 自己算出了无穷小,而是 Softmax 之前人为加上的限制。对一行分数做 Softmax 时:
被挡位置的指数项为 0,归一化后权重也是 0,后续乘 V 时这些未来 Token 完全不贡献。实验右侧会显示当前行的 Softmax 权重:打开掩码后,右上角对应的条会塌成 0,而行权和仍约等于 1。
为什么不能用 0 代替 −∞
Softmax 看的是一行里各分数的相对大小。如果把禁止位置改成普通的 0,而允许位置恰好是负数,那么 0 反而可能变成这一行里最大的分数,得到更高权重。遮罩就失效了。
写成 -∞(工程上常用数值类型能安全表示的极小值)是为了保证:无论其他分数是正是负,被挡位置在 Softmax 后都必须是 0。关键不在字面写了多少,而在「禁止位置不能再参与竞争」。
多头注意力在做什么
多头注意力(Multi-Head Attention)不是把一句话切成几段,也不是「头 1 只看前半句、头 2 只看后半句」。更准确的说法是:
- 把每个 Token 的表示投影到若干更小的子空间;
- 每个头有自己的
W_Q / W_K / W_V,可以学习不同的匹配模式; - 每个头仍然看到完整序列里允许看见的位置,只是在不同投影空间里打分。
实验里的「局部关系 / 远距离关系」只是示意:两个头的原始分数可以不同,但同一位置的因果禁区必须一致——掩码是规则,头是可学习的关系探测器。
各头结果怎样回到主维度
假设模型主维度是 6,分成两个头,每头维度 3。两个头各自输出 n×3,沿特征维拼接成 n×6,再乘输出投影 W_O,回到主维度。
拼接不是把两张注意力权重矩阵相加,而是把两个头提取出的特征并排放回同一行,再由 W_O 做一次线性混合。W_O 也是训练参数。
不同头不保证自动变成可命名的「语法头」「指代头」;独立参数只是给它们机会在不同子空间里学不同模式,最终分工取决于训练结果。
训练时整句都在,为什么还不算偷看
为了并行,训练时常把整段 Token 一次放进矩阵。若没有掩码,第 1 行确实能读到第 3 列的答案。加上因果掩码后,第 i 行只能使用第 i 个位置及其左侧信息:右边的 Token 虽然物理上存在于同一批次,但对当前行的输出不可见。
这与推理时的可见范围一致。生成第 3 个 Token 时,手里只有前两个;训练第 3 个位置时,也只能使用前两个。掩码让训练阶段的信息边界与真实生成相符。
因果掩码和 Padding Mask 不要混
因果掩码阻止读取未来位置;Padding Mask(填充掩码)忽略为了凑齐批次长度而补上的空位。两者都能让某些 Softmax 权重变为 0,但原因不同:前者维护生成顺序,后者排除无效数据。
实现里两种遮罩可能合并后一起加到注意力分数上。读代码时要分清:被挡是因为「列在未来」,还是因为「该位置是填充」。
编码器一定要右上角遮罩吗
不一定。双向编码器若要理解整段输入,通常允许每个位置同时读取左右上下文,因此不会采用自回归右上角遮罩。GPT 一类解码器为了预测下一个 Token,才必须保持因果可见范围。
所以遮罩形状来自任务约束,不是 Attention 公式天然要求。QKᵀ 只负责算分数;系统随后决定哪些分数可以进入 Softmax。
建议这样操作实验
- 先关掉因果掩码,点任意格子,看 Softmax 条会把权重分给三个位置;
- 再打开掩码,确认右上角变成
-∞,对应权重条归零; - 切换头 1 / 头 2:分数图案可以变,被挡的格子集合不变;
- 逐行核对:第 1 行只留第 1 列,第 2 行留前两列,第 3 行全开。
头负责学习「在允许范围内读谁更有用」;掩码负责执行「哪些位置根本不允许读」。把这两件事分开看,多头与因果掩码就不会搅在一起。