DistilQwen2.5的原理与代码实践

前文提到,在模型蒸馏领域,存在黑盒蒸馏和白盒蒸馏两种范式。

DistilQwen2.5[1]这篇工作结合了这两种范式,并开源了代码[2]。

本文将参照其代码,从实践的角度,进一步理解这两种蒸馏方式是怎么做的。

原理回顾

DistilQwen2.5 的博客[3]展现了其模型的训练过程。

训练过程分三个步骤:

  • 数据预处理:采集各种数据,进行数据清洗,难度划分等工作,在训练前完成
  • 黑盒蒸馏:借助闭源大模型的API,对SFT指令数据进行处理
  • 白盒蒸馏:师生模型联合训练,进行蒸馏

环境配置

下面克隆easydistill的代码,它是一个模型蒸馏的工具包,除了 DistilQwen2.5 之外,还包括其它模型的蒸馏算法。

复制代码
git clone https://github.com/modelscope/easydistill.git

使用 uv 来配置基本依赖:

复制代码
uv venv --python 3.11
source .venv/bin/activate
python setup.py install

实现黑盒蒸馏

easydistill的用法教程几乎没有,连一些基础的用法说明都没有。

好在通过AI,我找了它的正确使用方式。

它的设计逻辑有点类似于mmdetection,做了很多配置文件,通过命令行去加载这些配置文件,以实现功能。

除命令行用法之外,更直观的方式是通过python直接去运行脚本文件。

下面尝试使用硅基流动[4]的api,进行黑盒蒸馏。

配置模型需要修改 configs/kd_black_box_api.json 这个文件,需要填写如下图所示的url和api_key。

它默认没有 model 的参数选项,如果直接配置,它会调用api平台的第一个模型,在硅基流动中,第一个默认模型是Pro版本的GLM-4.7,它不能用赠金,只能用余额。

如果不选择模型,会直接将钱包烧成负数。

我fork了该仓库,适配了一下硅基流动平台,增加了模型配置选项,如果有同类使用方式,可参考我的代码实现[5]。

蒸馏的数据集同样在该文件中进行配置:

  • instruction_path:输入的数据集
  • labeled_path:输出的数据集

数据集必须是json格式,我这里从alpaca_en_demo这个数据集(总共有1k条数据)中,随机挑选了100条数据。

准备好后,运行以下代码,开始蒸馏。

复制代码
python -m easydistill.kd.infer --config configs/kd_black_box_api.json

蒸馏完成后,会得到labeled_path配置的文件。

黑盒蒸馏过程解析

实质上,黑盒蒸馏就是借助大模型,对数据集进行逐条优化。

在代码中,默认的系统提示词是You are a helpful assistant.,这个提示词很泛用,也可以根据具体任务去做修改。

优化的效果如何呢,下面看几个例子。

第一个例子:指令要求输入一个url,然后输出它的主域名。

原始的数据集答案(output)是"google.com",显然这是个偏差,答案不够泛化。

输入到大模型之后(这里需要注意,模型只看见系统提示词和指令,看不到原始答案),结果变成了右边非常具有泛化性的输出。

第二个例子:要求模型想象在罗马旅行,会看见的内容,类似于写故事。

原始的答案很简短,优化后的答案变得很长,下图中没完成放下,只进行了截取。

但是,有些内容优化后之后,未必会更好。

第三个例子:让模型直接写一段话的反义词,但优化后的模型扯了一堆废话,还不如原本的答案简洁。

白盒蒸馏过程解析

在DistilQwen2.5这个模型中,白盒蒸馏是使用教师模型的logits概率分布作为软标签,然后通过KL散度损失让学生学习教师的概率分布。

使用该方法进行白盒蒸馏有个前提:教师模型和学生模型的分词器(tokenizer)要对齐,最简单的方式就是用同一个分词器。

就像文章中提到的用Qwen3-Max去蒸馏Qwen-0.5B,都是Qwen系列,适配同一个分词器。

代码中,有两种方式可以进行白盒蒸馏:

  • 异步蒸馏:在算力资源有限的情况下,可以预先通过 kd/infer.py 去计算教师模型的logits概率分布,后面训小模型时,直接加载这个预先计算好的数据。
  • 同步蒸馏:直接运行 kd/train.py,同时加载大小两个模型,边计算边使用。

