Chapter 12

完整实战:从 base 模型到对齐模型

📌 commit 8221a9fSFT → DPO 三天跑完一个对齐模型

本章把前 11 章的 TRL 用法拼成一条完整工作流,目标是用 Qwen2.5-7B 训出一个能用的对齐 chat 模型。给出实际命令、时间预算、关键 checkpoint。

12.1三阶段全景

flowchart LR Base[Qwen2.5-7B base] -->|2 day SFT| SFT[SFT 模型] SFT -->|0.5 day RM| RM[Reward Model] SFT -->|0.5 day DPO| DPO[DPO 模型] RM -->|可选 1 day GRPO| GRPO[GRPO 模型] DPO -->|可选 1 day GRPO| GRPO

路径 A(实用主义):SFT → DPO。两天搞定,效果通常够用。
路径 B(追极致):SFT → RM → GRPO 或 PPO。四天,能拿到 R1 风格推理能力。

12.2阶段 1:SFT

数据

# 选 Capybara(5k)+ tulu-3-sft-mixture(一部分)
from datasets import load_dataset, concatenate_datasets

ds1 = load_dataset("trl-lib/Capybara", split="train")
ds2 = load_dataset("allenai/tulu-3-sft-mixture", split="train[:50000]")
sft_ds = concatenate_datasets([ds1, ds2]).shuffle(seed=42)

训练

accelerate launch examples/scripts/sft.py \
    --model_name_or_path Qwen/Qwen2.5-7B \
    --dataset_name trl-lib/Capybara \
    --output_dir ./sft_out \
    --per_device_train_batch_size 2 \
    --gradient_accumulation_steps 16 \
    --num_train_epochs 2 \
    --learning_rate 2e-4 \
    --max_length 4096 \
    --packing \
    --assistant_only_loss \
    --bf16 \
    --attn_implementation flash_attention_2 \
    --gradient_checkpointing \
    --use_peft --lora_r 16 --lora_alpha 32 \
    --logging_steps 10 --save_strategy epoch

预算(8×A100-80GB):~30k samples × 2 epoch ≈ 18 小时。

12.3阶段 2:DPO

accelerate launch examples/scripts/dpo.py \
    --model_name_or_path ./sft_out/final \
    --dataset_name trl-lib/ultrafeedback_binarized \
    --output_dir ./dpo_out \
    --per_device_train_batch_size 1 \
    --gradient_accumulation_steps 8 \
    --num_train_epochs 1 \
    --learning_rate 5e-6 \
    --beta 0.1 \
    --max_length 2048 \
    --bf16 \
    --gradient_checkpointing \
    --use_peft --lora_r 16

预算:60k 偏好对 × 1 epoch ≈ 8 小时(8×A100)。

评估

# 训完用 MT-Bench / AlpacaEval / IFEval 跑一遍
git clone https://github.com/EleutherAI/lm-evaluation-harness
lm_eval --model hf --model_args pretrained=./dpo_out/final --tasks alpaca_eval --batch_size 8

12.4阶段 3(可选):GRPO 训推理能力

想做"会推理"的版本(R1 风格):

accelerate launch examples/scripts/grpo.py \
    --model_name_or_path ./sft_out/final \
    --dataset_name openai/gsm8k --dataset_config_name main \
    --output_dir ./grpo_out \
    --per_device_train_batch_size 1 \
    --num_generations 8 \
    --max_prompt_length 512 --max_completion_length 1024 \
    --learning_rate 1e-6 \
    --beta 0.04 \
    --bf16 \
    --use_vllm --vllm_mode colocate \
    --use_peft --lora_r 16 \
    --num_train_epochs 1

预算:~5k prompt × G=8 ≈ 18 小时(8×A100)。

12.5合并 LoRA、导出 HF 格式

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B", torch_dtype="bfloat16")
model = PeftModel.from_pretrained(base, "./dpo_out/final")
model = model.merge_and_unload()
model.save_pretrained("./qwen2.5-7b-dpo-merged")
AutoTokenizer.from_pretrained("./dpo_out/final").save_pretrained("./qwen2.5-7b-dpo-merged")

