掘金 人工智能 前天 10:03
使用Unsloth微调DeepSeek-R1蒸馏模型:低显存高效训练实践
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

Unsloth是一个开源库,专为高效微调大型语言模型(LLMs)而设计。它基于Hugging Face Transformers和PEFT技术,旨在减少微调过程中的资源消耗,如显存和训练时间。通过Unsloth,开发者和研究人员可以在消费级GPU上高效地微调Qwen3、Llama 4、Gemma 3、Phi-4等先进的大型语言模型。该库提供多种安装方式,并详细介绍了如何加载预训练模型、准备数据集、添加LoRA层、进行模型训练和推理,以及如何保存和加载微调后的模型。

🛠️Unsloth是一个开源库,旨在帮助开发者和研究人员以更低的资源消耗高效微调大型语言模型(LLMs),它基于Hugging Face Transformers和PEFT技术。

📚Unsloth提供了详细的安装指南,包括Conda和Pip两种方式,并针对可能出现的依赖问题提供了解决方案,例如强制重新安装Unsloth及其依赖项。

🚀Unsloth详细介绍了如何加载预训练模型(如DeepSeek-R1-Distill-Qwen-1.5B),并提供了代码示例,展示了如何使用FastLanguageModel类进行快速加载和初始化。

🧮Unsloth展示了如何通过SFTTrainer和TrainingArguments定义监督式微调的训练流程,包括设置批量大小、梯度累积步数、学习率等参数,以及如何使用混合精度训练和优化器来提高训练效率。

💾Unsloth演示了如何保存和加载微调后的LoRA适配器,包括本地保存和上传到Hugging Face Hub,并提供了详细的代码示例和步骤说明。

unsloth

概述

Unsloth是一个专注于高效微调大语言模型(LLMs)的开源库,旨在帮助开发者和研究人员以更低的资源消耗(如显存、训练时间)对大型语言模型进行微调。它基于Hugging Face Transformers和PEFT(Parameter-Efficient Fine-Tuning)技术构建,并提供了一些优化手段,使得在消费级GPU上也能高效地微调像Qwen3、Llama 4、Gemma 3、Phi-4等现代大语言模型。

GitHub:https://github.com/unslothai/unsloth

文档:https://docs.unsloth.ai/

安装unsloth

Conda安装

创建conda虚拟环境

conda create --name unsloth_env \    python=3.11 \    pytorch-cuda=12.1 \    pytorch cudatoolkit xformers -c pytorch -c nvidia -c xformers \    -y

激活虚拟环境

conda activate unsloth_env

安装unsloth

pip install unsloth

Pip安装

如果设备已有torch 2.5CUDA 12.4,为了减少额外依赖下载,可使用以下命令安装(解决依赖性问题):

pip install --upgrade pippip install "unsloth[cu124-torch250] @ git+https://github.com/unslothai/unsloth.git"

或者在终端中运行以下命令以获取最佳pip安装命令:

wget -qO- https://raw.githubusercontent.com/unslothai/unsloth/main/unsloth/_auto_install.py | python -

例如得到如下安装命令

pip install --upgrade pip && pip install "unsloth[cu121-ampere-torch212] @ git+https://github.com/unslothai/unsloth.git"

如果遇到Unsloth依赖问题,可以尝试通过强制卸载并重新安装Unsloth来解决

pip install --upgrade --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth.gitpip install --upgrade --force-reinstall --no-cache-dir --no-deps git+https://github.com/unslothai/unsloth-zoo.git

推荐安装

创建一个全新环境

conda create -n unsloth python=3.10

直接执行以下命令安装,会自动处理Unsloth相关依赖问题,缺点是下载依赖过多,好处是异常问题少

pip install unsloth

加载预训练模型

加载并初始化一个预训练的DeepSeek-R1蒸馏模型和其对应的分词器,这里测试使用小参数量模型:DeepSeek-R1-Distill-Qwen-1.5B

