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.5
和CUDA 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
在HuggingFace
的Settings-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大模型的格式转换、量化、推理、部署