编程与 AI14 分钟阅读更新于 2026-08-03

Transformer 的掩码机制:填充掩码、因果掩码与注意力可见范围

从注意力分数和可见范围出发,区分填充掩码、因果掩码与编码器—解码器注意力中的掩码,理解掩码如何在 Softmax 前后影响信息流动。

相关工具

掩码解决的是“能不能看”

注意力会让一个位置根据相关性读取其他位置,但并不是所有位置都应该参与计算。批量处理时,较短序列后面会补 PAD;解码器生成时,当前位置不能读取未来词;跨序列注意力还可能需要忽略源序列中的填充位置。掩码就是用来表达这些可见范围的。

位置编码回答“这个词在哪里”,掩码回答“这个位置是否允许被关注”。两者都影响注意力,却不是同一种信息。位置编码进入表示,掩码通常作用于注意力分数或权重。

学习掩码时,先不要急着记代码里的布尔值是 True 还是 False。不同实现的约定可能相反,真正应该确认的是:哪些位置保留,哪些位置在 Softmax 后权重为 0。

Transformer 填充掩码和因果掩码作用对象与矩阵对比图
填充掩码与因果掩码

填充掩码忽略批量中的 PAD 位置,因果掩码屏蔽当前位置之后的未来位置。

填充掩码为什么是必要的

为了把不同长度的序列组成同一个批次,通常会在较短序列末尾补上 PAD,使它们拥有相同的长度。PAD 只是占位符,不代表真实内容。如果不屏蔽它,注意力可能把一部分权重分给这些无意义位置。

填充掩码通常根据输入中的有效词元标记生成。有效位置保留,PAD 位置屏蔽。对于编码器自注意力,它可以阻止所有查询位置读取 PAD;对于解码器中的跨注意力,它则用于阻止解码器读取编码器输出里的填充位置。

填充掩码不关心词元位于序列前面还是后面,只关心这个位置是否是真实内容。即使 PAD 出现在句子末尾,它也不能因为位置靠后就被当作普通词读取。

因果掩码为什么是下三角矩阵

因果掩码用于自回归生成。第 i 个查询位置只能关注第 1 到第 i 个键位置,不能关注 i 之后的未来位置。把查询位置作为行、键位置作为列时,可见区域自然形成下三角矩阵。

例如第 1 个位置只能看自己,第 2 个位置可以看第 1 和第 2 个位置,第 3 个位置可以看前三个位置。随着位置向后移动,可见范围逐步扩大,但任何位置都不会看到未来。

因果掩码不是把未来词从输入张量里删除,而是在注意力计算中阻止它们产生有效权重。输入仍然可以作为一个完整的训练序列送入模型,掩码负责把不该使用的信息挡住。

掩码如何改变注意力分数

假设原始注意力分数是 S,掩码矩阵是 M。常见做法是在 Softmax 之前计算 S'=S+M。允许关注的位置在 M 中加 0,不允许关注的位置加上负无穷或一个足够小的数。

Softmax 会把每一行分数变成权重。被加上负无穷的位置,其指数结果趋近于 0,最终权重也就变成 0;可见位置的分数不受影响,会继续参与归一化。

实现中不一定真的保存数学意义上的负无穷,可能使用一个很小的负数,例如 -1e9。只要在数值精度下能够让被屏蔽位置的 Softmax 权重接近 0,就达到了同样的目的。

Transformer 原始分数加掩码后经 Softmax 得到最终注意力权重的流程图
掩码如何改变注意力分数

掩码在 Softmax 前加入原始分数,使不可见位置的最终注意力权重变为 0。

掩码放在 Softmax 前还是后

从数学和实现习惯看,最常见的是在 Softmax 前修改分数。这样被屏蔽位置在归一化时就不会参与分母,剩余可见位置的权重会在它们之间重新分配。

如果先做 Softmax 再把部分权重设为 0,就需要再次处理每行权重之和,否则剩余权重可能不再归一化。也可以设计等价的后处理,但实现更容易出错。

因此,看到注意力公式时,通常可以寻找类似 softmax((QKᵀ/√d_k)+M) 的形式。M 就是把可见范围写进分数的地方。

编码器自注意力中的掩码

编码器自注意力通常允许源序列中的有效位置互相查看,因为编码器要理解完整源序列。它一般不使用因果掩码,但在批量序列长度不一致时,仍然需要填充掩码。

如果源序列长度都相同且没有 PAD,编码器的可见范围可以接近完整矩阵;如果有填充位置,所有查询行都应避免把注意力分给无效的填充列。

注意力矩阵的每一行代表一个查询位置,每一列代表一个可被关注的位置。填充掩码主要屏蔽列对应的无效位置,而不是因为查询位置本身在句子末尾就限制它查看前面的内容。

解码器自注意力中的掩码

解码器自注意力同时可能需要两种限制:因果掩码屏蔽未来目标位置,填充掩码屏蔽无效的 PAD 位置。实际实现中,这些限制可以组合成一个更完整的掩码。

