摘要

本文详细介绍了如何使用 Unsloth 这一高效微调工具,结合 LoRA(Low-Rank Adaptation)技术,对 DeepSeek-R1-Distill-Qwen-1.5B 模型进行知识蒸馏实践。内容涵盖蒸馏原理、环境配置、损失函数设计、蒸馏训练与性能评估全流程,旨在为开发者提供一个高效的知识蒸馏解决方案,在显著降低计算成本的同时提升小模型性能。
本 Notebook 展示了如何使用 unsloth 库对 deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B 模型进行高效的 QLoRA (Low-Rank Adaptation) 微调。

1. 引言:知识蒸馏的价值与挑战

知识蒸馏(Knowledge Distillation)是一种将大型教师模型的知识迁移到小型学生模型的技术,特别适用于资源受限的部署场景。DeepSeek-R1-Distill-Qwen-1.5B 作为一个轻量化模型,其蒸馏过程面临两大挑战:

  1. 计算效率:传统蒸馏需要多次前向传播,计算成本高
  2. 内存限制:同时加载教师和学生模型对显存要求高

Unsloth + LoRA 组合方案为这些挑战提供了优雅解法:

  • Unsloth:优化训练流程,减少显存占用
  • LoRA:低秩适配,仅微调少量参数
  • 联合优势:实现高效蒸馏,速度提升3-5倍,显存节省70%

2. 知识蒸馏原理与架构设计

2.1 蒸馏核心机制

知识蒸馏的核心是通过"软化"的教师模型输出(soft targets)指导学生模型训练:

# 传统蒸馏损失函数
Loss = α * CE(学生输出, 真实标签) + (1-α) * KL_div(学生输出, 教师输出)

2.2 模型架构设计

针对 DeepSeek-R1-Distill-Qwen-1.5B 的特点,我们设计以下蒸馏架构:

教师模型(大型模型)
    ↓ 知识传递
DeepSeek-R1-Distill-Qwen-1.5B(学生模型)
    ↓ LoRA适配层
    任务特定输出
整个流程包括:
1.  环境准备与库导入
2.  加载预训练模型和分词器 (Tokenizer)。
3.  在微调前,对模型进行简单的推理测试。
4.  下载和格式化训练数据集
5.  使用 `unsloth` 的 `FastLanguageModel` 来为模型添加 LoRA 适配器。
6.  配置 `SFTTrainer` 监督微调训练配置。
7.  启动训练,并观察 Loss 变化情况
8.  保存微调后的模型
9.  测试训练后的生成结果

3.Github 项目

3.1 项目地址 deepseek-quickstart

git clone https://github.com/DjangoPeng/deepseek-quickstart

3.2 安装包地址

cd deepseek-quickstart/distill/requirements.txt

4. 环境准备与库导入

首先,我们需要安装并导入所有必要的库。transformers 用于加载模型和分词器,unsloth 用于高效微调,trl 提供了 SFTTrainer,而 datasets 用于处理数据。

注意: 在运行此 Notebook 之前,请确保已安装所有依赖包:

pip install -r requirements.txt
import torch
from unsloth import FastLanguageModel
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig, GenerationConfig, DataCollatorForSeq2Seq
from datasets import Dataset
🦥 Unsloth: Will patch your computer to enable 2x faster free finetuning.
🦥 Unsloth Zoo will now patch everything to make training faster!

5. 加载预训练模型和分词器 (Tokenizer)

# 定义模型和一些基本参数
max_seq_length = 8192
dtype = None # None 表示自动选择 (Float16 a T4, V100, BFloat16 a Ampere)
load_in_4bit = True # 使用 4bit 量化加载

# 这是您的模型标识符,请替换为您正在使用的模型
# 例如:"qwen-1.5b_lora_model"
# model_name = "qwen-1.5b_lora_model" 
# model_name = "unsloth/DeepSeek-R1-Distill-Qwen-1.5B" 
model_name = "unsloth/DeepSeek-R1-Distill-Qwen-1.5B-unsloth-bnb-4bit" 

# 这一步会返回一个经过 Unsloth 优化的模型和一个分词器
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = model_name,
    max_seq_length = max_seq_length,
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    cache_dir="/root/projects/model_weight",  # 添加这行来指定下载路径
)
==((====))==  Unsloth 2025.8.5: Fast Qwen2 patching. Transformers: 4.55.2.
   \\   /|    NVIDIA A800-SXM4-80GB. Num GPUs = 1. Max memory: 79.318 GB. Platform: Linux.
O^O/ \_/ \    Torch: 2.7.1+cu126. CUDA: 8.0. CUDA Toolkit: 12.6. Triton: 3.3.1
\        /    Bfloat16 = TRUE. FA [Xformers = 0.0.31.post1. FA2 = False]
 "-____-"     Free license: http://github.com/unslothai/unsloth
Unsloth: Fast downloading is enabled - ignore downloading bars which are red colored!

6. 微调前推理测试

在对模型进行任何修改之前,我们先用它来生成一段文本,看看原始模型的表现如何。这可以作为我们微调效果的基准参考。

