对比SFT
SFT、离线 KD 与 OPD:核心区别在于训练前缀的来源
1. 先用一句话说明这页的核心
SFT、离线蒸馏和 OPD,不只是损失函数不同,更重要的是:训练时,模型根据谁已经说出来的话,预测下一步?
对于自回归 ASR:
其中:
x:输入音频。y_t:第t个输出 token。y_{<t}:此前的输出,也就是“前缀”。p_S:学生模型的概率分布。
例如识别“明天去南京南站”,预测“京”时,模型不仅看音频,还看此前的“明天去南”。
同一段音频,前缀不同,下一 token 的概率也可能不同。 这就是为什么训练前缀的来源很重要。
下面用字或词作教学单位,不代表真实 tokenizer 的切分。
2. SFT:沿正确答案的前缀学习
SFT 使用人工参考转写(Ground Truth,GT)作为训练目标。
损失函数:
这里:
y^*:人工参考转写,即 GT。y_t^*:当前位置的正确 token。y_{<t}^*:当前位置之前的正确参考前缀。m_t^*:位置掩码,参与损失为 1,否则为 0。N^*:有效 token 数。- 负对数:正确 token 概率越低,惩罚越大。
有效 token 数:
例如:
音频:明天去南京南站
训练输入前缀:明天去
当前位置目标:南
如果学生给“南”的概率是 0.3,该位置的损失为:
提高到 0.8 后:
因此,CE 推动模型提高正确 token 的概率。
但是,即使学生本来会把“南”识别成“难”,标准 SFT 在训练后续位置时,仍然给它正确前缀“明天去南”,不会把它自己的“明天去难”接进去。
这种训练方式称为 Teacher Forcing。
可以口头解释为:
SFT 在正确路线上的每一步教模型怎么走,但模型部署时一旦走错,后面遇到的状态可能是训练时很少见过的。
这不代表 SFT 无效,而是在说明训练与自由生成之间的前缀分布差异,即 Exposure Bias。
3. 离线 KD:沿教师答案或固定数据前缀学习
图中展示的是序列级蒸馏(Sequence-Level KD):教师先生成一份答案 y^T,缓存下来,学生再用 CE 学习:
它和 SFT 的形式基本一样,变化是:
标签从人工 GT 换成教师生成的文本。
例如教师输出“明天去南京南站”,学生训练时就沿着这条固定答案学习。即使当前学生已经会输出其他内容,缓存的教师前缀也不会随它变化。
3.1 离线 KD 不一定使用硬标签
这里需要口头补充:离线 KD 不只有硬标签 CE,也可以蒸馏教师的软概率分布。
CE 本身也支持概率形式的目标。
参考:PyTorch CrossEntropyLoss 文档
对固定前缀,软标签交叉熵为:
而 Forward KL 为:
其中,教师分布的熵为:
当教师分布停止梯度、前缀固定时,教师熵不随学生参数变化,所以两者对学生产生相同的梯度。
也就是:
因此:
所以,这页不能讲成“离线 KD 用 CE,OPD 用 KL”。
正确的区别是:
离线蒸馏主要沿固定数据前缀训练;OPD 沿当前学生生成的前缀训练。
需要注意,离线 KD 也可以沿 GT 前缀或其他预先收集的前缀计算教师软分布,不必一定使用教师生成的文本作为前缀。
4. OPD:沿学生实际生成的前缀,向教师请教
OPD(On-Policy Distillation)的核心:
学生先使用当前策略生成序列,教师再在学生实际生成的前缀上提供概率分布监督。
统一的蒸馏目标可以写为:
可以从外到内读。
4.1 第一层:学生先生成
其中:
\hat y:当前学生根据音频生成的序列。p_S:当前学生策略。x:输入音频。
这个序列不是 GT,也不是教师生成的答案。
随着学生更新,它生成的序列及前缀分布也会变化。
4.2 第二层:教师和学生在同一个前缀上打分
学生下一 token 概率:
教师下一 token 概率:
其中:
v:词表中的候选 token。p_{S,t}:学生对下一 token 的完整分布。p_{T,t}:教师对同一位置的完整分布。c:教师可选的额外条件。
重点:教师和学生必须基于相同的学生前缀打分。
但教师可以拥有额外条件 c。
例如:
普通强教师 OPD
- 学生输入:音频 + 学生生成前缀。
- 教师输入:音频 + 相同的学生生成前缀。
- 教师不一定需要额外条件。
Keywords-GT OPSD
- 学生输入:音频 + 学生生成前缀。
- 教师输入:音频 + 学生生成前缀 + GT 槽位信息。
- 学生不直接看到 GT。
这种额外条件使教师有机会提供更准确的指导,但不保证教师在每种错误前缀下都能正确恢复。
4.3 第三层:逐位置比较分布,再平均
D 表示教师和学生之间的分布差异,可以选择:
- Forward KL:教师分布到学生分布。
- Reverse KL:学生分布到教师分布。
- JSD:Jensen-Shannon Divergence。
- 其他合适的分布匹配目标。
m_t 用于排除不参与训练的位置。
有效 token 数:
外面的期望:
可以理解为“对学生可能生成的轨迹求平均”。
实际训练时,通常用采样出的有限数量轨迹估计。
学生轨迹及停止采样梯度的设计,与 GKD 的 On-Policy 蒸馏实现一致。
参考:GKD 原论文
需要注意:
上述目标描述的是在学生当前策略产生的前缀分布上进行蒸馏。
常见的 stop-gradient OPD 实现不会穿过离散 token 采样反向传播,而是将采样后的前缀视为固定输入,再对学生的预测分布求梯度。
5. 一个可以现场口头讲的数值例子
以下概率都是教学构造,不是实测结果。
音频 / GT:明天去南京南站
学生已经输出:明天去难京
现在需要预测下一步。
教师和学生都必须基于“明天去难京”打分,不能一个看正确前缀,另一个看错误前缀。
把候选词表简化为三项:
| 下一 token | 教师 p_T | 学生 p_S |
|---|---|---|
| 南 | 0.80 | 0.30 |
| 站 | 0.15 | 0.30 |
| EOS,结束 | 0.05 | 0.40 |
在这个人为构造的状态下:
- 教师认为应该继续输出“南”。
- 学生却比较倾向于提前结束。
5.1 Forward KL 如何计算
采用 Forward KL:
定义:
代入数值:
这个值衡量当前教师和学生的下一 token 分布差异。
5.2 KL 如何更新学生
假设学生的 logit 为 z_v。
在温度为 1、教师固定的情况下,对学生 logit 的梯度是:
所以:
对“南”:
梯度下降会提高“南”的 logit。
对“站”:
梯度下降会降低“站”的 logit。
对 EOS:
梯度下降会降低提前结束的倾向。
5.3 软分布为什么比硬标签信息更多
如果只有硬标签,监督信号只是:
正确 token = 南
但是教师软分布提供了更多信息:
南:0.80
站:0.15
EOS:0.05
它不仅告诉学生“南”更合适,还告诉学生:
- “站”具有一定概率,但不应该这么高。
- EOS 概率应该很低,不应该提前结束。
- 不同错误候选的惩罚程度并不相同。
可以口头总结:
教师不仅告诉学生“下一步选南”,还告诉它:不要直接跳到站,更不要现在结束。这就是软分布比单个硬标签多出的信息。
这里需要强调:该例子的教师分布是人为假设的。
现实中,当学生前缀已经严重偏离音频内容时,教师可能也无法提供正确的恢复指导。
6. 最容易讲错的两个地方
6.1 第一,OPD 不会直接修改已经生成的错误
上例中,“难”已经在固定前缀里,当前位置的蒸馏不会把它改成“南”。
但在这条轨迹更早的位置,前缀还是“明天去”时,教师也会打分。
如果教师更支持“南”而不是“难”,更新就能降低学生以后重新识别时犯同类错误的概率。
所以 OPD 可以学习两件事:
- 减少错误发生:在错误出现之前,提高正确 token 的概率。
- 错误后继续生成:在已经出现错误的前缀下,学习更合理的后续输出。
但不保证教师在所有错误前缀下都能提供正确指导。
需要区分:
训练时更新模型参数,不等于直接修改本次已经生成的 token。
6.2 第二,rollout 不反传,不等于学生不训练
常见的 OPD 训练过程:
Step 1:学生无梯度生成序列
|
v
Step 2:固定生成前缀
|
v
Step 3:教师无梯度评分
|
v
Step 4:学生带梯度重新评分
|
v
Step 5:计算蒸馏损失
|
v
Step 6:反向传播,更新学生
对应的简化伪代码:
# Step 1:学生生成 rollout,不计算梯度
with torch.no_grad():
sampled_tokens = student.generate(audio)
# Step 2:固定学生生成的序列
tokens = sampled_tokens.detach()
# Step 3:教师在学生前缀上评分
with torch.no_grad():
teacher_logits = teacher(
audio=audio,
tokens=tokens,
)
# Step 4:学生重新评分,保留梯度
student_logits = student(
audio=audio,
tokens=tokens,
)
# Step 5:计算 token-level 分布蒸馏损失
loss = distillation_loss(
student_logits,
teacher_logits,
mask,
)
# Step 6:更新学生参数
optimizer.zero_grad()
loss.backward()
optimizer.step()
说明:以上是流程伪代码。实际实现必须处理自回归 shift、EOS、padding mask、loss mask,以及教师和学生 logits 的位置对齐。
关键是:
Rollout 阶段不需要对离散采样反向传播,但评分阶段必须重新计算带梯度的学生概率分布。
梯度通过学生重新计算的 logits 传播,不穿过离散采样过程。
教师只需要在学生前缀上打分,不需要重新生成一整句答案。
需要区分两种梯度:
第一种:固定前缀上的分布蒸馏梯度
来自学生重新评分得到的 logits。
第二种:学生策略改变引起的未来采样轨迹分布变化
涉及轨迹概率对学生参数的依赖。
常见 stop-gradient OPD/GKD 实现直接使用第一种,不显式计算第二种策略梯度项。
7. SFT、离线 KD 与 OPD 的最终对比
| 对比维度 | SFT | 离线 KD | OPD |
|---|---|---|---|
| 训练前缀来源 | 人工 GT | 固定教师输出或数据前缀 | 当前学生 rollout |
| 下一 token 监督 | GT token | 教师硬标签或软分布 | 通常使用教师软分布 |
| 常用损失 | CE | CE、KL | KL、JSD 等 |
| 是否依赖当前学生输出轨迹 | 否 | 通常否 | 是 |
| 前缀是否随学生更新变化 | 否 | 通常否 | 是 |
| 是否覆盖学生错误前缀 | 标准 SFT 不直接覆盖 | 取决于固定数据 | 直接覆盖采样到的错误前缀 |
| 能否学习错误后恢复 | 缺乏直接覆盖 | 取决于数据 | 可以学习采样到的错误状态 |
| 主要限制 | Exposure Bias | 前缀分布不匹配 | Rollout 成本、教师质量、错误轨迹覆盖 |
7.1 数学上的核心区别
SFT:沿参考答案前缀学习
离线 KD:沿固定教师或数据前缀学习
其中也可以使用其他预先确定的固定前缀。
OPD:沿当前学生生成的前缀学习
因此,真正重要的训练范式差异是:
训练监督发生在哪种前缀分布上,而不是单纯使用 CE、KL 还是 JSD。
7.2 从状态分布角度理解
可以把自回归生成中的每个前缀视为一个状态:
SFT
监督主要来自参考答案访问过的状态。
离线 KD
监督主要来自预先确定的教师轨迹或固定数据状态。
OPD
监督来自当前学生策略实际访问的状态。
这也是 On-Policy 的核心含义。
8. 这页最后可以这样收尾
SFT 是在正确参考前缀上学习正确答案;离线蒸馏是在固定前缀上模仿教师;OPD 则让当前学生先走一遍,再让教师在学生实际到达的每个状态上提供指导。
关键不是用了 CE 还是 KL,而是监督发生在谁的轨迹上。
这样做的目的,是让训练时模型面对的前缀,更接近它真实推理时会遇到的前缀。
下一页再讲这套流程如何落实到训练代码。
9. 最终核心结论
SFT:
沿参考答案前缀训练。
Offline KD:
沿固定数据或教师前缀训练。
OPD:
沿当前学生生成的前缀训练。
可以用三个公式总结:
SFT:
Offline KD(序列级):
OPD:
最终结论:训练范式的关键差异,是监督发生在哪种前缀分布上,而不仅仅是损失函数使用 CE、KL 还是 JSD。
10. 参考资料
-
GKD:On-Policy Distillation of Language Models: Learning from Self-Generated Mistakes
https://arxiv.org/abs/2306.13649 -
PyTorch CrossEntropyLoss
https://docs.pytorch.org/docs/stable/generated/torch.nn.CrossEntropyLoss.html
正在连接 GitHub 评论…