1.1 大模型核心专题:DeepSeek-R1 思维链蒸馏全流程实战

1.1 大模型核心专题:DeepSeek-R1 思维链蒸馏全流程实战

本专题围绕一个完整项目展开:将 DeepSeek-R1 的思维链(Chain-of-Thought)特性蒸馏到一个小体量模型中,全流程覆盖五个阶段:

阶段 核心内容 关键词
项目介绍 背景约束与目标 3B 模型、单卡 4090、三天 baseline
大模型蒸馏 压缩思路与蒸馏方式 裁剪、量化、蒸馏;软标签、中间层特征、硬标签
组织数据集 语料构建与批量生产 生成问题、生成答案、生产者消费者
模型训练 LoRA 微调与工具 LoRA、LLaMA-Factory、关键参数
模型高效部署 vllm 与性能优化 KV-Cache、PageAttention、prompt 优化

一、项目背景与目标

DeepSeek-R1 的 671B 版本思维链模式在解决复杂问题上表现出色。本项目引入模型蒸馏,让小体量模型学会像 DeepSeek 一样思考。以后遇到新模型具有某种优良特性、或精通某个领域知识,都可采用类似方式处理。

公司希望把 DeepSeek 的思维链特性应用到现有服务,但业务上无法支持部署全量版 DeepSeek,根据 QPS 预估只能支持 3B 级别模型。约束:无现成数据集、无人协助、仅一张 4090 显卡、要求三天出 baseline。

蒸馏效果对比(同一问题测试):

  • 训练前:直接给出结论性回答,平铺直叙,缺乏推理过程
  • 训练后:先输出 <think>...</think> 思考过程(分析意图、逐层推理),再给出结构化结论——先思考、再作答,正是思维链特性的体现

二、大模型压缩与蒸馏原理

2.1 大模型应用成本估算

以 DeepSeek-R1-671B(6710 亿参数)为例,估算部署成本:

  • 假设每个参数占用 4 字节(float32),总内存需求约为 2.684 TB
  • 单块 NVIDIA A100/H100 80GB 约存 20B 参数(80GB ÷ 4 字节 ≈ 20B),最低需 671B ÷ 20B ≈ 34 块 GPU
  • 京东 A100 80G 报价约 17 万/块,仅存模型就需 17万 × 34 = 578 万
  • 推理还需为 KV-Cache 和临时变量预留空间,实际约需 38 块,总报价 17万 × 38 = 646 万

可见全量部署成本极高,必须通过模型压缩降低成本。

2.2 模型压缩的三种思路

模型裁剪——减少参数总量,分两类:

  • 非结构化裁剪:基于权重将不重要参数置 0(训练后权重整体符合正态分布,可据此判断参数重要性)
  • 结构化裁剪:基于模型特定结构裁剪
优点 缺点
显著减少参数量和计算量,加快推理速度 结构化裁剪可控性较差
结构化剪枝后可适配轻量化设备 需要重训或调优
非结构化裁剪需要专用库支持

模型量化——减少每个参数的大小(高精度数字转化为低精度数字)。分类:按时间点(训练后量化 PTQ、量化感知训练 QLoRA)、按粒度(统一/混合如 ViT)、按对称性(对称/非对称)。

优点 缺点
减小模型体积和显存占用 有损压缩,必然有精度损失(可用混合精度和量化感知训练缓解)
加快推理速度 并非所有硬件都支持
泛化能力下降,对噪声敏感度提升

模型蒸馏——将复杂模型(教师模型)的知识迁移到更简单高效的模型(学生模型)中的技术。核心思想是通过模仿教师模型的输出来训练学生模型,使其在保持较小规模的同时尽可能接近教师模型的性能。

基本步骤(三步)

  1. 训练教师模型:在大规模数据上训练一个高性能但复杂的教师模型
  2. 知识迁移:用教师模型对训练数据进行推理,生成可训练的数据,输出可以是中间层特征、软标签或硬标签
  3. 训练学生模型:通过学习教师模型生成的数据,使学生模型预测结果接近教师模型
