黑盒蒸馏(技巧蒸馏) 黑盒蒸馏所用到的仅仅是教师模型的回答(有时也包括输出的概率分布,即软目标,但是不会用到logits)。 黑盒蒸馏意味着教师模型的输出是我们唯一能获取到的训练资源,因此,黑盒蒸馏整体的思路可以分成两步: 从教师模型收集问答数据 使用这些数据对学生模型进行微调 这份教程会详细介绍三种涌现能力的主流蒸馏方式以及改进方向。 值得一提的是,黑盒蒸馏和监督微调很相似,所以了解监督微调的概念将有利于本节的学习。不过不了解也没关系,只要知道一般模型训练的概念(损失函数、梯度下降等)也足够。 In-context learning 蒸馏 大模型的一大特有的能力是上下文学习(In-context learning, ICL),即在推理阶段进行少样本学习。
黑盒蒸馏所用到的仅仅是教师模型的回答(有时也包括输出的概率分布,即软目标,但是不会用到logits)。
黑盒蒸馏意味着教师模型的输出是我们唯一能获取到的训练资源,因此,黑盒蒸馏整体的思路可以分成两步:
这份教程会详细介绍三种涌现能力的主流蒸馏方式以及改进方向。
值得一提的是,黑盒蒸馏和监督微调很相似,所以了解监督微调的概念将有利于本节的学习。不过不了解也没关系,只要知道一般模型训练的概念(损失函数、梯度下降等)也足够。
大模型的一大特有的能力是上下文学习(In-context learning, ICL),即在推理阶段进行少样本学习。
上下文学习的能力是一种类似于“举一反三”的能力,我们可以在Prompt中先给出几个问-答示例,接着给出一个问题,
模型就能按照示例的格式和模式输出回答。
以下是一个简单的ICL例子:

模型成功模仿了示例中的答题思路和答题格式。
然而,由于算力限制,不可能将具有ICL能力的大模型部署到实时推理系统上。
有没有一种技术能够将大模型的ICL能力迁移到一个小模型中呢?这就是ICL蒸馏要解决的问题。
首先,让我们对ICL做一个形式化的定义:ICL是指输入给模型以下格式的内容:
x_1, y_1; x_2, y_2; x_3,
模型会输出:
y_3
其中x表示问题,y表示回答。x,y是成对的。
示例的格式不一定在训练时见过,但是模型可以在推理时模仿其中的格式和模式。
即只要在prompt前加几个例子,模型就能学到其中的格式和逻辑,从而不用更新参数就能学习。
训练之前,我们会收集如下含有提示词和标签的数据:

这一部分将介绍一系列的用于ICL微调的损失函数,需要读者具备条件概率、熵和交叉熵相关的知识。
一句话概况,ICL微调就是让模型的输出y_3的概率尽量大。即在给定模型参数\theta, 示例1和2, 以及x_3时, 模型输出 y_3 的概率 p(y_3|x_3, (x_1, y_1, x_2, y_2), \theta) 越大越好。
为了计算稳定,可以转变为对数概率,即log \space p(y_3|x_3, (x_1, y_1, x_2, y_2), \theta) 越大越好. 损失函数一般是求最小化,所以再添加一个负号:
上面这个例子有两个示例问答对,所以可以叫做 two-shot, 实际中也可以使用 zero-shot, one-shot, ...。
假设示例问答对的数量是k, k-shot 的示例问答我们可以记作 S_k,正式的问题记作 x,期望的回答记作 y,最终的损失函数可以写成
以上是一条微调数据的损失,一般微调需要几千到上万条数据,每一条数据都有各自的x, y, S^x_k,其中S^x_k代表对应于问题 x 的示例前缀。
所以微调时总的损失如下
这就是ICL微调的损失函数,但只是针对一种任务类型的。
一般我们会针对同一个类型的任务收集许多数据微调,所以一个任务\mathcal{T}会对应一个数据集 \mathcal{D_T}, 所以针对任务\mathcal{T}微调的损失函数如下:
上面我们讲了针对任务\mathcal{T}进行ICL微调的损失函数,而实际中往往在多个任务上微调,以获得更好的泛化性能。
因此,对多个任务微调的总损失如下:
其中\mathcal{T_{train}}可以是\mathcal{T_{meta}}或\mathcal{T_{target}}。 它们有什么区别呢?
如果使用\mathcal{T_{target}},训练时的任务就是我们的目标任务;
而使用Meta-ICL,则是训练时避免使用目标任务,希望模型能通过学习其他任务学习到“学习方法”,即学习上下文的能力,然后在我们的目标任务上应用它所学到的学习能力。了解Meta-ICL Tuning 和 MultiTask-ICL Tuning的损失函数将有利于理解ICL蒸馏的损失函数。
ICL蒸馏同时使用软目标和硬目标作为损失函数:
让我们分别认识软目标和硬目标。
其中第一项是学生和老师的交叉熵损失:
其中c是数据中的<lable>。后面的 概率*log概率 是计算针对某个可能的Label的交叉熵。
第二项是语言建模损失:
这里的训练数据就是一般的网络文本:
除了学习老师的经验,学生还要学习ground-truth,这就是硬损失的意义。
参见code/ICL。
模型上下文学习的性能和上下文中的例子质量紧密相关,所以有人研究专门设计了一个例子检索器,检索高质量的示例[6]。
我们知道大模型的主流训练范式有三步,第一步是预训练,第二步是指令微调,第三步是RLHF。指令跟随的能力是在第二步获得的。
仅仅经过预训练的大模型没有指令微调的能力,它只能续写用户prompt。经过指令微调的模型具有执行prompt中的指令的能力,而不是续写用户的prompt。
指令微调数据包含指令、输入、输出,但是指令和输入没有明确界限,把输入当成指令也可以, 而输入部分可以是空的。比如这是一份指令微调数据. Self-Instruct生成的数据输入部分也可能是空的。
如何将指令跟随能力蒸馏到小模型中呢?容易想到的思路是从大模型收集若干<指令-输入-输出>数据,然后用这些数据来微调小模型。
那么如何从大模型收集<指令-输入-输出>数据呢?有没有成本低,效率高的方法?答案是Self-Instruct。