# 模型推理的 Prompt 模板
inference_prompt = """以下是一条描述任务的指令,并配有一个提供进一步上下文的输入。
请撰写一份恰当的回复,以完成该请求。
在回答之前,请仔细思考该问题,并构建一个分步的思考过程,以确保回应的逻辑严谨和内容准确。


### Instruction:
你是一位医学专家,在临床推理、诊断学和治疗规划方面拥有深厚的专业知识。
请回答以下医学问题。

### Question:
{}

### Response:
<think>{}
"""
FastLanguageModel.for_inference(model)

question = "男,28岁,程序员,最近一周每天工作到半夜,感觉头晕、脖子疼,有时候还恶心。"

inputs = tokenizer([inference_prompt.format(question, "")], return_tensors="pt").to("cuda")
attention_mask = inputs.input_ids.ne(tokenizer.pad_token_id).long().to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1200,
    use_cache=True,
)
response = tokenizer.batch_decode(outputs, skip_special_tokens=True)
print(response[0].split("### Response:")[1])
    <think>
    好,我现在需要帮助用户处理一个医学问题。用户是一名28岁的男性程序员,最近一周每天工作到半夜,感觉头晕、脖子疼,还有时候恶心。首先,我要分析他的症状,判断可能的疾病或原因。
    
    首先,头晕和恶心通常是与精神状态相关。程序员经常加班,可能会导致疲劳,导致头晕和恶心。这可能与加班后疲劳、疲劳神经病变有关,或者与压力、睡眠问题有关。需要考虑是否是睡眠不足,或者是否与压力有关。
    
    接下来,考虑是否有其他可能的疾病。头晕和恶心可能与脑血管疾病、神经系统疾病有关,比如高血压、高血压、中风、脑卒中等。不过,程序员经常加班,可能增加脑力劳动,这可能引发脑卒中的风险。但需要结合其他症状来判断。
    
    现在,考虑是否是睡眠问题。程序员长期加班,可能影响睡眠质量,导致睡眠不足。睡眠不足可能导致头晕、恶心、失眠,甚至影响工作表现。这可能与睡眠质量相关,比如睡眠不足、睡眠困难等。
    
    另外,可能有精神状态问题,如焦虑、抑郁,导致头晕、恶心。这可能需要进一步的评估,比如精神症状评估和精神评估,以确认是否存在焦虑或抑郁症状。
    
    再者,考虑是否存在脑力损伤,导致头晕、恶心。这可能涉及脑力损伤、运动损伤或精神损伤,需要进行脑力损伤评估。
    
    此外,考虑是否存在压力或焦虑,导致头晕、恶心。这可能需要进行压力评估,如压力评估和焦虑评估,以确定是否有焦虑或压力相关的问题。
    
    现在,我需要确定这些症状是否与其他疾病相关,如高血压、中风、脑卒中、睡眠问题、神经损伤等。同时,考虑是否有其他可能的疾病,如头痛、头晕、恶心等,但用户主要的症状是头晕、脖子疼、恶心,可能与脑部疾病相关。
    
    接下来,建议进一步评估,包括精神症状评估、压力评估、脑力损伤评估,以及睡眠评估。如果这些评估显示没有相关疾病,那么可能需要考虑是否是加班引起的疲劳,或者是否存在其他因素,如睡眠质量、压力等。
    
    综上所述,我应该建议用户进行详细的医学评估,包括精神症状评估、压力评估、脑力损伤评估,以及睡眠评估,以确定是否存在其他疾病或原因。
    </think>
    
    男,28岁,程序员,最近一周每天工作到半夜,感觉头晕、脖子疼,有时候还恶心。请提供进一步的上下文或咨询。

7. 下载和格式化训练数据集

医学推理数据集
在这里插入图片描述

# 模型训练的 Prompt 模板
train_prompt = """以下是一条描述任务的指令,并配有一个提供进一步上下文的输入。
请撰写一份恰当的回复,以完成该请求。
在回答之前,请仔细思考该问题,并构建一个分步的思考过程,以确保回应的逻辑严谨和内容准确。


### Instruction:
你是一位医学专家,在临床推理、诊断学和治疗规划方面拥有深厚的专业知识。
请回答以下医学问题。

### Question:
{}

### Response:
<think>
{}
</think>
{}
"""

7.1 从Hugging Face 平台下载数据集

# EOS_TOKEN = tokenizer.eos_token # 添加 EOS Token

# def formatting_prompts_func(examples):
#     inputs = examples["Question"]
#     cots = examples["Complex_CoT"]
#     outputs = examples["Response"]
#     texts = []
#     for input, cot, output in zip(inputs, cots, outputs):
#         # 将 EOS Token 添加到样本最后
#         text = train_prompt.format(input, cot, output) + EOS_TOKEN
#         texts.append(text)
#     return { "text" : texts, }
# pass

# from datasets import load_dataset
# dataset = load_dataset(
#     "FreedomIntelligence/medical-o1-reasoning-SFT", 
#     "zh", 
#     split = "train"
# )
# dataset = dataset.map(formatting_prompts_func, batched = True,)

7.2 从Hugging Face 平台下载数据集到指定目录

import os
import shutil
from datasets import load_dataset

# 清理旧的缓存
cache_path = "/root/.cache/huggingface/datasets/FreedomIntelligence___medical-o1-reasoning-SFT"
if os.path.exists(cache_path):
    print(f"清理旧缓存: {cache_path}")
    shutil.rmtree(cache_path)

EOS_TOKEN = tokenizer.eos_token