优点 缺点
轻量化,参数显著压缩(BERT-base 110M→DistilBERT 66M 减 40%;TinyBERT 14.5M 仅保留 13%) 不可能超过教师模型
推理加速 2-5 倍(TinyBERT 手机 CPU 延迟 500ms→120ms) 对温度敏感
学习中间特征和软标签可提升泛化能力 学习中间层特征和软标签成本高
可跨架构蒸馏

2.3 模型蒸馏的三种实现方式

(1)学习软标签:softmax 输出的概率分布(值>0 且总和为 1,指数函数拉开差距)就是软标签,内容比硬标签(0/1)更丰富,能捕捉类别间隐含关系,很适合作为学生模型的学习目标。计算两个分布差异用 KL 散度

军事 文学 家居 体育 财经 其他
软标签 0.2 0.7 0.0015 0.00003 0.001 ……
硬标签 0 1 0 0 0 ……

(2)学习中间层特征:指模型特定步骤输出的向量(隐藏层输出如 BERT 第 k 层 encoder/decoder、注意力向量),隐含大量词法、句法、语法信息(如 BERT 逐层抽象出上下文敏感的词汇表征、Transformer 注意力向量捕获词间复杂依赖关系),可有效改善学生模型效果。学习策略:逐层匹配(学生第 m 层对齐教师第 m×k 层)、跨层融合(对齐教师第 (m-1)×k+1 层到 m×k 层池化后的向量)。损失:特征向量用 MSE 或余弦距离,注意力特征用 KL 散度。

(3)学习硬标签:即最终分类结果。如果有条件应尽量学习中间特征和软标签,但在以下情况需用硬标签:

  1. 教师模型不可用或是闭源模型
  2. 获取教师模型中间特征或软标签成本过高(如 DeepSeek-R1)
  3. 训练资源受限
  4. 需要学习的领域或特性不是非常复杂
  5. 学生模型已有相当好的基础

三步:组织数据集 → 获取教师模型推理结果 → 训练学生模型。

优点 缺点
无需获取中间层特征或软标签 信息受损
训练成本低 效果上限不高
迭代速度快 易于过拟合

本项目因 DeepSeek-R1 闭源,采用硬标签蒸馏

小结思考:模型压缩都有哪几种方式?模型蒸馏有哪几种实现方式?学习概率分布应使用什么损失函数?什么情况下用到学习硬标签?


三、组织数据集

两个"三":组织语料三步走(生成问题→生成答案→组织语料集)、批量跑数三方法(大数据、切块、生产者消费者)。整体流程:得到问题 → 获取答案 → 答案质量评估 → 组织数据集格式

3.1 得到问题

方式一:公共语料——学习通用特性或泛领域知识(如思维链)可从公共语料选取。

方式二:自动化生成——学习特殊特性或特定领域知识必须自己组织。以"中国婚姻法"为例:

  1. 子领域划分:让大模型规划子领域和语料数量
你是一位中国婚姻法方面的专家,我需要整理一份婚姻法方面的训练语料,共5000条,
需要考虑婚姻法的哪几个子领域,各自语料的数量是多少?用一个表格输出结果,
每一行是一个子领域,第一列是子领域的名称,第二列是语料的数量,第三列是对这个子领域的详细说明。
不允许输出其他字符。
  1. 按类型生成问题:针对每个子领域生成具体问题
你是一位婚姻法方面的专家,根据【特定类别】的定义,针对其中夫妻权利义务方面的规定,
向学生提供20道练习题,你能提出哪些问题?每个问题单独一行,只允许输出问题本身,
不允许输出任何其他字符。
  1. 问题检查:用另一个高质量大模型对问题打分(0~9),删掉低分问题
你是一位婚姻法方面的专家,如果要把【输入文本】分类到【特定类别】中,那么输入文本
属于夫妻权利义务是否准确?给出一个准确性评分,该评分是0到9之间的正整数,
其中0表示完全错误,9表示非常准确。【输入文本】中的每一行是一条独立数据,需要单独判定。
输出时每条数据的结果单独输出一行,只允许输出准确性评分,不允许输出其他任何字符。
  1. 去重:用 simhash(含分桶优化)做短文本去重,删掉重复问题。注意保持样本库比例