小模型的损失计算由两部分组成:

  • 交叉熵损失:小模型自身输出的损失
  • 蒸馏损失:用KL散度来进行衡量

Ltotal=(1−λ)LLM + λLKD\mathcal { L } _ { \mathrm { t o t a l } } = \left( 1 - \lambda \right) \mathcal { L } _ { \mathrm { L M } } \ + \ \lambda \mathcal { L } _ { \mathrm { K D } }Ltotal=(1−λ)LLM + λLKD

式中,λ\lambdaλ为平衡系数,默认为0.5。

在代码中,对于KL散度损失的计算有两种实现方式。

前向KLD(Forward KLD)指的是:教师觉得"有可能"的词,学生不给概率,就会被更多惩罚。

例如:

输出Token = {A, B, C}

教师输出概率={0.6, 0.3, 0.1}

学生输出概率={0.9, 0.1, 0.0}

KL损失因选项C(教师认为有可能,而学生认为不可能)而增加。

反向KLD(Reverse KLD)指的是:学生"自己打算说"的词,如果教师觉得不可能,就会被更多惩罚。

例如:

输出Token = {A, B, C}

教师输出概率={0.6, 0.3, 0.1}

学生输出概率={0.4, 0.1, 0.5}

KL损失因选项C(学生认为概率大,教师认为概率小)而增加。

默认采用的是前向KLD。

局限性讨论

看完之后,实际上会发现一个问题,在白盒蒸馏的过程中,DistilQwen2.5直接用KL散度去进行评估。

而KL散度的一个前提是:大小模型的输出长度要一样才能计算

如果长度不一样,代码中会以短的作为基准,对长的进行截断。

举个例子,任务是写一个故事:

复制代码
prompt =「Write a story」
教师模型:<bos> Once upon a time ... (500 tokens) ... The End <eos>
学生模型:<bos> Once upon a time ... (120 tokens) <eos>

教师模型输出了500个tokens,学生模型输出了120个tokens。

此时,KL散度仅计算前120 tokens,教师模型的121-500 token,不参与计算。

问了一下AI,这个点确实有一些研究思路:

  • 方法 1:Teacher-forcing 蒸馏
    关键思想:不是让学生自由生成,而是"强行走教师的 token 轨迹"
  • 方法 2:EOS / length-aware 蒸馏
    显式引入:EOS token 的 KL,让学生学会:"在这个位置,老师还没停,我也不该停"
  • 方法 3:Sequence-level 蒸馏
    用 teacher 输出作为 pseudo-label,直接对完整序列做 CE
  • 方法 4:多轨迹采样 + 蒸馏
    教师生成多个不同长度版本,覆盖多种停止模式,让学生学到"长度分布",而不是单一长度

参考

1\] https://arxiv.org/abs/2504.15027 \[2\] https://github.com/modelscope/easydistill \[3\] https://developer.aliyun.com/article/1653842 \[4\] https://cloud.siliconflow.cn/i/bjDoFhPf \[5\] https://github.com/zstar1003/easydistill

相关推荐
Ro Jace1 天前
基于互信息的含信息脑电图自适应窗口化情感识别
人工智能·python
蓝程序1 天前
Spring AI学习 程序接入大模型(框架接入)
人工智能·学习·spring
RichardLau_Cx1 天前
AI设计工具提示词模板清单
人工智能
腾视科技1 天前
腾视科技TS-NV-P200车载系列AI边缘算力盒子:引领车路协同新时代,赋能多元场景应用
人工智能·科技
DX_水位流量监测1 天前
水雨情在线监测系统的技术特性与实践应用
大数据·网络·人工智能·信息可视化·架构
吴名氏.1 天前
电子书《Java机器学习》
人工智能·机器学习·java机器学习
Tezign_space1 天前
GEA的架构科普:生成式引擎优化架构详解与实战指南
人工智能·架构·生成式ai·知识图谱·搜索引擎优化·生成式搜索引擎·gea
棒棒的皮皮1 天前
【深度学习】YOLO实战之模型训练
人工智能·深度学习·yolo·计算机视觉
草莓熊Lotso1 天前
脉脉独家【AI创作者xAMA】| 开启智能创作新时代
android·java·开发语言·c++·人工智能·脉脉