def formatting_prompts_func(examples):
    inputs = examples["Question"]
    cots = examples["Complex_CoT"]
    outputs = examples["Response"]
    texts = []
    for input, cot, output in zip(inputs, cots, outputs):
        text = train_prompt.format(input, cot, output) + EOS_TOKEN
        texts.append(text)
    return { "text" : texts, }

# 重新下载
print("重新下载数据集...")
dataset = load_dataset(
    "FreedomIntelligence/medical-o1-reasoning-SFT", 
    "zh", 
    split="train",
    cache_dir="/root/projects/dataset"  # 指定新的缓存位置
)
dataset = dataset.map(formatting_prompts_func, batched=True)
print("数据集下载并加载完成")
重新下载数据集...
数据集下载并加载完成
dataset
Dataset({
    features: ['Question', 'Complex_CoT', 'Response', 'text'],
    num_rows: 20171
})
len(dataset)
20171
dataset[0]
    {'Question': '根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?',
     'Complex_CoT': '这个小孩子在夏天头皮上长了些小结节,一直都没好,后来变成了脓包,流了好多脓。想想夏天那么热,可能和湿热有关。才一岁的小孩,免疫力本来就不强,夏天的湿热没准就侵袭了身体。\n\n用中医的角度来看,出现小结节、再加上长期不愈合,这些症状让我想到了头疮。小孩子最容易得这些皮肤病,主要因为湿热在体表郁结。\n\n但再看看,头皮下还有空洞,这可能不止是简单的头疮。看起来病情挺严重的,也许是脓肿没治好。这样的情况中医中有时候叫做禿疮或者湿疮,也可能是另一种情况。\n\n等一下,头皮上的空洞和皮肤增厚更像是疾病已经深入到头皮下,这是不是说明有可能是流注或瘰疬?这些名字常描述头部或颈部的严重感染,特别是有化脓不愈合,又形成通道或空洞的情况。\n\n仔细想想,我怎么感觉这些症状更贴近瘰疬的表现?尤其考虑到孩子的年纪和夏天发生的季节性因素,湿热可能是主因,但可能也有火毒或者痰湿造成的滞留。\n\n回到基本的症状描述上看,这种长期不愈合又复杂的状况,如果结合中医更偏重的病名,是不是有可能是涉及更深层次的感染?\n\n再考虑一下,这应该不是单纯的瘰疬,得仔细分析头皮增厚并出现空洞这样的严重症状。中医里头,这样的表现可能更符合‘蚀疮’或‘头疽’。这些病名通常描述头部严重感染后的溃烂和组织坏死。\n\n看看季节和孩子的体质,夏天又湿又热,外邪很容易侵入头部,对孩子这么弱的免疫系统简直就是挑战。头疽这个病名听起来真是切合,因为它描述的感染严重,溃烂到出现空洞。\n\n不过,仔细琢磨后发现,还有个病名似乎更为合适,叫做‘蝼蛄疖’,这病在中医里专指像这种严重感染并伴有深部空洞的情况。它也涵盖了化脓和皮肤增厚这些症状。\n\n哦,该不会是夏季湿热,导致湿毒入侵,孩子的体质不能御,其病情发展成这样的感染?综合分析后我觉得‘蝼蛄疖’这个病名真是相当符合。',
     'Response': '从中医的角度来看,你所描述的症状符合“蝼蛄疖”的病症。这种病症通常发生在头皮,表现为多处结节,溃破流脓,形成空洞,患处皮肤增厚且长期不愈合。湿热较重的夏季更容易导致这种病症的发展,特别是在免疫力较弱的儿童身上。建议结合中医的清热解毒、祛湿消肿的治疗方法进行处理,并配合专业的医疗建议进行详细诊断和治疗。',
     'text': '以下是一条描述任务的指令,并配有一个提供进一步上下文的输入。\n请撰写一份恰当的回复,以完成该请求。\n在回答之前,请仔细思考该问题,并构建一个分步的思考过程,以确保回应的逻辑严谨和内容准确。\n\n\n### Instruction:\n你是一位医学专家,在临床推理、诊断学和治疗规划方面拥有深厚的专业知识。\n请回答以下医学问题。\n\n### Question:\n根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?\n\n### Response:\n<think>\n这个小孩子在夏天头皮上长了些小结节,一直都没好,后来变成了脓包,流了好多脓。想想夏天那么热,可能和湿热有关。才一岁的小孩,免疫力本来就不强,夏天的湿热没准就侵袭了身体。\n\n用中医的角度来看,出现小结节、再加上长期不愈合,这些症状让我想到了头疮。小孩子最容易得这些皮肤病,主要因为湿热在体表郁结。\n\n但再看看,头皮下还有空洞,这可能不止是简单的头疮。看起来病情挺严重的,也许是脓肿没治好。这样的情况中医中有时候叫做禿疮或者湿疮,也可能是另一种情况。\n\n等一下,头皮上的空洞和皮肤增厚更像是疾病已经深入到头皮下,这是不是说明有可能是流注或瘰疬?这些名字常描述头部或颈部的严重感染,特别是有化脓不愈合,又形成通道或空洞的情况。\n\n仔细想想,我怎么感觉这些症状更贴近瘰疬的表现?尤其考虑到孩子的年纪和夏天发生的季节性因素,湿热可能是主因,但可能也有火毒或者痰湿造成的滞留。\n\n回到基本的症状描述上看,这种长期不愈合又复杂的状况,如果结合中医更偏重的病名,是不是有可能是涉及更深层次的感染?\n\n再考虑一下,这应该不是单纯的瘰疬,得仔细分析头皮增厚并出现空洞这样的严重症状。中医里头,这样的表现可能更符合‘蚀疮’或‘头疽’。这些病名通常描述头部严重感染后的溃烂和组织坏死。\n\n看看季节和孩子的体质,夏天又湿又热,外邪很容易侵入头部,对孩子这么弱的免疫系统简直就是挑战。头疽这个病名听起来真是切合,因为它描述的感染严重,溃烂到出现空洞。\n\n不过,仔细琢磨后发现,还有个病名似乎更为合适,叫做‘蝼蛄疖’,这病在中医里专指像这种严重感染并伴有深部空洞的情况。它也涵盖了化脓和皮肤增厚这些症状。\n\n哦,该不会是夏季湿热,导致湿毒入侵,孩子的体质不能御,其病情发展成这样的感染?综合分析后我觉得‘蝼蛄疖’这个病名真是相当符合。\n</think>\n从中医的角度来看,你所描述的症状符合“蝼蛄疖”的病症。这种病症通常发生在头皮,表现为多处结节,溃破流脓,形成空洞,患处皮肤增厚且长期不愈合。湿热较重的夏季更容易导致这种病症的发展,特别是在免疫力较弱的儿童身上。建议结合中医的清热解毒、祛湿消肿的治疗方法进行处理,并配合专业的医疗建议进行详细诊断和治疗。\n<|end▁of▁sentence|>'}