3.2 获取答案

调用硅基流动的 DeepSeek-R1 API,特别注意温度参数和结果输出格式

def callServer(prompt, model="deepseek-ai/DeepSeek-R1"):
    url = "https://api.siliconflow.cn/v1/chat/completions"
    headers = {"Authorization": f"Bearer **********", "Content-Type": "application/json"}
    payload = {
        "model": model,
        "messages": [{"role": "user", "content": prompt}],
        # 温度越高多样性越好但越易出问题;越低越稳定但死板
        "temperature": 0.6,
        "max_tokens": 15000
    }
    success, msg, think, result = True, "OK", "", ""
    count = 0
    # 重试机制
    while count < 3:
        count += 1
        try:
            response = requests.post(url, json=payload, headers=headers)
            response.raise_for_status()
            # deepseek 输出分为思考(reasoning_content)和结论(content)两部分
            # 学习思维链风格需把两部分都记录下来
            think = response.json()['choices'][0]['message']['reasoning_content']
            result = response.json()['choices'][0]['message']['content']
            break
        except Exception as e:
            success = False
            msg = "API调用异常:" + str(e)
    return success, msg, think, result

结果格式处理:DeepSeek 的输出分为思考部分(reasoning_content)结论部分(content),学习思维链风格需把两部分都记录下来。将思考部分用 <think></think> 包裹后与结论拼接。大模型非常善于学习各种格式性强的结构,可以充分利用这一点

def askBatch(fileName, targetName):
    resultList = []
    dataList = commonTool.loadRawDataSet(fileName)
    for data in dataList:
        success, msg, think, result = callServerStimu(data["instruction"])
        if success:
            data["output"] = "<think>\n" + think + "\n</think>\n\n\n" + result
            resultList.append(data)
        print(data["id"], success, msg, think, result)
    commonTool.writeData(resultList, targetName)

3.3 删除过长的数据

为什么要删除过长的数据

  • 节省资源:过长数据消耗更多显存和算力
  • 对数据集影响不大:从样本长度分布看,绝大多数集中在 1000~4000 字区间,超长样本占比极低
长度区间 样本数量
0~1000 616
1000~2000 4817
2000~3000 6660
3000~4000 1703
4000~5000 439
5000~6000 228
…… ……(递减)
30000+ 1~2

截取方式:小数据集按计算资源截取;大数据集按长度累计数量的拐点截取。注意截取比例大时不要失衡。

3.4 答案质量评估

打分模型选择最好不要用同一个模型给自己的答案打分,应选择另一个公认的高质量模型打分。本项目选用 Qwen2.5-72B-instruct。

打分 prompt

你是一位问答对的质量评估专家,请对以下问答对的质量作出评估,分数从0到9,
0表示答案和问题无关,9表示【答案】非常好地回答了【问题】,
只允许输出分数,不允许输出其他任何字符。
【问题】
......
【答案】
......

编写要点:身份限定、清晰任务、输出格式限定、问答用强格式、temperature 要小

打分 prompt 注意事项:

  • prompt 里最好有大模型的身份限定
  • 要清晰地说明任务
  • 最好有输出格式的限定
  • 问题和答案部分要用比较强的格式,方便大模型识别
  • temperature 参数要比较小

可靠程度评估:生成答案任务较为复杂,假设只有 80% 正确率;打分任务较为简单,假设有 90% 正确率。只取打分最高的一批样本,整体准确率估算为 1 - (1-80%) × (1-90%) = 98%

3.5 数据集格式

JSONL 格式,每行一条:

{"instruction": "提出问题1", "input": "", "output": "对应的答案1"}
{"instruction": "提出问题2", "input": "", "output": "对应的答案2"}

目录下必须有 dataset_info.json{"chat-train": {"file_name": "train.jsonl"}}

3.6 生产环境批量跑数

方法 优点 缺点
大数据系统 方便快捷,可利用集群算力 环境冲突,不一定支持 GPU,时间/任务冲突
数据切块,不同进程/机器跑 方便,互不影响 要等最晚进程完成,不便扩展
生产者消费者模式 几乎同时完成、便于看进度、可灵活扩展、适用范围广 需自己组织协调软硬件