# 从unsloth库中导入FastLanguageModel类,这是一个用于快速加载和使用大型语言模型的工具。from unsloth import FastLanguageModel# 导入PyTorch库,主要用于张量运算和深度学习模型的构建与训练。import torch# 定义模型路径,指定从哪里加载预训练的DeepSeek-R1模型文件。model_path = "/root/DeepSeek-R1-Distill-Qwen-1.5B"# 设置最大序列长度,表示模型在一次前向传递中可以处理的最大令牌数量。max_seq_length = 2048 # 定义数据类型,设置为None表示将自动选择合适的数据类型(通常根据环境或模型默认值决定)。dtype = None # 启用4位加载模式,可提高模型加载速度,但需要确保硬件支持4位精度。load_in_4bit = True # 调用FastLanguageModel.from_pretrained()方法加载预训练的模型和对应的Tokenizer(分词器)。model, tokenizer = FastLanguageModel.from_pretrained(    model_name = model_path,    max_seq_length = max_seq_length,    dtype = dtype,    load_in_4bit = load_in_4bit,)

异常问题

这里基于已存在的Torch与CUDA,直接获取最佳命令执行安装后,在加载模型过程中遇到了以下几个异常

异常1:

ImportError: Unsloth: Please install unsloth_zoo via `pip install unsloth_zoo`

根据提示额外安装以下依赖

pip install unsloth_zoo

异常2:

/root/miniconda3/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html

根据提示更新jupyter ipywidgets等库

pip install --upgrade jupyter ipywidgets

异常3:

ImportError: cannot import name 'UnencryptedCookieSessionFactoryConfig' from 'pyramid.session' (unknown location)

最终定位于pyramid版本过高导致,降低其版本即可

(base) root@gjob-dev-548033559378931712-taskrole1-0:~/apex-master# pip list |grep pyramidpyramid                        2.0.2pyramid-mailer                 0.15.1
pip install pyramid==1.10.8

数据集准备

自定义一个提示词模板,根据需求与训练数据,设置模型风格,例如企业内部的智能管家、医药领域的专家医生

# 定义字符串模板,用于格式化输入指令、输入内容和输出内容。alpaca_prompt = """以下是一条说明任务的指令,请写一个适当的回复来完成这个请求。说明:{}### 输入:{}回应:{}"""

如果需要微调后的模型回答同时包含推理部分与最终回复部分,那么需要提示词模版添加<think>标签

alpaca_prompt = """以下是描述任务的说明,在回答之前,请仔细思考问题,并创建循序渐进的思路,以确保回答合乎逻辑且准确。### Instruction:你是企业内部智能客服专家,需要回答用户企业规则制度问题。### Question:{}### Response:<think>{}</think>{}"""

准备一个用于微调的数据集,把数据集加载进来,对数据集进行一些格式化,再打印一条出来看看:

# 定义 EOS_TOKEN 作为结束标记EOS_TOKEN = tokenizer.eos_token  # 必须添加 EOS_TOKEN,否则生成可能会无限进行# 格式化提示的函数def formatting_prompts_func(examples):    instructions = examples["instruction"]  # 获取指令(instruction)    inputs = examples["input"]              # 获取输入(input)    outputs = examples["output"]            # 获取输出(output)    texts = []                              # 存储格式化后的文本    # 遍历每一对指令、输入和输出    for instruction, input, output in zip(instructions, inputs, outputs):        # 使用 alpaca_prompt 格式化文本,并添加 EOS_TOKEN        text = alpaca_prompt.format(instruction, input, output) + EOS_TOKEN        texts.append(text)  # 将格式化后的文本添加到列表中    # 返回包含格式化文本的字典    return {"text": texts}# 加载指定的 JSON 文件from datasets import load_datasetdataset = load_dataset("json", data_files="/root/test.json", split="train")  # 从 test.json 文件加载数据print(dataset.column_names)# 应用格式化函数,并批量处理数据dataset = dataset.map(formatting_prompts_func, batched=True)print(dataset['text'][0])

