外观
微调实战:LoRA 全流程
微调不是"把模型喂给新数据",而是用受控的训练过程修改模型的权重分布——LoRA 的贡献,是把"修改全部权重"压缩成"修改两个低秩矩阵",让微调的成本从"大厂专属"降到"单卡可做"。
本文给出一个从数据到评估、可直接照着做的 LoRA 全流程。原理层面的"为什么 LoRA 有效"(低秩分解 W = W0 + BA、r 与 α 的含义)见微调:SFT 与参数高效微调,对齐(RLHF/DPO)见对齐。
一、先回答:这次该不该微调?
微调是成本最高、风险最大的模型改动手段。动手前用下面的检查清单自问:
| 你的诉求 | 更合适的做法 | 什么时候才轮到微调 |
|---|---|---|
| 让模型知道你的私有文档/事实 | RAG(检索增强) | 微调不适合灌知识 |
| 让输出更遵循某种格式 | 提示 + 结构化输出 | 提示已到极限且格式必须"天生如此" |
| 让输出风格像某个人/某类文本 | few-shot 示例 | 示例放不下/不稳定 |
| 让模型掌握专有任务与领域口径 | —— | ✅ 这是微调的主场 |
判断标准一句话:微调改变的是"模型的行为习惯"(风格、格式、任务口径、角色),而不是"模型的记忆"(知识)。 知识问题走 RAG 实战。如果你的目标其实是风格/格式,先读提示工程实践再决定。
三个"微调不如提示"的信号
- 数据量 < 几百条:提示/示例可能更划算;
- 需求是"某个事实":RAG 即可,微调还会带来遗忘风险;
- 没有评估手段:微调前后无法量化对比时,先补评估实战。
二、数据准备:质量 > 数量
1. 数据格式
LoRA 微调的主流数据格式是对话格式(每轮是完整的 user/assistant 对话),因为基座模型大多以对话模板组织输入。最通用的是 ChatML 风格:
json
[
{
"messages": [
{"role": "system", "content": "你是某公司客服助手,回答要简洁、礼貌。"},
{"role": "user", "content": "退款多久到账?"},
{"role": "assistant", "content": "退款一般在 3~5 个工作日到账,具体以银行处理时间为准。"}
]
}
]对应 HF transformers 的对话模板(apply_chat_template)会在训练时自动加上 <|im_start|> 等特殊标记。训练时对 assistant 部分的 token 计算损失,user/system 部分通常 mask 掉(TRL 的 SFTTrainer 默认如此)。
2. 数量与配比经验值
| 数据维度 | 经验区间 | 说明 |
|---|---|---|
| 总量 | 数百 ~ 数万条 | LoRA 微调常见千级;质量高时几百条也能见效 |
| 重复 | 2~3 epoch,防止过拟合 | epoch 超过 3 极易过拟合(见第五节) |
| 长度 | 与目标场景一致 | 训练时 max_seq_length 过短会截断长回答 |
| 质量筛选 | 去重、去噪音、纠正事实 | 一条错误标注顶十条好标注 |
数据质量 > 数据量的实证
业界反复验证:同样的预算,500 条高质量、格式统一、去重后的数据,效果显著好于 5000 条自动抓取的脏数据。数据清洗的功夫(去重、去毒性、纠错)直接决定微调上限,这也呼应预训练中"数据即模型"的判断——只是把规模换成了精度。
数据增强与去噪的常用手段
| 手段 | 做法 | 用途 |
|---|---|---|
| 去重 | 按文本相似度/embedding 去重 | 防过拟合、防"背数据" |
| 质量过滤 | 剔除乱码、超长、无意义回复 | 提升训练信号 |
| 格式统一 | 全部按同一对话模板与标点规范 | 提升输出一致性 |
| 构造负样本 | 加入"不应这样回答"的样本 | 明确边界、降幻觉 |
| 多轮扩展 | 单轮问答人工扩成多轮对话 | 提升多轮一致性 |
负样本要克制
负样本("这是错误示范")比例过高会让模型变得过于谨慎、拒答率上升。经验上以正样本为主、负样本点缀(约 5~10%),并人工抽查效果。
三、基座选择
| 维度 | 建议 |
|---|---|
| 能力基线 | 基座决定微调上限,弱基座微调不出新能力;先跑通提示版再微调 |
| 参数量 vs 显存 | 7B/8B 单卡可训;70B 级需要多卡或 QLoRA(见第八节) |
| 开源许可 | 商用场景注意模型许可(如 Llama 的社区许可条款) |
| 中文场景 | 优先中文强基座(Qwen 系列等),中文微调成本更低 |
| 对齐程度 | 微调任务用对齐过的基座(chat 版);"纯能力"实验可用 base 版 |
模型档案与选型见主流模型档案。
四、LoRA 配置:四个关键超参
LoRA 把权重更新约束为低秩:W' = W0 + (B·A)·α/r。核心超参:
| 超参 | 含义 | 常见起点 | 调整方向 |
|---|---|---|---|
r(秩) | 低秩矩阵的秩,决定可学习参数量 | 8~16 | 任务难/数据多时加大(32/64),否则过拟合 |
lora_alpha | 缩放系数,控制更新强度 | 16~32(约为 r 的 2 倍) | 效果弱加大,过拟合减小 |
target_modules | 注入 LoRA 的模块 | q_proj, v_proj 起;任务难时加 k_proj, o_proj, gate_proj, up_proj, down_proj | 覆盖面越大越强、越易过拟合 |
| 学习率 | LoRA 层的学习率 | 1e-4 ~ 2e-4 | 不稳时降到 5e-5 或 3e-5 |
dropout | LoRA 层的随机失活 | 0.05~0.1 | 过拟合时加大 |
完整训练脚本(HF PEFT + TRL,开箱可跑)
python
pip install transformers datasets peft trl accelerate bitsandbytes
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig
from trl import SFTTrainer
# 1. 基座与 tokenizer
model_id = "Qwen/Qwen2.5-7B-Instruct" # 以中文 7B 为例
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype="auto",
device_map="auto",
trust_remote_code=True,
)
# 2. LoRA 配置
lora_config = LoraConfig(
r=16, # 秩
lora_alpha=32, # 缩放
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
# 3. 训练参数(LLaMA-Factory 等工具的默认值与此同源)
training_args = TrainingArguments(
output_dir="outputs/lora-checkpoints",
per_device_train_batch_size=1, # 显存不够时梯度累积,见下方
gradient_accumulation_steps=8, # 等效 batch size = 1 × 8 = 8
learning_rate=2e-4,
num_train_epochs=2,
logging_steps=10,
save_steps=200,
evaluation_strategy="steps",
eval_steps=200,
fp16=True, # 不支持 bf16 的老卡用 fp16
report_to="tensorboard",
)
# 4. 训练(自动应用对话模板;assistant 部分计算损失)
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=load_dataset("json", data_files="train.jsonl")["train"],
eval_dataset=load_dataset("json", data_files="eval.jsonl")["train"],
tokenizer=tokenizer,
peft_config=lora_config,
max_seq_length=2048,
# 新版 TRL 会自动应用对话模板、只对 assistant 部分计算损失;
# 老版本如需指定字段可加:dataset_text_field="messages"
)
trainer.train()
trainer.save_model("outputs/lora-final")等效 batch size 怎么算
等效 batch size = per_device_train_batch_size × gradient_accumulation_steps × 设备数。显存不足时优先减小 batch、用梯度累积补回 batch size——大 batch 训练更稳,这是 LoRA 微调最常见的第一课。
五、训练监控:盯住过拟合
1. 三件事必做
- 打开 TensorBoard(
report_to="tensorboard"已开):盯 train loss 与 eval loss 两条曲线。 - 定期人工验证:每 N 步加载最新 checkpoint,跑 5~10 条真实问题看输出。损失不能替代人工判断。
- 记录基线:微调前先用同样的 10 条问题跑基座,留存输出以便对比。
2. 损失曲线判读
| 曲线形态 | 含义 | 处置 |
|---|---|---|
| train / eval loss 同步下降 | 正常学习 | 继续 |
| train loss 继续降、eval loss 拐头回升 | 过拟合 | 提前停(用 eval 最好的 checkpoint)、减小 epoch、加数据 |
| 两者都高且不降 | 学习率/数据问题 | 检查学习率、数据格式、对话模板 |
| loss 震荡剧烈 | 学习率过高 / batch 太小 | 降学习率、加大累积 |
过拟合的直接证据:背数据
微调过拟合的表现之一是模型开始"背诵"训练集中的长句——训练数据里出现过的提问,回答几乎逐字一致。这通常意味着 epoch 太多或 r 太大。规律是:LoRA 可学参数少,过拟合主要靠 epoch 和 r 控制。
3. 训练超参速查表
| 超参 | 常见区间 | 生效逻辑 |
|---|---|---|
| 学习率 | 1e-4 ~ 3e-4 | 过高发散、过低训不动 |
| warmup 步数 | 总步数 1~5% | 稳定早期训练(大模型必须) |
| weight decay | 0.01~0.1 | 权重正则,抑制过拟合 |
| 梯度裁剪 | 1.0(max norm) | 防止 loss 突刺 |
| cosine 衰减 | 常用 | 后期收敛更稳 |
| batch size(等效) | 8~64 | 大 batch 更稳、更快收敛 |
一套可复制的起点
lr=2e-4 + warmup(100 步) + weight_decay=0.01 + 梯度裁剪 1.0 + cosine 衰减 + epoch=2,从这套组合出发,再按第五节曲线微调。多数 LoRA 任务在这套基础上只需改 lr 与 epoch。
六、合并与导出
LoRA 权重默认是"旁挂"的低秩矩阵(PeftModel),部署前通常要合并进主模型:
python
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "Qwen/Qwen2.5-7B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
base = AutoModelForCausalLM.from_pretrained(model_id, torch_dtype="auto", device_map="auto")
# 加载 LoRA 适配器并合并
model = PeftModel.from_pretrained(base, "outputs/lora-final")
merged = model.merge_and_unload() # 合并回主模型权重
merged.save_pretrained("outputs/model-merged") # 导出为标准 HF 模型
tokenizer.save_pretrained("outputs/model-merged")合并后的模型可以直接走部署与服务化的 vLLM 等推理框架。也可以不合并:部署时用支持 PeftModel 的服务(如 vLLM 支持 --enable-lora)动态挂载,省显存但略增复杂度。
七、评估对比:微调前 vs 微调后
微调是否成功,必须拿同一份评测集对比微调前后:
| 对比项 | 做法 |
|---|---|
| 固定评测集 | 微调前就冻结 50~200 条(含训练集外的留出集) |
| 指标 | 任务准确率 + 风格/格式人工打分 + 通用能力回归(防"灾难性遗忘") |
| 对照维度 | 基座 vs 微调后;必要的话再对比"仅提示"版本 |
| 通用能力回归 | 跑 MMLU 子集等通用基准,确认微调没把基座能力洗掉 |
bash
# 通用能力回归示例:用 lm-eval-harness 跑合并后模型
lm_eval --model hf \
--model_args pretrained=outputs/model-merged \
--tasks mmlu \
--batch_size 4 \
--output_path results/after_finetune.json评估体系(golden set、回归、LLM-as-a-judge)见评估实战。
多轮迭代:微调不是一次性活动
一次微调很少直接达标,标准流程是**"微调 → 评估 → 找差距 → 补数据 → 再微调"**的循环:
| 轮次 | 目标 | 常见动作 |
|---|---|---|
| 第 1 轮 | 验证数据格式与流程 | 用最小数据(如 200 条)跑通 |
| 第 2 轮 | 逼近目标效果 | 扩数据、调 lr/epoch、加负样本 |
| 第 3 轮 | 修复评估发现的系统性错误 | 针对错误类型补数据(见错误分析) |
| 收尾 | 防遗忘回归 | 跑通用基准,确认能力未下降 |
别在"同一批数据"上无限调超参
如果第 2 轮后分数不再涨,问题通常在数据(不够、不均衡、标注不一致)而非超参。此时该补数据或改标注口径,而不是继续搜索学习率。
八、QLoRA 显存估算
QLoRA 在 LoRA 基础上把主模型量化到 4-bit 加载,并冻结之,只训练 LoRA 参数,显存需求大幅下降。经验公式:
推理显存 ≈ 权重字节数(2 字节/参数,fp16)
QLoRA 训练显存 ≈ 权重 4-bit(0.5 字节/参数) + LoRA 梯度/优化器 + 激活 + 少量| 模型 | 全参微调(fp16) | LoRA(fp16) | QLoRA(4-bit) | 一张 24GB 卡能做什么 |
|---|---|---|---|---|
| 0.5B | 约 1GB | 约 2~3GB | 约 2~3GB | 轻松 |
| 7B/8B | 约 14GB(刚够) | 约 16~18GB | 约 6~8GB | ✅ 常用组合 |
| 13B | 约 26GB(超) | 约 30GB+(超) | 约 10~12GB | ✅ QLoRA 可训 |
| 70B | 约 140GB(需多卡) | 约 160GB(需多卡) | 约 45~50GB | 单卡极限;多卡更稳 |
数字说明
以上为估算区间(训练显存还受 batch size、序列长度、上下文激活影响),具体以实测为准。QLoRA 论文的基准结论是:在 48GB 单卡上可微调 65B 模型(参考资料)。序列长度翻倍会显著增加激活显存,这是小卡上最常被忽视的变量。
九、常用工具对比
| 工具 | 定位 | 优势 | 适合 |
|---|---|---|---|
HF peft + transformers | 底层库 | 灵活、可控、生态最广 | 自定义流程、学习原理 |
HF trl(SFTTrainer 等) | 上层训练器 | 对话模板、DPO/PPO 全家桶 | SFT/对齐一体化 |
| LLaMA-Factory | 一键训练平台 | 中文友好、WebUI、多种方法开箱即用 | 快速实验、参数调优 |
| Axolotl | 配置驱动训练器 | YAML 配置、支持多卡与高级技巧 | 复现研究配置、批量实验 |
新手路线建议
第一轮用 LLaMA-Factory 或 TRL 跑通全流程(半天),第二轮再落到 peft 细看每个参数的作用。工具是手段,理解"训练目标 + 数据 + 超参"才是核心。
十、常见失败与排错
| 失败现象 | 根因 | 修复 |
|---|---|---|
| 显存不足 OOM | batch/序列太长、激活爆炸 | 减小 per_device_train_batch_size、max_seq_length;上 QLoRA |
| 训练正常但输出没变化 | LoRA 没生效(target_modules 错)、学习率过低、epoch 太少 | 打印可训练参数数核对;检查 target_modules 与模型实际层名 |
| 训练损失降、效果反而差 | 数据质量问题 / 数据格式不一致 | 人工抽查 20 条数据;统一模板与标注 |
| 答非所问/胡言乱语 | 过度微调或数据污染 | 回退到 eval 最好的 checkpoint;减小 r 与 epoch |
| 中文变差 | 基座中文弱、数据中文少 | 换中文基座;确认数据语言配比 |
| 对话模板不生效 | tokenizer 没有 apply_chat_template | 确认模型有对话模板;用新版本 transformers |
延伸阅读
- 微调:SFT 与参数高效微调 —— LoRA 原理(低秩分解、α/r)、全参 vs LoRA 对比
- 对齐:RLHF 与 DPO —— SFT 之后的对齐步骤,何时需要
- 评估实战 —— 微调前后对比与回归测试的完整方法
- 提示工程实践 —— 微调之前先试的"零成本"手段
- RAG 实战 —— 知识型需求的替代方案
- 数据集与基准档案 —— 指令数据(Alpaca/ShareGPT 等)与评测基准
- 部署与服务化 —— 微调产物的上线路径
参考资料
- LoRA: Low-Rank Adaptation of Large Language Models(arXiv:2106.09685) —— LoRA 原始论文
- QLoRA: Efficient Finetuning of Quantized LLMs(arXiv:2305.14314) —— QLoRA 原始论文,含显存基准数据
- Hugging Face PEFT(GitHub) —— LoRA 等参数高效微调官方库
- Hugging Face TRL(GitHub) —— SFTTrainer / DPOTrainer 官方库
- LLaMA-Factory(GitHub) —— 一键式微调平台
- Axolotl(GitHub) —— 配置驱动微调框架
- Hugging Face Transformers 文档 —— 模型加载、对话模板、训练器官方文档
- lm-evaluation-harness(GitHub) —— 微调前后通用基准回归测试工具