生产者消费者模式队列为核心:队列可用语言自带数据结构(Python multiprocessing.Queue,单机)或中间件消息队列(Kafka/RabbitMQ/RocketMQ,可多机)。生产者生产任务放入输入队列(一般 IO 密集型);消费者消费任务把结果放入输出队列(一般多个)。IO 密集型用线程,计算密集型用进程

优点

  • 几乎同时完成,最早和最晚的消费者最多只差一条数据
  • 便于把握整体进度,有最终输出,可随时看进度
  • 可灵活动态扩展:单机用 multiprocessing.Queue 并行,换成消息队列可多机并行,随时增减消费者
  • 适用范围广,可任意部署所需环境

缺点:需要自己组织协调软硬件。

简化版代码

from multiprocessing import Queue
import threading, time, random

taskQueue = Queue()
resultQueue = Queue()

# 生产者
def produceTask():
    for i in range(100):
        taskQueue.put("task " + str(i))

# 实际任务
def doSomething(task, model="deepseek-ai/DeepSeek-R1"):
    time.sleep(random.randint(2, 5))  # 模拟等待远程服务器
    return "task " + str(task) + " has been processed"

# 消费者
def consumTask(threadIndex):
    while True:
        task = taskQueue.get(block=True)
        result = doSomething(task)
        resultQueue.put(result)

# 整体流程
def askBatchMT(processorCount):
    threading.Thread(target=produceTask).start()  # 启动生产者
    for i in range(processorCount):               # 启动多个消费者
        threading.Thread(target=consumTask, args=(i,)).start()
    while True:                                    # 接收结果
        print(resultQueue.get(block=True))

生产环境实现要点:生产者需考虑崩溃重启(读取结果文件记录已完成 Id)、限制队列总量(内存有限,如 taskQueue.qsize() < 20)、大文件逐行读取;结果写入时 target.flush() 实时落盘,错误结果打印警告但不写入。

# 生产者:崩溃重启 + 限制队列总量 + 逐行读取
def produceTask(fileName, targetName):
    finishedSet = set()
    if os.path.exists(targetName):
        with open(targetName, "r", encoding="utf-8") as file:
            for line in file:
                data = json.loads(line.strip())
                if "id" in data:
                    finishedSet.add(data["id"])
    with open(fileName, "r", encoding="utf-8") as file:
        for line in file:
            data = json.loads(line.strip())
            if data['id'] not in finishedSet:
                while True:
                    if taskQueue.qsize() < 20:
                        taskQueue.put(data)
                        break
                    else:
                        time.sleep(1)

小结思考:生产环境下跑海量数据都有哪些方法?资源充足时最好用哪种?计算密集型且环境复杂的任务最好用哪种?样本不多的简单任务最好用哪种?


四、模型训练

训练模型三重点:训练方式、基座模型、工具

4.1 LoRA 微调

LoRA(Low-Rank Adaptation,低秩适配)通过引入低秩矩阵分解,仅训练少量参数实现大模型适配。

全参数微调的缺陷

  1. 训练成本高:需要更新千亿甚至万亿级参数,消耗大量计算资源,为调整海量参数时不跑偏还需准备相当规模的数据集,费时费力
  2. 训练时间长:有些场合需要快速迭代,全参数微调跟不上业务节奏
  3. 灾难性遗忘:根据新任务调整和重构所有参数,可能导致原有知识表征被破坏,新任务表现好但旧任务表现急剧下降

LoRA 如何避免这些问题:为避免灾难性遗忘,训练时不调整原始矩阵,而是训练一个同样大小的矩阵。但此时参数量仍未减少,借鉴 PCA 的矩阵分解思路(LSA、协同过滤也用类似方法):不真正训练巨大的矩阵,而是训练两个小型矩阵 A 和 B,使 A × B 足够近似目标矩阵。只要 r(秩)远小于 M、N,就能极大压缩参数量。

