多教师蒸馏按 token 分账

TRL 的多教师在线蒸馏示例跑了 60 步,指令跟随拿了 20% 的 prompt,训练 token 只分到 1% 到 3%。Open-MOPD 那篇论文把这件事叫 token 预算错配,修完之后可回收的上限从 35.6% 提到 83.4%。

TRL 的多教师在线蒸馏示例跑了 60 步。指令跟随拿了 20% 的 prompt,训练 token 只分到 1% 到 3%,它是三科里唯一没学动的。Open-MOPD 那篇论文把这个现象叫 token 预算错配,修完之后可回收的能力上限从 35.6% 提到 83.4%。

片子一分五十八秒,中文配音,字幕烧在画面里。

一、六十步之后,只有一条线没动#

2026 年 11 月,Hugging Face 的 Sergio Paniego 把 Open-MOPD 放出的三个专家接到 TRL 的 AsyncDistillationTrainer 上。学生是 SmolLM3-3B 的 SFT 检查点,三个教师分别用 GRPO 在数学、代码、指令跟随上训过。跑 60 步,大约是论文训练量的百分之一。

训练日志里出现两件事。teacher_token_frac 显示指令跟随每步只拿到 1% 到 3% 的训练 token,而它在 prompt 里占 20%。teacher_jsd 显示学生到数学、代码两个教师的距离一路往下,到指令跟随那个几乎是一条平线。

评测端跟着这个形状走。

模型AIME24AIME25IFEval
MixSFT 起点17.519.267.5
60 步 MOPD24.222.573.0
论文终版19.225.075.8

AIME24 那一列有一处要说明。60 步的 24.2 比论文终版的 19.2 高,看着像短训赢了长训。每题只采四个样本,这个量级下几个点的差距在噪声里,读不出结论。稳的信号在 IFEval,起点 67.5,60 步到 73.0,离论文终版还差 2.8 个点,而它正是 token 最少的那一科。

二、三阶段配方,第三段只打分#

多教师在线蒸馏(MOPD)是一条三段式的后训练配方,名字来自小米 2026 年 1 月给 MiMo-V2-Flash 写的那篇技术报告。

第一段是通用 SFT,一个模型在混合数据上把每个领域的基本功学会,这一份检查点是后面所有东西的起点。第二段按领域各训一个 RL 专家,数学一个、代码一个、指令跟随一个,谁也不管别人的活。第三段是融合,学生重新回答各个领域的 prompt,每条 prompt 由它自己领域的那个专家逐 token 打分,学生朝那个分布挪。

三个性质决定了它为什么成立。

性质后果
样本由学生自己生成教师评的是学生真正写出来的话。拿教师输出做 SFT,学生只在教师写的文本上训过,推理时却要续自己的文本,早期一个错误就把它带到训练里没见过的分布
每个 token 都有信号RL 一条答案一个 reward,这里每个位置都拿到教师的分布,loss 是学生与教师在学生 token 上的散度
一条 prompt 只进一个教师数学 prompt 只由数学专家打分,不做平均,也不做集成

教师通常与学生同规模、同一起点,它们有用的原因是在单一领域上被 RL 推过一遍。它换来的是排期,多领域一起训被拆成分别训好再合成。

产业侧已经这么干了一阵。Kimi K3 用了九个专家,按领域和推理档位切开。Nemotron 3 Ultra 用十多个教师,跑两轮 MOPD,第一轮合成之后从合成模型重新训教师。DeepSeek-V4 用十多个领域教师替掉了 V3.2 里那个混合 RL 阶段。Mistral Large 4 的切法不一样,按推理档位高中低分三个教师,领域不再是切分依据。

图 1 三段配方与融合阶段的路由。学生自己生成,教师只读不写,一条 prompt 只交给一个专家
轻点放大

三、论文把病因定在预算#

