本地微调 LLM:ollama + unsloth 实战经验
目录
先说什么时候值得微调
微调是成本很高的操作。在动手之前先问自己:
问题 1:prompt engineering 能解决吗?
→ 能:不要微调
问题 2:RAG 能解决吗?
→ 能:不要微调
问题 3:模型本身能力不够吗?
→ 是:考虑微调值得微调的典型场景:
- 角色/语气:让模型用特定风格回答
- 格式输出:固定格式的 API 响应
- 垂直领域术语:医疗、法律、金融等专业词汇
- 私有知识:公司内部概念、流程
不值得微调的:
- 引入新知识(RAG 更好)
- 修复幻觉(RAG + fact-checking)
- 提升推理能力(更强的 base 模型更值)
工具选择
Ollama + Modelfile
Ollama 支持通过 Modelfile 微调模型:
# 创建微调配置
FROM llama3.2
PARAMETER temperature 0.7
SYSTEM """
你是一个资深 Go 工程师。
回答时用中文,代码示例用 Go。
"""这个不算真正的微调,但用 System Prompt 调语气够用了。
Unsloth(真正微调)
如果要做真正的微调,用 Unsloth:
# 安装
pip install unsloth
# 支持的模型
# - Llama 3.2 (1B, 3B, 8B, 70B)
# - Phi-3.5 (3.8B)
# - Gemma 2 (2B, 9B, 27B)Unsloth 的优势:QLoRA 微调,24GB GPU 显存可以微调 70B 模型。
数据准备
数据格式
{"text": "<|user|>\n帮我写一个 FastAPI endpoint\n<|assistant|>\nfrom fastapi import FastAPI\n\napp = FastAPI()\n\[email protected](\"/users/{user_id}\")\ndef get_user(user_id: int):\n return {\"id\": user_id}"}格式要点:
- user/assistant 角色标记清晰
- 每个样本独立完整
- 1000-10000 条足够,不要盲目堆量
数据质量远比数量重要
# 差数据(10000 条)
{"text": "帮我写代码\n写代码"}
{"text": "解释这个\n那个"}
# 好数据(1000 条)
{"text": "<|user|>...<|assistant|>..."} # 每条完整、有意义实测:500 条高质量数据 > 10000 条噪音数据。
训练流程
完整流程
from unsloth import FastLanguageModel
import torch
# 1. 加载模型(4-bit 量化)
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/llama-3.2-3B",
max_seq_length=2048,
load_in_4bit=True,
)
# 2. 添加 LoRA adapter
model = FastLanguageModel.get_peft_model(
model,
r=16,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_alpha=16,
lora_dropout=0,
)
# 3. 训练
from trl import SFTTrainer
from transformers import TrainingArguments
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=train_dataset,
dataset_text_field="text",
max_seq_length=2048,
args=TrainingArguments(
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
warmup_steps=10,
max_steps=100,
fp16=not torch.cuda.is_bf16_supported(),
logging_steps=10,
),
)
trainer.train()推理
FastLanguageModel.for_inference(model)
inputs = tokenizer([prompt], return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=256)常见坑
1. 过拟合(最常见)
# 症状:训练后模型在训练数据上表现完美,泛化很差
# 解决:
# - 减少训练步数
# - 增加数据集多样性
# - 降低 LoRA r 值
# 判断标准:看 validation loss 不只是 train loss2. 灾难遗忘
# 症状:微调后模型丢失了原有能力(比如中文理解)
# 解决:
# - 用 DPO (Direct Preference Optimization) 而不是纯 SFT
# - 在训练数据中混入通用数据(20-30%)3. 训练不稳定
# 症状:loss 发散、NaN
# 解决:
# - 降低 learning rate(从 2e-4 → 5e-5)
# - 增加 warmup steps
# - 检查数据格式是否有问题什么配置的 GPU 能跑
| GPU | 可微调模型 | 时间(1000 steps) |
|---|---|---|
| RTX 4070 8GB | Llama 3.2 1B / Phi-3.5 3B | ~3 小时 |
| RTX 4080 12GB | Llama 3.2 3B / Gemma 2 2B | ~2 小时 |
| RTX 4090 24GB | Llama 3.2 8B / Gemma 2 9B | ~3 小时 |
| A100 40GB | Llama 3.2 70B | ~1 小时 |
结论
本地微调已经平民化了。RTX 4080 以上就能玩。
但微调前先问自己:prompt engineering 和 RAG 真的不够吗?微调的成本(时间 + 数据准备 + 迭代)往往比想象的更高。
真正值得微调的场景不多,但一旦用对地方,效果很显著。