参数压缩示例:M=300000,N=500000,原始 M×N=1500 亿;取 r=16,则 A=M×r=480 万,B=r×N=800 万,总参数 1280 万,仅为原始的 0.000085(不到万分之一)。

实际做法:每个 Transformer 层添加低秩旁路;矩阵 A 高斯初始化,矩阵 B 全 0 初始化(不能全 0,否则初期无梯度;不能全高斯,否则破坏初始状态)。

小结思考:全参数微调有什么缺点?LoRA 为什么能避免灾难性遗忘?为什么能减小训练参数量?A、B 矩阵如何初始化,为什么不能全 0 或全高斯?

4.2 获取基座模型

科学上网可去 HuggingFace;无法科学上网通过**魔搭社区(ModelScope)**获取:https://www.modelscope.cn/models

modelscope download --model Qwen/Qwen2.5-3B-Instruct-GPTQ-Int4 \
  --local_dir /workspace/deepseekDistllation/models/Qwen/Qwen2.5-3B-Instruct-GPTQ-Int4

本项目基座模型:Qwen2.5-3B-Instruct-GPTQ-Int4(3B 参数、Int4 量化,适合单卡 4090)。

4.3 使用训练工具 LLaMA-Factory

LLaMA-Factory 是封装完善的 LLM 微调工具,可仅用命令行与 WebUI 微调大模型:

  • 多种模型:LLaMA、LLaVA、Mistral、Mixtral-MoE、Qwen、Qwen2-VL、DeepSeek、Yi、Gemma、ChatGLM、Phi 等 100+
  • 集成方法:(增量)预训练、(多模态)指令监督微调、奖励模型训练、PPO/DPO/KTO/ORPO 训练等
  • 多种精度:16 比特全参数微调、冻结微调、LoRA 微调,以及基于 AQLM/AWQ/GPTQ/LLM.int8/HQQ/EETQ 的 2/3/4/5/6/8 比特 QLoRA 微调
  • 先进算法:GaLore、BAdam、APOLLO、Adam-mini、DoRA、LongLoRA、LLaMA Pro、Mixture-of-Depths、LoRA+、LoftQ、PiSSA
  • Git 地址:https://github.com/hiyouga/LLaMA-Factory

环境搭建

conda create --name llamafactory-3.11 python==3.11
conda activate llamafactory-3.11
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e ".[torch,metrics]"
# 如遇 setuptools 报错改用清华镜像
pip install -r requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
pip install --no-build-isolation --no-index --find-links=./ --no-deps -e ".[torch,metrics]"
pip install -U "triton-windows<3.4" -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
pip install auto_gptq optimum

WebUI 报错 argument of type 'bool' is not iterable 时执行 pip install pydantic==2.10.6

WebUIllamafactory-cli webui,浏览器访问 http://localhost:7860

命令行训练

llamafactory-cli train \
  --stage sft --do_train True \
  --model_name_or_path /workspace/deepseekDistllation/models/Qwen/Qwen2.5-3B-Instruct-GPTQ-Int4 \
  --preprocessing_num_workers 16 --finetuning_type lora --template qwen --flash_attn auto \
  --dataset_dir /workspace/deepseekDistllation/data/chatData --dataset chat-train \
  --cutoff_len 6000 --learning_rate 0.0001 --num_train_epochs 1.0 --max_samples 100000 \
  --per_device_train_batch_size 2 --gradient_accumulation_steps 2 --lr_scheduler_type cosine \
  --max_grad_norm 1.0 --logging_steps 5 --save_steps 100 --warmup_steps 10 --packing False \
  --report_to none \
  --output_dir /workspace/deepseekDistllation/models/lora/Qwen2.5-3B-instruct-GPTQ-Int4/train_2025-04-22-07-55-05 \
  --bf16 True --plot_loss True --trust_remote_code True --ddp_timeout 180000000 \
  --include_num_input_tokens_seen True --optim adamw_torch \
  --quantization_bit 4 --quantization_method bitsandbytes --double_quantization True \
  --lora_rank 16 --lora_alpha 16 --lora_dropout 0 --lora_target all

4.4 训练配置与关键参数

