猴哥的第 160 期分享,欢迎追看
前两天,和大家分享了阿里最新开源的推理模型:
比肩满血DS,阿里新王 QwQ-32B 本地部署,Ollma/vLLM 实测对比
有朋友反馈:模型还是太大了。。。
其实,很多情况下,我们只需解决一些特定场景的问题,完全没必要搞这么大的模型。
指令微调了解下?
最近,DeepSeek算命大师很火,今日分享,以算命为例,用 Unsloth 来微调一个垂直领域的推理模型。
- Unsloth 简介
关于大模型指令微调,笔者之前有过分享:
【大模型指令微调实战】小说创作,一键直达天池挑战赛Top50
当时微调用的 peft 框架。
不得不感叹,这个领域技术更新太快,最新出的 Unsloth 极大加速了模型微调的速度,同时降低显存占用。
开源四个月,GitHub 已斩获 34k star。
老规矩,简短介绍下 Unsloth 亮点:
- 所有内核均基于 OpenAI 的 Triton 重写,大幅提升模型训练速度,降低显存占用。
- 实现中不存在近似计算,模型训练的精度损失为零。
- 支持绝大多数主流的 NVIDIA GPU 设备,CUDA 计算能力 7.0+。
- 支持 4bit 和 16bit QLoRA / LoRA 微调(基于 bitsandbytes)
来看看惊人的加速比和显存节省:
- Unsloth 微调教程
2.1 Unsloth 安装
pip 一键安装:
pip install unsloth
2.2 模型准备
这里查看所有支持的模型列表:https://docs.unsloth.ai/get-started/all-our-models
最新的 QwQ-32B 也已支持,包括量化版和原始模型:
为兼顾到绝大部分同学,本次微调选用 DeepSeek 蒸馏版 Qwen2.5 1.5B:
首先,从 huggingface 下载模型:(国内伙伴可引入镜像)
export HF\_ENDPOINT=https://hf-mirror.com # 引入镜像地址
huggingface-cli download --resume-download unsloth/DeepSeek-R1-Distill-Qwen-1.5B --local-dir ./ckpts/qwen-1.5b
2.3 数据集准备
数据是燃料,是模型微调成功的关键。
就像是给孩子补课的教材,这些数据往往需要审核(标注),以便模型有样学样。
比如,如果要让模型学会算命,就得准备一些标注好的命理学知识。
开源社区已有这样的数据集:https://huggingface.co/datasets/Conard/fortune-telling
不妨先下载来试试:
export HF\_ENDPOINT=https://hf-mirror.com # 引入镜像地址
huggingface-cli download --repo-type dataset --resume-download Conard/fortune-telling --local-dir data/fortune-telling
注:采用 huggingface-cli 下载数据集时,加上 --repo-type dataset
数据集格式如下:
OK,一切准备就绪,下面开始炼丹!
2.4 模型微调
模型微调,只需按照以下 8 步走~
step 1:引入依赖
from unsloth import FastLanguageModel, is\_bfloat16\_supported
from transformers import TrainingArguments
from trl import SFTTrainer
from datasets import load\_dataset
step 2:加载模型
max\_seq\_length = 8192 # 模型处理文本的最大长度
# 加载模型
model, tokenizer = FastLanguageModel.from\_pretrained(
model\_name = "ckpts/qwen-1.5b",
max\_seq\_length = max\_seq\_length,
dtype=None, # 自动检测合适的类型
load\_in\_4bit = True,
# device\_map="balanced" # 多卡训练时均衡分布模型权重,默认为sequential
)
step 3:加载数据集
# 定义训练数据格式化字符串模板
train\_prompt\_style="""请遵循指令回答用户问题。
在回答之前,请仔细思考问题,并创建一个逻辑连贯的思考过程,以确保回答准确无误。
### 指令:
你是一位精通八字算命、紫微斗数、风水、易经卦象、塔罗牌占卜、星象、面相手相和运势预测等方面的算命大师。
请回答以下算命问题。
### 问题:
{}
### 回答:
<think>{}</think>
{}
"""
# 加载数据集
dataset = load\_dataset("data/fortune-telling", split="train")
def formatting\_data(examples):
questions = examples["Question"]
cots = examples["Complex\_CoT"]
responses = examples["Response"]
texts = []
for q, c, r in zip(questions, cots, responses):
text = train\_prompt\_style.format(q, c, r) + tokenizer.eos\_token
texts.append(text)
return {"text": texts}
dataset = dataset.map(formatting\_data, batched=True)
可以打印一行数据,输出看看:
Generating train split: 207 examples [00:00, 1319.25 examples/s]
Dataset size: 200 ['Question', 'Response', 'Complex\_CoT']
step 5:定义 LoRA
# 添加 LoRA 权重
model = FastLanguageModel.get\_peft\_model(
model,
r = 16, # Rank of the LoRA matrix
target\_modules = ["q\_proj", "k\_proj", "v\_proj", "o\_proj", "gate\_proj", "up\_proj", "down\_proj",], # Layers to apply LoRA to
lora\_alpha = 16, # LoRA alpha value
lora\_dropout = 0, # Supports any, but = 0 is optimized,防止过拟合,0 表示不drop任何参数
bias = "none", # Supports any, but = "none" is optimized
use\_gradient\_checkpointing = "unsloth", # True or "unsloth" for very long context
random\_state = 3407,
use\_rslora = False, # We support rank stabilized LoRA
loftq\_config = None, # And LoftQ
)
step 6:定义 trainer
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train\_dataset = dataset,
dataset\_text\_field = "text",
max\_seq\_length = max\_seq\_length,
dataset\_num\_proc = 2,
packing = False, # Can make training 5x faster for short sequences.
args = TrainingArguments(
per\_device\_train\_batch\_size = 2, # 每个GPU上的batch size
gradient\_accumulation\_steps = 4, # 梯度累积步数
warmup\_steps = 10,
# max\_steps = 200, # 最大训练步数
num\_train\_epochs=3, # 训练轮数 和 max\_steps 二选一
learning\_rate = 2e-4, # 学习率,默认值是 2.0e-5
fp16 = not is\_bfloat16\_supported(),
bf16 = is\_bfloat16\_supported(),
logging\_steps = 2,
output\_dir = "outputs",
optim = "adamw\_8bit",
seed = 3407,
),
)
step 7:开始训练
train\_stats = trainer.train()
Unsloth 会根据加载的模型和设备情况自动选择 GPU 数量。
如果默认 device\_map="sequential",只有当单卡显存不够时,才占用其他卡。
如果设定 device\_map="balanced",会占用所有卡,并均衡分布模型权重:
看到损失下降,说明成功开始训练:
step 8:模型保存
最后,别忘了保存训练好的模型权重:
model.save\_pretrained("ckpts/lora\_model")
tokenizer.save\_pretrained("ckpts/lora\_model")
注:这里只会保存 LoRA 权重,在adapter\_config.json会指定原始模型位置:
至此,我们成功走完了模型微调之旅,需要完整代码的小伙伴,文末自取!
对于 qwen2.5 1.5B而言,4G 显存即可开启微调:
2.5 模型测试
模型测试代码如下:
# 加载模型
model, tokenizer = FastLanguageModel.from\_pretrained(
model\_name = "ckpts/lora\_model",
max\_seq\_length = max\_seq\_length,
load\_in\_4bit = True,
)
FastLanguageModel.for\_inference(model)
question = '1995年七月初十生,今年是2025年,了解未来五年的运势'
inputs = tokenizer([prompt\_style.format(question)], return\_tensors='pt', max\_length=max\_seq\_length).to("cuda")
outputs = model.generate(inputs['input\_ids'], attention\_mask=inputs['attention\_mask'], max\_length=max\_seq\_length, use\_cache=True)
answer = tokenizer.batch\_decode(outputs, skip\_special\_tokens=True)[0]
print(answer)
注:如果单卡放不下模型权重,会报错,因为模型权重切分了,但 inputs 并没有切分,因此可考虑采用
vLLM 推理。
2.6 vLLM 推理
不了解 vLLM 推理 的小伙伴,可翻看上篇:比肩满血DS,阿里新王 QwQ-32B 本地部署,Ollma/vLLM 实测对比
首先,我们要将微调后的模型保存为 GGUF 格式:
model.save\_pretrained\_gguf("ckpts/merged", tokenizer, quantization\_method="q4\_k\_m")
Unsloth 会自动下载编译 llama.cpp 进行格式转换:
过程中先转成 BF16,然后再进行 4bit 量化,权重大小分别为 3G 和 1G:
转换成功后,一键开启 vLLM 推理:
vllm serve ckpts/merged/unsloth.Q4\_K\_M.gguf --api-key 123 --port 3002
写在最后
本文分享了开源大模型微调工具 Unsloth,并通过一个简单例子,带大家走完了微调全流程。
如果对你有帮助,欢迎点赞收藏 备用。
本文微调+测试完整代码已上传云盘,需要的朋友,公众号后台回复微调自取!
为方便大家交流,新建了一个 AI 交流群,公众号后台「联系我」,拉你进群。
👇 关注猴哥,快速入门AI工具
# AI 工具:
盘点9家免费且靠谱的AI大模型 API,统一封装,任性调用!
免费GPU算力本地跑DeepSeek R1,无惧官方服务繁忙!
# AI应用** :**
弃坑 Coze,我把 Dify 接入了个人微信,AI小助理太强了
我把「FLUX」接入了「小爱」,微信直接出图,告别一切绘画软件!
