# 掩码：整句并行训练，模型为什么不偷看后面的词

因果掩码与 padding 掩码各解决什么问题，为什么要在 softmax 之前加负无穷，以及因果性如何穿过几十层保持住

> 自注意力机制 · Transformer 架构 · 序列建模基础 · 约 4 分钟 · 10 月 26 日

## 本篇要点

1. 因果掩码解决的是一个矛盾：训练时整句并行送入，但每个位置只能看见自己和前面的词。
2. 做法是在 softmax 之前给不允许的位置的分数加上 $-\infty$，$e^{-\infty}=0$ 让它的权重为零，剩余位置由 softmax 自动重新归一化到和为 1；例子里被屏蔽的 0.665 会按 1:2 分给前两个位置。
3. 不能在 softmax 之后清零权重：那样一行的和小于 1，加权和的量级就错了；若再补一次除法校正，数学上等价，但要多算一遍。
4. 因果掩码是下三角矩阵（含对角线），形状 $(T,T)$，与 batch 和注意力头无关；实现上常用该浮点类型的最小有限值代替 $-\infty$，避免整行被屏蔽时算出 NaN。
5. 因果性在几十层之间不会被绕开：残差、层归一化、前馈网络都是逐位置运算，唯一的跨位置运算被掩码限制为只看过去。
6. padding 掩码只看列、形状 $(B,T)$，与因果掩码按逻辑与合并；右 padding 时因果掩码已顺带挡住 padding，因此漏写 padding 掩码往往只在批量评测或改用左 padding 时暴露。
7. 注意力掩码决定谁看谁，loss mask（如 ignore_index）决定哪些位置参与计分，两者不是一回事。

---

上一篇讲架构时，解码器里有一个子层叫“掩码多头自注意力”，我只用一句话带过。这句简单的描述藏着整个训练方式的关键，今天要把它想清楚。

## 那个看似矛盾的场面

训练语言模型时，我们把“今天 天气 不错”整句同时送进网络，一次前向计算就得到三个位置的向量，各自负责一个预测：位置 0 预测“天气”，位置 1 预测“不错”，位置 2 预测结束符。一句话一次算完三个训练样本，这就是 Transformer 训练效率的来源。

问题是：位置 0 做注意力时，“天气”和“不错”就在输入里，它凭什么看不见？

如果真让它看见，模型很容易学会“后面是什么就照着报什么”，训练损失迅速接近零；但真到生成时，第一个词的位置后面什么也没有，模型立刻失效。这和拿训练集当测试集是同一种自欺。

所以我们要的是一对看似矛盾的要求：**整句并行读入，每个位置却只能看见自己和前面的词。** 掩码（mask）做的就是这件事。

## 把分数改成负无穷，而不是事后把权重清零

自注意力的最后一步是：每个位置对所有位置打出分数，softmax 把分数变成一组加起来等于 1 的权重，再用权重加权求和 Value 向量。

softmax 可以理解成“按分数分配一个固定预算”。要屏蔽某个位置，就把它的分数改成 $-\infty$：因为 $e^{-\infty}=0$，它分到的权重正好是 0；而 softmax 只对剩下的项做归一化，于是剩下的位置自动把预算重新分完，不需要再补一步归一化。

具体看位置 1 对三个位置打出的分数 $[1.0,\ 2.0,\ 3.0]$。不掩码时，$e^{1}=2.718$、$e^{2}=7.389$、$e^{3}=20.09$，和为 30.20，权重是 $[0.090,\ 0.245,\ 0.665]$，未来的“不错”拿走了 66% 的注意力。掩码后分数变成 $[1.0,\ 2.0,\ -\infty]$，和为 10.11，权重是 $[0.269,\ 0.731,\ 0]$。那 0.665 并没有消失，而是按 1:2 被前两个位置分掉了。

反过来做不行：先算完整 softmax，再把未来位置的权重清零，得到 $[0.090,\ 0.245,\ 0]$，和只有 0.335，输出的量级就错了。若再除以 0.335 校正，数学上确实等价，但要额外遍历一次、做一次除法。所以标准做法是在 softmax 之前加上那张表。

## 这张表就是下三角

“位置 $i$ 只能看 $j \le i$”写成矩阵就是下三角（下面用 1 表示允许），对角线要保留，因为预测下一个词时，当前这个位置本身也是输入：

```
1 0 0 0
1 1 0 0
1 1 1 0
1 1 1 1
```

对应的代码也短：

```python
allowed = torch.tril(torch.ones(T, T, dtype=torch.bool))   # 下三角，含对角线
scores  = q @ k.transpose(-2, -1) / math.sqrt(d_k)          # (T, T)
scores  = scores.masked_fill(~allowed, torch.finfo(scores.dtype).min)
weights = scores.softmax(dim=-1)                            # 每行和为 1
```