配置项 取值
基座模型 Qwen2.5-3B-Instruct-GPTQ-Int4
微调方法 LoRA
量化等级 4
训练阶段 Supervised Fine-Tuning
重要参数 说明
学习率 0.1 仅探索;0.01 从头训练;0.001 接近目标细致调整;0.0001 接近收敛微调;0.00005 预训练最后微调。本项目 1e-4 或 5e-5
截断长度 本次选 6000
训练轮数 特性简单 1 轮即可;数据集太小不应多轮,可考虑数据增强
批处理大小 根据显存调整
梯度累计 批处理大小 × 梯度累计决定梯度更新频率
预热步数 建议使用,防止参数剧烈震荡
LoRA 的秩 学到信息的丰富程度
LoRA 缩放系数 LoRA 的重要程度
LoRA 随机丢弃 随机丢弃 LoRA 层权重概率,正则化防止过度依赖

训练阶段还包括:

训练阶段 说明
Supervised Fine-Tuning 进一步做有监督训练(本项目选用)
Reward Modeling 奖励建模,用于强化学习
PPO 强化学习算法,增强多次迭代稳定性
DPO 强化学习算法,避免策略和价值函数相互影响,提高学习效率
KTO 知识转移方法,强化学习效率和最终效果
Pre-Training 预训练

问题排查:学习率过大+轮数过多会过拟合,应降低学习率减少轮数;CUDA out of memory 应减小批处理大小、截断长度或降低量化位数。

4.5 检查训练效果

# 仅加载基座模型
set "CUDA_VISIBLE_DEVICES=0" && set "API_PORT=8000" && llamafactory-cli api ^
  --model_name_or_path C:\Users\foxba\.cache\modelscope\hub\Qwen\Qwen2___5-3B-Instruct-GPTQ-Int4
# 加载基座模型 + LoRA
set "CUDA_VISIBLE_DEVICES=0" && set "API_PORT=8000" && llamafactory-cli api ^
  --model_name_or_path C:\Users\foxba\.cache\modelscope\hub\Qwen\Qwen2___5-3B-Instruct-GPTQ-Int4 ^
  --adapter_name_or_path C:\Users\foxba\saves\Qwen2.5-3B-Instruct-GPTQ-Int4\lora\train_2025-05-24-17-56-24\checkpoint-120 ^
  --finetuning_type lora

可同时加载多个不同任务的 LoRA(分类、三元组、思维链等),按需切换。

def callServer(input):
    client = OpenAI(api_key="0",
        base_url="http://localhost:{}/v1".format(os.environ.get("API_PORT", 8000)))
    messages = [{"role": "user", "content": input}]
    result = client.chat.completions.create(messages=messages, model="test", max_tokens=6000)
    return result.choices[0].message.content

实测 LLaMA-Factory 部署的 API server 推理约 5~7 字/秒,输出 1900 字耗时约 376 秒。训练后模型已具备思维链特性。可用 nvidia-smi -l 1(Win)或 watch -n 1 nvidia-smi(Linux)观察显卡状态。


五、模型高效部署

优化部署四条路:模型、工具、prompt、业务

5.1 vllm 介绍

同一测试问题,vllm 部署微调模型:

部署方式 输出长度 耗时 输出速度
LLaMA-Factory API 约 1900 字 376 秒 约 5 字/秒
vllm API 约 1794 字 16 秒 约 112 字/秒

vllm 推理速度提升约 22 倍

vllm 的优势

(1)KV-Cache:Decoder-only 模型推理时,自注意力部分是整个推理过程中计算量最大的部分,每输出一个字符都要从头反复计算 QKV,造成巨大算力浪费。工程上常用空间换时间的思路(如字典、倒排索引)把中间阶段的 K 和 V 值存入缓存,这就是 KV-Cache——每生成新 token 时之前的 K、V 不变,只需算新 token 的 Q 复用历史 K、V 即可。

为什么是 KV-Cache 而不是 QKV-Cache?因为只有 Q 需要重新计算,历史 K、V 可直接复用。

