模型微调与对齐


一、先分清:微调和对齐是两条轴

很多人把这两个词混着用,结果配置全写错了方向。它们的区别是:

微调(Fine-tuning) 对齐(Alignment)
改什么 会不会做(能力、格式、领域适配) 愿不愿意做、怎么做(偏好、风格、安全)
数据形态 输入 → 期望输出(一对一) 好答案 vs 差答案(成对比较)
典型方法 SFT、LoRA、QLoRA DPO、RLHF、GRPO/RLVR
失败的信号 做不对、格式不对、不覆盖领域 会做但不好好做:啰嗦、谄媚、绕开限制

按”你要解决什么”对号入座:

  • 想让模型按固定格式输出、懂你的领域术语 → SFT
  • 想让回答更简洁、更直接、更少谄媚 → 对齐(DPO 起步)
  • 想提升数学/代码这类可验证能力 → RLVR(GRPO)
  • 想让模型知道新知识/新文档 → 先考虑 RAG,不是微调(微调擅长”改变行为”,不擅长”注入事实”)

二、SFT 实操

数据:先管质量,再管数量

起步规模:1k~10k 条。这个量级足够让模型学会一种输出风格;再多就是边际递减,而一条脏数据的代价可能超过一百条好数据。

造数据的四条路子,按成本排序:

  1. 从真实日志里挖:把线上问答、客服记录、用户提问整理成 指令 → 好回答(最贴合真实分布)
  2. 人工写少量高质量示范:几百条统一的格式模板,效果常常超过一万条杂数据
  3. 用强模型批量生成 + 人工抽检:注意生成的结果要人工过一遍,否则会把强模型的坏习惯一起学进来
  4. 公开数据集:注意许可证和领域匹配度

数据检查清单(发布前逐条过):

  • 格式统一吗(同一个域内不要混多种模板)
  • 有没有”正确但冗长””错误但漂亮”的样本
  • 覆盖了该覆盖的吗(简单/边界/拒答三类都要有)
  • 有没有把测试集混进训练集(会直接让评测失效)

模板(template)是比参数更容易踩的坑:示例配置里的 template: qwen3_nothink 不是随便填的——它决定训练时怎么把数据拼成”模型见过的那个格式”。填错有两种典型表现:训练 loss 正常下降但推理时行为异常(比如把系统提示当成用户说的话),或者多轮对话里角色错位。规则很简单:template 必须和目标模型的官方 chat 模板一致,而且同一模型的不同版本模板也可能不同。

一个多轮样本(带 system 和工具调用)长这样:

{
  "conversations": [
    {"from": "system", "value": "你是一个严谨的技术助手,不确定时直接说不确定。"},
    {"from": "human", "value": "帮我看下这个报错"},
    {"from": "function_call", "value": "{\"name\":\"read_log\",\"arguments\":{}}"},
    {"from": "observation", "value": "java.lang.NullPointerException ..."},
    {"from": "gpt", "value": "空指针发生在第 42 行的 userService,先确认 userId 是否可能为 null。"}
  ]
}

注意角色顺序,以及”哪些内容会被学”——只有 gpt 和 function 的内容计入 loss,human 和 observation 不计。

数据够不够,看两个信号:验证集 loss 是否还在降(不降了说明这批数据学到头了);任务评测是否还在涨(不涨说明缺的不是数据量,而是别的东西)。

参数:照这个抄

下面是 LLaMA-Factory 示例配置(examples/train_lora/qwen3_lora_sft.yaml)里的真实取值,可以直接作为起点:

### 方法
finetuning_type: lora
lora_rank: 8
lora_target: all

### 数据
template: qwen3_nothink
cutoff_len: 2048          # 截断长度:按你的数据实际长度设,别默认往大里开
max_samples: 1000

### 训练
per_device_train_batch_size: 1
gradient_accumulation_steps: 8   # 等效 batch = 1 × 8 × 卡数
learning_rate: 1.0e-4
num_train_epochs: 3.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true

