第3章:TRL 与强化学习实战 (SFT / DPO / PPO)#
本章定位:从微调(SFT)到对齐(Alignment)。我们将复现 Hugging Face 官方 Alignment Handbook 的核心流程,但为了让每位读者都能跑通,我们将基座模型替换为轻量级的 Qwen2-0.5B。无论你是在 Colab 还是单卡 3090,都能完整体验 RLHF 的全过程。
目录#
- 1. 完整的对齐流水线 (The Alignment Pipeline)
- 2. SFT:让 Qwen-0.5B 学会指令
- 3. DPO:工业界对齐首选
- 4. PPO:经典 RLHF 三阶段 (进阶)
- 5.新兴趋势:ORPO 与 KTO
- 本章小结
1. 完整的对齐流水线 (The Alignment Pipeline)#
一个标准的工业级 LLM 训练流程包含三个阶段:
- Pre-training (PT): 海量文本,学习"续写"。
- Supervised Fine-Tuning (SFT): 指令数据,学习"对话"。
- Preference Alignment (DPO/PPO): 偏好数据,学习"价值观"。
本章我们将使用 TRL 库,基于 Qwen2-0.5B 完成后两个阶段。
2. SFT:让 Qwen-0.5B 学会指令#
SFT 不仅仅是微调,更是让模型适应特定的对话格式。
2.1 数据格式与 Chat Template#
对于 Qwen2,我们必须严格遵守 ChatML 格式:
<|im_start|>user\n{msg}<|im_end|>\n<|im_start|>assistant\n{msg}<|im_end|>
TRL 的 SFTTrainer 可以自动处理这个,前提是你配置好了 chat_template。
2.2 核心技巧:Packing (序列打包)#
SFTTrainer 支持 packing=True。它将多个短对话拼接到 max_seq_length (如 2048),用 attention_mask 隔开。
- 收益:训练速度通常提升 3-5 倍。
- 代价:需要更多显存(但对于 0.5B 模型,这不是问题)。
2.3 实战代码#
以下代码可在单卡 T4 (Colab 免费版) 上运行。
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from trl import SFTTrainer, SFTConfig
from datasets import load_dataset
# 1. 准备模型与数据
model_id = "Qwen/Qwen2-0.5B-Instruct"
# 使用 HuggingFaceH4 的精选数据集 (Ultrafachat)
dataset = load_dataset("HuggingFaceH4/ultrachat_200k", split="train_sft[:1%]")
tokenizer = AutoTokenizer.from_pretrained(model_id)
# ⚠️ Qwen 的 pad_token 有时需要手动指定,避免 loss 为 NaN
tokenizer.pad_token = tokenizer.eos_token
# 2. 配置参数
args = SFTConfig(
output_dir="./qwen-sft",
max_seq_length=2048,
packing=True, # 核心加速
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-5, # SFT 典型学习率
lr_scheduler_type="cosine",
logging_steps=10,
fp16=True, # T4 用 fp16, A100 用 bf16
)
# 3. 开始训练
trainer = SFTTrainer(
model=model_id,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="messages", # 数据集中的列名
args=args,
)
trainer.train()
trainer.save_model("./qwen-sft-final")3. DPO:工业界对齐首选#
现在我们的 Qwen-0.5B 主要学会了说话,但可能还会胡说八道。我们要用 DPO (Direct Preference Optimization) 来对齐人类偏好。
3.1 数据集:偏好对是如何构建的?#
DPO 数据必须是成对的:(prompt, chosen, rejected)。
- Chosen (胜):详细、有用、无害的回答。
- Rejected (负):简短、错误或有害的回答。
我们使用 HuggingFaceH4/ultrafeedback_binarized,这是目前质量最高的开源偏好数据集之一。
3.2 关键超参:Beta 的魔法#
beta 是 DPO 损失函数中的 KL 惩罚系数。
- Zephyr 配方:
beta=0.1。 - 直觉:beta 越大,模型越保守(贴近原始模型);beta 越小,模型越激进(贴近 chosen 数据)。对于 Qwen-0.5B 这种小模型,建议
beta=0.1以防止过度遗忘。
3.3 实战:使用 Qwen-0.5B 跑通 DPO#
from trl import DPOTrainer, DPOConfig
# 1. 加载 SFT 后的模型 (作为 Policy Model)
model_id = "./qwen-sft-final"
# 2. 加载数据
dataset = load_dataset("HuggingFaceH4/ultrafeedback_binarized", split="train_prefs[:1000]")
# 3. DPO特殊的配置
# 注意:DPO 的学习率通常比 SFT 低一个数量级 (5e-6 vs 2e-5)
dpo_args = DPOConfig(
output_dir="./qwen-dpo",
beta=0.1,
learning_rate=5e-6,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
fp16=True,
)
# 4. 初始化 Trainer
# TRL 会自动加载 ref_model (也就是 model 的一份拷贝,冻结参数)
trainer = DPOTrainer(
model=model_id,
ref_model=None, # 自动处理
args=dpo_args,
train_dataset=dataset,
tokenizer=tokenizer,
max_length=1024,
max_prompt_length=512,
)
trainer.train()4. PPO:经典 RLHF 三阶段 (进阶)#
虽然 DPO 很火,但 PPO (Proximal Policy Optimization) 依然是理解 RLHF 的基石。如果你的数据集没有成对的偏好,只有一个 Scalar Reward(比如代码通过没通过测试),那么 PPO 是唯一的选择。
4.1 训练 Reward Model (RM)#
在 PPO 之前,我们需要一个裁判模型(Reward Model)。它通常是一个 BERT 或者同架构的 Decoder 模型,将最后输出层改为一个标量回归头。
from trl import RewardTrainer, RewardConfig
# 定义模型:AutoModelForSequenceClassification (num_labels=1)
model = AutoModelForSequenceClassification.from_pretrained(
"Qwen/Qwen2-0.5B", num_labels=1
)
trainer = RewardTrainer(
model=model,
args=RewardConfig(output_dir="./qwen-rm", learning_rate=1e-5),
train_dataset=dataset, # 包含 chosen/rejected
tokenizer=tokenizer
)
trainer.train()4.2 PPO 流程详解 (Actor-Critic)#
这部分的代码非常复杂,涉及 4 个模型:
- Actor: 我们要训练的 Qwen-0.5B。
- Ref Model: 原始 Qwen-0.5B(冻结),用于计算 KL 散度,防止 Actor 跑偏(Reward Hacking)。
- Critic: 价值函数网络,估计 V(s)。
- Reward Model: 刚才训练好的裁判。
核心代码逻辑:
# 伪代码流程
ppo_trainer = PPOTrainer(...)
for batch in dataloader:
query = batch["input_ids"]
# 1. Actor 生成回复
response = ppo_trainer.generate(query)
# 2. RM 打分
reward = reward_model(query, response)
# 3. PPO Update Step
# 这一步会综合 reward 和 KL(actor, ref) 来更新 actor
stats = ppo_trainer.step(query, response, reward)5. 新兴趋势:ORPO 与 KTO#
5.1 ORPO (单阶段微调)#
ORPO (Odds Ratio Preference Optimization) 试图将 SFT 和 DPO 合二为一。
- 原理:在 SFT 的 Loss 上增加一项,专门惩罚 rejected 生成的概率。
- 优势:不需要 SFT -> DPO 两步走,一步到位。
- 代码:将
DPOTrainer替换为ORPOTrainer即可,接口几乎一致。
5.2 KTO (非成对数据)#
KTO (Kahneman-Tversky Optimization) 解决了 DPO 必须要有成对数据 (A > B) 的痛点。
- 如果你的数据只有 “A 是好的” (点赞) 和 “B 是坏的” (点踩),没有配对关系,KTO 是最佳选择。
本章小结#
本章我们用最轻量的 Qwen2-0.5B 跑通了最硬核的 RLHF 流程:
- SFT: 用
packing=True高效教会模型指令格式。 - DPO: 用
beta=0.1和成对数据,低成本实现偏好对齐(工业界 MVP)。 - PPO: 理解了 Actor/Critic/RM 的复杂博弈(学术界基石)。
- 前沿: ORPO 和 KTO 提供了更灵活的选择。
现在,你手中的 Qwen-0.5B 不仅能说话,还能说出“符合人类偏好”的话。下一章,我们将探讨如何利用 DeepSpeed 将这一套流程扩展到 7B、70B 甚至更大的模型上。