Open-MOPD(arXiv 2608.19098,8 月 19 日)做了一件很少见的事,它把路由问题先摘掉。基准建在 SmolLM3-3B-Base 上,用 oracle routing,保证每条 prompt 都送到正确的专家手里。剩下的差距只能来自融合本身。

结论是一对数字。相对一个按领域路由的 oracle 集成,标准 MOPD 只回收了 35.6% 的可用上限,加上论文的三个修法之后到 83.4%。同一个学生、同一批教师,差出来的 47.8 个点全在训练过程里。

论文对病因的判断很直接,问题出在 token 层级的优化预算分配,不是梯度冲突。作者拆出三个原因。各领域的序列长度差得太远,写数学的答案几千 token,写指令跟随的几百 token。各领域的收敛速度不一样,学得快的先占住梯度。异步管线里的 reward 会陈旧,旧样本配新策略,权重还会漂。

对应的三个修法分别是 token 份额平衡、按差距动态分配预算、给学生刷新 reward。名字听着抽象,前两条处理的是同一件事,谁产出多少 token,谁就在 loss 里占多大份额,这个份额可以调。

四、按 token 平均,等于按答案长度投票#

原因在 loss 的形状。散度在每个 token 上算,最后在 token 上求平均,一个领域的话语权就等于它产出的训练 token 数除以总 token 数。

prompt 份额和 token 份额因此可以差一个数量级。示例数据按 40/40/20 切数学、代码、指令跟随,指令跟随出了五分之一的题,却只出百分之一到三的字。数学和代码的答案是一条长推理链,几千 token 起。指令跟随的答案常常是「写两段话,中间用星号隔开」,几百 token 就完了。60 步之后数学和代码的答案还在变长,指令跟随的份额还会继续掉。

图 2 同一个 batch 里 prompt 份额与训练 token 份额的对照。指令跟随占五分之一的题,出一到三个百分点的字
轻点放大

TRL 的文档把这件事写进了指标说明。teacher_token_frac/<id> 记录每个教师拿走的 token 份额。文档的注释说,被饿着的教师照样报一个健康的 teacher_jsd/<id>,所以路由偏斜不看这个指标就发现不了。只盯着 loss 的绝对值,指令跟随那一科看起来一直在学,实际每一步只分到几十条样本的梯度。

五、TRL 里三个默认值#

在 TRL 里跑 MOPD,配置里有三个默认值决定了这笔预算怎么记。

beta 默认 0.0,也就是前向 KL,均值寻求。MOPD 论文第三段用的是 1.0,反向 KL,众数寻求。文档把这个差别单独写了一段。直接抄官方示例、删掉 beta 这一行,跑的就是另一个目标函数,日志照样在降。

teacher_top_k 默认 8,但只在 beta=0.0 时决定形状。beta 非零时,支撑集缩到两个候选,教师的 top-1 和完成序列里的真实 token。HTTP 协议只保证这两个身份有 logprob。示例里写 teacher_top_k=1 就是为了让每个教师的回包小一点,它在这个 beta 下不再影响 loss 的形状。

图 3 beta 决定教师端传回多少候选。前向 KL 用满 top-k 加一个尾桶,反向 KL 只有两个
轻点放大

第三个要单独说,它是一条硬约束,每个教师必须与学生共用分词器。学生的完成序列以原始 token id 发给教师,教师回来的候选 id 被直接用来索引学生自己的词表。教师的词表不一样,训练就在对的分布上算错的 token,而且只在教师词表比学生大时才报错。

# examples/async_distillation_mopd/async_distillation_mopd.py
config = AsyncDistillationConfig(
    teacher_server_urls={
        "math": "http://localhost:8001",
        "code": "http://localhost:8002",
        "if":   "http://localhost:8003",
    },
    beta=1.0,         # 反向 KL,与论文第三段一致;默认值是 0.0
    teacher_top_k=1,  # beta 非零时支撑集已经只有两个候选,这项只影响回包大小
    max_completion_length=16384,
    vllm_server_timeout=3600,
)

六、五个进程,四张卡#

