当前位置: 首页 > news >正文

基于unsloth微调大模型并上传到huggingface

本文基于B站up主 code秘密花园  的代码编写,源码地址 ,并说明有关潜在的坑。如果无法打开也可以在文章后面找到源码。该代码使用unsloth框架微调DeepSeek-R1-Distill-Llama-8B,数据集使用https://huggingface.co/datasets/Conard/fortune-telling, 旨在将模型微调为一个算命大师。

数据集格式:{ Question, Response,  Complex_CoT } ,因为微调的是一个推理模型,所以包含思维链。

微调的大致流程如下

环境安装

需要安装conda、cuda、unsloth。conda和cuda不赘述了,unsloth可在官方仓库中找到安装方法。

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

pip install unsloth

源码

设置代理

这一步不是源码给出的。主要用于设置代码涉及网络请求的时候设置代理,避免下载失败。前提是已经有了代理,比如clash的代理端口是7890,这时就可以直接直接将127.0.0.1:7890作为代理地址。

import os
os.environ["http_proxy"] = "http://127.0.0.1:7890"
os.environ["https_proxy"] = "http://127.0.0.1:7890"

加载模型

from unsloth import FastLanguageModel  # 导入FastLanguageModel类,用来加载和使用模型
import torch  # 导入torch工具,用于处理模型的数学运算

max_seq_length = 2048  # 设置模型处理文本的最大长度,相当于给模型设置一个“最大容量”
dtype = None  # 设置数据类型,让模型自动选择最适合的精度
load_in_4bit = True  # 使用4位量化来节省内存,就像把大箱子压缩成小箱子

# 加载预训练模型,并获取tokenizer工具
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="unsloth/DeepSeek-R1-Distill-Llama-8B",  # 指定要加载的模型名称
    max_seq_length=max_seq_length,  # 使用前面设置的最大长度
    dtype=dtype,  # 使用前面设置的数据类型
    load_in_4bit=load_in_4bit,  # 使用4位量化
    token="hf_...",  # 如果需要访问授权模型,可以在这里填入密钥
)

运行效果如下,这一步是在从huggingface上下载unsloth/DeepSeek-R1-Distill-Llama-8B模型,也就是 https://huggingface.co/unsloth/DeepSeek-R1-Distill-Llama-8B 。其中token参数不需要填写。

如果下载的时候报错,可以参考下设置代理的步骤。可能不会出现进度条,但是通过clash的显示可以确定,他正在疯狂下载模型。网速飙到了40MB/s...

模型下载地址默认在 ~/.cache/huggingface/hub/models--unsloth--deepseek-r1-distill-llama-8b-unsloth-bnb-4bit/下。 (ubuntu)

微调前测试

prompt_style = """以下是描述任务的指令,以及提供进一步上下文的输入。
请写出一个适当完成请求的回答。
在回答之前,请仔细思考问题,并创建一个逻辑连贯的思考过程,以确保回答准确无误。

### 指令:
你是一位精通卜卦、星象和运势预测的算命大师。
请回答以下算命问题。

### 问题:
{}

### 回答:
<think>{}"""
# 定义提示风格的字符串模板,用于格式化问题

question = "1992年闰四月初九巳时生人,女,想了解健康运势"
# 定义具体的算命问题
FastLanguageModel.for_inference(model)
# 准备模型以进行推理

inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")
# 使用 tokenizer 对格式化后的问题进行编码,并移动到 GPU

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)
# 解码模型生成的输出为可读文本

print(response[0])
# 打印生成的回答部分

一个可能的回复。

加载数据集

# 定义结束标记(EOS_TOKEN),用于指示文本的结束
EOS_TOKEN = tokenizer.eos_token  # 必须添加结束标记

# 导入数据集加载函数
from datasets import load_dataset
# 加载指定的数据集,选择中文语言和训练集的前200条记录
dataset = load_dataset("Conard/fortune-telling", 'default', split = "train[0:200]", trust_remote_code=True)
# 打印数据集的列名,查看数据集中有哪些字段
print(dataset.column_names)

数据集保存在~/.cache/huggingface/datasets下

执行会加载数据集,并打印数据集的已有字段,即{ Question, Response,  Complex_CoT }

格式化数据集

# 定义一个函数,用于格式化数据集中的每条记录
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_style.format(input, cot, output) + EOS_TOKEN
        texts.append(text)  # 将格式化后的文本添加到列表中
    return {
        "text": texts,  # 返回包含所有格式化文本的字典
    }

