Skip to content

微调:SFT / LoRA / 数据构造 ​

微调改变的是「行为」,不是「知识」。 记住这句,能避开 80% 的失败。

一、先做决定:要不要微调 ​

你想要的该做什么
模型知道我的文档/数据库RAG
模型按固定格式输出Prompt + 结构化输出
模型说话像我们的客服/编辑微调
模型稳定完成一类窄任务、且要便宜微调小模型 + 蒸馏
模型会用我的工具Agent / function calling,不用训
降成本降延迟量化 + 小模型,不是微调

微调塞知识的典型翻车

微调模型读一遍《产品手册》后再问细节,它会用非常专业、非常自信的口吻编造。 因为微调教的是「这类问题的回答长什么样」,不是「这些事实是什么」。 知识用 RAG,风格用微调。

二、三种粒度 ​

方式训练参数显存(7B)效果用途
全参数微调100%80GB+上限最高资源充足、领域差异大
LoRA0.1%–1%16–24GB接近全参默认选择
QLoRA0.1%–1%(4bit 基座)8–16GB略低于 LoRA单卡消费级显卡

LoRA 原理(一句话) ​

冻结原权重 $W$,在旁边加一个低秩旁路:$W' = W + \frac{\alpha}{r}BA$,其中 $B$ 是 $d\times r$、$A$ 是 $r\times k$,$r \ll d$。

  • 只需训练 $A, B$(参数量从 $d\times k$ 降到 $r(d+k)$)
  • 训练完可把 $BA$ 合并回 $W$,推理零额外开销
参数含义常用值
r(rank)旁路秩,表达力8 / 16 / 32 / 64(任务越复杂越大)
alpha缩放系数,常取 2r16 / 32
dropout防过拟合0.05–0.1
target_modules加在哪些层通常全部 q/k/v/o + gate/up/down

三、数据构造:成败的 90% 在这里 ​

一句话

数据质量 > 数据数量 > 模型选择 > 超参数。 1000 条精心构造的数据,胜过 10 万条爬来的脏数据。

一条 SFT 数据的结构 ​

json
{
  "messages": [
    {"role": "system", "content": "你是某电商平台的售后助手,语气克制专业。"},
    {"role": "user", "content": "买的手机屏幕有坏点,能换吗?"},
    {"role": "assistant", "content": "可以的。签收 15 天内出现屏幕坏点属于质量问题,支持换新。请提供订单号与坏点位置照片,我为您发起换货申请。"}
  ]
}

数据构造五条硬标准 ​

标准说明反例
一致性同样的输入,回答风格统一有的很客气有的很生硬
正确性事实必须对,最好有人工校验编造政策条款
多样性覆盖真实输入的各种表达1000 条全是「怎么退款」
长度合理与你期望的输出长度分布匹配全是 500 字长文,实际要 2 句话
无泄漏测试样本不能出现在训练集同一问题换个标点算新样本

数据从哪来(按性价比排序) ​

来源成本质量建议
真实客服/业务日志(人工精修)中最高首选
强模型生成 + 人工抽检低中高主力方案
强模型生成 + 规则过滤极低中冷启动
公开数据集零参差只做补充
蒸馏(大模型答小模型学)低高成本敏感场景

用强模型造数据的正确姿势 ​

python
PROMPT = """你是资深{domain}专家。请针对下面的用户问题,写出符合以下要求的回答:
1. 先给结论,再给依据
2. 涉及政策时只引用 <policy> 中的条款,不得编造
3. 长度 60–150 字
4. 语气:专业、克制、不承诺超出政策的内容

<policy>
{policy_text}
</policy>

用户问题:{question}
回答:"""

关键:造数据时也要给它 RAG 上下文,否则生成的内容会脱离你的业务事实。

数据量与配比 ​

目标数据量训练轮次
调风格/口吻200–500 条1–3 epoch
窄任务(分类/抽取)500–3000 条2–3 epoch
复杂指令跟随5000–50000 条1–2 epoch

