Chapter 04

核心概念:reference / reward / policy 三模型

📌 commit 8221a9f对齐算法里最容易混乱的概念

RLHF 系算法(PPO / DPO / GRPO)里反复出现 reference / reward / policy 三种"模型"。它们什么时候是同一个、什么时候不同、显存怎么算——这些是后续章节的底座。

4.1三个角色的本质

角色作用初始权重通常更新
Policy当前正在训的模型(生成 response)SFT 模型✅ 更新
ReferenceKL 散度的参考"原点",防止 policy 偏离太远SFT 模型(冻结副本)❌ 冻结
Reward Model给 (prompt, response) 打标量分RM 训练得到❌ 冻结

4.2显存账本(7B 模型为例)

算法policyreferenceRMcritic总显存 (bf16)
SFT14 GB14 GB
DPO14 GB14 GB28 GB
PPO14 GB14 GB14 GB14 GB56 GB
GRPO14 GB14 GB14 GB42 GB
ORPO14 GB14 GB
KTO14 GB14 GB28 GB

这就是为什么 RLHF 系比 SFT "贵"得多——一份 base 模型要在 GPU 上同时存好几份。

4.3KL 散度:reference 的角色

所有"对齐"算法的目标都是"让 policy 接近 RM 偏好,但又别离 reference 太远"

$$\max_\pi \;\mathbb{E}_{x\sim D}\big[R(x, \pi(x))\big] - \beta \;\mathrm{KL}\!\big(\pi \;\|\; \pi_\text{ref}\big)$$

$\beta$ 越大、policy 越被 reference 拉住;$\beta$ → 0 则只追 reward,容易 reward hacking。典型 $\beta = 0.1$。

4.4用 LoRA 省一份模型

显存紧时常用招:policy 上 LoRA,reference 直接等于"关掉 LoRA 后的 policy"。TRL 自动支持:

from peft import LoraConfig
peft_cfg = LoraConfig(r=16, target_modules="all-linear")

trainer = DPOTrainer(
    model=model,
    ref_model=None,           # ★ 不传 ref_model,TRL 自动用 disable_adapters
    args=cfg,
    train_dataset=ds,
    peft_config=peft_cfg,
)

实现:训练前 with model.disable_adapter(): ref_logprobs = ...,节省一份模型显存。

4.5三个角色的时间线

flowchart LR Base[Base 模型] --> SFT[SFT 训练] SFT --> SFTModel[SFT model] SFTModel --> Pol[Policy 初始权重] SFTModel --> Ref[Reference 冻结副本] Pref[偏好数据] --> RMTrain[RM 训练] SFTModel --> RMInit[RM body 复用] RMTrain --> RM[Reward Model] Pol & Ref & RM --> Align[PPO / DPO / GRPO 对齐] Align --> Final[Aligned 模型]

4.6哪些算法不需要 reference

这些算法显存压力小、训练简单,缺点是无法严格控制偏离

4.7哪些算法不需要 RM

4.8_BaseTrainer:TRL 自己的根类

读 TRL 源码时会注意到一件事:所有主 trainer 都继承 _BaseTrainertrl/trainer/base_trainer.py:30),而 _BaseTrainer 才继承 transformers.Trainer不是直接继承 HF Trainer

设计要点原因 / 影响
"自包含" 哲学 每个 trainer 文件故意复制代码而非抽象 —— 读 dpo_trainer.py 不用跳来跳去;改 grpo_trainer.py 不会影响 dpo
_BaseTrainer 只放最少共享逻辑model card 生成、telemetry、几个轻量 helper
子类经常重写 train()PPO/GRPO 自带 rollout 循环,不复用 HF Trainer 的 step 循环
Online preference 有自己的继承链 OnlineDPOTrainer_BaseTrainerNashMDTrainer / XPOTrainer 再继承 OnlineDPOTrainer

"为什么 TRL 不抽出 BasePreferenceTrainer 之类的基类?" —— 维护者明确选择了"代码重复 vs 接口稳定"中的前者。算法迭代太快,强抽象反而成枷锁。

4.9reference 模型的三种省显存招

4.4 给出了"LoRA + disable_adapter" 一招。TRL 实际上有三招,对应不同显存预算和精度需求。来源:DPOConfig(dpo_config.py:23)的几个字段。

招式YAML 字段代价典型用
LoRA + disable_adapter() 不写 ref_model + 传 peft_config每次需要 ref logp 时切换两次 adapter 状态常规 DPO
预计算 ref logprobs precompute_ref_log_probs: true 训练前过一遍 ref 模型存盘,之后扔掉大 ref 模型,但 dataset 小且固定
TR-DPO 同步 sync_ref_model: true + ref_model_sync_steps定期把 policy 拷给 ref,模拟"软 reference"需要 reference 跟得上 policy 漂移时

三招互斥:

4.10PEFT 路径下 ref 模型的真实状态

不传 ref_model 但传了 peft_config 时,DPOTrainer 在 __init__dpo_trainer.py:595–600附近)做的事:

  1. 把 policy model 用 PEFT 包成带 LoRA adapter;
  2. "ref adapter" = adapter 关掉时的同一份 base 模型
  3. 每次需要 ref logp,调 model.disable_adapter() 进 ctx manager 拿一次 forward;
  4. 退出 ctx manager 后 adapter 恢复启用,policy forward 正常。

这种做法的 caveat:

4.11这章你需要带走的