用 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 公式讲清楚:
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 rule | reward model 或可验证规则 |
| log_prob(action) | response token log probability 的和或均值 |
所以本章的 yes/no/ok 只是把“生成一段回答”压缩成了一个离散选择,方便先看清 ratio、clipping 和 KL。
Policy、Action 和 Reward
本章实验里有三个 prompt:
say yes
say no
say okpolicy 要在三个 action 里选一个:
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:
old_log_prob = log pi_old(action | prompt)更新时,再用新的 policy 计算:
new_log_prob = log pi_new(action | prompt)两者相减再取指数:
ratio = exp(new_log_prob - old_log_prob)如果 ratio 很大,说明新 policy 比旧 policy 更想选择这个 action;如果 ratio 很小,说明新 policy 想远离它。
Advantage
advantage 表示“这个 action 比预期好多少”。完整 PPO 常常会用 value model 估计 baseline。
本章为了保持实验透明,直接用 rule reward 作为 advantage:
advantage = reward这样读者可以先看懂 clipping,后面再补 value/GAE 等更完整的 RL 细节。
更完整的直觉是:
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 的核心是限制策略更新不要太猛:
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:
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 则鼓励策略保持一定探索性:
loss = loss - entropy_coef * entropy运行实验
uv run python -m llm_tutor.experiments.train_ppo_bandit --epochs 30smoke 版:
uv run python -m llm_tutor.experiments.train_ppo_bandit --epochs 3样例输出:
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 直接模仿标准答案:
prompt + response -> next-token lossPPO 则先让 policy 采样 action,再根据 reward 强化或削弱这个 action:
sample -> reward -> policy update这就是 RLHF 相比 SFT 多出来的关键一步。
下一步
下一章会讲 DPO。DPO 不需要 PPO 这种在线采样循环,而是直接用 chosen/rejected 偏好对构造 loss。