dataset = dataset.map(formatting_prompts_func, batched = True)
dataset["text"][0]

设置lora参数

FastLanguageModel.for_training(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"],
    lora_alpha = 16,  # 设置 LoRA 的超参数,影响可训练参数的训练方式
    lora_dropout = 0,  # 设置防止过拟合的参数,这里设置为 0 表示不丢弃任何参数
    bias = "none",    # 设置是否添加偏置项,这里设置为 "none" 表示不添加
    use_gradient_checkpointing = "unsloth",  # 使用优化技术节省显存并支持更大的批量大小
    random_state = 3407,  # 设置随机种子,确保每次运行代码时模型的初始化方式相同
    use_rslora = False,  # 设置是否使用 Rank Stabilized LoRA 技术,这里设置为 False 表示不使用
    loftq_config = None,  # 设置是否使用 LoftQ 技术,这里设置为 None 表示不使用
)

设置SFT的超参数

from trl import SFTTrainer  # 导入 SFTTrainer,用于监督式微调
from transformers import TrainingArguments  # 导入 TrainingArguments,用于设置训练参数
from unsloth import is_bfloat16_supported  # 导入函数,检查是否支持 bfloat16 数据格式

trainer = SFTTrainer(  # 创建一个 SFTTrainer 实例
    model=model,  # 传入要微调的模型
    tokenizer=tokenizer,  # 传入 tokenizer,用于处理文本数据
    train_dataset=dataset,  # 传入训练数据集
    dataset_text_field="text",  # 指定数据集中 文本字段的名称
    max_seq_length=max_seq_length,  # 设置最大序列长度
    dataset_num_proc=2,  # 设置数据处理的并行进程数
    packing=False,  # 是否启用打包功能(这里设置为 False,打包可以让训练更快,但可能影响效果)
    args=TrainingArguments(  # 定义训练参数
        per_device_train_batch_size=2,  # 每个设备(如 GPU)上的批量大小
        gradient_accumulation_steps=4,  # 梯度累积步数,用于模拟大批次训练
        warmup_steps=5,  # 预热步数,训练开始时学习率逐渐增加的步数
        max_steps=75,  # 最大训练步数
        learning_rate=2e-4,  # 学习率,模型学习新知识的速度
        fp16=not is_bfloat16_supported(),  # 是否使用 fp16 格式加速训练(如果环境不支持 bfloat16)
        bf16=is_bfloat16_supported(),  # 是否使用 bfloat16 格式加速训练(如果环境支持)
        logging_steps=1,  # 每隔多少步记录一次训练日志
        optim="adamw_8bit",  # 使用的优化器,用于调整模型参数
        weight_decay=0.01,  # 权重衰减,防止模型过拟合
        lr_scheduler_type="linear",  # 学习率调度器类型,控制学习率的变化方式
        seed=3407,  # 随机种子,确保训练结果可复现
        output_dir="outputs",  # 训练结果保存的目录
        report_to="none",  # 是否将训练结果报告到外部工具(如 WandB),这里设置为不报告
    ),
)

开始训练

trainer_stats = trainer.train()

然后显卡就开始疯狂旋转了~~

设置SFT的超参数步骤里,steps设置的75,75步之后,loss为0.001200,结束训练了。

# 将模型切换到推理模式,准备回答问题
FastLanguageModel.for_inference(model)
question = "我是2001年9月出生,我的学业运势如何??"
# 将问题转换成模型能理解的格式,并发送到 GPU 上
inputs = tokenizer([prompt_style.format(question, "")], return_tensors="pt").to("cuda")

# 让模型根据问题生成回答,最多生成 4000 个新词
outputs = model.generate(
    input_ids=inputs.input_ids,  # 输入的数字序列
    attention_mask=inputs.attention_mask,  # 注意力遮罩,帮助模型理解哪些部分重要
    max_new_tokens=4000,  # 最多生成 4000 个新词
    use_cache=True,  # 使用缓存加速生成
)

# 将生成的回答从数字转换回文字
response = tokenizer.batch_decode(outputs)

# 打印回答
print(response[0])

测试模型

ennn说实话我看不出来。。。。管他呢,不瞎回答就行了。保存模型!

保存模型

