借助DeepSeek之东风扬帆AI海洋(六):体验蒸馏:如何蒸馏一个自己的DeepSeek-R1大模型

36 阅读17分钟

# 借助DeepSeek之东风扬帆AI海洋(五):微调DeepSeek-R1大模型

什么是蒸馏

其实蒸馏呢,本质上也是微调的一种类型。传统微调是为了让大模型获取一些私域知识,比如股票、医疗等等,这是让大模型的知识面增加了,但没有改变大模型的能力。而蒸馏不一样,蒸馏不光教知识,还要教能力。所谓授之以鱼,不如授之以渔,蒸馏就是要让被训练的模型能够学会教师模型的能力。

传统的一些快速响应模型,比如 qwen2.5、llama3 等等模型是不带思维链的。但 DeepSeek-R1 模型带有思维链,而且思考能力很强。因此对 DeepSeek-R1 蒸馏的意义就是要让 qwen2.5 等模型也学会思维链,就是这么简单。

蒸馏的流程

首先,需要准备好一份传统的数据集,比如一些新闻标题的数据。

CF生存特训:落地“加特林”,还剩一滴血,千钧一发,队友来救!
崔海涛篆刻艺术展将于5月12日在青岛举行
球员生涯还剩一个月,吴伟超回忆申花邀请:曾以为是个玩笑

之后我们需要将这些数据喂给满血版的 DeepSeek-R1:671B 模型,让 DeepSeek-R1:671B 为我们输出带有思考过程和结果的回答,这便是我们的教学数据。那 DeepSeek-R1:671B 输出的回答的实际格式是什么样的呢?我们在之前的模型部署课程上,曾经用 curl 命令对 DeepSeek-R1 模型进行过提问,模型给出的回复格式是:

{"id":"chat-7b3532b3c7e3481b86104f17f744f26c","object":"chat.completion","created":1740925630,"model":"deepseek-r1","choices":[{"index":0,"message":{"role":"assistant","
content":"<think>我 是 DeepSeek-R1, 一 个 由 深 度 求 索 公 司 开 发 的 智 能 助 手 , 我 擅 长 通 过 思 考 来 帮 您 解 答 复 杂 的 数 学 , 代 码 和 逻 辑 推 理 等 理 工 类 问 题 。 \n</think>\n\n我 是 DeepSeek-R1, 一 个 由 深 度 求  
索 公 司 开 发 的 智 能 助 手 , 我 擅 长 通 过 思 考 来 帮 您 解 答 复 杂 的 数 学 , 代 码 和 逻 辑 推 理 等 理 工 类 问 题 。 ","tool_calls":[]},"logprobs":null,"finish_reason":"stop","stop_reason":null}],"usa
ge":{"prompt_tokens":9,"total_tokens":108,"completion_tokens":99},"prompt_logprobs":null}

也就是说是如下形式:

<think>思考过程 \n</think>\n\n结果

当我们拿这些教学数据再去微调小模型时,就可以让小模型学会 DeepSeek-R1:671B 同款的输出回答的模式,这种微调方式叫做监督微调。 示例转存失败,建议直接上传图片文件 到此,蒸馏的流程就讲完了,整个过程其实非常简单,一点也不神秘,无非就是教学数据的生成与传统微调的教学数据相比,增加了思考过程。

那讲完了理论,接下来我们就开始实践。我们以 qwen2.5-7B 模型为例,蒸馏出一个我们自己的 DeepSeek-R1-Distill-Qwen2.5-7B 模型。

如何生成教学数据

首先从生成教学数据开始。

前面我们已经知道想要,生成用于蒸馏小模型的教学数据,需要将原始数据喂给 DeepSeek-R1 大模型,从而生成带有高格调思考过程的问答对。

因此教学数据的模型,本质上还是 prompt 工程的范畴,需要我们根据实际业务情况编写合适的 prompt,这个每一种业务都不一样,无法形成一个标准的模板,但我们可以用新闻分类器的例子演示一下思路。

由于 DS 官方的服务器最近不是特别稳定,我依然使用的阿里云百炼提供的满血版 DeepSeek-R1 671B 服务。我准备的原始数据集的样式如下:

新闻分类:《美国队长4》被调侃为《关云长4:周仓传》
新闻分类:特朗普与泽连斯基在白宫举行会谈时爆发激烈争吵
....

之后我的提示词这样写:

system = """
你是一个新闻分类器,擅长根据新闻标题识别新闻的类型,新闻种类包括:政治、经济、科技、娱乐、体育、教育、健康、国际、国内、社会。用户会在需要进行分类的新闻标题前加入"新闻分类:"字样,你需要给出该新闻的种类。要求包含思考过程和最终答案。


#要求格式:
<think>
思考过程(分步骤解释如何从给定信息中推导出答案)
</think>


答案(政治、经济、科技、娱乐、体育、教育、健康、国际、国内、社会中的某一种)


#示例1:
human: 新闻分类:给力!中国两次出手,美到手的订单黄了,该国从此只认中国制造!
gpt:
<think>
首先,我需要分析给出的新闻标题:“给力!中国两次出手,美到手的订单黄了,该国从此只认中国制造!”
接下来,根据标题内容进行分类。标题中提到了中国两次行动导致美国订单出现问题,并且其他国家开始依赖中国制造,说明这涉及国家之间的经济合作和社会影响。
结合新闻种类,考虑到涉及国际贸易和经济合作,最合适的分类是“经济”。所以,这条新闻应该归类到“经济”类别中。
</think>


经济
"""

这份提示词首先描述了需求,规定了新闻的分类有哪些种,还告诉了大模型当用户输入什么格式的内容时,大模型需要进行分类。之后的要求格式的样式,大家可以当作模板,然后根据自己的实际业务修改答案部分的描述。

比较难的部分在于示例,这块就是我给很多同学回复留言时说的,要懂业务,如果不懂业务,提示词都写不出来。这是因为我们需要写一个 think 的例子,例子要逼真,符合 DS-R1 的口吻,这样才能得到好的效果。

之后就是常规的向大模型提问,以及反馈答案的代码了。我们就以“新闻分类:《美国队长 4》被调侃为《关云长 4:周仓传》”为例,进行测试。

completion = client.chat.completions.create(
    model="deepseek-r1",
    messages=[
        {'role': 'system', 'content': system},
        {'role': 'user', 'content': '新闻分类:《美国队长4》被调侃为《关云长4:周仓传》'},
    ]
)


# 通过reasoning_content字段打印思考过程
print("<think>")
print(completion.choices[0].message.reasoning_content)
print("</think>")


# 通过content字段打印最终答案
print(completion.choices[0].message.content)


输出为:

<think>
首先,我需要分析用户提供的新闻标题:“《美国队长4》被调侃为《关云长4:周仓传》”。标题中提到了《美国队长4》,这是一部著名的超级英雄电影,属于娱乐产业的内容。此外,标题提到该电影被调侃为《关云长4:周仓传》,后者可能涉及对中国传统故事或影视作品的戏仿,这种调侃通常出现在娱乐新闻
中,用于吸引观众注意或制造话题。


接下来,考虑新闻分类的选项:政治、经济、科技、娱乐、体育、教育、健康、国际、国内、社会。这里的关键词是电影名称和调侃,明显与影视娱乐相关,没有涉及其他如经济、政治或科技的元素。因此,最合适的分类应为“娱乐”。
</think>




娱乐

我们就生成了第一条教学数据,我们只需要再将这条教学数据包装成问答对的形式即可。格式如下:

[
    {
        "conversations": [
            {
                "from": "human",
                "value": "新闻分类:《美国队长4》被调侃为《关云长4:周仓传》"
            },
            {
                "from": "gpt",
                "value": "<think>\n首先,我需要分析用户提供的新闻标题:“《美国队长4》被调侃为《关云长4:周仓传》”。标题中提到了《美国队长4》,这是一部著名的超级英雄电影,属于娱乐产业的内容。此外,标题提到该电影被调侃为《关云长4:周仓传》,后者可能涉及对中国传统故事或影视作品的戏仿,这种调侃通常出现在娱乐新闻中,用于吸引观众注意或制造话题。\n\n接下来,考虑新闻分类的选项:政治、经济、科技、娱乐、体育、教育、健康、国际、国内、社会。这里的关键词是电影名称和调侃,明显与影视娱乐相关,没有涉及其他如经济、政治或科技的元素。因此,最合适的分类应为“娱乐”\n</think>\n\n娱乐"
            }
        ]
    }
]

接下来的事情就是苦力活了,你可以写一个循环执行的程序,有多少条原始数据就执行多少次以上步骤。所以这个过程是很慢的,建议最好多开几个账号并行执行。

微调与测试

什么是微调?

所谓微调,就是对模型进行微微的调整。我举一个例子,我们每个人都接受过九年义务教育和三年高考五年模拟等海量“填鸭式”知识训练。但等到高中毕业以后,就会发现这些天文地理历史政治太过通用,不足以让我们在社会上混饭吃。于是就需要针对某一项技能,进行专业训练。比如有的人学了计算机;有的人学了金融;有的人去了蓝翔,学了挖掘机等等,都是为了应对某项细分工作。

模型微调就是这样,厂商训练出的原始模型是要符合大众化口味的,但对于垂直领域的专业知识,是不可能会的。因此为了让大模型为我所用,我们需要用一些专业数据,比如口腔科的病例数据等等对模型进行二次训练,让模型具备我们想要的能力。口腔科的病例数据再多,可能也就几万份数据,毕竟病人虽多,但重复的病例也多。而模型初始训练时的数据量都是几亿、几十亿、几百亿级别的。因此数据量是微调的“微”的直观体现。

如何微调?

微调通常有三种方式,第一种是模型供应商提供了商业模型的在线微调能力,比如 OpenAI 的 GPT 3.5 等模型就支持在线微调。这种模式是基于商业大模型的微调,因此微调后模型还是商业大模型,我们去使用时依然要按 token 付费。

第二种是云厂商做的一些模型在线部署、微调平台。比如阿里云的 PAI 平台的模型广场功能,就具备模型的部署和训练功能。这种模式我们只需要租用云厂商的 GPU 算力即可。这些模型部署训练功能都是云厂商为了卖卡而推出的增值服务。

第三种就是如果你或你的公司手里有足够的卡,希望完全本地私有化部署和微调,此时就可以使用一些开源方案,部署一个微调平台来进行模型微调。

LLama-factory

LLama-factory 是一款整合了主流的各种高效训练微调技术,适配市场主流开源模型,而形成的一个功能丰富、适配性好的训练框架。LLama-factory 提供了多个高层次抽象的调用接口,包含多阶段训练、推理测试、benchmark 评测、API Server 等,使开发者开箱即用。同时提供了基于 gradio 的网页版工作台,方便初学者迅速上手操作,开发出自己的第一个模型。

实践:微调 DeepSeek-R1:7B

环境准备

实验环境:

模型文件与数据集准备

LLama-factory 支持选择待微调的模型后,自动下载模型,但是我们在 ModelScope 下载模型时,经常会出现文件下载不全或者下载中断的情况,因此 LLama-factory 官方建议自己手动进行下载。下载命令为:

pip install modelscope

modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --cache_dir /root/ms_cache

模型会下载到 /root/ms_cache/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B/ 目录。

数据集你可以使用如下命令进行下载:

wget https://atp-modelzoo-sh.oss-cn-shanghai.aliyuncs.com/release/llama_factory/data_news_300.zip

下载完成后解压,会得到三个文件,分别是 dataset_info.json、train.json 以及 eval.json。其中 dataset_info.json 是目录文件,LLama-factory 需要通过读取该文件,才能知道有哪些可用的数据集。train.json 是训练数据集,内置了包含 300 组对话样本,是根据新闻分类器的要求准备的,要求有两条:

  1. 输入为新闻标题,输出为新闻所属的种类,不需要额外解释,但保留思维链的内容;并且在输入的新闻标题前加上“新闻分类:”等类似标签来标明询问目的。
  2. 新闻种类包括:政治、经济、科技、娱乐、体育、教育、健康、国际、国内、社会。

例如:

[
    {
        "conversations": [
            {
                "from": "human",
                "value": "新闻分类:给力!中国两次出手,美到手的订单黄了,该国从此只认中国制造!"
            },
            {
                "from": "gpt",
                "value": "<think>\n首先,我需要分析给出的新闻标题:“给力!中国两次出手,美到手的订单黄了,该国从此只认中国制造!”\n\n接下来,根据标题内容进行分类。标题中提到了中国两次行动导致美国订单出现问题,并且其他国家开始依赖中国制造,说明这涉及国家之间的经济合作和社会影响。\n\n结合新闻种类,考虑到涉及国际贸易和经济合作,最合适的分类是“经济”。所以,这条新闻应该归类到“经济”类别中。\n</think>\n\n经济"
            }
        ]
    }
]

eval.json 是评估数据集,相当于测试用例,LLama-factory 会通过自动执行测试用例来评估训练的效果。

将这三个文件,放到 /root/data 目录下。

编译运行 LLama-factory

由于运行 LLama-factory 需要依赖 pytorch 等一堆 python 包。我不想每次更换服务器环境都重新安装一遍,因此我选择使用 docker 的方式。但是 LLama-factory 官方没有提供编译好的 docker 镜像,所以我们需要将源码下载下来,自行编译成镜像。

源码的下载地址如链接所示:Release v0.9.1: Many Vision Models, Qwen2.5 Coder, Gradient Fix · hiyouga/LLaMA-Factory,下载到服务器后解压。

在代码中有两个特别重要的目录,第一个是 data,里面存放的是官方为我们准备的测试数据集,新手使用这些数据集,特别容易上手。第二个是 docker 文件夹,里面为我们准备了不同架构平台的 Dockerfile,有了 Dockerfile,我们就可以直接将源码编译成镜像。

因为使用的是 NVIDIA 的卡,所以可以在代码的根目录执行如下命令进行编译:

docker build -f ./docker/docker-cuda/Dockerfile \
    --build-arg INSTALL_BNB=false \
    --build-arg INSTALL_VLLM=false \
    --build-arg INSTALL_DEEPSPEED=false \
    --build-arg INSTALL_FLASHATTN=false \
    --build-arg PIP_INDEX=https://pypi.org/simple \
    -t llamafactory:v0.9.1 .

镜像编译完成后,可以执行如下命令,运行镜像:

docker run -dit --gpus=all \
    -v /root/ms_cache/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B/:/root/.cache/modelscope/hub/models/DeepSeek-R1-Distill-Qwen-7B \
    -v /root/data:/app/modeldata/data \
    -v /root/config:/app/config \
    -v /root/saves:/app/saves \
    -v /root/output:/app/output \
    -p 7860:7860 \
    -p 8000:8000 \
    --name llamafactory \
    llamafactory:v0.9.1

7860 端口是 webui 的访问端口,8000 是 API 访问端口。

之后我们进入到 docker 容器内部,执行下面的命令,启动 llama-factory 的 webui。

llamafactory-cli webui

之后在浏览器输入 < 公网 ip>:7860,就可以进入到 WebUI。

WebUI 功能介绍

模型微调的控制台默认语言是英文,可以点击 Language 下拉框改为中文显示。在 Model name 页面可以点击下拉框选择模型,在 Model path 页面可以输入模型的路径。

控制台提供了三种微调方法,分别是 full、freeze 和 lora。

  • Full 是全参数微调,意思是把模型所有的权重都放开,用新数据从头到尾训练一遍。这种方法训练效果好,但是所需资源量非常大。
  • Freeze 是冻结微调,意思是冻结模型的大部分底层参数,比如前 80% 的层,只训练顶部的几层。这种方法使用的数据量比较少,适合微调简单的任务,比如简单的文本分类等等。
  • Lora 中文叫低秩自适应,其原理是不修改原模型参数,而是插入低秩矩阵(理解为轻量级的适配层),只训练这些新增的小参数。优点是超级省资源,且效果很好,解决 Full 微调太耗费资源的问题。但对于用户的要求较高,需要充分理解每个参数的含义和效果。

控制台上,还支持设置 QLoRA 的量化等级等参数。

QLoRA 是 LoRA 的改进版本,Q 的意思是量化。QLoRA 的原理是先把模型进行量化,然后再进行 LoRA 微调。这样的好处在于能进一步节省显存消耗。比如我在一张 16G 的 T4 卡上使用 LoRA 微调 7B 模型,会报显存不足的错误,但如果使用 QLoRA,便可以进行微调。

控制台的四大核心功能,分别是训练(微调)、评估 & 优化、对话和导出。训练包含了我们进行模型微调时的一些步骤,包括数据集的选择、参数的设置等等。参数的设置,需要用户根据微调任务的实际情况自行配置。此外控制台还支持保存训练参数到指定目录。最后点击开始按钮,可以一键开始微调任务。微调结束后产生的检查点文件,也会自动保存。

评估页面会使用评估工具对模型性能进行评估。

对话页面可以直接加载原始模型或者微调过后的模型,之后通过与模型进行对话,来人为地检验模型的效果。

最后一个页面是导出。意思是将对话页面加载的模型输出成模型文件,这里通常有两种使用场景:

  1. 用户希望把原始模型转换成量化模型。此时可以通过在 chat 页面加载原始模型,然后在导出页面设置导出量化等级来完成。
  2. 用户希望把微调过后的模型导出。此时就可以通过在 chat 页面加载微调后的模型,然后在导出页面导出。

开始实践

首先加载模型,测试一下原始的 DeepSeek-R1:7B 模型的新闻分类效果。

对话模板选择 DeepSeek3,然后切换到 Chat 页面,点击加载模型。

输入一个 train.json 中的问题,测试一下效果:

可以看到 7B 并没有进行分类。所以接下来我们就进行微调,微调过后再来重新测试。

首先如下图所示,选择数据集,并设置学习率为 5e-6,梯度累积为 2,有利于模型拟合。

之后进行 LoRA 参数设置,我设置 LoRA+ 学习率比例为 16,LoRA+ 被证明是比 LoRA 学习效果更好的算法。在 LoRA 作用模块填 all,意思是将 LoRA 层挂载到模型的所有线性层上,提高拟合效果。

设置完成后,点击开始,就会开始训练。下方会输出训练日志以及进度。

借助DeepSeek之东风扬帆AI海洋(七):模型上下文协议MCP