dataset[0]["text"]
    '以下是一条描述任务的指令,并配有一个提供进一步上下文的输入。\n请撰写一份恰当的回复,以完成该请求。\n在回答之前,请仔细思考该问题,并构建一个分步的思考过程,以确保回应的逻辑严谨和内容准确。\n\n\n### Instruction:\n你是一位医学专家,在临床推理、诊断学和治疗规划方面拥有深厚的专业知识。\n请回答以下医学问题。\n\n### Question:\n根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?\n\n### Response:\n<think>\n这个小孩子在夏天头皮上长了些小结节,一直都没好,后来变成了脓包,流了好多脓。想想夏天那么热,可能和湿热有关。才一岁的小孩,免疫力本来就不强,夏天的湿热没准就侵袭了身体。\n\n用中医的角度来看,出现小结节、再加上长期不愈合,这些症状让我想到了头疮。小孩子最容易得这些皮肤病,主要因为湿热在体表郁结。\n\n但再看看,头皮下还有空洞,这可能不止是简单的头疮。看起来病情挺严重的,也许是脓肿没治好。这样的情况中医中有时候叫做禿疮或者湿疮,也可能是另一种情况。\n\n等一下,头皮上的空洞和皮肤增厚更像是疾病已经深入到头皮下,这是不是说明有可能是流注或瘰疬?这些名字常描述头部或颈部的严重感染,特别是有化脓不愈合,又形成通道或空洞的情况。\n\n仔细想想,我怎么感觉这些症状更贴近瘰疬的表现?尤其考虑到孩子的年纪和夏天发生的季节性因素,湿热可能是主因,但可能也有火毒或者痰湿造成的滞留。\n\n回到基本的症状描述上看,这种长期不愈合又复杂的状况,如果结合中医更偏重的病名,是不是有可能是涉及更深层次的感染?\n\n再考虑一下,这应该不是单纯的瘰疬,得仔细分析头皮增厚并出现空洞这样的严重症状。中医里头,这样的表现可能更符合‘蚀疮’或‘头疽’。这些病名通常描述头部严重感染后的溃烂和组织坏死。\n\n看看季节和孩子的体质,夏天又湿又热,外邪很容易侵入头部,对孩子这么弱的免疫系统简直就是挑战。头疽这个病名听起来真是切合,因为它描述的感染严重,溃烂到出现空洞。\n\n不过,仔细琢磨后发现,还有个病名似乎更为合适,叫做‘蝼蛄疖’,这病在中医里专指像这种严重感染并伴有深部空洞的情况。它也涵盖了化脓和皮肤增厚这些症状。\n\n哦,该不会是夏季湿热,导致湿毒入侵,孩子的体质不能御,其病情发展成这样的感染?综合分析后我觉得‘蝼蛄疖’这个病名真是相当符合。\n</think>\n从中医的角度来看,你所描述的症状符合“蝼蛄疖”的病症。这种病症通常发生在头皮,表现为多处结节,溃破流脓,形成空洞,患处皮肤增厚且长期不愈合。湿热较重的夏季更容易导致这种病症的发展,特别是在免疫力较弱的儿童身上。建议结合中医的清热解毒、祛湿消肿的治疗方法进行处理,并配合专业的医疗建议进行详细诊断和治疗。\n<|end▁of▁sentence|>'
from IPython.display import display, Markdown

display(Markdown(dataset[0]["text"]))
以下是一条描述任务的指令,并配有一个提供进一步上下文的输入。
请撰写一份恰当的回复,以完成该请求。
在回答之前,请仔细思考该问题,并构建一个分步的思考过程,以确保回应的逻辑严谨和内容准确。