但 KV-Cache 仍存在问题:必须为 key、value 分配显存,但输出序列长度无法预测,预留不够会溢出,预留太多会浪费。

(2)缓存优化 PageAttention:传统 KV-Cache 直接分配物理显存,无法量身分配导致浪费。PageAttention 借鉴操作系统虚拟内存分页:分配固定大小连续空间(默认 block 16),多个进程通过块表关联内存页,内存不足才请求增加一个页,最坏只有最后页没写满,显存利用率达 96%

(3)批量任务优化:PageAttention 提高显存使用率,可增加并行任务数。vllm 不要求所有任务处于同一阶段,资源充足即可随时开始新任务,减少批次等待;超出并行量则抢占(preemption)

(4)加载多个 LoRA:可同时加载多个 LoRA 随时切换,方便对比测试、节约服务器资源、方便负载均衡。

5.2 vllm 部署过程

Windows 对 vllm 支持不好,选用 WSL2/Docker/Linux 之一,本项目用 WSL2(Windows Subsystem for Linux 2,与主机 Windows 共用同一套硬件资源)。

安装 WSL2

  1. 启动 WSL 功能:控制面板 → 程序 → 启动或关闭 Windows 功能,启用"Virtual Machine Platform"和"适用于 Linux 的 Windows 子系统",完成后重启
  2. 安装 WSL:命令行输入 wsl --install,几分钟后设置用户名密码
  3. 基本命令
    • 查看版本:wsl -l -v
    • 切换版本:wsl.exe --set-version Ubuntu-22.04 2
    • 启动:wsl
  4. Windows 下数据挂载到 /mnt 目录,如 C:\Program Files/mnt/c/Program Files

安装并启动 vllm

pip install vllm

CUDA_VISIBLE_DEVICES=0 API_PORT=8000 vllm serve \
  /workspace/deepseekDistllation/models/Qwen/Qwen2.5-3B-Instruct-GPTQ-Int4 \
  --enable-lora \
  --lora-modules add1=/workspace/deepseekDistllation/models/lora/Qwen2.5-3B-instruct-GPTQ-Int4/train_2025-03-20-20-41-50/checkpoint-1250/

访问时用 model 参数指定加载哪个 LoRA,model="" 则直接用基座模型:

def callServer(input):
    client = OpenAI(api_key="0",
        base_url="http://localhost:{}/v1".format(os.environ.get("API_PORT", 8000)))
    result = client.chat.completions.create(
        messages=[{"role": "user", "content": input}], model="add1", max_tokens=20000)
    return result.choices[0].message.content

5.3 性能优化思路

模型优化:裁剪、量化、蒸馏

工具优化:vllm、HuggingFace TGI、FasterTransformer、DeepSpeed-Inference

prompt 优化

  • 结构化:用明确标记组织,便于大模型识别各部分
你是一位人力资源专家,现在需要你分析一份【简历】,按照【入职标准】,
分析【简历】的哪些方面不符合【入职标准】。
【简历】
XXXXXXXX
【入职标准】
XXXXXXXX
  • 批处理:一次抽取多个字段,减少请求次数
你是一位人力资源专家,现在需要你分析一份【简历】,并从中抽取【特定信息】,
并且按照【指定格式】输出,不允许输出其他任何字符,如果特定信息不存在,输出"无",
不允许自行编造。
【特定信息】
姓名,年龄,籍贯,政治面貌,毕业院校,工作经历,项目经验
【指定格式】
{"姓名":"张大彪","年龄":"32",......}
【简历】
XXXXXXX
  • 缩短输出:输出长度对效率影响极大,应尽力压缩,必要时压缩到一个 token
你是一位问答对的质量评估专家,请对以下问答对的质量作出评估,分数从0到9,
0表示答案和问题无关,9表示【答案】非常好地回答了【问题】,
只允许输出分数,不允许输出其他任何字符。
【问题】
XXXXXX
【答案】
XXXXXXXX

