DeepSeek :使用 Unsloth + LoRA 微调模型
DeepSeek :使用 Unsloth + LoRA 微调模型
前言
在前几期,我们通过 RAG(检索增强生成)解决了模型“不知道”的问题。但在企业实战中,我们经常遇到另一种瓶颈——“学不会”。
比如:
- 医疗场景: 模型能查到病历,但写出的诊断书语气像科普文章,而不是医生风格。
- 开发场景: 模型能写 Python,但永远学不会你们公司内部那套
MyCorpSDK.do_magic()的写法。 - 格式控: 你要求输出极度复杂的嵌套 JSON,模型在长文本下总是偶尔漏个括号。
这时候,RAG 已经不够用了。我们需要 SFT (Supervised Fine-Tuning)。如果说 RAG 是给学生一本书让他开卷考试,那么 SFT 就是让学生去实习三个月,彻底掌握内功。
本文将带你使用目前全球最快的微调框架 Unsloth,在单卡消费级显卡上,对 DeepSeek-R1-Distill-Llama-8B 进行微调,让它学会识别并使用特定的行业术语。
一、 SFT 的核心逻辑:LoRA 与 QLoRA
微调一个大模型(比如 671B 的 DeepSeek-V3)需要几百万美元的算力,这显然不现实。但我们有 LoRA (Low-Rank Adaptation)。
- 原理: 冻结模型原本的亿万参数,只在旁边“外挂”两个非常小的矩阵进行训练。
- QLoRA: 进一步将模型量化为 4-bit,大幅降低显存需求。
- 结论: 我们可以用不到 16GB 的显存(RTX 3060/4060Ti 16G 或 RTX 3090),去微调 DeepSeek 的 8B 蒸馏版模型。
二、 环境准备:神器 Unsloth
为什么选 Unsloth?
- 快: 训练速度比 HuggingFace 官方库快 2-5 倍。
- 省: 显存占用减少 50% 以上。
- 傻瓜化: 解决了复杂的依赖冲突问题。
1. 安装依赖
建议使用 Linux 环境或 WSL2(Windows Subsystem for Linux)。
pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps "xformers<0.0.27" "trl<0.9.0" peft accelerate bitsandbytes
三、 数据准备:高质量胜过大数据
微调的核心不是代码,是数据。SFT 需要的是 (Instruction, Input, Output) 格式的数据对。
假设我们要训练一个**“中医诊断助手”**,通用模型可能会说“建议去医院检查”,而我们需要它输出专业的中医术语。
构造数据集 (data.json)
[
{
"instruction": "根据患者描述进行中医诊断。",
"input": "患者男,45岁。主诉失眠多梦,伴有心悸健忘,面色无华,舌淡苔白,脉细弱。",
"output": "【诊断】心血不足证。\n【治法】养血安神。\n【方药】归脾汤加减。方中用黄芪、人参、白术、当归、茯神、远志、酸枣仁..."
},
{
"instruction": "根据患者描述进行中医诊断。",
"input": "患者自觉胃脘灼痛,吞酸嘈杂,口干口苦,舌红苔黄,脉弦数。",
"output": "【诊断】肝火犯胃证。\n【治法】清肝泻火,和胃止痛。\n【方药】左金丸合化肝煎加减..."
}
]
Pro Tip: 刚开始实验时,准备 50-100 条高质量的高人工撰写数据,效果往往好于 1 万条机器生成的垃圾数据。
四、 实战代码:开启微调之旅
创建一个 Python 脚本 train.py。
1. 加载模型
from unsloth import FastLanguageModel
import torch
max_seq_length = 2048 # 支持 RoPE 缩放,可达 128k
dtype = None # 自动检测 Float16/Bfloat16
load_in_4bit = True # 开启 4bit 量化 (QLoRA)
# 加载 DeepSeek-R1 的 8B 蒸馏版 (基于 Llama3 架构)
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,
)
2. 添加 LoRA 适配器
model = FastLanguageModel.get_peft_model(
model,
r = 16, # LoRA 秩,建议 8, 16, 32
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",],
lora_alpha = 16,
lora_dropout = 0, # Unsloth 建议为 0
bias = "none",
use_gradient_checkpointing = "unsloth", # 显存优化技术
random_state = 3407,
)
3. 加载与格式化数据
我们需要将 JSON 数据转换成模型能听懂的 Prompt 格式(Alpaca 格式)。
alpaca_prompt = """Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.
### Instruction:
{}
### Input:
{}
### Response:
{}"""
from datasets import load_dataset
def formatting_prompts_func(examples):
instructions = examples["instruction"]
inputs = examples["input"]
outputs = examples["output"]
texts = []
for instruction, input, output in zip(instructions, inputs, outputs):
text = alpaca_prompt.format(instruction, input, output) + tokenizer.eos_token
texts.append(text)
return { "text" : texts, }
dataset = load_dataset("json", data_files="data.json", split="train")
dataset = dataset.map(formatting_prompts_func, batched = True)
4. 开始训练
使用 HuggingFace 的 SFTTrainer。
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train_dataset = dataset,
dataset_text_field = "text",
max_seq_length = max_seq_length,
dataset_num_proc = 2,
args = TrainingArguments(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
warmup_steps = 5,
max_steps = 60, # 演示用,实际建议根据 loss 收敛情况设定 (如 1-3 epochs)
learning_rate = 2e-4,
fp16 = not torch.cuda.is_bf16_supported(),
bf16 = torch.cuda.is_bf16_supported(),
logging_steps = 1,
optim = "adamw_8bit", # 8bit 优化器,进一步省显存
output_dir = "outputs",
),
)
trainer.train()
五、 效果验证与保存
训练完成后,我们可以直接进行推理,看看它是否学会了“中医范儿”。
# 推理测试
FastLanguageModel.for_inference(model) # 开启原生 2倍速 推理
inputs = tokenizer(
[
alpaca_prompt.format(
"根据患者描述进行中医诊断。", # Instruction
"患者女,痛经,小腹冷痛,得热痛减,手脚冰凉。", # Input
"", # Output - leave blank for generation
)
], return_tensors = "pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens = 128, use_cache = True)
print(tokenizer.batch_decode(outputs)[0])
预期结果:
模型不再说“多喝热水”,而是输出:“【诊断】寒凝血瘀证…【方药】少腹逐瘀汤…”
保存模型
为了在 Ollama 或其他地方使用,我们将 LoRA 权重保存下来,甚至直接合并为 GGUF 格式。
# 1. 保存 LoRA 适配器 (仅几百MB)
model.save_pretrained("lora_model")
# 2. 保存并合并为 GGUF (可直接导入 Ollama!)
model.save_pretrained_gguf("model_gguf", tokenizer, quantization_method = "q4_k_m")
拿到 model_gguf.gguf 后,你只需要在 Ollama 中创建一个 Modelfile:
FROM ./model_gguf.gguf
SYSTEM "你是一个专业的中医AI助手。"
然后 ollama create my-tcm-model -f Modelfile,你就拥有了一个完全私有的、甚至可以离线运行的行业专家模型!
六、 避坑指南 (Pro Tips)
- 过拟合风险: 如果数据太少(<20条)且训练步数太多,模型会变成“复读机”,只能回答训练集里的问题。解决方法是增加数据多样性或降低
max_steps。 - Prompt 格式: 训练时的 Prompt 格式(Alpaca)必须与推理时完全一致,连换行符都不能差,否则效果大打折扣。
- 灾难性遗忘: 微调后的模型可能会变“笨”,比如不再会做数学题。这是正常的,因为 SFT 是为了专门任务。如果需要保留通用能力,需要混合通用数据集一起训练。
七、 总结
通过 SFT,可以不再受限于 DeepSeek 原始的能力,而是可以改造模型,使其成为自己想要的助手。
请大家点点关注,谢谢大家了
更多推荐
所有评论(0)