27届大模型面试准备(十六):后训练全攻略——SFT、RLHF、DPO、PPO 一次讲透
上一篇《高效推理全攻略》讲的是"怎么让模型推理又快又省",属于部署侧。这一篇回到训练侧,把"预训练之后的所有事"——也就是后训练(Post-Training)——一次性讲透。覆盖 SFT、Reward Model、RLHF(PPO)、DPO 四条主线,外加一条工程副线(数据配方、课程学习、拒绝采样)。每节都给:原理 → 数学/算法骨架 → 代码片段 → 面试速答 + 高频追问。配合《LoRA/PEFT》(候选 A18)一起看,刚好串成"对齐怎么做、怎么省显存做"。
一、先理清"预训练"和"后训练"的职责边界
很多面试者把二者混为一谈,第一句就露怯。一句话区分:
- 预训练(Pre-training):在海量无标注语料上做 next-token prediction,给模型"通识知识"和"语言流畅度"。此时模型像一本读过的百科全书,但不会按你的指令办事。
- 后训练(Post-training):在预训练之后,用"指令 / 对话 / 偏好"数据把模型对齐(alignment)到"人类想要的回答方式"。包括 SFT、RLHF、DPO、安全对齐等。
预训练 后训练 ┌───────────────────────┐ ┌──────────────────────────────┐ │ 8B~万亿 token 语料 │ │ SFT(指令) → RM(偏好) │ │ next-token 预测 │──▶│ → RLHF(PPO) / DPO(直接偏好) │ │ 产出: 基座模型 base │ │ 产出: 对话模型 chat / instruct │ └───────────────────────┘ └──────────────────────────────┘ 通识 + 流畅 有用 + 无害 + 听指令面试常问:"为什么不直接用预训练模型上线?" 答案是:base 模型只会"续写",不会"对话"——你给它"请写一首诗",它可能接着写"请写一首诗的步骤如下"。SFT 就是把"续写者"变成"对话者"。
二、SFT:后训练的第一块基石
2.1 SFT 在做什么
SFT(Supervised Fine-Tuning,监督微调)用"指令-回答"配对数据,继续做 next-token prediction,但数据从"网页语料"换成"高质量对话/任务样本"。目标函数和预训练一致,只是数据分布变了:
预训练损失: L_pt = -Σ log P(x_t | x_<t) # x 是网页文本 SFT 损失: L_sft = -Σ log P(a_t | a_<t, q) # q 是指令, a 是理想回答注意:通常只在answer 部分算 loss,instruction/prompt 部分 mask 掉(label 置 -100),否则模型会学"怎么复述问题"而不是"怎么回答"。
2.2 一个最小 SFT 数据样本
{"instruction":"把下面这句话翻译成英文:今天天气真好。","input":"","output":"The weather is really nice today."}多轮对话则组织成 messages 列表:
{"messages":[{"role":"system","content":"你是一个严谨的翻译助手。"},{"role":"user","content":"把'今天天气真好'翻译成英文。"},{"role":"assistant","content":"The weather is really nice today."}]}2.3 SFT 质量的三个关键点
| 维度 | 常见错误 | 正确做法 |
|---|---|---|
| 数据量 | 认为越多越好,堆几百万条噪声 | 几万条高质量远胜几百万条低质;质量 > 数量 |
| 多样性 | 单一任务(全是翻译) | 覆盖推理/创作/代码/安全拒答等多题型 |
| 格式 | 混入系统 prompt 噪声 | prompt 部分 mask 掉 loss,只训回答 |
用 HuggingFace TRL 跑 SFT 的最小骨架:
fromtrlimportSFTTrainer,SFTConfigfromdatasetsimportload_datasetdataset=load_dataset("json",data_files="sft_data.jsonl")["train"]trainer=SFTTrainer(model="Qwen/Qwen2.5-7B",args=SFTConfig(per_device_train_batch_size=4,max_seq_length=2048,num_train_epochs=3,learning_rate=2e-5,packing=True,# 把短样本拼接到一条,提升吞吐),train_dataset=dataset,)trainer.train()面试速答:SFT 为什么只用 answer 算 loss?因为 instruction 是"已知条件",模型在推理时已经知道;我们要优化的是"给定问题后生成好回答"的概率,prompt 部分的预测不需要学。
高频追问:
1. packing 和 padding 的区别?packing 把多条样本拼接避免浪费,padding 用 0 补齐到相同长度(浪费算力)。
2. SFT 学习率一般比预训练大还是小?更大(如 1e-5~3e-5),因为数据少、要快速适配指令分布。
3. 全量 SFT 和 LoRA-SFT 怎么选?数据少/防灾难性遗忘选 LoRA;要深度改变行为选全量。
三、Reward Model:RLHF 的"裁判"
RLHF 需要一个人来打分,这个"人"就是 Reward Model(RM)。它把"回答质量"映射成一个标量分数。
3.1 RM 的训练数据:成对偏好
RM 不用绝对分数,而用成对比较数据(人类标注"回答 A 比回答 B 好"):
{"chosen":"北京是中国的首都。","rejected":"北京是美国的一个城市。"}3.2 RM 的损失函数(Bradley-Terry 模型)
把 RM 记作 r(x, y),希望 chosen 分数高于 rejected:
L_RM = -E[ log σ( r(x, y_chosen) - r(x, y_rejected) ) ]σ 是 sigmoid。直观:差距越大、符号越对,损失越小。RM 通常就是"基座模型 + 一个回归头(把 hidden 压成 1 维)"。
# RM 头:取最后一个 token 的 hidden 投射到标量classRewardModel(nn.Module):def__init__(self,base):self.base=baseself.value_head=nn.Linear(base.config.hidden_size,1)defforward(self,input_ids,attention_mask):out=self.base(input_ids,attention_mask).last_hidden_state# 取每个序列最后一个非 pad tokenscores=self.value_head(out[:,-1,:])returnscores.squeeze(-1)面试速答:为什么用成对比较而不是绝对打分?因为人类对"绝对分数"标定很不一致,但对"哪个更好"的相对判断稳定得多,标注成本低、信噪比高。
高频追问:
1. RM 过优化(reward hacking)是什么?模型找到骗 RM 拿高分的捷径(比如啰嗦、拍马屁),而非真正变好。
2. 怎么缓解?加 KL 惩罚、定期用新模型数据重训 RM、做 RM 集成。
四、RLHF 的核心:PPO 算法
PPO(Proximal Policy Optimization)是 RLHF 经典算法。把"生成回答"看成强化学习:策略 π 是待训练的语言模型,奖励来自 RM,约束是不偏离原始 SFT 模型太远(防崩)。
4.1 总目标
L_PPO = E[ r_t(θ) * A_t ] - β * KL( π_θ(y|x) || π_ref(y|x) ) 其中: r_t(θ) = π_θ(y_t|y_<t, x) / π_old(y_t|y_<t, x) # 概率比 A_t = 来自 GAE 的优势估计(由 RM 打分驱动) KL 项 = 防止模型跑太偏,β 是系数4.2 PPO 四件套
┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ Actor (π) │ │ Critic (V) │ │ Reward(RM) │ │ Reference │ │ 待更新策略 │ │ 价值估计 │ │ 打分裁判 │ │ 初始锚点 │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ 生成 y │ 估 V │ 打 r │ 算 KL 锚 └────────────────┴─────── 共同计算 PPO loss ────────┘4.3 TRL 跑 PPO 的最小骨架
fromtrlimportPPOTrainer,PPOConfig,AutoModelForCausalLMWithValueHeadfromtransformersimportAutoTokenizermodel=AutoModelForCausalLMWithValueHead.from_pretrained("sft-model")ref_model=AutoModelForCausalLMWithValueHead.from_pretrained("sft-model")tokenizer=AutoTokenizer.from_pretrained("sft-model")config=PPOConfig(batch_size=16,learning_rate=1e-6,kl_penalty="kl",init_kl_coef=0.02)ppo=PPOTrainer(config,model,ref_model,tokenizer)forbatchindataloader:query=tokenizer(batch["prompt"],return_tensors="pt")response=model.generate(**query,max_new_tokens=128)reward=rm_score(query,response)# RM 打分stats=ppo.step(query["input_ids"],response,reward)面试速答:RLHF 里 KL 惩罚的作用?防止策略为了骗 RM 而偏离 SFT 模型太远导致语言崩坏(满嘴乱码)。它是"有用"和"不像人话"之间的安全绳。
高频追问:
1. Critic 和 RM 的区别?RM 给整句打分(外部裁判),Critic 预估每步价值(内部基线,降低方差)。
2. 为什么 PPO 比普通 policy gradient 稳?用了 clip 把概率比限制在一定范围,避免一步更新过猛。
五、DPO:绕开 RM 和 PPO 的直接偏好优化
PPO 又贵又脆(要四个模型同时在线)。2023 年提出的 DPO(Direct Preference Optimization)证明:在 KL 约束下,最优策略和 RM 有闭式关系,于是可以直接在偏好数据上用分类损失训,不需要显式 RM、不需要 RL 循环。
5.1 DPO 损失
L_DPO = -E[ log σ( β * ( log π_θ(y_w|x)/π_ref(y_w|x) - log π_θ(y_l|x)/π_ref(y_l|x) ) ) ] y_w = chosen, y_l = rejected关键点:它把"奖励差"隐式编码进策略比里,β 控制偏离 ref 的强度。
5.2 DPO vs PPO 对比
| 维度 | PPO/RLHF | DPO |
|---|---|---|
| 需要 RM | 是(单独训练) | 否(隐式) |
| 在线采样 | 需要(rollout) | 不需要,离线 |
| 显存/算力 | 高(4 模型) | 低(2 模型) |
| 训练稳定性 | 较脆,超参敏感 | 较稳 |
| 动态探索 | 强 | 弱(依赖静态数据) |
5.3 TRL 跑 DPO 的最小骨架
fromtrlimportDPOTrainer,DPOConfigdpo=DPOTrainer(model="sft-model",ref_model="sft-model",# 固定参考args=DPOConfig(beta=0.1,learning_rate=5e-6,per_device_train_batch_size=4),train_dataset=pref_dataset,# 含 chosen / rejected 字段)dpo.train()面试速答:DPO 为什么不需要 reward model?因为 Bradley-Terry + KL 约束下,最优策略的奖励可以写成"策略比 ref 的对数概率差",这个量在训练时直接可算,于是 RM 被消掉了。
高频追问:
1. DPO 的弱点?数据静态,模型只能从已有 chosen/rejected 学,缺乏 PPO 的在线探索,容易被数据分布限制。
2. β 太大或太小会怎样?太大→几乎不更新(贴近 ref);太小→可能过拟合偏好、语言退化。
3. 线上该选哪个?资源紧/求稳选 DPO;要最强效果且有 RL 工程能力选 PPO。
六、工程副线:数据配方与课程学习
面试高阶题常考"后训练怎么排兵布阵",这里给一个可落地配方:
阶段1 SFT(通用指令) —— 让模型学会按指令办事 阶段2 SFT(领域/硬任务) —— 注入代码、数学、安全拒答等硬能力 阶段3 DPO / RLHF(偏好) —— 对齐"风格、无害、有用" 阶段4 安全对齐 + 红蓝对抗 —— 专门修越狱、补拒答课程学习(curriculum):先易后难、先广后专。常见坑:
- 灾难性遗忘:后训练把预训练知识冲掉。缓解:混入少量预训练续写数据(replay)、用 LoRA 而非全量。
- 长度崩坏:RL 阶段模型学会"越长分越高"。缓解:RM 加长度惩罚、KL 约束。
- 分布漂移:偏好数据来自少数标注员,模型被窄化。缓解:多源标注、定期重采。
面试速答:为什么后训练要分阶段而不是一把梭?不同阶段目标冲突(通用性 vs 专业性 vs 偏好),一把梭会让梯度互相打架;分阶段能让每阶段专注一类能力,且便于单独 debug。
七、面试速答 + 高频追问清单(汇总)
速答 TOP 8:
1. 预训练给知识,后训练给对齐(指令遵循 + 偏好)。
2. SFT 只在 answer 算 loss,prompt 部分 mask。
3. RM 用成对比较训练,输出标量奖励。
4. PPO 四件套:Actor / Critic / Reward / Reference,KL 防崩。
5. DPO 用偏好数据直接训,隐式消去 RM。
6. DPO 省算力但缺在线探索;PPO 效果好但脆。
7. β 控制偏离 ref 的强度。
8. 后训练分阶段是为避免目标冲突与遗忘。
追问清单:
- 为什么 RM 用 Bradley-Terry 而不是 MSE?
- reward hacking 有哪些典型表现,怎么发现?
- 多轮对话的 RLHF 怎么处理?
- DPO 的隐式奖励公式推导一遍。
- 全量微调 vs LoRA 在后训练各阶段怎么搭配?
八、下一篇预告
后训练讲完"对齐怎么做",下一篇(候选 A17)可以深入分布式训练 DeepSpeed / FSDP / Megatron——把"训练一个大模型"的工程底座讲明白;或者 A18 的LoRA/QLoRA/PEFT 省显存全家桶。如果你更想听推理侧,A15 的高效推理已经就位。评论区告诉我你想先啃哪个。