Skip to content

微调实战:LoRA 全流程

本页速览 用 LoRA 走完"数据准备→基座选择→LoRA 配置→训练监控→合并导出→评估对比"的完整微调流程,给出 QLoRA 显存估算表、常用工具对比与常见失败排错,并明确"微调不如提示"的判断标准。

微调实战:LoRA 全流程

微调不是"把模型喂给新数据",而是用受控的训练过程修改模型的权重分布——LoRA 的贡献,是把"修改全部权重"压缩成"修改两个低秩矩阵",让微调的成本从"大厂专属"降到"单卡可做"。

本文给出一个从数据到评估、可直接照着做的 LoRA 全流程。原理层面的"为什么 LoRA 有效"(低秩分解 W = W0 + BA、r 与 α 的含义)见微调:SFT 与参数高效微调,对齐(RLHF/DPO)见对齐

一、先回答:这次该不该微调?

微调是成本最高、风险最大的模型改动手段。动手前用下面的检查清单自问:

你的诉求更合适的做法什么时候才轮到微调
让模型知道你的私有文档/事实RAG(检索增强)微调不适合灌知识
让输出更遵循某种格式提示 + 结构化输出提示已到极限且格式必须"天生如此"
让输出风格像某个人/某类文本few-shot 示例示例放不下/不稳定
让模型掌握专有任务与领域口径——✅ 这是微调的主场

判断标准一句话:微调改变的是"模型的行为习惯"(风格、格式、任务口径、角色),而不是"模型的记忆"(知识)。 知识问题走 RAG 实战。如果你的目标其实是风格/格式,先读提示工程实践再决定。

三个"微调不如提示"的信号

  1. 数据量 < 几百条:提示/示例可能更划算;
  2. 需求是"某个事实":RAG 即可,微调还会带来遗忘风险;
  3. 没有评估手段:微调前后无法量化对比时,先补评估实战

二、数据准备:质量 > 数量

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 掉TRLSFTTrainer 默认如此)。

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
dropoutLoRA 层的随机失活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. 三件事必做

  • 打开 TensorBoardreport_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 decay0.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 细看每个参数的作用。工具是手段,理解"训练目标 + 数据 + 超参"才是核心。

十、常见失败与排错

失败现象根因修复
显存不足 OOMbatch/序列太长、激活爆炸减小 per_device_train_batch_sizemax_seq_length;上 QLoRA
训练正常但输出没变化LoRA 没生效(target_modules 错)、学习率过低、epoch 太少打印可训练参数数核对;检查 target_modules 与模型实际层名
训练损失降、效果反而差数据质量问题 / 数据格式不一致人工抽查 20 条数据;统一模板与标注
答非所问/胡言乱语过度微调或数据污染回退到 eval 最好的 checkpoint;减小 r 与 epoch
中文变差基座中文弱、数据中文少换中文基座;确认数据语言配比
对话模板不生效tokenizer 没有 apply_chat_template确认模型有对话模板;用新版本 transformers

延伸阅读

参考资料