注意: 不同模型在微调时使用的提示词模板格式均不同,需根据具体模型进行适当调整

添加LoRA层

添加LoRA(Low-Rank Adaptation,低秩适应)适配器,应用LoRA技术到一个经过初始化的model上,以优化模型推理性能,同时保持合理的准确性。

model = FastLanguageModel.get_peft_model(    model,    r=16, # 设置LORA的秩,值越大,表示模型能力越强,但占用的内存和计算资源也会增加。    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",                   "gate_proj", "up_proj", "down_proj",], # 指定要在哪些模块上应用PEFT优化    lora_alpha=16, # 设置LORA超参数。通常与r参数一起使用,lora_alpha越大,对低秩增广的影响越显著。    lora_dropout=0, # 在LoRA优化过程中应用到的dropout率,这里设置为0表示不使用dropout,可以防止过拟合,但需要权衡模型性能。    bias="none", # 控制是否引入偏置(bias)项。“none”表示不使用偏置,其他选项可能包括“auto”或其它特定配置。    use_gradient_checkpointing="unsloth",    random_state=3407, # 为随机数生成器的种子,确保模型优化过程的一致性和可重复性,便于调试和实验比较。    use_rslora=False, # 控制是否使用Rank-Stabilized LoRA (RSLORA)方法,一种提升低秩更新稳定性的技术。这里设置为False,采用标准的LoRA方法。    loftq_config=None # 允许对LoFTQ进行进一步配置,但此处未作配置,表示不使用额外功能或保持默认设定。)

模型训练

定义一个监督式微调的训练流程,使用 SFTTrainer 和 TrainingArguments 来配置模型、数据集和训练参数。通过混合精度训练和优化器设置,可以在保证训练效果的同时提高训练效率。

# 导入 SFTTrainer 类,用于监督式微调(Supervised Fine-Tuning)训练from trl import SFTTrainer# 导入 TrainingArguments 类,用于定义训练参数from transformers import TrainingArguments# 导入 is_bfloat16_supported 函数,用于检查当前硬件是否支持 bfloat16 数据类型from unsloth import is_bfloat16_supported# 创建 SFTTrainer 实例,用于管理模型的训练过程trainer = SFTTrainer(    # 指定要训练的模型    model = model,    # 指定用于文本处理的 tokenizer    tokenizer = tokenizer,    # 指定训练数据集    train_dataset = dataset,    # 指定数据集中包含文本的字段名(即数据集中每一行的文本字段)    dataset_text_field = "text",    # 设置输入序列的最大长度,超过此长度的文本将被截断    max_seq_length = max_seq_length,    # 设置数据预处理的并行进程数,这里设置为 2    dataset_num_proc = 2,    # 是否启用序列打包(packing),设置为 False 表示不启用。如果启用,可以显著加快短序列的训练速度(最多 5 倍)    packing = False,    # 定义训练参数,使用 TrainingArguments 类    args = TrainingArguments(        # 每个设备(如 GPU)上的训练批次大小        per_device_train_batch_size = 2,        # 梯度累积步数,用于模拟更大的批次大小        gradient_accumulation_steps = 4,        # 预热步数,用于在训练初期逐步增加学习率        warmup_steps = 5,        # 最大训练步数,训练将在达到此步数时停止        max_steps = 60,        # 学习率,控制模型参数更新的速度        learning_rate = 2e-4, # 2e-4即 0.0002        # 是否使用 fp16(16 位浮点数)混合精度训练        # 如果硬件不支持 bfloat16,则启用 fp16        fp16 = not is_bfloat16_supported(),        # 是否使用 bfloat16(16 位浮点数)混合精度训练        # 如果硬件支持 bfloat16,则启用        bf16 = is_bfloat16_supported(),        # 每隔多少步记录一次日志        logging_steps = 1,        # 使用的优化器,这里使用 8 位 AdamW 优化器        optim = "adamw_8bit",        # 权重衰减系数,用于正则化,防止过拟合        weight_decay = 0.01,        # 学习率调度器类型,这里使用线性调度器        lr_scheduler_type = "linear",        # 随机种子,用于确保实验的可重复性        seed = 3407,        # 训练输出目录,保存模型和日志的路径        output_dir = "outputs",        # 是否将训练日志报告给外部工具(如 WandB)        # 这里设置为 "none",表示不报告        report_to = "none",    ),)trainer_stats = trainer.train()

注意:

批量大小(Batch Size):由每个设备GPU上的批量大小梯度累积步数共同决定

per_device_train_batch_size * gradient_accumulation_steps = 2 * 4 = 8

训练轮数(Epochs):通过最大训练步数数据集大小计算得出

max_steps * batch_size / data_size = epochs = 60 * 8 / 80 =6

模型推理

FastLanguageModel.for_inference(model) # 启用原生2倍速度的推理# 将输入文本通过tokenizer处理并返回PT张量,然后将其移至GPU设备上inputs = tokenizer(    [        alpaca_prompt.format(            "你是企业内部智能客服专家", # 指令(instruction)            "你是谁",      # 输入内容(input)            ""       # 输出内容(output,留空表示开始生成)        )    ],    return_tensors="pt"  # 返回PyTorch张量格式).to("cuda")             # 将tensor移至CUDA GPU设备# 使用模型生成输出,指定最长生成300个新token,并启用缓存以提高性能outputs = model.generate(    # **inputs, # 解包输入参数    input_ids= inputs.input_ids, # 序列化Tokens    attention_mask=inputs.attention_mask, # 帮助模型理解重要部分    max_new_tokens=300,     # 最大生成长度为300个tokens    use_cache=True          # 启用内存缓存,提升模型解码效率)# 将生成的token解码为纯文本形式response = tokenizer.batch_decode(outputs)# 打印最终生成的内容print(response)

流式输出

使用 FastLanguageModel 进行高效的文本生成推理,并结合 TextStreamer 实现实时流式输出。

# 启用模型的推理模式,优化推理速度(通常可以提升 2 倍推理速度)FastLanguageModel.for_inference(model)# 使用 tokenizer 对输入文本进行编码inputs = tokenizer(    [        # 使用 alpaca_prompt 模板格式化输入        alpaca_prompt.format(            "你是企业内部智能客服专家",  # 指令(instruction)            "你是谁",  # 输入内容(input)            "",  # 输出内容(output),留空表示由模型生成        )    ],    return_tensors="pt"  # 返回 PyTorch 张量格式).to("cuda")  # 将输入张量移动到 GPU 上# 导入 TextStreamer 类,用于实时流式输出生成的文本from transformers import TextStreamer# 创建 TextStreamer 实例,传入 tokenizer 以解码生成的 tokentext_streamer = TextStreamer(tokenizer)# 使用模型生成文本# **inputs: 将编码后的输入张量传递给模型# streamer=text_streamer: 使用 TextStreamer 实时输出生成的文本# max_new_tokens=128: 限制生成的最大 token 数量为 128_ = model.generate(**inputs, streamer=text_streamer, max_new_tokens=200)

保存与加载微调模型

保存

调用save_pretrained方法,将训练好的模型(model)与分词器(tokenizer)保存到本地磁盘上的指定目录 "lora_model" 中

# 将训练好的模型保存到本地目录 "lora_model" 中model.save_pretrained("lora_model")  # Local saving# 将分词器保存到本地目录 "lora_model" 中,与模型保存位置一致tokenizer.save_pretrained("lora_model")

注意:这只会保存LoRA适配器,而不是完整模型。

调用 push_to_hub 方法,将本地模型与分词器上传到 Hugging Face Hub的远程仓库中

# 将模型上传到 Hugging Face Hub 的远程仓库,仓库名为 "your_name/lora_model",需要提供认证 tokenmodel.push_to_hub("your_name/lora_model", token = "...") # Online saving# 将分词器上传到 Hugging Face Hub 的远程仓库,与模型同一仓库,同样需要 tokentokenizer.push_to_hub("your_name/lora_model", token = "...") # Online saving

HuggingFaceSettings-Access Tokens下创建一个Token,并配置写权限

# Token令牌Token ="hf_DvrhFnUBZxnL1234NnHvvBRvJcRutcmPeNB"# 将模型上传到 Hugging Face Hub 的远程仓库,仓库名为 "your_name/lora_model",需要提供认证 tokenmodel.push_to_hub("lora_model", token = Token) # Online saving# 将分词器上传到 Hugging Face Hub 的远程仓库,与模型同一仓库,同样需要 tokentokenizer.push_to_hub("lora_model", token = Token) # Online saving

加载

加载刚刚保存用于推理的LoRA适配器

# 从 unsloth 库中导入 FastLanguageModel 类,用于加载和优化语言模型from unsloth import FastLanguageModel# 从预训练模型加载模型和分词器,指定本地模型目录和其他参数model, tokenizer = FastLanguageModel.from_pretrained(    model_name = "lora_model",  # 指定用于训练的模型名称,此处为本地保存的 "lora_model" 目录    max_seq_length = max_seq_length,  # 设置最大序列长度,限制输入和输出的 token 数量    dtype = dtype,  # 指定数据类型(如 float16、bfloat16),影响模型的计算精度和内存使用    load_in_4bit = load_in_4bit,  # 是否以 4 位量化加载模型,以减少内存占用)# 配置模型以启用原生 2 倍加速推理模式FastLanguageModel.for_inference(model)  # Enable native 2x faster inference# 使用 tokenizer 对输入文本进行编码inputs = tokenizer(    [        # 使用 alpaca_prompt 模板格式化输入        alpaca_prompt.format(            "你是企业内部智能客服专家",  # 指令(instruction)            "你是谁",  # 输入内容(input)            "",  # 输出内容(output),留空表示由模型生成        )    ],    return_tensors="pt"  # 返回 PyTorch 张量格式).to("cuda")  # 将输入张量移动到 GPU 上# 导入 TextStreamer 类,用于实时流式输出生成的文本from transformers import TextStreamer# 创建 TextStreamer 实例,传入 tokenizer 以解码生成的 tokentext_streamer = TextStreamer(tokenizer)# 使用模型生成文本_ = model.generate(**inputs, streamer = text_streamer, max_new_tokens = 200)

模型合并与保存

标准Hugging Face Transformers模型格式

使用unsloth库将模型以不同格式(16 位、4 位和仅 LoRA 适配器)合并并保存到本地或上传到 Hugging Face Hub。

# 将模型合并为 16 位格式并保存到本地和远程# 使用 "merged_16bit" 方法将模型和 LoRA 适配器合并为 16 位浮点格式,保存到本地 "model" 目录model.save_pretrained_merged("model", tokenizer, save_method = "merged_16bit",)# 将合并后的 16 位模型上传到 Hugging Face Hub 的 "hf/model" 仓库,需要提供 tokenmodel.push_to_hub_merged("hf/model", tokenizer, save_method = "merged_16bit", token = "")# 将模型合并为 4 位格式并保存到本地和远程# 使用 "merged_4bit" 方法将模型和 LoRA 适配器合并为 4 位量化格式,保存到本地 "model" 目录imodel.save_pretrained_merged("model", tokenizer, save_method = "merged_4bit",)  # 注意:此处疑似笔误,应为 "model"# 将合并后的 4 位模型上传到 Hugging Face Hub 的 "hf/model" 仓库,需要提供 tokenmodel.push_to_hub_merged("hf/model", tokenizer, save_method = "merged_4bit", token = "")# 仅保存 LoRA 适配器到本地和远程# 使用 "lora" 方法,仅保存 LoRA 适配器(不合并基础模型),保存到本地 "model" 目录model.save_pretrained_merged("model", tokenizer, save_method = "lora",)# 将 LoRA 适配器上传到 Hugging Face Hub 的 "hf/model" 仓库,需要提供 tokenmodel.push_to_hub_merged("hf/model", tokenizer, save_method = "lora", token = "")

示例:

model.save_pretrained_merged("/root/new-model", tokenizer, save_method = "merged_16bit",)

结果:

# ll new-model/ -htotal 3.4Gdrwxr-xr-x 2 root root  190 Feb 24 08:36 ./drwx------ 1 root root 4.0K Feb 24 08:36 ../-rw-r--r-- 1 root root  821 Feb 24 08:36 config.json-rw-r--r-- 1 root root  231 Feb 24 08:36 generation_config.json-rw-r--r-- 1 root root 3.4G Feb 24 08:36 model.safetensors-rw-r--r-- 1 root root  472 Feb 24 08:36 special_tokens_map.json-rw-r--r-- 1 root root  11M Feb 24 08:36 tokenizer.json-rw-r--r-- 1 root root 6.7K Feb 24 08:36 tokenizer_config.json

GGUF模型格式

使用unsloth库将模型保存为GGUF格式,支持不同的量化方法,并可以保存到本地或上传到Hugging Face Hub。

GGUF是一种高效的格式,它支持多种量化方法(如 4 位、8 位、16 位量化),可以显著减小模型文件的大小,便于存储和传输,适合在资源受限的设备上运行模型,例如在Ollama上部署时。量化后的模型在资源受限的设备上运行更快,适合边缘设备或低功耗场景。

# 将模型保存为 8 位 Q8_0 GGUF 格式到本地model.save_pretrained_gguf("model", tokenizer,)# 提示用户前往 https://huggingface.co/settings/tokens 获取 token,并将 "hf" 替换为自己的用户名model.push_to_hub_gguf("hf/model", tokenizer, token = "")# 将模型保存为 16 位 GGUF 格式到本地,指定量化方法为 "f16"model.save_pretrained_gguf("model", tokenizer, quantization_method = "f16")# 将 16 位 GGUF 模型上传到 Hugging Face Hub,指定量化方法为 "f16"model.push_to_hub_gguf("hf/model", tokenizer, quantization_method = "f16", token = "")# 将模型保存为 q4_k_m GGUF 格式到本地,指定量化方法为 "q4_k_m"model.save_pretrained_gguf("model", tokenizer, quantization_method = "q4_k_m")# 将 q4_k_m GGUF 模型上传到 Hugging Face Hub,指定量化方法为 "q4_k_m"model.push_to_hub_gguf("hf/model", tokenizer, quantization_method = "q4_k_m", token = "")# 同时保存并上传多种 GGUF 格式(q4_k_m、q8_0、q5_k_m),效率更高    model.push_to_hub_gguf(        "hf/model",  # 将"hf"替换为自己的用户名        tokenizer,   # 分词器对象,用于保存分词器配置        quantization_method = ["q4_k_m", "q8_0", "q5_k_m",],  # 指定多种量化方法        token = "",  # 需要从 https://huggingface.co/settings/tokens 获取 token    )
# 将模型保存为 8 位 Q8_0 GGUF 格式到本地model.save_pretrained_gguf("/root/model-gguf", tokenizer,)

注意:

unsloth的save_pretrained_gguf方法依赖于llama.cpp库来执行模型转换和量化操作。如果llama.cpp未安装,或者其目录中缺少必要的文件,就会导致错误。

RuntimeError: Unsloth: The file ('llama.cpp/llama-quantize' or 'llama.cpp/llama-quantize.exe' if you are on Windows WSL) or 'llama.cpp/quantize' does not exist.But we expect this file to exist! Maybe the llama.cpp developers changed the name or check extension of the llama-quantize file.

llama.cpp的安装参考:使用llama.cpp实现LLM大模型的格式转换、量化、推理、部署

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Unsloth 大语言模型 微调 PEFT Hugging Face
相关文章