一个易被忽略的原则

保留 5%–10% 的通用数据混进去,能显著缓解灾难性遗忘(模型忘了怎么说人话)。

四、动手:QLoRA 微调一个 7B ​

python
# pip install transformers peft trl bitsandbytes datasets accelerate
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from trl import SFTTrainer, SFTConfig

base = "Qwen/Qwen2.5-7B-Instruct"
tok = AutoTokenizer.from_pretrained(base, trust_remote_code=True)
tok.pad_token = tok.eos_token

bnb = BitsAndBytesConfig(
    load_in_4bit=True, bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True)

model = AutoModelForCausalLM.from_pretrained(
    base, quantization_config=bnb, device_map="auto", trust_remote_code=True)
model = prepare_model_for_kbit_training(model)

lora = LoraConfig(
    r=16, lora_alpha=32, lora_dropout=0.05, bias="none",
    target_modules=["q_proj","k_proj","v_proj","o_proj",
                    "gate_proj","up_proj","down_proj"],
    task_type="CAUSAL_LM")
model = get_peft_model(model, lora)
model.print_trainable_parameters()   # 通常 ~0.5%

args = SFTConfig(
    output_dir="out/lora", num_train_epochs=2,
    per_device_train_batch_size=2, gradient_accumulation_steps=8,   # 等效 bs=16
    learning_rate=1e-4, lr_scheduler_type="cosine", warmup_ratio=0.03,
    logging_steps=10, save_strategy="epoch", bf16=True,
    max_length=1024, dataset_text_field="text", report_to="none")

trainer = SFTTrainer(model=model, args=args,
                     train_dataset=train_ds, tokenizer=tok)
trainer.train()
trainer.save_model("out/lora/final")

合并权重(推理时用) ​

python
from peft import PeftModel
from transformers import AutoModelForCausalLM

base_model = AutoModelForCausalLM.from_pretrained(base, torch_dtype=torch.bfloat16)
merged = PeftModel.from_pretrained(base_model, "out/lora/final").merge_and_unload()
merged.save_pretrained("out/merged")
tok.save_pretrained("out/merged")

也可以不合并,用 vLLM 直接加载 LoRA adapter(多 adapter 热切换时更方便)。

五、微调故障排查 ​

症状原因处理
loss 降到 0.1 但回答很差过拟合 / 数据重复加数据多样性、降 epoch、加 dropout
模型变笨,通用能力崩灾难性遗忘混入通用数据、降 lr、减 epoch、LoRA 而已
回答被截断max_length 太小提高截断长度,或过滤长样本
重复输出同一句数据里有大量重复样本去重,加 repetition penalty
格式对但内容空洞数据回答质量低换数据,不是调参
loss 不降lr 太小 / target_modules 太少 / 数据格式错检查模板与 mask
显存 OOM序列太长 / batch 太大梯度累积 + 降 max_length

关于 loss 曲线

SFT 的 loss 降到 0.5 以下往往已经过拟合。看 loss 不如看评测:每 epoch 跑一次固定测试集。

六、怎么证明微调有价值 ​

不要只报 loss。必须做三方对比:

方案准确率格式合法率平均延迟单次成本
Prompt-only(大模型)82%94%2.1s¥0.030
Prompt-only(小模型)61%88%0.6s¥0.002
微调后(小模型)88%99.5%0.6s¥0.002

这张表才是微调的成果 —— 「比大模型准、比小模型原来强 27 个点、成本降 15 倍」。

七、什么时候该停下来 ​

出现以下情况,说明微调不是正确答案:

  • 失败样本的共同点是「模型根本不知道这个事实」→ 用 RAG
  • 训练 3 轮后验证集不再提升,且错误类型分散 → 数据质量到顶,去补数据
  • 你已经调了两周超参,指标只涨 1 个点 → 收益已尽,去做工程优化

通过标准 ​

下一步 ​

→ 对齐与评测:RLHF / DPO / 基准

AI 学习路线 · 从入门到专家