几个经验值的来历和调整方向:

  • 学习率 1e-4:LoRA 微调的常用档位。全参微调要低一个量级(1e-5 ~ 2e-5)
  • epoch 3:这是”跑几遍数据”的上限;如果你的数据小(几百条),可以到 35,数据大则 12
  • rank 8:LoRA 的秩,官方示例就用 8——先别急着往 64 上堆(「高效微调」那篇解释过 rank 太大会失去意义)
  • cutoff_len:这个参数最容易埋雷。设得太小,长样本会被截断(还可能在截断处学出”半句话就结束”的习惯);设得太大,显存又爆
  • batch 1 + 梯度累积 8:显存不够时的标准做法,等效 batch 一样,只是慢一点

三个高频坑

  1. loss 掩码搞错:只应训练”回答”部分,指令是条件。掩码写错会让模型学着模仿用户的语气
  2. 格式过拟合:模型把模板的腔调学会了(动不动分点、堆小标题),但能力没涨——评测分高、真实使用僵
  3. 忘了留验证集:官方示例里 eval 段默认是注释掉的,别真按默认来——把 val_size: 0.1 打开,否则你没有任何信号判断”该停在第几个 epoch”

训练时看什么

现象 判读 动作
训练 loss 降,验证 loss 也降 健康 继续
训练 loss 降,验证 loss 先降后升 过拟合 回退到验证 loss 最低的 checkpoint
训练 loss 几乎不动 学习率太小,或数据格式/模板不对 先查格式与模板,再调 lr
loss 突然 NaN 或爆掉 学习率太大、bf16 环境混入 fp16 数据、异常长样本 降 lr + 检查数据长度分布

三、DPO 实操

DPO 是当前对齐的默认起点:比 RLHF 简单、比 SFT 更能改”偏好”。前提是先有一个 SFT 过的模型(对齐是在会做事的基础上调偏好)。

数据:成对,而且要成体系

规模上 5k~50k 对是常见区间,但比数量更重要的是成对的构造质量——每一对的 chosen 和 rejected 应该只差在想改的那个维度上:

想让回答更简洁 → chosen 是简洁版,rejected 是啰嗦版(内容都对)
想让模型不谄媚 → chosen 是礼貌纠正,rejected 是附和用户
想加安全边界 → chosen 是解释后拒答,rejected 是无条件配合

如果 chosen 和 rejected 的差别五花八门,模型学到的偏好就是混乱的。

同样列出组合的检查清单:

  • 长度分布:统计 chosen 和 rejected 的长度——如果 chosen 普遍更长,你会训出一个话痨
  • 多样性:每个维度的样本不要来自同一个模板或同一个生成模型
  • 留一批”没见过的维度”:用于验证对齐没有把通用能力带坏

参数

成对数据从哪来(按成本排序):

  1. 人工标注:质量最高、成本最高,适合几千对但极关键的维度
  2. 强模型生成:给强模型明确的”分别写一个好回答和一个坏回答”的指令,再人工抽检
  3. 规则扰动:拿现成的好回答做可控破坏——抽掉关键信息、替换成错误事实、把简洁版扩写成啰嗦版。成本最低,而且天然满足”只差一个维度”这个要求

reference model 是谁:就是做 DPO 之前那个 SFT 模型的副本(权重冻结,不参与训练)。它给 beta 提供参照系——偏离它越多,惩罚越大。

DPO 的 loss 怎么看:正常是缓慢下降。如果很快降到接近 0,通常是 beta 太小或数据太单一——模型已经学会用极端表达刷分(reward hacking 的 DPO 版)。这时该做的不是接着训,而是回头把数据做多样。

顺手记住两个变体(都在往”去参考模型”方向走):ORPO(2403.07691)把 SFT 和对齐合成一步、不需要参考模型;SimPO(2405.14734,NeurIPS 2024)用序列的平均对数概率当隐式奖励,同样去掉了参考模型。