因果掩码保证第 i 个位置只能依据已经出现的前缀,填充掩码保证 PAD 不会被当作真实词使用。一个限制信息的时间方向,一个限制批次中的无效位置,作用对象不同但可以叠加。

训练时,完整目标序列一次性输入解码器,因果掩码让所有位置并行完成合法的下一词预测;生成时,前缀本身不断变长,掩码规则仍然保持不变。

编码器—解码器注意力中的可见范围

交叉注意力的 Query 来自解码器目标位置,Key 和 Value 来自编码器源序列输出。目标位置可以根据当前状态读取源序列中的有效位置,通常不需要对源序列使用因果掩码。

但如果源序列经过批量填充,编码器输出中的 PAD 仍然应该被屏蔽。否则,解码器可能把一部分跨注意力分配给没有真实内容的源位置。

因此,交叉注意力的矩阵不是“源序列和目标序列全部互相可见”的简单全连接。它允许目标查询访问源序列有效位置,同时遵守源序列的填充约束。

编码器自注意力、解码器掩码自注意力和编码器解码器注意力的可见范围对比图
三种注意力机制的可见范围

编码器自注意力通常全量查看源序列有效位置,解码器自注意力使用因果掩码,跨注意力从目标查询读取源序列有效位置。

布尔掩码与加法掩码有什么区别

从概念上看,布尔掩码只表达可见或不可见;加法掩码则直接提供要加到分数上的数值。实现可能先使用布尔条件构造一个加法矩阵,再将它加入注意力分数。

不同框架对 True 和 False 的含义可能不同。有的接口约定 True 表示屏蔽,有的接口约定 True 表示允许关注。如果直接复制一段代码而不确认约定,可能把整个可见区域反过来。

最可靠的检查方式是使用一个很小的矩阵,手工观察屏蔽位置在 Softmax 后是否为 0。不要只看变量名叫 mask 就假设它的布尔含义。

掩码最容易出现的几个错误

第一,把填充掩码和因果掩码混为一谈,导致编码器不必要地屏蔽后文,或者解码器错误地读取未来。第二,掩码的行列方向弄反,把查询位置和键位置的含义交换。

第三,掩码形状没有正确广播到批次、头数和查询—键长度。自注意力通常是 L×L,跨注意力则可能是目标长度×源长度,两者不能只按同一个正方形矩阵处理。

第四,掩码值或数据类型不适合当前精度,导致屏蔽位置仍然获得明显权重,或者在混合精度计算中出现数值异常。排查时要同时检查形状、方向、数值和 Softmax 后结果。

用四个问题检查掩码

第一问:当前注意力的查询来自哪里,键和值来自哪里?第二问:哪些位置在任务逻辑上应该可见?第三问:查询长度和键长度分别是多少?第四问:屏蔽位置经过 Softmax 后是否真的变成了接近 0 的权重?

把这四个问题应用到编码器、解码器和交叉注意力,通常就能构造出正确的掩码。对于批量输入,再额外确认 PAD 的位置是否被覆盖。

结构图可以帮助理解方向,矩阵小例子可以帮助验证实现。两者结合,比单独记住“下三角就是因果掩码”更可靠,因为跨注意力和填充掩码未必是正方形下三角结构。

掩码检查的四个步骤
步骤 1
确认来源

明确 Query、Key、Value 分别来自哪一段序列。

步骤 2
确认规则

判断要屏蔽 PAD、未来位置,还是两者同时屏蔽。

步骤 3
确认形状

检查查询长度、键长度以及批次和头数的广播关系。

步骤 4
确认权重

观察 Softmax 后被屏蔽位置是否接近 0。

把掩码放回 Transformer 主线

掩码没有改变注意力的基本公式,它改变的是哪些分数有机会参与归一化。填充掩码保证无效位置不影响表示,因果掩码保证生成过程不偷看未来,跨注意力掩码则保证解码器只读取源序列中的有效内容。

把掩码和位置编码、残差、层归一化区分开,Transformer 的信息流会更清楚:位置编码提供顺序,掩码限制可见范围,注意力计算关系,前馈网络加工表示。

以后看到一个注意力实现,先问“哪些位置应该看见”,再看代码如何把答案写进分数矩阵。这个思路比死记某个框架的 mask 参数更不容易出错。

常见问题

填充掩码和因果掩码可以同时使用吗?

可以。解码器自注意力常常需要同时屏蔽未来位置和 PAD 位置,实际实现会把两类限制组合起来。

为什么屏蔽位置要加负无穷?

因为在 Softmax 前将不可见位置的分数设为极小值,可以让它们经过指数运算后得到接近 0 的最终权重。

跨注意力也需要因果掩码吗?

通常不需要对源序列使用因果掩码,但仍可能需要填充掩码来屏蔽源序列中的 PAD 位置。