1. 从「蒸馏」这个词说起:它到底指什么
先把话说在前头,我不是来给哪家公司站台的,也不是来断案的。我就是个大模型方向的开发工程师,平时工作里既做过微调,也做过蒸馏,还帮团队搭过私有化部署的推理服务。看到「7 家中国公司被点名蒸馏」这个标题的时候,我第一反应不是「谁抄谁」,而是——蒸馏这件事本身,在大模型圈子里到底是个什么位置?为什么它会被拿出来说事?
如果你刚接触大模型,可能对「蒸馏」这个词有点陌生。但如果你做过模型压缩、端侧部署、或者搞过小模型落地,那你一定绕不开它。简单说,知识蒸馏(Knowledge Distillation)就是让一个小模型去学一个大模型的行为。大模型是「老师」,小模型是「学生」,学生不直接看原始数据的标准答案,而是看老师给出的「软标签」——也就是老师对每个样本输出的概率分布。
举个例子。假设你在做一个服装检测的工业 AI 项目,客户要求模型必须跑在单机工控机上,不能联网,算力有限。你手上有一个在云端跑得很好的大模型,但它的参数量是几十亿,根本塞不进工控机。这时候你有两条路:一是自己从头训一个小模型,二是用大模型去蒸馏一个小模型。前者需要大量标注数据和算力,后者只需要大模型的输出作为监督信号。蒸馏的核心价值就在这里:把大模型的能力「压缩」到小模型里,让小模型在特定任务上接近大模型的表现。
那为什么「蒸馏」会跟「偷」扯上关系?因为蒸馏的前提是——你得能拿到大模型的输出。如果大模型是你自己训的,那没问题,随便蒸。但如果大模型是别人的,你通过 API 大量调用它,拿它的输出来训自己的小模型,这就涉及到一个灰色地带:你蒸的是能力,还是抄的是结果?
这个问题在行业里其实吵了很久。OpenAI 的服务条款里明确禁止用其输出来训练竞品模型,但「竞品模型」怎么定义?我蒸一个专门做服装检测的小模型,算不算竞品?我蒸一个只用来做 K 线图分析的小模型,算不算?这些边界非常模糊。所以当有报道说「7 家中国公司被点名蒸馏」的时候,我一点都不意外。这不是技术问题,这是技术、商业、规则三者交织出来的必然摩擦。
提示:蒸馏本身是中性技术,不违法也不违规。问题出在「蒸谁的」「怎么蒸」「蒸完干什么」这三个环节上。做技术的人要清楚边界在哪里,别踩线。
2. 蒸馏的技术底座:KL 散度、软标签与温度系数
既然要聊蒸馏,那就得把技术底座讲清楚。不然你只知道「小模型学大模型」,但不知道具体怎么学,遇到问题就没法排查。
2.1 KL 散度:衡量两个概率分布有多像
蒸馏的损失函数里,最核心的就是KL 散度(Kullback-Leibler Divergence)。它衡量的是两个概率分布之间的差异。在蒸馏里,一个是老师模型的输出分布,一个是学生模型的输出分布。KL 散度越小,说明学生越像老师。
公式长这样:
KL(P || Q) = Σ P(x) * log(P(x) / Q(x))其中 P 是老师分布,Q 是学生分布。注意,KL 散度是不对称的,KL(P||Q) 不等于 KL(Q||P)。在蒸馏里,我们通常用老师分布作为 P,学生分布作为 Q,因为我们要让学生去逼近老师。
但实际实现的时候,不会只用 KL 散度。通常还会加一个「硬标签损失」,也就是学生模型对真实标签的交叉熵。最终损失是两者的加权和:
Loss = α * KL(teacher || student) + (1 - α) * CrossEntropy(student, true_label)α 是个超参数,一般取 0.5 到 0.9 之间。我自己的经验是,如果老师模型很强、数据量很大,α 可以调高一点,比如 0.7;如果老师模型本身一般,或者数据量少,α 要调低,不然学生会被老师带偏。
2.2 温度系数:让软标签「软」下来
老师模型的输出通常经过 Softmax,概率分布会很「尖」——正确类别的概率接近 1,其他类别接近 0。这种分布信息量很少,学生学不到什么东西。所以 Hinton 在原始论文里引入了温度系数 T:
softmax(x / T)T 越大,分布越平滑,类别之间的相对关系越明显。比如老师对一张服装图片的输出是「衬衫 0.9,T恤 0.08,外套 0.02」,T=1 的时候就是这样。但如果 T=5,可能变成「衬衫 0.6,T恤 0.3,外套 0.1」。学生就能学到「这件衣服有点像 T 恤,但更像衬衫」这种细粒度信息。
T 一般取 2 到 20 之间。我实测下来,T=4 到 T=8 是比较稳的区间。T 太小,软标签不够软;T 太大,分布太均匀,噪声也大。
2.3 蒸馏的三种主流做法
行业里蒸馏大致分三类:
| 蒸馏类型 | 核心思路 | 适用场景 | 代表工作 |
|---|---|---|---|
| 响应蒸馏 | 学生学老师的最终输出 | 分类、生成任务 | Hinton 原始论文 |
| 特征蒸馏 | 学生学中间层特征 | 视觉任务、检测 | FitNets |
| 关系蒸馏 | 学生学样本间关系 | 度量学习、检索 | RKD |
大模型时代,响应蒸馏用得最多,因为大模型的中间层很难对齐——参数量、层数、结构都不一样,特征蒸馏成本太高。所以现在说「蒸馏大模型」,基本都是在说响应蒸馏:用大模型的 API 输出,或者用开源大模型的 logits,来训一个小模型。
注意:如果你用的是闭源 API,拿不到 logits,只能拿到文本输出。这时候蒸馏就退化成「用大模型的生成结果做监督」,效果会比 logits 蒸馏差一截。这也是为什么很多团队宁愿用开源大模型做老师,也不愿意用闭源 API。
3. 大模型蒸馏的实操流程:从数据到部署
光讲原理没意思,我直接把我做过的一个蒸馏项目拆开讲。场景是:客户要做一个工业 AI 检测模型,检测服装瑕疵,必须单机部署,不能联网,算力只有一张 8G 显存的工控卡。我们手上有一个 7B 的开源大模型,在瑕疵描述和分类上表现不错,但太大,跑不动。目标:蒸一个 1B 左右的小模型,精度损失控制在 3% 以内。
3.1 第一步:数据准备与老师输出生成
蒸馏的第一步不是训学生,是让老师跑数据。我们收集了 5 万张服装瑕疵图片,每张图片配一段文本描述(比如「左袖口有 2cm 线头」)。然后把这 5 万条数据喂给 7B 老师模型,让它输出分类结果和置信度。
这里有个坑:老师模型的输出不一定全对。如果老师错了,学生也会跟着错。所以我们在生成老师输出之后,做了一轮人工抽检,把明显错误的样本剔除。5 万条里剔了大概 1200 条,占比 2.4%。这个比例可以接受,但如果超过 5%,就要考虑换老师模型了。
生成老师输出的代码大概长这样:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch teacher_model = AutoModelForCausalLM.from_pretrained( "path/to/teacher-7b", torch_dtype=torch.float16, device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained("path/to/teacher-7b") def get_teacher_logits(prompt): inputs = tokenizer(prompt, return_tensors="pt").to("cuda") with torch.no_grad(): outputs = teacher_model(**inputs) logits = outputs.logits[:, -1, :] # 取最后一个 token 的 logits probs = torch.softmax(logits / 4.0, dim=-1) # T=4 return probs注意,这里我们取的是最后一个 token 的 logits,因为分类任务通常把类别映射到特定 token 上。如果是生成任务,就要取整个序列的 logits。
3.2 第二步:学生模型结构与初始化
学生模型我们选了一个 1B 的开源模型,结构跟老师类似,但层数和隐藏维度都小很多。初始化的时候,不要随机初始化,最好用老师模型的部分权重来初始化学生。比如取老师的 embedding 层和前几层,复制到学生模型里。这样学生起点更高,收敛更快。
具体做法:
student_model = AutoModelForCausalLM.from_pretrained( "path/to/student-1b", torch_dtype=torch.float16 ) # 用老师的 embedding 初始化学生 student_model.model.embed_tokens.weight.data = \ teacher_model.model.embed_tokens.weight.data[:student_model.config.vocab_size, :student_model.config.hidden_size]这个操作不是必须的,但实测能加快收敛 20% 左右。如果学生和老师的词表不一样,就不能直接复制,需要做映射。
3.3 第三步:训练循环与损失设计
训练循环里,每个 batch 同时喂给老师和学生。老师不更新参数,只前向传播;学生既前向又反向。损失函数就是我们前面说的 KL 散度加交叉熵。
import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, T=4.0, alpha=0.7): # 软标签损失 soft_loss = F.kl_div( F.log_softmax(student_logits / T, dim=-1), F.softmax(teacher_logits / T, dim=-1), reduction="batchmean" ) * (T * T) # 硬标签损失 hard_loss = F.cross_entropy(student_logits, labels) return alpha * soft_loss + (1 - alpha) * hard_loss注意 KL 散度那里乘了T * T,这是 Hinton 论文里的做法,目的是让软标签损失的梯度尺度跟硬标签损失一致。不乘的话,T 越大,梯度越小,训练会变慢。
训练参数:
| 参数 | 取值 | 说明 |
|---|---|---|
| batch size | 32 | 受显存限制,用了梯度累积 |
| learning rate | 2e-5 | 比正常微调小一点 |
| T | 4.0 | 温度系数 |
| alpha | 0.7 | 软标签权重 |
| epochs | 3 | 早停 |
| optimizer | AdamW | weight decay 0.01 |
3.4 第四步:评估与部署
训练完之后,我们在留出的测试集上评估。老师模型准确率 94.2%,学生模型 91.8%,掉了 2.4 个百分点,在可接受范围内。推理速度上,学生模型在 8G 工控卡上单张图片 12ms,老师模型要 180ms,快了 15 倍。
部署的时候用 ONNX Runtime 或者 TensorRT 做量化,INT8 量化之后模型只有 500MB 左右,显存占用不到 2G,完全满足单机部署要求。
实操心得:蒸馏完之后一定要做量化,不然小模型的推理速度优势发挥不出来。INT8 量化通常掉 0.5% 到 1% 的精度,但速度能翻倍。如果精度掉太多,可以试试 FP16 量化,速度提升小一点,但精度几乎不掉。
4. 「被点名蒸馏」背后的行业争议与边界
技术讲完了,回到标题里的争议。为什么「蒸馏」会被拿出来说事?我个人的观察是,这里面有三层矛盾。
4.1 第一层:技术能力与商业规则的错位
蒸馏是公开技术,论文发了十几年,代码开源得到处都是。但大模型厂商的服务条款里,往往写着「禁止用输出训练竞品」。问题在于,「竞品」的定义太宽了。我蒸一个服装检测模型,跟大模型厂商的通用对话模型,算竞品吗?我蒸一个股票 K 线分析模型,算竞品吗?
从技术角度看,这些都不算竞品,因为任务不同、场景不同。但从商业角度看,大模型厂商可能认为,你用了我的输出,就是在「搭便车」。这个边界,目前行业里没有共识。
4.2 第二层:开源模型与闭源 API 的待遇不同
如果你用开源大模型做老师,比如 Llama、Qwen、DeepSeek,基本没人管你。因为开源协议允许你这么做,只要遵守协议里的署名、商用限制等条款。但如果你用闭源 API 做老师,比如 GPT-4、Claude,那就容易踩线。
所以现在很多团队的做法是:用开源大模型做老师,或者用闭源 API 做数据生成,但只用来做冷启动,后续用自己标注的数据做微调。这样既利用了闭源模型的能力,又规避了直接蒸馏的风险。
4.3 第三层:蒸馏与「偷」的本质区别
我个人的观点是,蒸馏不等于偷。偷是直接复制权重、复制代码、复制数据。蒸馏是学行为、学分布、学能力。学生模型的结构、参数、训练数据都是自己的,只是监督信号来自老师。这跟人类学习很像——你看了别人的文章,学了别人的写作风格,然后写自己的文章,这算偷吗?
当然,如果学生模型跟老师模型结构完全一样,参数初始化也直接复制,训练数据也是老师的训练数据,那确实有抄袭嫌疑。但这种情况在工程里很少见,因为没必要——结构一样的话,直接微调老师就行了,何必蒸馏。
提示:做蒸馏项目的时候,保留好自己的数据来源、训练日志、模型结构设计文档。万一被质疑,这些就是你的证据。行业里已经有公司因为没留证据,被误伤过。
5. 常见问题与排查技巧实录
蒸馏项目做多了,踩的坑也不少。我整理了一个速查表,都是实际遇到过的问题。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 学生模型 loss 不下降 | 学习率太小 / T 太大 | 打印梯度范数 | 调大学习率,调小 T |
| 学生模型过拟合 | alpha 太高 / 数据太少 | 看验证集 loss | 调低 alpha,加数据增强 |
| 学生输出全是同一类 | 老师输出太集中 | 看老师输出分布 | 调大 T,或换老师 |
| 蒸馏后精度掉太多 | 学生容量不够 | 对比参数量 | 换大一点的学生,或做特征蒸馏 |
| 推理速度没提升 | 没做量化 / batch 太小 | 测推理耗时 | 做 INT8 量化,调大 batch |
| 显存不够 | batch 太大 / 模型太大 | 看显存占用 | 梯度累积,或换小模型 |
再分享几个独家避坑技巧:
技巧一:老师模型不要选太强的。很多人觉得老师越强越好,其实不是。如果老师太强,学生学不动,反而效果差。我一般选比学生大 5 到 10 倍的老师。比如学生 1B,老师选 7B 到 13B 就够了,没必要上 70B。
技巧二:蒸馏数据要多样化。如果只用单一场景的数据,学生只会在这个场景下表现好,换个场景就崩。我一般会混入 20% 的通用数据,让学生的泛化能力不至于太差。
技巧三:分阶段蒸馏。先蒸一个中等模型,再用中等模型蒸小模型。这样比直接从大模型蒸小模型效果更好,因为中等模型已经帮学生「过滤」了一遍噪声。
技巧四:监控 KL 散度的变化。训练过程中,KL 散度应该是逐渐下降的。如果 KL 散度先降后升,说明学生开始过拟合老师的噪声了,这时候要早停。
技巧五:用多个老师做集成蒸馏。如果有多个大模型,可以让它们分别输出,然后取平均作为软标签。这样能减少单个老师的偏差,学生学到的分布更稳。
6. 蒸馏之外:大模型落地的其他路径
蒸馏不是万能的。有些场景下,蒸馏效果不好,或者成本太高,就得考虑其他路径。
6.1 微调 vs 蒸馏:怎么选
微调是让模型学新任务,蒸馏是让模型变小。两者可以结合,也可以分开用。如果你的目标是「让大模型学会新任务」,那就微调;如果你的目标是「让小模型接近大模型」,那就蒸馏。
我一般这么判断:
- 如果任务跟大模型原有能力接近,比如文本分类、摘要,直接微调就行,不用蒸馏。
- 如果任务跟大模型原有能力差很远,比如工业检测、医疗诊断,那就要先微调大模型,再蒸馏小模型。
- 如果算力充足,直接部署大模型,不用蒸馏。
- 如果算力受限,必须蒸馏。
6.2 量化与剪枝:蒸馏的补充手段
蒸馏之后,还可以做量化和剪枝。量化是把 FP16 转成 INT8,剪枝是去掉不重要的权重。两者都能进一步压缩模型,但都会掉精度。
我的经验是:先蒸馏,再量化,最后剪枝。顺序不要反。先蒸馏能把大模型的能力压缩到小模型,再量化能进一步压缩,最后剪枝能去掉冗余。如果先剪枝,模型结构变了,蒸馏就不好做了。
6.3 本地部署大模型的现实选择
现在很多人想在本地部署大模型,让个人电脑智能化。但现实是,7B 以上的模型,普通电脑跑不动。这时候有几个选择:
- 用蒸馏后的小模型,1B 到 3B,配合量化,能在 8G 显存的电脑上跑。
- 用 Ollama 这类工具,它内置了量化模型,安装即用。
- 用云 API,但这就不是本地部署了。
我实测下来,1B 到 3B 的蒸馏模型,在普通游戏本上跑,速度可以接受,精度也够用。但如果你要做复杂推理,比如数学题、代码生成,还是得上大模型。
实操心得:本地部署大模型,显存是瓶颈。8G 显存最多跑 3B 的 INT8 模型,4G 显存只能跑 1B。买电脑的时候,显存比 CPU 重要。
7. 我个人的一些体会
做蒸馏项目这几年,我最大的体会是:技术本身没有对错,关键看你怎么用。蒸馏能让小模型变强,能让 AI 落地到更多场景,这是好事。但如果用来抄别人的成果,那就变味了。
行业里现在对蒸馏的争议,本质上是规则没跟上技术。大模型厂商想保护自己的投入,开发者想用更低的成本做出产品,两边都有道理。作为一线开发者,我的建议是:尽量用开源模型做老师,保留好自己的训练证据,别碰闭源 API 的灰色地带。这样既能做出东西,又不会惹麻烦。
另外,蒸馏不是终点。模型压缩、量化、剪枝、推理优化,这些技术都要会一点。只靠蒸馏,解决不了所有问题。我见过太多团队,蒸馏做完就以为万事大吉,结果部署的时候发现推理速度还是不够,又回头做量化,浪费了很多时间。
最后分享一个小技巧:蒸馏的时候,把老师模型的输出保存下来,别每次训练都重新跑老师。老师模型跑一遍很慢,保存下来能省很多时间。我一般会把老师输出存成.pt文件,训练的时候直接加载,速度能快 10 倍以上。
这个方向后续还可以扩展的地方很多,比如多模态蒸馏、跨语言蒸馏、联邦蒸馏,都是现在比较热的研究方向。如果你在做类似的项目,欢迎交流。