🎯 手把手教你做模型微调
以 LoRA 为例,从数据准备到部署的完整实战
模型微调是将通用的预训练大模型适配到特定任务/领域的关键技术。本文以最流行的 LoRA(Low-Rank Adaptation) 方法为例,从原理到实战,完整走一遍微调流程。
一、什么时候需要微调?
| 场景 | 怎么做 | 推荐方法 |
|---|---|---|
| 想让模型学会特定格式(如 JSON 输出、客服话术) | 给它看一批符合格式的例子 | ⭐ LoRA / QLoRA |
| 想让模型掌握特定领域知识(如医疗、法律) | 用领域数据继续训练 | LoRA / 全参微调 |
| 想让模型记住企业私有数据 | 用 RAG(不用改模型) | RAG(不需要微调!) |
| 不确定效果,先试试 | 写 Prompt 就够 | Prompt Engineering(零成本) |
💡 原则:能写 Prompt 解决的不要微调,能 RAG 解决的不要微调。微调只有在你需要改变模型的行为方式时才值得做。
二、LoRA 原理(3 分钟搞懂)
LoRA(Low-Rank Adaptation,低秩适配)的核心思想极其简单:
模型原来的权重矩阵 W(例如 4096×4096)
↓
我们不修改 W,而是在旁边挂两个小矩阵 A 和 B:
W' = W + A × B
┌────────────┐ ┌───┐
│ W │ + │ A │ × │ B │
│ (4096×4096)│ │ │ └───┘
│ │ │ │
└────────────┘ │ │
└───┘
r=8 (低秩)
A: 4096×8, B: 8×4096
参数对比:
• 原矩阵 W: 4096×4096 = 16,777,216 个参数
• LoRA 矩阵 A×B: 4096×8 + 8×4096 = 65,536 个参数
• 只训练 0.39% 的参数!
| 对比项 | 全参微调 | LoRA | QLoRA |
|---|---|---|---|
| 训练参数量 | 100% | < 1% | < 0.1% |
| 显存需求(7B 模型) | ~120GB | ~28GB | ~12GB(4bit) |
| 训练速度 | 慢 | 快 | 较快 |
| 效果 | 最佳(理论上) | 接近全参 | 稍差但可接受 |
| 推荐场景 | 有 H100 集群 | 大多数人 | 消费级显卡 |
三、环境准备
3.1 硬件要求
| 模型大小 | 最低显存(LoRA) | 最低显存(QLoRA) | 推荐显卡 |
|---|---|---|---|
| 1~3B | 8GB | 4GB | RTX 3060 / M系列 Mac |
| 7~8B | 24GB | 12GB | RTX 4090 / A10 |
| 13~14B | 48GB | 24GB | A100 40GB / 2xRTX 4090 |
| 33~34B | 80GB+ | 48GB | A100 80GB |
| 70~72B | 多卡 | 80GB | A100x2 或 H100 |
3.2 安装 LLaMA-Factory(推荐)
# 1. 克隆仓库 git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory # 2. 安装依赖 pip install -r requirements.txt # 3. 安装额外功能(推荐) pip install .[torch,metrics] # 4. 验证安装 llamafactory-webui --help
3.3 直接使用 HuggingFace PEFT(灵活)
pip install transformers datasets peft accelerate bitsandbytes # bitsandbytes 用于 QLoRA(4bit 量化)
四、数据准备(最关键的一步)
⚠️ 微调的效果 80% 取决于数据质量,20% 取决于技术参数。
4.1 数据格式
LLaMA-Factory 支持多种数据格式。以下是最常用的 ShareGPT 格式(支持多轮对话):
[
{
"conversations": [
{
"from": "human",
"value": "请帮我写一封请假邮件,我要请三天假去参加婚礼。"
},
{
"from": "assistant",
"value": "好的,这是为您准备的请假邮件草稿:\n\n尊敬的张经理:\n\n您好!因参加亲友婚礼,我计划于 6 月 10 日至 6 月 12 日请假三天。期间我会妥善交接工作,确保不影响团队进度。\n\n恳请批准。\n\n此致\n敬礼\n\n王小明"
}
]
},
// ... 更多数据
]
或者更简单的 Alpaca 格式(单轮问答):
[
{
"instruction": "请帮我写一封请假邮件",
"input": "我要请三天假去参加婚礼",
"output": "尊敬的张经理:\n\n您好!..."
}
]
4.2 数据质量原则
| 原则 | 为什么 | 怎么做 |
|---|---|---|
| 数量 | LoRA 通常需要 500~5000 条高质量数据 | 太少学不会,太多边际效益递减 |
| 质量 | 一条脏数据=十条好数据的负面效果 | 人工审核,去掉格式错误、内容冲突 |
| 多样性 | 覆盖各种输入情况,避免过拟合 | 确保输入指令、问题类型分布均匀 |
| 平衡 | output 中不要全是同一个模式 | 输出风格、长度、结构多样化 |
| 对齐 | 保持和基础模型的格式一致 | 参考模型的 Chat Template |
4.3 数据生成技巧(用大模型造数据)
没有现成数据?用 GPT-4 / DeepSeek 等大模型帮你造:
Prompt 模板:
"你是一个数据生成助手。请为以下场景生成 50 条高质量的微调训练数据。
每条数据包括 instruction 和 output。
场景:{描述你的场景}
要求:{具体输出要求}
请确保数据多样化。以 JSON 数组格式返回。"
五、实战训练
5.1 方案 A:使用 LLaMA-Factory WebUI(推荐新手)
第一步:启动 WebUI CUDA_VISIBLE_DEVICES=0 llamafactory-webui --host 0.0.0.0 --port 7860 打开浏览器访问 http://localhost:7860 第二步:配置训练参数 页面字段: 你的填写: ───────────────────────────────────────────────── 语言 zh(中文) 模型名称 Qwen2.5-7B-Instruct(选你要的模型) 微调方法 lora 数据集 选择你准备好的数据 学习率 5e-5 训练轮数 3.0 最大序列长度 2048 LoRA rank 8 LoRA alpha 16 LoRA target q_proj,v_proj(7B 模型) 第三步:点击「开始训练」 等待进度条走完...
5.2 方案 B:用 Python 脚本(灵活可控)
import torch
from transformers import (
AutoModelForCausalLM, AutoTokenizer,
TrainingArguments, BitsAndBytesConfig
)
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
from trl import SFTTrainer
# 1. 加载模型(4bit 量化,节省显存)
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16
)
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
quantization_config=bnb_config,
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
# 2. 配置 LoRA
lora_config = LoraConfig(
r=16, # LoRA 秩(越大越强,也越贵)
lora_alpha=32, # 缩放系数
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# 3. 加载数据
dataset = load_dataset("json", data_files="your_data.json")
# 4. 配置训练参数
training_args = TrainingArguments(
output_dir="./qwen-lora-output",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=5e-5,
num_train_epochs=3,
logging_steps=10,
save_steps=100,
fp16=True,
report_to="none"
)
# 5. 启动训练
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
tokenizer=tokenizer,
max_seq_length=2048
)
trainer.train()
# 6. 保存 LoRA 权重
model.save_pretrained("./my-lora-adapter")
tokenizer.save_pretrained("./my-lora-adapter")
5.3 方案 C:使用 LLaMA-Factory 命令行
# 写一个 YAML 配置文件 train_config.yaml model_name_or_path: Qwen/Qwen2.5-7B-Instruct dataset: your_dataset_name dataset_dir: ./data output_dir: ./output/qwen-lora finetuning_type: lora quantization_bit: 4 # QLoRA (4bit) quantization_type: nf4 lora_rank: 8 lora_alpha: 16 lora_dropout: 0.05 lora_target: q_proj,v_proj per_device_train_batch_size: 4 gradient_accumulation_steps: 4 learning_rate: 5e-5 num_train_epochs: 3.0 max_samples: 500 # 调试时减少数据量 max_seq_length: 2048 fp16: true logging_steps: 10 save_steps: 100 # 运行训练 llamafactory-cli train train_config.yaml
六、LoRA 关键参数详解
| 参数 | 推荐值 | 调高↑ | 调低↓ |
|---|---|---|---|
| r(秩) | 8~32 | 更强拟合能力,更多参数 | 更少过拟合,更快训练 |
| alpha | 16~64 | LoRA 权重更大 | 原始权重更大 |
| dropout | 0.05~0.1 | 更强的正则化 | 更少随机性 |
| 学习率 | 1e-4 ~ 5e-5 | 收敛更快(可能不稳定) | 更稳定(可能太慢) |
| target_modules | q_proj,v_proj | 所有 Linear 层(效果更好) | 只改注意力的关键层 |
💡 经验法则:
• 第一次调参用 r=8, alpha=16, lr=5e-5
• 先跑 100~500 条数据看 loss 曲线(如果 loss 不降→ 学习率太高或数据有问题)
• 效果不够好:增大 r(16→32→64),增大 alpha
• 过拟合:减小 r,增大 dropout,减少 epoch
• 第一次调参用 r=8, alpha=16, lr=5e-5
• 先跑 100~500 条数据看 loss 曲线(如果 loss 不降→ 学习率太高或数据有问题)
• 效果不够好:增大 r(16→32→64),增大 alpha
• 过拟合:减小 r,增大 dropout,减少 epoch
七、评估模型效果
7.1 对话测试
# LLaMA-Factory WebUI → Chat 标签页
# 直接输入问题测试
# 或用代码:
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载基础模型
base_model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen2.5-7B-Instruct",
device_map="auto",
torch_dtype=torch.float16
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
# 加载 LoRA 权重
model = PeftModel.from_pretrained(base_model, "./my-lora-adapter")
# 测试
inputs = tokenizer.apply_chat_template(
[{"role": "user", "content": "请帮我写一封请假邮件"}],
add_generation_prompt=True,
return_tensors="pt"
).to("cuda")
output = model.generate(inputs, max_new_tokens=512)
print(tokenizer.decode(output[0]))
7.2 几种对比测试
| 测试类型 | 方法 | 看什么 |
|---|---|---|
| AB 对比 | 同一问题,原模型 vs 微调后回答 | 微调后的输出是否符合预期格式/风格 |
| 测试集 Loss | 用未参与训练的测试数据算 loss | loss 越低越好(跟原始 loss 对比) |
| 人工评分 | 请人给 100 条输出打分 | 准确率、可用率、偏好率 |
| GPT 评分 | 用 GPT-4 当裁判评估输出质量 | 批量自动化评估(GPT-as-Judge) |
八、导出与部署
8.1 导出 LoRA 权重
# 方法一:只导出 LoRA 权重(小文件,推荐)
# 训练时已经自动保存了 ./my-lora-adapter/ 目录
# 包含 adapter_model.safetensors + adapter_config.json
# 方法二:合并回基础模型(大文件,推理更快)
# 用 LLaMA-Factory WebUI → Export 标签页
# 或命令行:
python src/export_model.py \
--model_name_or_path Qwen/Qwen2.5-7B-Instruct \
--adapter_name_or_path ./my-lora-adapter \
--export_dir ./merged-model \
--export_quantization_bit 4 # 可选:导出为 4bit 量化
8.2 部署推理
# 方案一:用 Ollama(最简单)
# 将合并后的模型导出为 GGUF 格式
# 然后 ollama create my-model -f Modelfile
# ollama run my-model
# 方案二:用 vLLM(高性能,生产推荐)
pip install vllm
python -m vllm.entrypoints.openai.api_server \
--model ./merged-model \
--port 8000 \
--dtype auto
# 然后就能用 OpenAI 兼容 API 调用了:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "./merged-model",
"messages": [{"role": "user", "content": "你好"}]
}'
# 方案三:用 Ollama + LoRA(不合并,直接加载)
ollama pull qwen2.5:7b
# 在 Modelfile 中指定 LoRA 权重
# FROM qwen2.5:7b
# ADAPTER ./my-lora-adapter
九、完整微调流程速查
┌─────────────────────────────────────────────────────────┐ │ LoRA 微调完整流程速查表 │ ├─────────────────────────────────────────────────────────┤ │ │ │ Step ① 确定需求 │ │ ├── 要改变模型"做什么" → 微调 │ │ ├── 要改变模型"知道什么" → RAG(别微调) │ │ └── 先试试能不能写 Prompt 解决 │ │ │ │ Step ② 准备数据 │ │ ├── 收集或生成 500~5000 条高质量数据 │ │ ├── 格式化成 ShareGPT 或 Alpaca 格式 │ │ ├── 检查数据质量(去重、去脏、验格式) │ │ └── 分训练集 90% + 测试集 10% │ │ │ │ Step ③ 选择方法 │ │ ├── 有 24GB+ 显存 → LoRA(推荐 r=8~16) │ │ ├── 有 12GB+ 显存 → QLoRA(4bit) │ │ └── 有 A100 集群 → 全参微调 │ │ │ │ Step ④ 训练 │ │ ├── 新手 → LLaMA-Factory WebUI(可视化) │ │ ├── 熟练 → LLaMA-Factory 命令行 / Python 脚本 │ │ └── 监控 loss 曲线(下降就对了!) │ │ │ │ Step ⑤ 评估 │ │ ├── 对话测试(看输出是否达到预期) │ │ ├── AB 对比(微调前 vs 微调后) │ │ └── 测试集 Loss(对比原始模型) │ │ │ │ Step ⑥ 导出部署 │ │ ├── 保留 LoRA 权重(小、灵活) │ │ ├── 合并回基础模型(快、省) │ │ └── 用 vLLM / Ollama 部署 │ │ │ └─────────────────────────────────────────────────────────┘
十、常见问题排查
| 问题 | 原因 | 解决 |
|---|---|---|
| Loss 不下降 | 学习率太高 / 数据问题 | 降低 lr(1e-5),检查数据格式 |
| 显存溢出(OOM) | batch_size 太大 | 减 batch_size,加 gradient_accumulation_steps |
| 训练完模型变傻了 | 灾难性遗忘 | 降低 lr,少跑 epoch,加原始数据混合 |
| 输出全是重复 | 过拟合 / temperature 太低 | 减少 epoch,检查数据多样性 |
| 加载模型报错 | 模型路径不对 / 量化方式不匹配 | 确认模型完整,检查 bnb 配置 |
| 训练特别慢 | 没有用 fp16 / 没开 flash_attn | 加上 fp16=True,安装 flash-attn |
| 导入数据报错 | JSON 格式错误 | 用 python -m json.tool data.json 验证 |
📌 一句话总结
LoRA 微调 = 准备高质量数据 + LLaMA-Factory 选 LoRA + 调好学习率跑几轮 + 评估导出。
80% 的时间应该花在数据准备上,剩下的 20% 才是调参。
登录后才能评论哦 ~ 立即登录