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 pip

pip 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.git
pip 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 pyramid
pyramid                        2.0.2
pyramid-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_dataset
dataset = 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 以解码生成的 token
text_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",需要提供认证 token
model.push_to_hub("your_name/lora_model", token = "...") # Online saving

# 将分词器上传到 Hugging Face Hub 的远程仓库,与模型同一仓库,同样需要 token
tokenizer.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",需要提供认证 token
model.push_to_hub("lora_model", token = Token) # Online saving

# 将分词器上传到 Hugging Face Hub 的远程仓库,与模型同一仓库,同样需要 token
tokenizer.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 以解码生成的 token
text_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" 仓库,需要提供 token
model.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" 仓库,需要提供 token
model.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" 仓库,需要提供 token
model.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/ -h
total 3.4G
drwxr-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大模型的格式转换、量化、推理、部署

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