GGUF是一种用于存储LLM数据的高效量化格式。源码里提供了3种保存方法,4b、8b、16b量化。代码选择的8b量化,在文件大小和运行速度之间折了个中。然后根目录就会多一个model文件夹,即模型文件夹。

# 将模型保存为 8 位量化格式(Q8_0)
# 这种格式文件小且运行快,适合部署到资源受限的设备
if True: model.save_pretrained_gguf("model", tokenizer,)

# 将模型保存为 16 位量化格式(f16)
# 16 位量化精度更高,但文件稍大
if False: model.save_pretrained_gguf("model_f16", tokenizer, quantization_method = "f16")

# 将模型保存为 4 位量化格式(q4_k_m)
# 4 位量化文件最小,但精度可能稍低
if False: model.save_pretrained_gguf("model", tokenizer, quantization_method = "q4_k_m")

保存有可能会报错:RuntimeError: Unsloth: The file 'llama.cpp/llama-quantize' or 'llama.cpp/quantize' does not exist,可以参考以下链接解决:

https://github.com/unslothai/unsloth/issues/1936

然后下次就可以通过直接指定本地目录就可以加载模型了

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="/home/zemelee/code/q11e/model",  # 模型保存路径
    max_seq_length=max_seq_length,  # 使用前面设置的最大长度
    dtype=dtype,  # 使用前面设置的数据类型
    load_in_4bit=load_in_4bit,  # 使用4位量化
)

将模型上传到hf

上传需要hf的令牌,可到hf的官网创建并填写在代码中。必须选择具有写权限的token,因为需要将模型上传到自己的仓库中。将自己的用户名和仓库名写入代码中,不需要提前创建仓库。

# huggingface 令牌
HUGGINGFACE_TOKEN = "hf_...."
# 导入 Hugging Face Hub 的 create_repo 函数,用于创建一个新的模型仓库
from huggingface_hub import create_repo

# 在 Hugging Face Hub 上创建一个新的模型仓库
create_repo("black-sugar/TVBS", token=HUGGINGFACE_TOKEN, exist_ok=True)

# 将模型和分词器上传到 HuggingFace Hub 上的仓库
model.push_to_hub_gguf("black-sugar/TVBS", tokenizer, token=HUGGINGFACE_TOKEN)

然后模型就会自动创建仓库并上传模型文件。虽然不一定有进度条,但是clash的上传速率飙到了49MB/s,说明正在上传。

然后查看:https://huggingface.co/black-sugar/TVBS  模型有了!

ollama跑模型

上传之后,可以通过以下命令加载模型。

ollama run hf.co/black-sugar/TVBS

试了一下ollama run /home/zemelee/code/q11e/model,无法加载,不清楚为什么。 

但是有可能会卡在小文件的下载上,这是需要将原命令中的“hf.co”改为“hf-mirror.com”,执行一次新的命令。然后就能下载完成了。

相关文章:

  • zephyr RTOS 中 bt_le_adv_start函数的功能应用
  • 基础知识:离线安装docker、docker compose
  • Docker 代理配置全攻略:从入门到企业级实践
  • iOS设备文件管理实践
  • 【数据分享】1981-2024年全国各城市逐日、逐月、逐年平均气温(Shp格式)
  • 高性能内存kv数据库Redis
  • 个人博客系统
  • 【转载翻译】Open3D和PCL的一些比较
  • 如何协调跨部门资源争夺
  • 【KWDB 创作者计划】_产品技术解读_1
  • 谈谈 typescript 中 namespace 的理解
  • AQchat
  • Vite配置postcss-px-to-viewport
  • 代理模式简述
  • 贪心算法(19)(java)重构字符串
  • 银河麒麟系统添加开机自启动
  • 【AI】使用Huggingface模型实现文本内容摘要器
  • DeepSeek 接入 Word 完整教程
  • shell 编程之循环语句
  • UNet深度学习实战遥感图像语义分割
  • 云南铁路:打造“铁路+金融+产业”融合发展生态
  • 民生银行一季度净利127.42亿降逾5%,营收增7.41%
  • 五一假期上海地铁部分线路将延时运营,这些调整请查收
  • 中国农业国际交流协会会长王守聪失联已逾半年,协会启动罢免
  • 深入贯彻中央八项规定精神学习教育中央指导组培训会议召开
  • 金融街:去年净亏损约110亿元,今年努力实现经营稳健和财务安全