实现里通常不写真的 $-\infty$，而用该浮点类型能表示的最小有限值（如 `finfo(dtype).min`），这样在整行都被屏蔽的情况下，softmax 内部也不会算出 NaN。

## 因果性怎么在几十层之间保住

有人会追问：第一层只让位置 1 看到 0 和 1，堆到 96 层以后，信息会不会从别处绕回来？

不会。位置 1 在第一层的输出只用到位置 0、1 的 Value；接下来的残差相加和层归一化是逐元素的，前馈网络是逐位置独立处理的，都不会混进别的词的信息。第二层再做因果注意力时，输入里仍然只含前两个位置的信息。一层层推下去，第 96 层位置 1 的表示依然只依赖前两个输入。

**“整句并行”和“不偷看”能同时成立，理由就是这两条：唯一的跨位置运算只有注意力，而它被掩码限制成只看过去；其余计算全都逐位置进行。**

## 第二种掩码：padding

真实训练要把长短不一的句子凑成一个 batch，短的补 padding 到最长那句的长度。padding 是填充物，真实词若从它身上取信息，同一句话的输出就会随“这一批最长的那句有多长”而变。

两种掩码看着像一回事，作用的维度不同：

- 因果掩码每一行都不一样，是否允许取决于行号和列号谁大，是 $(T,T)$ 的表，与 batch 无关。
- padding 掩码只看列：这一列是不是填充物，对所有 query 都一样，形状是 $(B,T)$，广播成 $(B,1,T,T)$。

合并规则是“两个都允许才允许”，也就是逻辑与；写成加法时恰好也是 $0+0=0$、任何数加 $-\infty$ 仍是 $-\infty$，所以一张加性掩码表装得下两条规则。

一个容易想错的边界：右 padding 时，每个真实词都在 padding 左边，也就是 padding 处在“未来”，因果掩码本来就不让看，所以真实位置的输出不受影响；漏写 padding 掩码的问题往往只在批量评测、或改成左 padding 时才暴露。另外，padding 行“不计分”是另一件事，要靠 loss mask（如交叉熵的 `ignore_index`）单独处理：注意力掩码决定谁看谁，loss mask 决定哪些位置算分。

掩码的全部内容，就是往分数上加了一张表。但没有这一行加法，“整句并行训练”就等于让模型抄答案，decoder-only 架构根本训不出来。下一篇继续：最后一层的向量怎么变成词表上的概率，模型又怎么一个词一个词地生成。

## 思维导图

```mermaid
flowchart LR
  A["矛盾：整句并行读入 vs 不能看未来"] --> B["做法：softmax 前给未来位置的分数加 -inf"]
  B --> C["e^-inf = 0，剩余位置自动重新归一化"]
  B --> D["不能事后清零权重：行和不足 1，量级错"]
  C --> E["因果掩码 = 下三角矩阵 (T,T)，含对角线"]
  E --> F["逐层保持：残差/LayerNorm/FFN 都是逐位置运算"]
  F --> G["结论：n 个位置的 next-token 预测一次算完"]
  A --> H["padding 掩码：(B,T) 只看列，与因果掩码取逻辑与"]
  H --> I["边界：右 padding 已被因果掩码挡住，漏写在批量评测时暴露"]
  H --> J["不计分靠 loss mask，不是注意力掩码"]
```

## 术语表

- 因果掩码：一张下三角表，规定第 $i$ 个位置只能看第 $0$ 到 $i$ 个位置，使训练时并行读整句也不泄露未来。
- 加性掩码：把“不允许”写成加到分数上的一个极大负数，而不是事后把权重清零，好处是让 softmax 自己完成重新归一化。
- padding 掩码：只按列标记哪些位置是补出来的填充物，让真实词不从填充物上取信息，形状随 batch 变化。
- loss mask：决定哪些位置参与损失计算的掩码，和决定谁看谁的注意力掩码是两件独立的事。

## 来源

1. [Causal masking and autoregressive prediction — 因果掩码的定义式、$ -\infty $ 的取值，以及为什么在 softmax 之前而不是之后掩码](https://www.textbookofai.com/chapter/attention-transformers/13-8-causal-masking-and-autoregressive-prediction.html)
2. [Masks, causal and padding — 因果掩码与 padding 掩码的对比、按逻辑与合并、右 padding 下因果掩码已挡住填充、loss mask 与注意力掩码的分工](https://zerotofrontier.app/attention/masks)
3. [Ch 09 · Attention — 自回归解码为何必须掩码，以及并行训练与逐词生成能并存的原因](https://textbook.darvinyi.com/ch/09)
4. [Hugging Face transformers: modeling_attn_mask_utils.py — 实际实现用 torch.finfo(dtype).min 构造负偏置的做法](https://github.com/huggingface/transformers/blob/main/src/transformers/modeling_attn_mask_utils.py)

---

原文：https://pangzhengboyin.com/articles/causal-mask-vs-padding-mask-47cad6e2

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具：每篇 5–10 分钟，边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