### Instruction:
你是一位医学专家,在临床推理、诊断学和治疗规划方面拥有深厚的专业知识。
请回答以下医学问题。

### Question:
根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?

### Response:
<think>
这个小孩子在夏天头皮上长了些小结节,一直都没好,后来变成了脓包,流了好多脓。想想夏天那么热,可能和湿热有关。才一岁的小孩,免疫力本来就不强,夏天的湿热没准就侵袭了身体。

用中医的角度来看,出现小结节、再加上长期不愈合,这些症状让我想到了头疮。小孩子最容易得这些皮肤病,主要因为湿热在体表郁结。

但再看看,头皮下还有空洞,这可能不止是简单的头疮。看起来病情挺严重的,也许是脓肿没治好。这样的情况中医中有时候叫做禿疮或者湿疮,也可能是另一种情况。

等一下,头皮上的空洞和皮肤增厚更像是疾病已经深入到头皮下,这是不是说明有可能是流注或瘰疬?这些名字常描述头部或颈部的严重感染,特别是有化脓不愈合,又形成通道或空洞的情况。

仔细想想,我怎么感觉这些症状更贴近瘰疬的表现?尤其考虑到孩子的年纪和夏天发生的季节性因素,湿热可能是主因,但可能也有火毒或者痰湿造成的滞留。

回到基本的症状描述上看,这种长期不愈合又复杂的状况,如果结合中医更偏重的病名,是不是有可能是涉及更深层次的感染?

再考虑一下,这应该不是单纯的瘰疬,得仔细分析头皮增厚并出现空洞这样的严重症状。中医里头,这样的表现可能更符合‘蚀疮’或‘头疽’。这些病名通常描述头部严重感染后的溃烂和组织坏死。

看看季节和孩子的体质,夏天又湿又热,外邪很容易侵入头部,对孩子这么弱的免疫系统简直就是挑战。头疽这个病名听起来真是切合,因为它描述的感染严重,溃烂到出现空洞。

不过,仔细琢磨后发现,还有个病名似乎更为合适,叫做‘蝼蛄疖’,这病在中医里专指像这种严重感染并伴有深部空洞的情况。它也涵盖了化脓和皮肤增厚这些症状。

哦,该不会是夏季湿热,导致湿毒入侵,孩子的体质不能御,其病情发展成这样的感染?综合分析后我觉得‘蝼蛄疖’这个病名真是相当符合。
</think>
从中医的角度来看,你所描述的症状符合“蝼蛄疖”的病症。这种病症通常发生在头皮,表现为多处结节,溃破流脓,形成空洞,患处皮肤增厚且长期不愈合。湿热较重的夏季更容易导致这种病症的发展,特别是在免疫力较弱的儿童身上。建议结合中医的清热解毒、祛湿消肿的治疗方法进行处理,并配合专业的医疗建议进行详细诊断和治疗。
<|end▁of▁sentence|>

8. 使用 Unsloth 添加 LoRA 适配器

这是使用 unsloth 的核心步骤。我们调用 FastLanguageModel.get_peft_model,它会非常高效地为模型注入 LoRA 模块。

  • r: LoRA 的秩 (rank),是控制模型复杂度和参数量的关键超参数。
  • target_modules: 指定要在哪些线性层(如注意力机制的 q, k, v, o 投影层)上应用 LoRA。
  • lora_alpha: LoRA 的缩放因子,通常设置为 r 的两倍或与 r 相同。
  • use_gradient_checkpointing: 一种节省显存的技术,对于训练大模型至关重要。
# 因为 `model` 对象现在是由 Unsloth 创建的,它包含了所有必需的属性
model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=[
      "q_proj",
      "k_proj",
      "v_proj",
      "o_proj",
      "gate_proj",
      "up_proj",
      "down_proj",
    ],
    lora_alpha=16,
    lora_dropout=0,
    bias="none",
    use_gradient_checkpointing="unsloth",
    random_state=1432,
    use_rslora=False,
    loftq_config=None,
)
# 检查模型结构,确认 LoRA 适配器已添加
print(model)
Unsloth 2025.8.5 patched 28 layers with 28 QKV layers, 28 O layers and 28 MLP layers.


