> For the complete documentation index, see [llms.txt](https://inwt233.gitbook.io/ai-learning/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://inwt233.gitbook.io/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-01/day-006.md).

# Day 006：特殊 Token、Attention Mask 与模型输入

{% hint style="info" %}
**Tokenizer 得到 Token ID 之后，怎样加入特殊 Token、截断和补齐，组成可批处理的模型张量？`attention_mask` 中的 0 又是怎样阻止模型读取 padding 的？**
{% endhint %}

## 学习目标

完成本节后，你应该能够：

1. 解释 `[PAD]`、`[CLS]`、`[SEP]`、`[MASK]`、BOS 与 EOS 的常见职责及模型边界；
2. 从原始文本追踪到 `input_ids`、`attention_mask`、`token_type_ids` 和 `special_tokens_mask`；
3. 根据批次大小与序列长度写出各张量的形状；
4. 解释 padding 与 truncation 为什么存在，以及左右补齐的工程差异；
5. 用加性偏置说明 padding mask 怎样在 softmax 前屏蔽注意力分数；
6. 严格区分 padding mask、causal mask、loss mask 与特殊 Token 标记；
7. 识别不同框架中布尔 mask 语义相反这一常见故障。

{% hint style="info" %}
本节衔接 [Day 001](/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-01/day-001.md) 的 Tokenizer 输入字段、[Day 003](/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-01/day-003.md) 的词表契约和特殊 Token，以及 [Day 005](/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-01/day-005.md) 的子词编码。重点不再是“文本怎样被切开”，而是“切分结果怎样变成一个模型可以安全计算的批次”。
{% endhint %}

## 1. 今天只回答一个问题：变长 Token 序列怎样进入定长张量

两句话经过同一个 Tokenizer 后，长度通常不同：

```
句子 A → 5 个 Token
句子 B → 9 个 Token
```

但常见深度学习张量要求一个批次中的每一行长度一致。于是模型输入链路还需要特殊 Token、截断与 padding：

```
原始文本或文本对
  ↓ 同一 normalizer、pre-tokenizer 与子词模型
普通 Token 序列
  ↓ 按模型模板加入特殊 Token
带边界/控制语义的 Token 序列
  ↓ 超长时 truncation
不超过长度上限的序列
  ↓ 同一批次 padding
等长 Token ID 矩阵 [B, L]
  ├─ attention_mask      [B, L]
  ├─ token_type_ids      [B, L]，仅部分模型需要
  ├─ special_tokens_mask [B, L]，通常是预处理元数据
  └─ position_ids        [B, L]，常由模型内部生成
       ↓ Embedding
隐藏状态 [B, L, d]
       ↓ Transformer
上下文化表示
```

其中：

* `B` 是 batch size，即一次处理的样本数；
* `L` 是该批次补齐后的序列长度；
* `d` 是隐藏维度；
* 形状相同不代表字段含义相同。

## 2. 特殊 Token 是词表中的控制符号

特殊 Token 与普通 Token 一样拥有 ID 和 Embedding 行，但它们通常不对应原文中的普通词。它们向模型或数据流水线表达边界、补齐或训练任务。

| 常见 Token          | 常见职责               | 必须保留的边界                     |
| ----------------- | ------------------ | --------------------------- |
| `[PAD]` / `<pad>` | 把短序列补到批次长度         | “是 padding”不自动等于“不会参与 loss” |
| `[UNK]` / `<unk>` | 表示无法覆盖的输入          | 不同未知字符串可能折叠到同一 ID           |
| `[CLS]`           | BERT 类输入开头，常用于句级表示 | 不是所有 Encoder 都使用            |
| `[SEP]`           | BERT 类序列结束或文本对分隔   | 不等同于所有模型的 EOS               |
| `[MASK]`          | 掩码语言模型的待预测位置       | 生成式模型不一定有它                  |
| `<bos>` / `<s>`   | 序列开始               | 是否自动添加由具体模板决定               |
| `<eos>` / `</s>`  | 序列结束               | 与“停止生成”的行为还受生成配置影响          |

特殊 Token 没有跨模型统一的名称、ID 和用法。例如：

* BERT 常见 `[CLS] A [SEP] B [SEP]`；
* 某些 Decoder-only 模型只有 BOS/EOS，没有原生 PAD；
* 某些推理配置会暂时把 EOS 同时用作 PAD，但两种**角色**仍不同；
* 同一个字符串是否自动加入输入，要看 Tokenizer 的 post-processor 或 chat template。

因此不能只凭字符串猜行为，应检查：

```python
# 这些字段能确认特殊 Token 的名称、ID，以及当前模型是否定义了 PAD/BOS/EOS。
tokenizer.special_tokens_map
tokenizer.all_special_tokens
tokenizer.all_special_ids
tokenizer.pad_token_id
tokenizer.bos_token_id
tokenizer.eos_token_id
```

{% hint style="warning" %}
特殊 Token 的 ID 属于 Tokenizer–模型契约。把 BERT 的 `[CLS]` ID 101 直接送给另一个模型，并不会自动获得“句级分类”语义。
{% endhint %}

## 3. BERT 的单句与文本对模板

以 BERT 为例，官方 Transformers 文档给出的常见格式是：

```
单句：[CLS] A [SEP]
文本对：[CLS] A [SEP] B [SEP]
```

文本对还可能产生 `token_type_ids`：

```
Tokens:          [CLS] A1 A2 [SEP] B1 B2 [SEP]
token_type_ids:      0  0  0     0  1  1     1
```

它表示 Token 属于第一个还是第二个片段。这里的 0/1 是**片段编号**，不是“可见/不可见”。

必须注意：

1. `token_type_ids` 不是 attention mask；
2. 很多模型不使用 `token_type_ids`；
3. `[SEP]` 属于哪个片段以及 padding 位填什么，由具体实现决定；
4. 应直接把配套 Tokenizer 返回的字段交给模型，不要凭记忆手工拼接。

## 4. Padding 与 truncation 分别解决什么

### 4.1 Padding：把批次补成矩形

假设两个序列带特殊 Token 后长度分别为 7 和 12。右侧 padding 到 12：

```
样本 1：t0 t1 t2 t3 t4 t5 t6 PAD PAD PAD PAD PAD
样本 2：t0 t1 t2 t3 t4 t5 t6 t7  t8  t9  t10 t11
```

对应 `attention_mask`：

```
样本 1：1  1  1  1  1  1  1  0   0   0   0   0
样本 2：1  1  1  1  1  1  1  1   1   1   1   1
```

Transformers 常见 padding 策略包括：

* `padding=True` 或 `"longest"`：补到当前批次最长序列；
* `padding="max_length"`：补到指定或模型最大长度；
* `padding=False`：不补齐。

动态补到批次最长通常减少无效计算；固定长度则可能让形状、缓存或硬件优化更简单。应以实际吞吐和显存测量为准。

### 4.2 Truncation：把超长序列限制在上限内

常见策略包括：

* `truncation=True` / `"longest_first"`：文本对中逐步从较长一侧删除；
* `"only_first"`：只截断第一段；
* `"only_second"`：只截断第二段。

截断是不可逆的信息丢失。分类任务可能丢掉结论，问答任务可能删掉答案，RAG 可能删掉证据。因此除了“没有报长度错误”，还要统计：

* 截断样本比例；
* 每条样本被删掉的 Token 数；
* 被截断字段和位置；
* 截断前后任务指标。

{% hint style="warning" %}
不要先把文本机械裁成固定字符数，再假定 Token 数也固定。字符、UTF-8 字节和 Token 是不同层，长度关系依赖具体 Tokenizer。
{% endhint %}

## 5. 实验：构造一个真实的 BERT 文本对批次

下面使用 `transformers==5.2.0` 与 `google-bert/bert-base-chinese`。第二条样本会因 `max_length=12` 被截掉第一段末尾的“趣”；第一条较短，因此右侧出现 5 个 `[PAD]`。

```python
from transformers import AutoTokenizer
import torch

tokenizer = AutoTokenizer.from_pretrained(
    "google-bert/bert-base-chinese"
)

# 两个列表按位置组成文本对；返回 PyTorch 张量并补到同一批次长度。
batch = tokenizer(
    ["AI学习", "自然语言处理很有趣"],
    ["Tokenizer", "🙂"],
    padding=True,
    truncation=True,
    max_length=12,
    return_special_tokens_mask=True,
    return_tensors="pt",
)

print("tokens:")
for ids in batch["input_ids"]:
    # ID 只在当前词表中有意义，先转回 Token 才容易检查截断与 padding。
    print(tokenizer.convert_ids_to_tokens(ids.tolist()))

for name, value in batch.items():
    # 同时打印形状和值，避免把多个 [B, L] 字段误认为同一种信息。
    print(f"{name}: shape={tuple(value.shape)}")
    print(value.tolist())
```

本次实测输出：

```
tokens:
['[CLS]', '[UNK]', '学', '习', '[SEP]', '[UNK]', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']
['[CLS]', '自', '然', '语', '言', '处', '理', '很', '有', '[SEP]', '[UNK]', '[SEP]']
input_ids: shape=(2, 12)
[[101, 100, 2110, 739, 102, 100, 102, 0, 0, 0, 0, 0],
 [101, 5632, 4197, 6427, 6241, 1905, 4415, 2523, 3300, 102, 100, 102]]
token_type_ids: shape=(2, 12)
[[0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]
attention_mask: shape=(2, 12)
[[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
 [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
special_tokens_mask: shape=(2, 12)
[[1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1]]
```

### 5.1 逐字段解释

`input_ids`：

* 是真正用于 Embedding 查表的离散索引；
* `[PAD]` 的 ID 为 0，但“ID 是 0”本身不是屏蔽规则；
* 第二条第一段原有 9 个汉字，因长度上限只保留到“有”。

`token_type_ids`：

* 第一段与相关特殊 Token 常为 0，第二段为 1；
* 第一条 padding 位也填 0，但 padding 是否可见由 `attention_mask` 决定。

`attention_mask`：

* 第一条前 7 位为真实输入，后 5 位为 padding；
* 第二条恰好占满 12 位，所以全为 1。

`special_tokens_mask`：

* 1 表示 `[CLS]`、`[SEP]`、`[PAD]` 等特殊 Token；
* 普通的 `[UNK]` 在这个返回值中是 0，因为它替代了原文片段；
* 它用于识别特殊 Token，不是送入 BERT 屏蔽注意力的 `attention_mask`。

{% hint style="info" %}
同样是 0/1 矩阵，`attention_mask` 与 `special_tokens_mask` 的 1 含义完全不同：前者通常表示“可作为真实输入”，后者表示“这是特殊 Token”。
{% endhint %}

## 6. Attention Mask 怎样在 softmax 前生效

单个注意力头的缩放点积注意力为：

$$
S=\frac{QK^\top}{\sqrt{d\_k}}
$$

其中：

* $$Q\in\mathbb{R}^{L\_q\times d\_k}$$ 是 Query；
* $$K\in\mathbb{R}^{L\_k\times d\_k}$$ 是 Key；
* $$S\in\mathbb{R}^{L\_q\times L\_k}$$ 是注意力分数；
* 第 i 行表示 Query 位置 i 对所有 Key 位置的分数。

若 Key 位置 j 是 padding，就在 softmax 前加入一个很大的负数：

$$
B\_{ij}=
\begin{cases}
0,& \text{位置 }j\text{ 可见}\\
-\infty,& \text{位置 }j\text{ 被屏蔽}
\end{cases}
$$

然后：

$$
A=\operatorname{softmax}(S+B)
$$

因为 $$e^{-\infty}=0$$，被屏蔽列的注意力权重变成 0，其余可见位置重新归一化。

在批量多头自注意力中，原始 padding mask 常为 `[B, L_k]`，实现可把它扩展为可广播的：

```
[B, 1, 1, L_k]
```

再与注意力分数 `[B, H, L_q, L_k]` 相加。它主要屏蔽 **Key 列**。因此不能简单断言“padding 位置的整行隐藏状态必然自动变成全 0”；下游汇聚和 loss 仍应明确忽略无效位置。

### 6.1 最小 masked softmax 实验

```python
# 第四个位置故意给最高分 4.0，用于验证 mask 是否真的生效。
scores = torch.tensor([[2.0, 1.0, 0.0, 4.0]])
keep_mask = torch.tensor([[1, 1, 1, 0]], dtype=torch.bool)

# ~keep_mask 找出不允许参与注意力的位置，并在 softmax 前写入 -inf。
masked_scores = scores.masked_fill(
    ~keep_mask,
    float("-inf"),
)
weights = torch.softmax(masked_scores, dim=-1)

print("masked_scores:", masked_scores.tolist())
print(
    "attention_weights:",
    [[round(x, 6) for x in row] for row in weights.tolist()],
)
print("row_sum:", weights.sum(dim=-1).tolist())
```

本次实测输出：

```
masked_scores: [[2.0, 1.0, 0.0, -inf]]
attention_weights: [[0.665241, 0.244728, 0.090031, 0.0]]
row_sum: [1.0]
```

虽然第四个原始分数 4.0 最大，但它被屏蔽后权重严格为 0。前三个位置重新归一化，总和仍为 1。

实际混合精度实现可能用当前数据类型可表示的极小有限值代替字面上的 `-inf`；数学目的相同。

## 7. 四种容易混淆的 mask

| 名称                       | 常见形状                          | 控制什么                      | 不控制什么            |
| ------------------------ | ----------------------------- | ------------------------- | ---------------- |
| padding attention mask   | `[B, L]`，内部广播                 | 不让真实 Query 读取 padding Key | 不自动决定哪些位置计算 loss |
| causal mask              | `[L_q, L_k]` 或可广播形式           | 自回归位置不能读取未来 Token         | 不负责识别 padding    |
| loss mask / label ignore | `[B, L]` 或通过 `labels=-100` 表达 | 哪些目标位置计入损失                | 不改变前向注意力可见性      |
| special tokens mask      | `[B, L]`                      | 标记哪些位置是特殊 Token           | 通常不直接送入注意力层      |

Decoder-only 模型进行批量训练或生成时，常同时需要 padding 与 causal 约束：

$$
\operatorname{allowed}(i,j)
\=\operatorname{real\_key}(j)\land(j\le i)
$$

这里的 $$j\le i$$ 只是最基本的下三角因果关系。缓存、非方形 Query/Key、前缀语言模型和特殊注意力结构会更复杂；路线图中的 Day 032 会专门深入 Causal Mask、Padding Mask 与广播。

当前阶段必须记住：

* `attention_mask=0` 不等于 `labels=-100`；
* BOS、EOS 等特殊 Token 通常仍然可以参与注意力；
* `[PAD]` 即使被注意力屏蔽，也要确认训练损失是否忽略；
* token type 0/1 不是允许/禁止矩阵。

## 8. 不同 API 的布尔 mask 语义可能相反

“True 到底表示保留还是屏蔽”不是跨框架定律：

| API                                                                | 布尔值语义              |
| ------------------------------------------------------------------ | ------------------ |
| Transformers 的 BERT `attention_mask`                               | `1` 表示不屏蔽，`0` 表示屏蔽 |
| `torch.nn.MultiheadAttention` 的 `key_padding_mask`                 | `True` 表示忽略该 Key   |
| `torch.nn.functional.scaled_dot_product_attention` 的布尔 `attn_mask` | `True` 表示允许参与注意力   |

PyTorch 官方文档还明确指出，`scaled_dot_product_attention()` 与 `MultiheadAttention` 的布尔 mask 语义相反。

安全做法是：

1. 变量名写出语义，如 `keep_mask`、`key_padding_mask`，不要只写 `mask`；
2. 在调用处显式转换，例如 `~keep_mask`；
3. 用一个“最大分数恰好在被屏蔽位置”的单元测试；
4. 同时检查输出权重、行和与梯度是否符合预期；
5. 升级框架时重新核对所用版本文档。

## 9. 左 padding 与右 padding

右 padding：

```
A B C PAD PAD
D E F G H
```

左 padding：

```
PAD PAD A B C
D   E   F G H
```

Encoder 类模型常见右 padding。Decoder-only 批量生成常使用左 padding，因为简单生成循环通常从批次最后一列的 logits 继续预测；如果短序列右侧最后一列是 PAD，就可能从错误位置继续。Transformers 的 LLM 生成指南因此建议 Decoder-only 批量生成使用左 padding，并显式传递 attention mask。

但这不是“所有 Decoder 训练都必须左补齐”的定律：

* 训练时可通过正确的 causal mask、padding mask、position IDs 和 loss mask 支持不同布局；
* 有些模型没有独立 PAD，可在特定推理配置中复用 EOS；
* 左 padding 会改变实际列号，自定义位置编码逻辑必须处理真实 Token 的位置；
* 生成库可能根据 `attention_mask` 自动构造 position IDs，自写循环则不能想当然。

因此要把下面四项作为一组测试：

```
padding_side
pad_token_id
attention_mask
position_ids / generation loop
```

## 10. 常见故障与诊断

### 故障 1：把 `special_tokens_mask` 当成 `attention_mask`

结果：`[CLS]`、`[SEP]` 等真正需要读取的边界 Token 被错误屏蔽，普通 Token 反而保留。

### 故障 2：只补 `input_ids`，没有传 attention mask

结果：模型可能把 PAD 当作普通上下文；即使某些封装能推断，也会在 PAD 与 EOS 共用 ID 等场景变得不可靠。

### 故障 3：认为 attention mask 会自动屏蔽 loss

结果：padding 目标仍进入交叉熵，训练信号被大量无效位置污染。

### 故障 4：把 0/1 语义跨 API 原样搬运

结果：整张 mask 反转，模型只读取 padding 或完全看不到有效上下文。

### 故障 5：文本对截断策略错误

结果：问答中只保留问题却删光上下文，或只保留文档开头而丢掉答案。

### 故障 6：Decoder-only 右 padding 后直接取 `logits[:, -1]`

结果：短样本最后一列对应 PAD，不是它最后一个真实 Token。

### 故障 7：改了 `pad_token_id` 却没有同步保存 Tokenizer 与模型配置

结果：训练、评估和部署对同一 ID 的角色理解不一致。

## 11. 练习与面试准备

### 11.1 30 秒回答

> 特殊 Token 是词表中的控制符号，用于表达开始、结束、分隔、掩码或补齐。批量输入先按模型模板加入特殊 Token，再根据长度策略截断并 padding，得到 `[B,L]` 的 `input_ids`。`attention_mask` 标出有效位置，模型内部通常把被屏蔽 Key 的注意力分数加上极大负数，使其 softmax 权重为 0。它不同于 causal mask、loss mask、special tokens mask 和 token type IDs；不同框架的布尔 mask 语义还可能相反，所以必须按具体 API 验证。

### 11.2 递进练习与面试题

#### 问题 1：`input_ids` 与 `attention_mask` 为什么形状相同但含义不同？

<details>

<summary>查看答案</summary>

`input_ids[b,j]` 是第 `b` 条样本第 `j` 个位置的词表索引，用于查 Embedding；`attention_mask[b,j]` 是该位置是否作为真实输入参与注意力的控制信息。前者取值范围通常是词表 ID，后者常是 0/1。

</details>

#### 问题 2：padding mask 怎样变成注意力权重 0？

<details>

<summary>查看答案</summary>

在 softmax 前给被屏蔽 Key 的分数加入 `-∞` 或数据类型中的极小值。指数化后该位置贡献为 0，其余可见位置重新归一化。批量多头场景中 `[B,L_k]` 常广播到 `[B,1,1,L_k]`。

</details>

#### 问题 3：`attention_mask=0` 为什么不等于 `labels=-100`？

<details>

<summary>查看答案</summary>

前者控制前向注意力能否读取相应 Key；后者常用于告诉交叉熵忽略某个目标位置。一个位置可以不计 loss 却仍作为上下文被读取，也可以在注意力中被屏蔽但因错误标签设置仍计入 loss。

</details>

#### 问题 4：为什么 Decoder-only 批量生成常用左 padding？

<details>

<summary>查看答案</summary>

自回归生成通常从每条提示词最后一个真实 Token 继续。若短序列右 padding，而实现直接使用最后一列 logits，取到的是 PAD 位置。左 padding 让每条样本的最后一列都是最后一个真实 Token；同时仍要正确传 attention mask 和处理 position IDs。

</details>

#### 问题 5：怎样诊断 mask 方向写反？

<details>

<summary>查看答案</summary>

构造一个极小分数向量，让被屏蔽位置拥有最大分数；检查 softmax 后它是否严格为 0、可见权重是否和为 1。再检查所用 API 对 True/False 的官方定义，打印扩展后的形状，并用无 padding 的单样本作为对照。

</details>

### 11.3 手写题：构造 padding 批次

不用高阶 padding API，实现右侧补齐：输入若干 ID 列表和 `pad_id`，返回 `input_ids` 与 `attention_mask`。

<details>

<summary>查看参考答案</summary>

```python
def pad_batch(sequences: list[list[int]], pad_id: int):
    """把变长 ID 序列右补齐，同时返回 1/0 attention mask。"""
    if not sequences:
        # 明确处理空批次，避免 max() 抛出异常。
        return [], []

    max_length = max(map(len, sequences))
    input_ids = []
    attention_mask = []

    for sequence in sequences:
        pad_count = max_length - len(sequence)
        # input_ids 写入 pad_id；mask 中真实位置为 1，补齐位置为 0。
        input_ids.append(sequence + [pad_id] * pad_count)
        attention_mask.append([1] * len(sequence) + [0] * pad_count)

    return input_ids, attention_mask


ids, mask = pad_batch([[5, 6, 7], [8]], pad_id=0)
assert ids == [[5, 6, 7], [8, 0, 0]]
assert mask == [[1, 1, 1], [1, 0, 0]]
```

时间复杂度和输出规模都是 `O(BL)`。生产代码还要处理空序列、最大长度、左右 padding、数据类型和设备。

</details>

### 11.4 项目答辩题：批量生成结果异常

一个 Decoder-only 模型单条生成正常，批量生成时短提示词输出乱码。日志显示右 padding、`pad_token_id=eos_token_id`、没有显式传 `attention_mask`，代码固定取 `logits[:, -1]`。如何定位？

<details>

<summary>查看答题要点</summary>

先复现单条与批量差异，打印每行 Tokens、`input_ids`、最后一个真实 Token 索引和 attention mask。右 padding 下短样本的 `-1` 是 PAD/EOS 角色位，因此固定取最后一列可能从错误隐藏状态继续；PAD 与 EOS 共用 ID 又让“仅按 ID 推断 mask”产生歧义。优先使用配套 `generate()`，设为左 padding并显式传 mask；若自写循环，则按每行有效长度选 logits，并验证 position IDs、KV Cache 和停止条件。用相同提示词单独/批量输出一致性、首 Token logits 差异和回归测试作为修复证据。

</details>

### 11.5 基础自测

<details>

<summary>1. `[PAD]` 有 Embedding 吗？被 mask 后它的向量是否必然为全 0？</summary>

通常有对应词表 ID 和 Embedding 行。Attention mask 主要阻止其他位置读取它作为 Key，不保证每层 padding 行隐藏状态自动为全 0；训练损失和下游汇聚仍需正确忽略无效位置。

</details>

<details>

<summary>2. `special_tokens_mask` 中的 1 与 `attention_mask` 中的 1 分别表示什么？</summary>

前者表示该位置是特殊 Token；后者在常见 Transformers 模型输入中表示该位置不被 padding mask 屏蔽。两者不可互换。

</details>

<details>

<summary>3. 文本对 `[CLS] A [SEP] B [SEP]` 为什么可能需要 `token_type_ids`？</summary>

它为模型提供 A/B 片段身份，让相同位置上的 Token 除了 Token 与位置表示外，还能带有句段信息。很多模型不用这个字段，所以必须以模型签名为准。

</details>

<details>

<summary>4. 为什么只比较 padding 后的 Token 数没有意义？</summary>

Padding 数量取决于批次组合、padding 策略和长度倍数，并非原文表示效率。应分别统计有效 Token、padding Token、截断量和批次利用率。

</details>

<details>

<summary>5. PyTorch SDPA 与 MultiheadAttention 的布尔 mask 有什么陷阱？</summary>

当前官方文档中，SDPA 布尔 `attn_mask=True` 表示允许参与；MultiheadAttention 的二进制 `key_padding_mask=True` 表示忽略。迁移代码时常需要取反，必须按所用版本验证。

</details>

## 12. 权威资料与延伸阅读

1. [BERT 原始论文](https://aclanthology.org/N19-1423/)\
   `[CLS]`、`[SEP]`、Token/Segment/Position Embedding 与 BERT 输入表示的来源。
2. [Hugging Face Transformers：BERT](https://huggingface.co/docs/transformers/model_doc/bert)\
   BERT 单句/文本对模板，以及 `input_ids`、`attention_mask`、`token_type_ids` 和 `position_ids` 的当前接口定义。
3. [Hugging Face Transformers：Padding 与 truncation](https://huggingface.co/docs/transformers/main/en/pad_truncation)\
   `padding`、`truncation`、`max_length` 和文本对截断策略的官方说明。
4. [Hugging Face Transformers：Tokenizer API](https://huggingface.co/docs/transformers/main_classes/tokenizer)\
   特殊 Token、padding side、返回张量与批量编码接口。
5. [Hugging Face Transformers：LLM generation](https://huggingface.co/docs/transformers/llm_tutorial)\
   Decoder-only 批量生成、左 padding、PAD/EOS 配置和显式传 attention mask 的实践边界。
6. [PyTorch：`scaled_dot_product_attention`](https://docs.pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html)\
   加性/布尔 mask、广播形状、causal 参数以及 True 表示“参与注意力”的官方语义。
7. [PyTorch：`MultiheadAttention`](https://docs.pytorch.org/docs/stable/generated/torch.nn.MultiheadAttention.html)\
   `key_padding_mask`、`attn_mask` 的形状和二进制 True 表示“忽略”的接口定义。

## 一页回顾

```
从 Token 到模型批次：
文本/文本对
→ Tokenizer
→ 按模型模板加入特殊 Token
→ truncation
→ padding
→ input_ids [B,L]
→ Embedding [B,L,d]

常见字段：
input_ids          = 词表索引
attention_mask     = 有效输入/屏蔽信息
token_type_ids     = 文本片段身份，模型可选
special_tokens_mask = 特殊 Token 元数据
position_ids       = 位置信息，常由模型内部生成

Padding mask 的数学作用：
score = QK^T / sqrt(d_k)
被屏蔽 Key 的 score 加 -∞
→ softmax 权重为 0

不要混淆：
padding mask ≠ causal mask
attention mask ≠ loss mask
attention mask ≠ special tokens mask
token_type_ids ≠ mask

API 陷阱：
Transformers BERT：1 保留，0 屏蔽
PyTorch MHA key_padding_mask：True 屏蔽
PyTorch SDPA bool attn_mask：True 保留

工程检查：
打印 Token 与全部输入字段
→ 核对截断位置和 padding side
→ 用极小例子验证 mask 方向
→ 同时检查 attention、position 与 loss

下一步：
Day 7 在同一份语料上训练并比较
WordPiece、字符 BPE、Unigram 与 Byte-level BPE
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://inwt233.gitbook.io/ai-learning/di-yi-bu-fen-nlp-ji-chu/week-01/day-006.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