同样来自官方示例(qwen3_lora_dpo.yaml):

pref_beta: 0.1
pref_loss: sigmoid      # 可选 sigmoid(dpo) / orpo / simpo
learning_rate: 1.0e-4   # 与 SFT 同档;若在 SFT 模型上继续训,可再降一半
num_train_epochs: 3.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
  • beta = 0.1:控制”允许偏离参考模型多远”。调大更保守(学得慢但不容易跑偏),调小更激进
  • loss 可选 sigmoid / orpo / simpo:ORPO 和 SimPO 是后续的变体,思路是减少对参考模型的依赖;先从 sigmoid(标准 DPO)开始
  • DPO 的坑集中在 beta:beta 太小 + 数据单一 = 学会极端表达(同一句话反复加强),这是 reward hacking 在 DPO 上的表现形式

四、RLHF 与 RLVR:什么时候值得

RLHF(PPO):要同时伺候策略、参考、奖励、价值四个模型,工程和显存成本都高。现在很少是首选——除非你要的偏好很难用成对数据表达(例如”整体对话质量”这种模糊标准)。如果真要做,先用一档:用 reward 模型(示例配置里 stage: rm,超参与 SFT 同档:lora_rank 8、lr 1e-4、epoch 3)把奖励模型训出来,再决定要不要上 PPO。

RLVR(可验证奖励):只有一类任务适用——答案能被自动验证(数学题、代码题、结构化的抽取任务)。它的数据不是”输入 → 输出”,而是”题目 + 标准答案”,奖励由规则算出来:

def reward(question, answer):
    score = 0.0
    if has_required_format(answer):     # 格式分:逼出稳定的输出结构
        score += 0.2
    if normalize(answer.final) == normalize(ground_truth[question]):
        score += 1.0                    # 正确性分:占主导
    return score

三个实操要点:

  1. 奖励要分层:格式分给一点权重让训练稳定,正确性分占主导,别让两者量级混淆
  2. 题目难度要有分布:全是难题 → 全对或全错 → 没有梯度信号;全简单题 → 学不到东西。官方工作里会把难易混合
  3. 别指望它改风格:RLVR 只在”能判对错”的维度上生效,写作、咨询这类没有标准答案的任务写不出奖励函数

LLaMA-Factory 的示例配置覆盖 SFT / DPO / KTO / reward 这几档;更完整的 RL 算法需要用支持强化学习流程的框架来做。

五、评测:怎么知道有没有效果

这是最容易被跳过、也最影响成本的一步。没有评测,你就是在盲训。

三件套,缺一不可:

1. 任务指标:留一个训练里没出现过的测试集(200~500 条足够),比较微调前后的输出质量。评测时温度设 0、跑两三次看方差——跑一次的结果不可信。

2. 通用能力回归:同一个通用问题集(几十条就够),对比微调前后的表现。如果明显下降,说明学得太狠或数据太窄。这一步专门用来抓”灾难性遗忘”。

3. 人工盲评(最有效也最容易被忽略):把 base 和微调后的输出打乱顺序放在一起,让人挑更好的那个。50~100 条就能看出趋势。要点:

  • 一定要盲(不告诉评审哪个是新模型)
  • 一定要打乱(人会对第一个回答有偏好)
  • 对齐任务的盲评要专门盯三个指标:长度、拒答率、是否附和

用强模型当裁判(LLM-as-judge):没有标准答案的任务,常见做法是让一个强模型按评分标准打分——便宜、可复现,但它有三个已知偏差,必须校准:

偏差 表现 对策
位置偏差 更偏爱排在前面的回答 两个回答对调顺序各评一次,只取两次一致的结论
长度偏差 更偏爱长回答 评分标准里显式声明”简洁不加分”,或先按长度分层再比
自我偏好 偏爱同族模型的输出 裁判模型不要用被评测模型同一族的

