Module 2 · LLM Lesson 13 下钻 · 生成机制

自回归 & 因果掩码

解决一个关键困惑:为什么算了所有位置的 hidden state,却只用最后一个来预测?以及"每个位置只能看前面"到底怎么回事。

Topic每个位置预测"它的下一个词" + 因果掩码 causal mask
Objectives看清训练用全部位置、生成只读最后位置;理解因果掩码为何存在
PrerequisitesModule 1 ✅(注意力、hidden state)、Module 2 概览 ✅
Outcome能解释"最后位置的 hidden state 怎么来、为什么只用它"

① 核心翻转:每个位置都在预测"它的下一个词"

你以为"只有最后一个位置在预测"。真相是:每个位置都各自做一个预测——预测紧跟它后面的那个词,且只用它左边(含自己)的词。以 The cat sat on the(5 个位置)为例:

因果掩码:行=某位置能看到哪些列(绿=能看,灰=挡住) The cat sat on the pos1 The → 预测 cat pos2 cat → 预测 sat pos3 sat → 预测 on pos4 on → 预测 the pos5 the → 预测 mat ← 要它! 绿色是"下三角" = 每个位置只能看自己和前面 前 4 行的预测(cat/sat/on/the)= 已有的词,生成时不关心 只有 pos5 预测的 mat 是"未知的新词" → 生成时只读它 (训练时前 4 个预测也全用上:和真实的 cat/sat/on/the 对比算误差)
下三角 = 因果掩码。每个位置用"它左边的词"预测"它的下一个词"。生成只读最后一行,训练用全部行。

② 解开你的两个困惑

为什么只用最后一个位置?

因为只有位置5预测的词是"新的、未知的"(第6个词还没有)。位置1~4预测的第2~5个词我们已经有了(就在输入里),生成时不关心。所以生成 = 只读最后一个位置的预测

前 4 个预测没浪费:训练时全用——拿"位置i的预测"和"真实第i+1词"比,一次前向得 N 个训练信号,非常高效。训练用全部,生成读最后一个。

"位置3只能看1~3"是什么意思?(因果掩码)

位置3的任务是用前3个词预测第4个词。它绝不能偷看第4个词(那就是答案,等于作弊)。所以规定每个位置只能看自己 + 前面的词——这叫因果掩码 causal mask。位置5恰好是最后一个,"它前面所有词"= 整句,所以它看到了全部。

③ 最后位置的 hidden state 怎么来的

层层计算,每层所有位置一起更新

输入 [5×d_model]
→ 第1层(注意力里 pos5 从 pos1~5 收集 + FFN)→ [5×d_model]
→ 第2层(同样)→ [5×d_model]
→ … 第N层 → [5×d_model]

位置5的 hidden state = 它在每一层都从"前面所有词"吸收信息、逐层加工 N 次的结果。到最后一层,它已"读懂整句 + 知道该接什么"。这个向量才被送去下一步(输出层)变成词表概率。

一句话收口:每个位置都预测"它的下一个词",因果掩码保证只用左边的词(不作弊)。训练用全部位置的预测,生成只读最后一个(因为只有它预测的是未知的新词)。

💬 深入问答(来自讨论)

Q:生成时"藏起已存在的 word4 不给 pos3"这种设定真的存在吗?

要分清:因果掩码是一条始终生效的规则(注意力里把"看向未来"的分数设成 −∞,softmax 后≈0),每次前向都在跑。但"挡住真实存在的未来"这个效果:

Q:模型知道自己是第几次循环吗?

不知道。模型是"无状态"的。每次循环它拿到的就是一个完整字符串,从头跑一遍前向、读最后一层最后位置——没有"我上次算过"的记忆。循环是外面的代码在做(把新词接到末尾 + 再调一次模型)。

Q:既然只用最后位置,为什么还算前面所有位置?(漂亮推论)

因为位置3永远只看 1-3,它的 hidden state 在 "The cat sat""The cat sat on the"完全一样(后面加多少词都不影响它)。这种"前面位置不受后面影响"的稳定性,正是为什么可以把前面算过的结果缓存复用——实际生成时甚至不用重算前面,只算新加的那个。

这个缓存机制叫 KV Cache(🅿️ Module 3);它成立的前提就是"因果掩码 → 前面位置稳定不变"。

Q:每层到底在更新什么?后面几个位置会先保持不变吗?

不会。每一层都会同时更新全部 N 个位置。因果掩码只限制"每个位置可以从哪些位置收集信息",不是让后面几行等待或冻结。

假设 The cat sat on the 有 5 个 token,且 d_model=3,初始矩阵是:

X⁰ [5×3] =
The [1,0,0]
cat [0,1,0]
sat [0,0,1]
on [1,1,0]
the [1,0,1]

一次注意力中,五行通常并行更新,但读取范围不同。下面把真正的“注意力权重 × V,再求和”展开(数字仅为示意):

pos1 新向量 = 1.0·V(The)

pos2 新向量 = 0.3·V(The) + 0.7·V(cat)

pos3 新向量 = 0.2·V(The) + 0.3·V(cat) + 0.5·V(sat)

pos4 新向量 = 0.1·V(The) + 0.2·V(cat) + 0.2·V(sat)
       + 0.5·V(on)

pos5 新向量 = 0.1·V(The) + 0.3·V(cat) + 0.2·V(sat)
       + 0.1·V(on) + 0.3·V(the)

每一行的系数都是该位置自己的 softmax 注意力权重,所以各自加起来等于 1。掩码让“不允许看的未来位置”权重变成 0:

这一层的关键图景:不是“pos5 把五个词拼起来”,而是它用自己的 Q 去和五个 K 打分,softmax 得到五个比例,再按这些比例混合五个 V,形成 pos5 的新 hidden state。

经过注意力、残差、LayerNorm、FFN 后,第一层仍输出 X¹ [5×3];第二层继续得到 X² [5×3]……最后一层仍是 Xᴸ [5×3]

最终生成时:完整结果仍是 [N×d_model] 的 hidden-state 列表,只取最后一行 h_last = Xᴸ[N],因为它负责预测位置 N+1。不是"只计算最后一行",而是"全部计算,最后只读取最后一行"(未使用 KV Cache 的朴素视角)。

Q:一次生成循环,输入 token 数量如何变化?

对。每一轮把当前全部已知 token作为上下文,预测一个新 token,再把它接到末尾:

第1轮:[The, cat, sat, on, the]   5 tokens
   → [5×d_model] → N层 → 取位置5 → 预测 mat

第2轮:[The, cat, sat, on, the, mat] 6 tokens
   → [6×d_model] → N层 → 取位置6 → 预测 "."

第3轮:7 tokens → 预测第8个 token → …

所以抽象循环是:已有 N 个 token → 预测第 N+1 个 → 接到末尾 → 下一轮变成 N+1 个输入

朴素实现 vs 实际优化:概念上可理解为每轮把整个序列重新计算;真实推理会用 KV Cache 复用前面 token 已算出的 K/V,下一轮只计算新加入的位置。两者逻辑结果等价,优化细节留到 Module 3。

✅ 快速检查(用自己的话答)

  1. 位置3的 hidden state 能看到哪些词?它负责预测哪个词?
  2. 既然每个位置都做了预测,为什么"生成"时只用最后一个位置的?前面那些预测在什么时候有用?
  3. 因果掩码为什么必须存在?(提示:不加会发生什么"作弊")

答完这 3 题,下一课下钻 输出层:最后位置那个向量,具体怎么变成"词表上每个词的概率"。