利用云端 GPU 动手实践:从零开始蒸馏小模型
随着大语言模型(LLM)的不断发展,如何高效地将大模型的能力“压缩”进小模型成为一个热门课题。知识蒸馏(Knowledge Distillation) 正是实现这一目标的重要手段。它允许我们在有限资源(如单卡 GPU)下,让小模型学习大模型的知识,从而达到在推理速度和部署成本上的双赢。
DeepSeek 团队基于 800k 高质量样本对 Qwen/Llama 等小模型进行蒸馏(论文链接),验证了几个关键结论:
- 蒸馏有效迁移推理能力至小模型,蒸馏模型在多项推理任务中表现出色。
- 蒸馏比强化学习更高效。在相同资 源下,蒸馏方法比直接对小模型进行强化学习训练更能提高性能,其训练成本更低。
本篇博客将带你从零开始,基于开源工具和公有云 GPU 的算力资源,动手实践一个完整的知识蒸馏流程。我们以 DeepSeek-R1
模型生成的回答作为教师知识,训练微软开源的小模型 phi-3-mini-4k-instruct
来模仿它的行为。
本文实践环节是基于 How to distill Deepseek-R1: A Comprehensive Guide 介绍如何在 Spader.AI 上提供的一张 A100 40G 单卡的全训练过程,完整的代码可以从 GitHub 或 Gitee 上下载。
一、准备数据:从指令到对话格式
我们使用由 DeepSeek-R1 生成的推理问答数据集 Magpie-Reasoning-V2-250K-CoT-Deepseek-R1-Llama-70B,其特点是涵盖了 Chain-of-Thought(思维链)风格的详细解题过程,非常适合用于训练小模型学习复杂推理结构。这个数据集包含大量由 Deepseek-R1(基于Llama-70B)模型生成的指令-回答对,以及详细的链式思考过程(Chain-of-Thought)。我们通过 load_dataset
加载其中一部分数据,并对格式进行转换:
from datasets import load_dataset
# 加载由教师模型生成的 Q&A 数据集(取训练集的前20%以加快演示)
dataset = load_dataset(
"Magpie-Align/Magpie-Reasoning-V2-250K-CoT-Deepseek-R1-Llama-70B",
split="train[:20%]"
token="<你的HuggingFace访问令牌>"
)
接着,我们将其统一格式化为对话形式(添加特殊标记):
def format_instruction(example):
return {
"text": (
"<|user|>\n"
f"{example['instruction']}\n"
"<|end|>\n"
"<|assistant|>\n"
f"{example['response']}\n"
"<|end|>"
)
}
formatted_dataset = dataset.map(format_instruction, batched=False, remove_columns=dataset.column_names)
formatted_dataset = formatted_dataset.train_test_split(test_size=0.1) # 划分90%训练、10%验证
上述代码将每条数据中的 instruction
和 response
字段拼接成一种对话格式:以 <|user|>
开头表示用户提问,<|assistant|>
表示模型回答,每段内容以 <|end|>
结束。这种格式便于让模型将用户提问作为上下文,并学习生成相应的回答。我们仅使用数据集的一部分(20%)进行训练,并留出其中的 10% 作为验证集,以便评估模型在未见过的数据上的表现。
二、加载学生模型与分词器
我们选用的是 phi-3-mini-4k-instruct
作为学生模型,它性能轻量、指令对齐能力较好,适合快速蒸馏实践。
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
model_id = "microsoft/phi-3-mini-4k-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
# 添加特殊标记用于链式思考的标记符
CUSTOM_TOKENS = ["<think>", "</think>"]
tokenizer.add_special_tokens({"additional_special_tokens": CUSTOM_TOKENS})
tokenizer.pad_token = tokenizer.eos_token
# 加载学生模型(因模型含自定义代码,需 trust_remote_code)
model = AutoModelForCausalLM.from_pretrained(
model_id,
trust_remote_code=True,
device_map="auto", # 自动将模型加载到 GPU
torch_dtype=torch.float16, # 使用 16 位精度减少显存占用
attn_implementation="flash_attention_2" # 使用 Flash Attention 2 加速
)
model.resize_token_embeddings(len(tokenizer)) # 模型词表扩充以适配新添加的特殊标记
代码解析:我们首先加载了分词器,并增添特殊标记 <think>
和 </think>
用于表示思考过程的起止。这是因为教师模型的回答中包含了思考步骤,我们希望学生模型也能学习这种链式推理格式。添加特殊标记后,需要调用 resize_token_embeddings
调整模型的词表大小,否则模型无法识别新标记。接着,用 AutoModelForCausalLM.from_pretrained
加载学生模型权重,并使用 device_map="auto"
将模型自动加载到可用 GPU 上(如果租用的是单卡 GPU,则模型会加载到该卡上)。torch_dtype=torch.float16
表示采用半精度浮点数,加速推理和训练。这里还启用了 flash_attention_2
来优化注意力计算(需要硬件支持),进一步提升训练效率。
说明:在知识蒸馏中,我们通常不需要加载教师模型的权重,因为我们使用的是教师模型预先生成的“答案”作为训练目标。这避免了在云端加载一个超大模型(70B 参数或更大)的开销,仅需要较小的学生模型即可完成训练。
三、配置 LoRA:高效微调的关键
由于学生模型(Phi-3)本身仍有 3.8B 参数,直接训练所有参数需要较大的 GPU 显存和计算。我们引入 LoRA (Low-Rank Adaptation) 技术对模型进行高效微调,只训练很小一部分参数,以降低资源占用。LoRA 通过为模型的权重增加低秩矩阵的偏置来进行训练,冻结原始权重不变。以下代码配置了 LoRA 的超参数:
from peft import LoraConfig
peft_config = LoraConfig(
r=8, # 低秩矩阵的秩 (rank)
lora_alpha=16, # LoRA的缩放系数
lora_dropout=0.2, # LoRA层的Dropout概率
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"], # 应用LoRA的目标层
bias="none",
task_type="CAUSAL_LM"
)
这里我们将 LoRA 的秩设为 8,表示每个被微调的权重矩阵将增加两个维度为 (原维度, 8) 和 (8, 原维度) 的低秩矩阵;lora_alpha=16
是缩 放因子,会在应用到模型时乘到 LoRA 的增量上;lora_dropout=0.2
则在训练中对 LoRA 分支施加20%的dropout以增强泛化。target_modules
指定只对模型中 Q、K、V、O 投影层应用 LoRA(这些是 Transformer 自注意力机制中的关键矩阵,参数量大且对模型输出影响大)。通过这样的配置,模型大部分参数保持冻结不动,仅有少量新引入的参数参与训练,使得在云端 GPU 上以较小显存开销即可完成模型微调。
提示:LoRA 大幅减少了训练所需的参数量。例如 Phi-3 模型原本有 3.8B 参数,引入 LoRA 后实际训练的参数仅占不到 1%,这意味着显存占用和计算量也大幅降低,非常适合在租用的单 GPU 环境中运行长时间训练。
四、数据预处理与整理
使用 DataCollatorForLanguageModeling
自动处理 padding 和标签构建。
from transformers import DataCollatorForLanguageModeling
# 数据整理器:为因果语言建模准备批次数据(不使用掩码语言模型)
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False,
pad_to_multiple_of=8 # 将长度填充对齐到8的倍数,提高 GPU 利用率
)