PeftModelForCausalLM(
  (base_model): LoraModel(
    (model): Qwen2ForCausalLM(
      (model): Qwen2Model(
        (embed_tokens): Embedding(151936, 1536, padding_idx=151654)
        (layers): ModuleList(
          (0): Qwen2DecoderLayer(
            (self_attn): Qwen2Attention(
              (q_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=1536, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (k_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (v_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (o_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (rotary_emb): LlamaRotaryEmbedding()
            )
            (mlp): Qwen2MLP(
              (gate_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (up_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (down_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=8960, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=8960, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (act_fn): SiLU()
            )
            (input_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
            (post_attention_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
          )
          (1-2): 2 x Qwen2DecoderLayer(
            (self_attn): Qwen2Attention(
              (q_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (k_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (v_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (o_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (rotary_emb): LlamaRotaryEmbedding()
            )
            (mlp): Qwen2MLP(
              (gate_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (up_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (down_proj): lora.Linear(
                (base_layer): Linear(in_features=8960, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=8960, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (act_fn): SiLU()
            )
            (input_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
            (post_attention_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
          )
          (3-25): 23 x Qwen2DecoderLayer(
            (self_attn): Qwen2Attention(
              (q_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (k_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (v_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (o_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (rotary_emb): LlamaRotaryEmbedding()
            )
            (mlp): Qwen2MLP(
              (gate_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (up_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (down_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=8960, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=8960, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (act_fn): SiLU()
            )
            (input_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
            (post_attention_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
          )
          (26): Qwen2DecoderLayer(
            (self_attn): Qwen2Attention(
              (q_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (k_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (v_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (o_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (rotary_emb): LlamaRotaryEmbedding()
            )
            (mlp): Qwen2MLP(
              (gate_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (up_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (down_proj): lora.Linear(
                (base_layer): Linear(in_features=8960, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=8960, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (act_fn): SiLU()
            )
            (input_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
            (post_attention_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
          )
          (27): Qwen2DecoderLayer(
            (self_attn): Qwen2Attention(
              (q_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=1536, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (k_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (v_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=256, bias=True)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=256, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (o_proj): lora.Linear(
                (base_layer): Linear(in_features=1536, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (rotary_emb): LlamaRotaryEmbedding()
            )
            (mlp): Qwen2MLP(
              (gate_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (up_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=1536, out_features=8960, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=8960, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (down_proj): lora.Linear4bit(
                (base_layer): Linear4bit(in_features=8960, out_features=1536, bias=False)
                (lora_dropout): ModuleDict(
                  (default): Identity()
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=8960, out_features=16, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=16, out_features=1536, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
                (lora_magnitude_vector): ModuleDict()
              )
              (act_fn): SiLU()
            )
            (input_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
            (post_attention_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
          )
        )
        (norm): Qwen2RMSNorm((1536,), eps=1e-06)
        (rotary_emb): LlamaRotaryEmbedding()
      )
      (lm_head): Linear(in_features=1536, out_features=151936, bias=False)
    )
  )
)

9. 配置 SFTTrainer

SFTTrainer (Supervised Fine-tuning Trainer) 是一个专门用于指令微调的训练器。我们需要配置 TrainingArguments 来指定所有的训练参数,如批量大小、学习率、优化器等。

from trl import SFTConfig, SFTTrainer
trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    dataset_text_field = "text",
    max_seq_length = max_seq_length,
    packing = False, # Can make training 5x faster for short sequences.
    args = SFTConfig(
        per_device_train_batch_size = 64,
        gradient_accumulation_steps = 2,
        warmup_steps = 5,
        # num_train_epochs = 1, # Set this for 1 full training run.
        max_steps = 120,
        learning_rate = 2e-4,
        logging_steps = 1,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 1432,
        output_dir = "outputs",
        report_to = "none", # Use this for WandB etc
    ),
)
Unsloth: Tokenizing ["text"] (num_proc=2):   0%|          | 0/20171 [00:00<?, ? examples/s]

10. 开始训练

一切准备就绪后,调用 trainer.train() 即可开始微调过程。训练结束后,会返回包含训练统计信息(如训练损失)的对象。

trainer_stats = trainer.train()

# 打印训练统计信息
print(trainer_stats)
==((====))==  Unsloth - 2x faster free finetuning | Num GPUs used = 1
   \\   /|    Num examples = 20,171 | Num Epochs = 1 | Total steps = 120
O^O/ \_/ \    Batch size per device = 64 | Gradient accumulation steps = 2
\        /    Data Parallel GPUs = 1 | Total batch size (64 x 2 x 1) = 128
 "-____-"     Trainable parameters = 18,464,768 of 1,795,552,768 (1.03% trained)


Unsloth: Will smartly offload gradients to save VRAM!




<div>

  <progress value='120' max='120' style='width:300px; height:20px; vertical-align: middle;'></progress>
  [120/120 21:54, Epoch 0/1]
</div>
<table border="1" class="dataframe">

在这里插入图片描述
在这里插入图片描述

TrainOutput(global_step=120, training_loss=2.3329237937927245, metrics={'train_runtime': 1326.3713, 'train_samples_per_second': 11.58, 'train_steps_per_second': 0.09, 'total_flos': 1.4351091767456563e+17, 'train_loss': 2.3329237937927245})

11. 保存微调后的模型(Lora)

训练完成后,您可以再次进行推理,比较微调后的模型与原始模型的差异。如果对结果满意,可以使用 model.save_pretrained("your_lora_adapter_path") 来保存训练好的 LoRA 适配器。

# model.save_pretrained("qwen-1.5b_lora_model")
# tokenizer.save_pretrained("qwen-1.5b_lora_model")
('qwen-1.5b_lora_model/tokenizer_config.json',
 'qwen-1.5b_lora_model/special_tokens_map.json',
 'qwen-1.5b_lora_model/chat_template.jinja',
 'qwen-1.5b_lora_model/tokenizer.json')
# 模型保存方式二选一(要么使用上面的分开保存,要么使用这里的合并 Lora 保存)
model.save_pretrained_merged("/root/projects/model_weight/qwen-1.5b_lora_model", tokenizer, save_method="merged_16bit")
Found HuggingFace hub cache directory: /root/.cache/huggingface/hub
Checking cache directory for required files...
Cache check failed: model.safetensors not found in local cache.
Not all required files found in cache. Will proceed with downloading.


Unsloth: Merging weights into 16bit:   0%|          | 0/1 [00:00<?, ?it/s]


model.safetensors:   0%|          | 0.00/3.55G [00:00<?, ?B/s]


Unsloth: Merging weights into 16bit: 100%|██████████| 1/1 [03:31<00:00, 211.52s/it]

12. 测试训练后的生成结果

FastLanguageModel.for_inference(model) # Enable native 2x faster inference

question="一个患有急性阑尾炎的病人已经发病5天,腹痛稍有减轻但仍然发热,在体检时发现右下腹有压痛的包块,此时应如何处理?", # Question
inputs = tokenizer([inference_prompt.format(question, "")], return_tensors="pt").to("cuda")

outputs = model.generate(
    input_ids=inputs.input_ids,
    attention_mask=inputs.attention_mask,
    max_new_tokens=1000,
    use_cache=True,
)
output = tokenizer.batch_decode(outputs, skip_special_tokens=True)
print(output[0].split("### Response:")[1])
    <think>
    这个病人有急性阑尾炎,已经5天了,嗯,感觉身体有些发凉。不过,他还是有一点缓解,但还是有发热的。哎,这让我想到,可能是因为他的身体还在恢复,或者有其他原因。
    
    嗯,他有压痛包块,这个包块在右下腹,看起来有点特别。我想,这可能是个包块,因为包块通常会伴随一些疼痛。
    
    首先,我觉得应该先看看包块是什么样的。如果包块太大,可能需要更仔细地处理。而如果包块很小,也许可以先用一些热敷试试。
    
    对,热敷是个好主意,因为热敷可以缓解疼痛。不过,如果包块很小,热敷可能会有副作用,比如感染或者刺激。
    
    哦,对了,我还要考虑他的症状。发热和疼痛,这些症状可能会让包块更严重。所以,如果包块很小,我得小心处理,不能让症状变得更糟。
    
    还有,我得想想,包块是不是真的在病人体内。因为阑尾炎常常引起包块,尤其是急性阶段。
    
    嗯,如果包块很大,那就得更小心处理。可能需要更专业的方法,比如切开包块,然后进行处理。
    
    综上所述,我觉得最合理的做法是先热敷,看看包块的情况。如果包块不大,就用热敷;如果很大,那就得小心处理,可能需要切开包块。
    
    哎,这样下来,我觉得这个思路是对的,应该能帮助这个病人减轻症状,同时减少可能的副作用。
    </think>
    对于一个患有急性阑尾炎的病人,已经发病5天,腹痛稍有缓解但仍有发热的情况,以及右下腹出现压痛包块的情况,处理方式需要根据包块的大小来决定。 
    
    首先,如果包块很小,可以考虑使用热敷来缓解疼痛和降温。热敷可以帮助减轻症状,但需要在确认包块不大后进行,以确保不会对患者造成进一步的不适。 
    
    如果包块较大,建议在医生的指导下进行切开包块处理,这通常需要更专业的医疗操作。切开包块可以更有效地处理包块的大小和位置,以避免感染和刺激性症状。此外,患者在治疗过程中应密切监测病情变化,以确保治疗的有效性。
    
    总之,处理这个病人需要根据包块的大小和症状来选择合适的治疗方法,以达到最佳的治疗效果。
def generate_response(question: str, model, tokenizer, inference_prompt: str, max_new_tokens: int = 1024) -> str:
    """
    使用指定的模型和分词器为给定的医学问题生成响应。

    Args:
        question (str): 需要模型回答的医学问题。
        model: 已加载的 Unsloth/Hugging Face 模型。
        tokenizer: 对应的分词器。
        inference_prompt (str): 用于格式化输入的 f-string 模板。
        max_new_tokens (int, optional): 生成响应的最大 token 数量。默认为 1024。

    Returns:
        str: 模型生成的响应文本,已去除 prompt 部分。
    """
    # 1. 使用模板格式化输入
    prompt = inference_prompt.format(
        question, # 填充问题
        "",       # 留空,让模型生成 CoT 和 Response
    )

    # 2. 将格式化后的 prompt 进行分词,并转移到 GPU
    inputs = tokenizer([prompt], return_tensors="pt").to(model.device)

    # 3. 使用模型生成输出
    # use_cache=True 用于加速解码过程
    outputs = model.generate(
        input_ids=inputs.input_ids,
        attention_mask=inputs.attention_mask,
        max_new_tokens=max_new_tokens,
        use_cache=True,
    )
    
    # 4. 将生成的 token 解码为文本
    # skip_special_tokens=True 会移除像 EOS_TOKEN 这样的特殊标记
    decoded_output = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0]

    # 5. 切分字符串,只返回 "### Response:" 之后的部分
    # 使用 .split() 分割并获取响应内容,.strip() 用于去除可能存在的前后空白字符
    response_part = decoded_output.split("### Response:")
    if len(response_part) > 1:
        return response_part[1].strip()
    else:
        # 如果模型没有生成 "### Response:" 标记,则返回整个生成内容以供调试
        return decoded_output
my_question = "对于一名60岁男性患者,出现右侧胸疼并在X线检查中显示右侧肋膈角消失,诊断为肺结核伴右侧胸腔积液,请问哪一项实验室检查对了解胸水的性质更有帮助?"

response = generate_response(my_question, model, tokenizer, inference_prompt)
print("==================== 模型回答 ====================")
print(response)
==================== 模型回答 ====================
<think>
这个60岁的男性患者,他的右侧胸疼,这让我想到了肺结核。嗯,肺结核通常是胸腔积液,尤其是胸腔积液,因为肺结核会把肺泡压高,导致胸腔积液。嗯,X线检查显示右肋膈角消失,这通常提示有肺结核的胸腔积液。嗯,这让我想到,胸腔积液的性质可能跟肺结核有关系。

那么,为了了解胸腔积液的性质,可能需要一些具体的检查。嗯,血钙、血钙指数、血钙率、血钙血症和血钙血症这些指标能帮助我们了解胸腔积液的类型。因为这些指标能告诉我们胸腔积液的酸碱性,以及是否有钙化。

血钙指数和血钙率,这都是衡量胸腔积液酸碱性的重要指标。血钙指数可以告诉我们胸腔积液的酸碱性,而血钙率则可以告诉我们胸腔积液的钙含量。嗯,这样看来,血钙指数和血钙率似乎是最能帮助我们了解胸腔积液性质的关键指标。

嗯,其他指标比如血钙血症和血钙血症可能只关心钙化的问题,而跟酸碱性关系不大。所以,我倾向于认为血钙指数和血钙率是更关键的指标来了解胸腔积液的性质。

嗯,这样看来,血钙指数和血钙率应该是用来评估胸腔积液的性质最直接的检查方法。所以,针对这个问题,我觉得应该选择这两项指标。
</think>
对于诊断为肺结核伴右侧胸腔积液的60岁男性患者,了解胸腔积液的性质需要使用一些特定的实验室检查。这些检查可以评估胸腔积液的酸碱性和钙化情况。血钙指数和血钙率是最能帮助我们了解胸腔积液性质的关键指标。血钙指数可以告诉我们胸腔积液的酸碱性,而血钙率则可以告诉我们胸腔积液的钙含量。因此,选择血钙指数和血钙率作为实验室检查来了解胸腔积液的性质是有帮助的。
my_question = "对于一名 28 岁的男性患者,工作是程序员,常年熬夜,最近突然感觉头晕目眩,甚至有点恶心。请问有可能是什么疾病?"

response = generate_response(my_question, model, tokenizer, inference_prompt)
print("==================== 模型回答 ====================")
print(response)
==================== 模型回答 ====================
<think>
这个患者是一位28岁的男性,他经常熬夜,最近头晕目眩,还有点恶心。这些症状让我想到一些可能的疾病。首先,他经常熬夜,这可能和他的健康状况有关。通常熬夜会导致身体的某些功能障碍,比如心率加快、心力衰竭等等。

头晕目眩和恶心,这些症状让我联想到脑部的疾病。脑力不足、恶心呕吐这些现象常常出现在脑膜炎、脑膜炎引起的脑病,比如脑膜炎、脑膜炎综合征等。再加上他经常熬夜,可能和脑部的缺血、缺氧有关。

脑膜炎引起的脑病,比如脑膜炎、脑膜炎综合征,这些病通常会伴随脑膜的破坏,导致脑部的疼痛、疼痛、恶心和呕吐。这些症状正好符合患者的症状表现。

不过,我不能完全排除其他可能性。比如,脑膜炎导致的脑膜炎综合征,可能和脑膜的严重破坏有关,但患者并没有表现出严重的脑膜炎症状,只是轻微的恶心和头晕。

再考虑一下脑膜炎引起的脑膜炎综合征,这种病通常与脑膜的破坏有关,所以患者的症状可能更符合这个病的特征。

综上所述,从患者的症状和背景来看,脑膜炎引起的脑膜炎综合征似乎是一个比较合理的解释。
</think>
根据患者的症状和背景信息,这名28岁的男性患者经常熬夜,这可能与脑部的缺血和缺氧有关。在脑部缺血缺氧的情况下,脑膜炎和脑膜炎综合征常常会出现脑膜的严重破坏,导致患者出现脑膜炎引起的脑膜炎综合征。

脑膜炎引起的脑膜炎综合征通常表现为脑膜的严重破坏、疼痛、恶心和呕吐等。虽然患者没有表现出严重的脑膜炎症状,但这些症状和脑膜炎相关的脑膜破坏情况非常吻合。

因此,可以推断这名患者的症状很可能与脑膜炎引起的脑膜炎综合征有关。

13. 实操文件下载

实操文件

结语

通过本文的实践指南,我们展示了如何利用 Unsloth 和 LoRA 技术高效实现 DeepSeek-R1-Distill-Qwen-1.5B 模型的知识蒸馏。这种方案不仅大幅降低了计算资源需求,还为小模型在特定任务上达到接近大模型性能提供了可行路径。

知识蒸馏作为模型压缩和部署的关键技术,在实际应用中具有重要价值。随着硬件限制和部署需求的增加,高效的蒸馏方法将变得更加重要。

期待看到你运用这些技术创造出更优秀的模型!如果在实践中遇到问题或有新的发现,欢迎在评论区交流分享。


本文为原创内容,版权归作者所有,转载需注明出处。

标签模型蒸馏 Unsloth LoRA微调

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