推上 HF Hub:

huggingface-cli upload your-name/qwen2.5-7b-dpo-aligned ./qwen2.5-7b-dpo-merged

12.6部署

# vLLM 部署
vllm serve ./qwen2.5-7b-dpo-merged --tensor-parallel-size 2 --port 8000

# 测试
curl http://localhost:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{"model": "qwen2.5-7b-dpo-merged",
         "messages": [{"role":"user","content":"Hello"}]}'

12.7整条 pipeline 的时间 / 成本

阶段耗时云成本(8×A100 按 $20/h)
SFT~18 h$360
DPO~8 h$160
GRPO(可选)~18 h$360
合并 + 部署~1 h$20
路径 A 总~27 h$540
路径 B 总~45 h$900

12.8第四条路径:Online DPO(SFT → 在线偏好)

2024-08 之后 TRL 加了 online preference 系(trl/experimental/online_dpo/)。它的 niche:不需要预先收集 chosen/rejected 数据,训练时实时让 reward 模型打分生成偏好对。

from trl.experimental.online_dpo import OnlineDPOTrainer, OnlineDPOConfig

cfg = OnlineDPOConfig(
    output_dir="./online_dpo_out",
    beta=0.1,
    learning_rate=5e-7,
    use_vllm=True,                    # ★ rollout 用 vLLM
    vllm_mode="colocate",
    num_generations=2,                 # 每条 prompt 采 2 个回复,RM 选 chosen/rejected
)

trainer = OnlineDPOTrainer(
    model=model,
    reward_model=rm,                   # ★ 用 RM 实时打分而不是离线 chosen/rejected
    args=cfg,
    train_dataset=ds,                  # 只有 prompt 列
)
trainer.train()

同族还有 NashMDTrainer(博弈论 nash equilibrium)和 XPOTrainer(exploration policy optimization),都继承 OnlineDPOTrainer,差别只在 chosen/rejected 选法上。

12.9第五条路径:RLOO(GRPO 之外的"无 critic RL")

RLOOTrainertrl/trainer/rloo_trainer.py:105)是 GRPO 之外的另一条 critic-less RL 路径,2024 论文 2402.14740 的实现:

维度GRPORLOO
baseline 估计 group 内 z-score leave-one-out(baseline = 其他 N-1 个的 reward 均值)
无偏 vs 有偏 有偏(z-score 归一) 无偏
变种数量 多(pass@k / Dr.GRPO / GFPO / ...) 少(设计简单)
实战收敛 更稳,主流 更快但波动大
vLLM 支持

"该选哪个?"实战经验:先 GRPO;如果不收敛 / 太慢,尝试 RLOO;如果还想再快但接受波动,看 GFPO(trl/experimental/gfpo/)。

12.10多 reward 加权的实战 recipe

ch07.4 提了"format reward + correctness reward"组合。完整 R1 风格 recipe:

def format_reward(completions, **kw):
    """要求 <think>思考</think><answer>答案</answer>"""
    return [1.0 if ("<think>" in c[0]["content"] and "<answer>" in c[0]["content"]) else 0.0
            for c in completions]

def length_reward(completions, **kw):
    """惩罚太长的 think 块"""
    return [max(0, 1 - len(c[0]["content"]) / 4096.0) for c in completions]

from trl.rewards import accuracy_reward

cfg = GRPOConfig(
    output_dir="./r1_repro",
    reward_weights=[1.0, 0.2, 0.1],    # ★ 三个 reward 的权重
    num_generations=8,
    beta=0.001,                          # R1 论文用很小的 KL
    use_vllm=True, vllm_mode="colocate",
)

trainer = GRPOTrainer(
    model=base_model,
    reward_funcs=[accuracy_reward, format_reward, length_reward],
    args=cfg,
    train_dataset=ds,
)
trainer.train()

三个 reward 的角色:

这是 Open-R1(2025-01)公开的几个关键配方之一。

12.11这章你需要带走的