融合阶段同时要跑五样东西。学生自己的 vLLM 服务负责生成,教师的 vLLM 服务只做打分,训练进程在中间拉队列。示例把五样东西摊在 4 张 H200 上。

卡跑什么为什么这么放
0学生的 vLLM每 weight_sync_steps 步要接收一次新权重
1数学与指令跟随两个教师两个小模型共享一张卡,先起一个再起另一个
2代码教师代码 prompt 最长,单独一张
3训练进程只做前向反向和优化器

图 4 四张卡上的五个进程。三个教师从不接收权重,只有学生的 vLLM 每步被 NCCL 推一次
轻点放大

异步管线的代价藏在两个默认值里。weight_sync_steps 默认 1,每一步都把学生的新权重推给 vLLM。max_staleness 默认 4,样本如果在队列里待了超过四次权重更新还没被训练,就直接丢掉。这两个数字一起决定了队列能跑多深,也决定了旧样本在梯度里占多少。

LoRA 那条路要单独算账。默认的权重同步会把适配器合并进基础模型再整份发过去,7B 模型一次大约 15 GB。让学生端用 --enable-lora 起,一次只发适配器,r=32 时约 160 MB。省下来的带宽有代价,vLLM 服务 LoRA 适配器时每 token 生成慢 1.33 到 1.39 倍,而生成通常就是墙钟时间的主要部分。

七、数字与来源#

指标值来源
上限回收,标准 MOPD35.6%论文摘要(arXiv 2608.19098)
上限回收,Open-MOPD 三个修法83.4%同上
指令跟随的 prompt 份额 / token 份额20% / 1% 到 3%Hugging Face 博客,60 步训练日志
MixSFT 起点 AIME24 / AIME25 / IFEval17.5 / 19.2 / 67.5同上,每题 4 个样本,IFEval 用严格 prompt 级准确率
60 步 MOPD 同上三项24.2 / 22.5 / 73.0同上
论文终版同上三项19.2 / 25.0 / 75.8同上
beta 默认值 / 论文第三段取值0.0 / 1.0TRL 文档
teacher_top_k 默认值8TRL 文档
teacher_top_k 超过 20 的前提教师端加 --max-logprobs -1TRL 文档
邻近框架的稀疏支撑miles 16,EasyOPD 64TRL 文档
LoRA 权重同步,7B,r=3215 GB / 160 MBTRL 文档
LoRA 服务的生成开销慢 1.33 到 1.39 倍TRL 文档

两栏的来路不同。35.6% 与 83.4% 是论文自己报的,没有第三方复现。60 步那一组是博客作者的实测,只有一次,只有 60 步,样本量不支持任何关于「短训优于长训」的结论。

八、读者能自己核什么#

这四件事都能在一小时内复现,不需要额外的机器。

要核什么去哪里看什么
路由偏斜真的存在训练日志的 teacher_token_frac/<id>指令跟随的份额是否远低于它的 prompt 占比
方向被默认值改掉了AsyncDistillationConfig 文档里 beta 那一条默认 0.0 是前向 KL,论文第三段是 1.0
top-k 在 beta 非零时不起作用同一条文档的支撑集说明支撑集缩到两个候选的那句话
分词器不一致会静默错teacher_server_urls 的注释词表小的时候不报错

九、两个我答不上来的问题#

一是教师比学生大的时候,这笔预算该怎么记。论文和示例里教师与学生同规模、同起点,token 份额与参数份额大致等价。换成大教师带小学生,一个 token 的信息量不一样了,还按 token 平均是否仍然公平,没有人测过。

二是路由本身。Open-MOPD 用 oracle routing 把路由的模糊性摘掉,才把差距归到预算上。线上没有 oracle,prompt 属于哪个领域由分类器判断,判错的 prompt 会被记到另一个教师的账上,这个误差会以什么方式混进 token 份额,论文里没有答案。

参考#

输入以在 96 篇文章中查找

浏览全部文章