要使用Self-Instruct生成数据,我们只需要推理一个经过预训练的大模型(不要经过指令微调哦)就可以了。这是因为预训练的大预言模型具有续写的能力,利用这个能力可以收集新的数据。
首先,人工设计若干任务,针对每个任务,撰写一个初始数据。这形成了初始的数据池。
然后,随机从数据池中抽取8条指令,让大模型续写更多的指令。可以使用如下的模板:
生成指令,直到遇到“Task 16”字符串或达到最大生成字数或模型自动停止生成。
生成指令之后,下一步,就应该生成输入和输出了。但是在此之前,先让大模型自己判断这个生成的每一个指令是否是分类问题。这里还是利用了续写能力。
对于是分类问题的任务,先生成输出,在基于输出生成输入;而对于非分类问题,先生成输入,基于输入生成输出。

参见 code/InsructFollowing.
对抗蒸馏(adversarial distillation)提出除了可以让知识单向地从教师注入学生,学生也可以产生“反馈”,
即通过对比学生的回答和老师的回答,老师可以发现困难的知识,并加生成更多困难知识的供学生学习。具体的细节可以查看参考文献[7]。
和之前说的一样,我们可以从大模型收集<问题-回答>对,然后用这些数据来微调小模型。
但是这时数据收集要注意数据的质量。回答中要包含正确的推理,并且最好是对于一个问题有多样的推理路径。
以下是一个训练gpt2进行时间推理的例子。
从教师模型收集推理样本,作为训练数据。在问题后面,我们加上一句"Let's think step by step."
USER: Q: <Question> A: Let's think step by step.
ROBOT: <Reasoning>
USER: Q: A: Let's think step by step. <Reasoning>\nTherefore, the answer is
ROBOT: <Answer>
其中第三行的针对一个问题多次运行后可能有不同的推理路径。
我们把以上收集的数据整理成某种格式化的数据,让他们具有统一的格式,方便训练。比如可以使用如下的格式:
input_format = "<Question> ###" label_format = "<Reasoning> --> <Answer>"
正式的微调Loss函数是交叉熵损失。
参见code/CoT.
实际上,除了以上三种涌现能力的蒸馏,只要是从教授模型收集某种类型的数据,然后用这些数据微调学生模型,都是黑盒蒸馏的应用范围。
因此,对于一些特定领域和特定需求的任务,也可以使用类似的方法达到希望的效果。比如随着OpenAI 强大的复杂推理模型O1的发布,对推理能力进行蒸馏也可以套用上面的方法。近期上海交通大学的O1复现论文就是一个很好的对教师模型的推理能力进行蒸馏的例子。
但是也有研究[5]指出黑盒蒸馏导致仅模仿但不理解的问题,要提高学习质量,还需学生有良好的天赋(base 模型的能力)。