业务优化

  • 大模型无法处理(慎用):统计性任务(文本聚类、信息检索)、低延迟任务(高频交易、舆情监控)
  • 大模型无优势(慎用):较简单的分类、一般序列标注(分词/词性标注/常见实体识别)、特别专业的领域(医药化工等依赖专业词典)
  • 与传统方法有机结合:用简单快速的方法处理大部分易处理数据,用大模型处理少量困难数据,层层拦截减轻大模型负担

5.4 混合策略案例:车系识别

某汽车互联网企业需短期识别某时间段内所有文本中的车系,挖掘浏览者购买意向,形成商业价值。数据约 2.86 亿篇文章,要求速度快、时间短、资源消耗少。

考虑方案

  • 直接用大模型:效果非常好,但时间来不及,也没有这么多资源可用
  • 序列标注模型:没有语料,没有标注人员支持,即使有现成模型,2.86 亿文章现有资源下也跑不完

任务特性:车系共 3758 个,其中无歧义 3129、常用词歧义 116(几何/哪吒/指挥官)、英文数字歧义 513(XC60/SL350)。文章中 84.3% 无车系、13.16% 仅无歧义、仅 2.54% 有潜在歧义。

最终方案(层层拦截):用 AC 自动机扫描所有车系字符串→无车系丢弃(过滤 84.3%)→无歧义直接记录(处理 13.16%)→潜在歧义取上下文(前后各 20 字)送大模型判定(仅处理 2.54%):

你是一位汽车行业的专家,需要你确定一组文本片段的特定字符串是不是代表一个车系,
如果是车系,输出是;否则输出否。不允许输出其他任何字符。下面提供一个例子
假如输入是:
[
  ["例如硬币、卡片、金属表面和物品)确保使用 XC360 安全充电。", "xc360"],
  ["现在行业内手自一体销量大增,XC360就是其中的佼佼者", "xc360"]
]
那么对应的输出是:
否是
现在真实的输入是:
[
  ["今年哪吒大火,票房达到了惊人的120亿美元", "哪吒"],
  ["大部分朋友对此有一个误解,其实哪吒的目标从来都不是商务用车", "哪吒"]
]

经济效益(前提:文章总量 2.86 亿、平均字数 1000,硅基流动 DeepSeek-R1 输入 4 元/M token、输出 16 元/M token):

  • 方案一(直接用大模型):总输入 2.86 亿×(1000+91)=3120.26 亿字,成本约 748862 元;总输出 10.86 亿字,成本约 10426 元;合计 759288 元
  • 方案二(混合策略):实际需大模型判断的文章 2.86 亿×2.54%=726.44 万篇,每条数据含上下文约 60 字,每 20 条写入一个 prompt,实际送交大模型 13.7 亿字、输出 0.1816 亿字,合计 3454.18 元
输入字符数 输出字符数 总成本
方案一(直接用大模型) 3120.26 亿 10.86 亿 759288 元
方案二(混合策略) 13.7 亿 0.1816 亿 3454.18 元
方案二 / 方案一 0.439% 1.672‰ 0.455%

混合策略成本仅为纯大模型方案的 0.455%,节约超过 75 万元。

小结思考:优化大模型性能有哪些方法?怎样利用 prompt 优化模型性能?哪些任务大模型没有优势,为什么?


总结

本项目完整演示了大模型蒸馏全流程:

  1. 原理层:模型压缩三思路(裁剪/量化/蒸馏)、蒸馏三方式(软标签/中间层特征/硬标签),因 DeepSeek-R1 闭源采用硬标签蒸馏
  2. 数据层:大模型自动化生成问题(子领域划分→按类型生成→检查→去重),API 获取带思维链答案,质量评估后组织 JSONL,生产环境用生产者消费者模式批量跑数
  3. 训练层LoRA 微调(参数量不到万分之一)避免灾难性遗忘,借助 LLaMA-Factory 完成 QLoRA 4 比特量化训练
  4. 部署层vllm(PageAttention 显存利用率 96%、动态批处理、多 LoRA)实现约 112 字/秒,相比 LLaMA-Factory 提速约 22 倍
  5. 优化层:模型/工具/prompt/业务四维度优化,核心是层层拦截、传统方法与大模型有机结合,车系识别案例证明混合策略可节约 99.5% 成本