人工盲评的最小可行做法:2 个评审 × 60 条 × 打乱顺序,先标”明显更好”和”分不出”,只统计”明显更好”的比例——60 条足够看出 20 个百分点以上的差异,不必追求统计学显著。

对齐专项检查(这三项能提前抓住 reward hacking):

指标 怎么看 异常信号
平均回答长度 对齐前后对比 明显变长 = 学成了话痨
拒答率 抽样统计 该答的也不答 / 该拒的全接
附和倾向 构造”用户说错”的样本 一味认同用户

六、完整路径

把上面的东西串起来,从零到上线的顺序是:

  1. 明确目标 → 判断是”能力问题”还是”偏好问题”(决定走 SFT 还是对齐)
  2. SFT → 造 1k~10k 条数据 → 官方示例参数起步 → 看验证集损失决定停在哪
  3. 用评测三件套验收 SFT → 不达标先修数据,别急着上对齐
  4. 需要改偏好 → 造 DPO 成对数据 → beta 0.1 起步 → 再跑一遍评测三件套
  5. 需要提可验证能力 → 设计分层奖励 → 上 RLVR
  6. 上线前:对齐专项检查(长度/拒答/附和)+ 一次盲评

每一档之间都要评测——不然出了问题无法判断是哪一步引入的。

七、汇总记录

阶段 坑 表现
数据(全程) 测试集污染 评测分虚高,上线就现原形
SFT loss 掩码错 学会了用户的语气
SFT cutoff 太小 长回答被腰斩,学出”半句话”习惯
SFT 没留验证集 不知道该停在第几个 epoch
SFT 数据太窄 通用能力掉、任务能力涨
DPO chosen 都比 rejected 长 训出话痨
DPO beta 太小 + 数据单一 极端表达(reward hacking)
RLVR 奖励不分层 训练震荡、没有稳定梯度
RLVR 难度全在同一档 全对或全错,学不到东西
评测 只看任务指标 不知道通用能力掉没掉
评测 不盲评 被自己的预期骗了

八、高频追问

微调能让模型学会新知识吗?
能记住一点,但很不可靠,还容易和已有知识打架。注入事实用 RAG,微调用来改行为和格式。 如果非要用微调记住,同一事实要在数据里以多种问法反复出现。

数据是不是越多越好?
不是。1k 条干净数据常常打得过 10 万条杂数据。顺序是:先扩多样性,再扩数量。

能不能跳过 SFT 直接 DPO?
技术上可以,实践上不建议。DPO 的参考模型通常就是 SFT 模型;没有它,模型连”该怎么回答”都还没建立,偏好学习就失去了稳固起点。

LoRA 和全参微调差多少?
大多数任务上差距很小;差在”学新知识、学新语言”这类底层能力时。先上 LoRA,确实不够再动全参。

对齐会不会把模型变笨?
会——如果学得太狠(灾难性遗忘)。对策:控制 epoch、混入一部分通用数据,并且用”通用能力回归”一直盯着。

怎么判断是不是 reward hacking 了?
三个信号:回答明显变长、开始附和用户的错误观点、输出高度模板化。出现就先回去检查偏好数据和 beta,而不是继续训。

总结

微调改”会不会”,对齐改”愿不愿意”;数据质量比数量重要一个量级;参数照官方示例起步就好;而评测——尤其是盲评和通用能力回归——是把上面所有努力变现的那一步。


文章作者: Halensea
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Halensea !
 上一篇
多模态模型 多模态模型
文本是主模态,其它模态都要先被翻译成它——多模态的全部工程问题都从这句话长出来。文本是一种高效的压缩格式,但不是世界的全部格式
2026-10-14
下一篇 
AI算力设施:算力服务器、数据存储、应用服务器 AI算力设施:算力服务器、数据存储、应用服务器
模型跑在什么上面。三类设施——算力服务器(发动机)、数据存储(油箱加仓库)、应用服务器(方向盘加仪表盘)——按数据流串起来看,各自的选型维度、要算的账和最容易撞的墙。
2026-10-12
  目录