返回章节列表

Post-training / Chapter 17

17. PPO:经典 RLHF 的核心优化器

用一个 tiny bandit 实验解释 PPO 的 policy、old policy、advantage、ratio、clipping、entropy 和 KL penalty。

本章实践入口代码和实验从这里开始

用 bandit 显微镜看 PPO 的 ratio、clipping、KL 和 reward 变化。

运行命令
uv run python -m llm_tutor.experiments.train_ppo_bandit --epochs 30

训练型脚本通常支持 --output-dir,可以把配置、指标、日志和模型产物保存到 runs/ 下,便于复盘。

本章学习契约

  • 新增概念:policy、reward、old policy、ratio、clipping、KL penalty。
  • 实验要验证:PPO 如何用 reward 鼓励更好的 action,同时用 clipping/KL 限制 policy 一步改得太猛。
  • 实验不验证:它不是完整 RLHF 系统,也没有训练语言模型生成长文本。
  • 跑完重点看:平均 reward 是否上升,policy 是否偏向高 reward action,KL/ratio 是否提醒更新幅度。

PPO 是 RLHF 里最经典的一类优化器。真实 RLHF 会让语言模型生成回答,再用 reward model 打分,并用 PPO 更新 policy。

这一章先不直接训练 mini-GPT 生成长文本,而是用一个 tiny bandit 实验把 PPO 公式讲清楚:

text
prompt -> policy 选择 action -> rule reward 打分 -> PPO 更新 policy

这样可以先看懂核心优化目标,再把它迁移到语言模型。

这个实验刻意省略了完整 RLHF 里的很多组件:没有 token-level generation、没有 reward model、没有 value model/GAE、没有 rollout buffer,也没有多轮 prompt-response。它只负责讲清楚 PPO 更新本身。

教学 bandit 和真实 LLM 的对应关系大致是:

Bandit 实验LLM/RLHF 里对应什么
prompt用户问题或上下文
action一段 response,内部由多个 token 组成
policy正在训练的语言模型
old policy采样这批回答时的模型快照
reward rulereward model 或可验证规则
log_prob(action)response token log probability 的和或均值

所以本章的 yes/no/ok 只是把“生成一段回答”压缩成了一个离散选择,方便先看清 ratio、clipping 和 KL。

Policy、Action 和 Reward

本章实验里有三个 prompt:

text
say yes
say no
say ok

policy 要在三个 action 里选一个:

text
yes / no / ok

规则 reward 很简单:

  • action 等于目标,reward = 1.0
  • action 不等于目标,reward = -0.2

真实 RLHF 里的 action 是一整段生成文本,reward 通常来自 reward model 或规则评估器。

Old Policy 和 Ratio

PPO 更新前会先用当前 policy 采样一批 action,并记录当时的 log probability:

text
old_log_prob = log pi_old(action | prompt)

更新时,再用新的 policy 计算:

text
new_log_prob = log pi_new(action | prompt)

两者相减再取指数:

text
ratio = exp(new_log_prob - old_log_prob)

如果 ratio 很大,说明新 policy 比旧 policy 更想选择这个 action;如果 ratio 很小,说明新 policy 想远离它。

Advantage

advantage 表示“这个 action 比预期好多少”。完整 PPO 常常会用 value model 估计 baseline。

本章为了保持实验透明,直接用 rule reward 作为 advantage:

text
advantage = reward

这样读者可以先看懂 clipping,后面再补 value/GAE 等更完整的 RL 细节。

更完整的直觉是:

text
advantage = reward - baseline

如果某个回答得分是 0.8,但模型本来预计这个 prompt 平均能拿 0.7,那么 advantage 只有 0.1;如果本来预计只能拿 0.2,那么 advantage 是 0.6。value model/GAE 的作用就是更稳定地估计这个 baseline。本章跳过它,是为了不让第一个 PPO 实验同时背太多 RL 概念。

Clipped Objective

PPO 的核心是限制策略更新不要太猛:

text
unclipped = ratio * advantage
clipped = clamp(ratio, 1 - eps, 1 + eps) * advantage
loss = -mean(min(unclipped, clipped))

PPO 论文常说“最大化 objective”。代码里写成负号,是因为 PyTorch 优化器默认最小化 loss。

直觉上:

  • 如果一个 action 很好,PPO 鼓励提高它的概率;
  • 但概率不能一下子提高太多;
  • 如果一个 action 很差,PPO 鼓励降低它的概率;
  • 但也不希望一步把策略推得太远。

KL Penalty 和 Entropy

真实 RLHF 通常还会让 policy 不要偏离 reference model 太远。这个约束常写成 KL penalty:

text
loss = ppo_policy_loss + beta * KL(policy || reference)

本章实验里 reference 是一个固定的均匀策略。

真实语言模型训练通常按生成 token 估计 KL penalty。本章为了教学,直接计算 categorical policy 的完整 KL(policy || reference)

这也是一个重要边界:bandit 的 action 空间只有 3 个,所以可以枚举完整分布;LLM 的回答空间巨大,通常只能在实际生成的 token 上近似计算 policy 和 reference 的 logprob 差。

entropy 则鼓励策略保持一定探索性:

text
loss = loss - entropy_coef * entropy

运行实验

bash
uv run python -m llm_tutor.experiments.train_ppo_bandit --epochs 30

smoke 版:

bash
uv run python -m llm_tutor.experiments.train_ppo_bandit --epochs 3

样例输出:

text
prompts=3 actions=3
epoch=001 sampled_reward=... greedy_reward=... policy_loss=... ratio_min=... ratio_max=... clipped_fraction=... kl=... entropy=...

policy
prompt='say yes' action='yes' target='yes'
...

这个实验的目标不是得到一个强模型,而是确认:

  • old log prob 和 new log prob 如何形成 ratio;
  • clipping 如何进入 loss;
  • 同一批 rollout 多轮 PPO 更新后,ratio 如何偏离 1;
  • KL penalty 如何约束 policy;
  • PPO 本质上是在优化“被 reward 引导的采样行为”。

和 SFT 的区别

SFT 直接模仿标准答案:

text
prompt + response -> next-token loss

PPO 则先让 policy 采样 action,再根据 reward 强化或削弱这个 action:

text
sample -> reward -> policy update

这就是 RLHF 相比 SFT 多出来的关键一步。

下一步

下一章会讲 DPO。DPO 不需要 PPO 这种在线采样循环,而是直接用 chosen/rejected 偏好对构造 loss。

上一章:16. SFT:让模型学会按指令回答