minimind/course/notes/23_ppo_approx_kl_notes.md

305 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# PPO approx_kl 这段代码在做什么
对应源码:
```text
trainer/train_ppo.py:178-189
```
核心代码:
```python
mb_resp_logp = F.log_softmax(res.logits[:, :-1], dim=-1) \
.gather(2, labels[inds].unsqueeze(-1)) \
.squeeze(-1) \
.gather(1, logp_pos[inds])
log_ratio = mb_resp_logp - old_resp_logp[inds]
approx_kl = (
0.5 * (log_ratio ** 2) * resp_policy_mask[inds]
).sum() / resp_policy_mask[inds].sum().clamp(min=1)
if approx_kl_val > args.early_stop_kl:
stop_ppo = True
```
## 1. 先看两个 logprob
`old_resp_logp`:
```text
rollout 时旧 actor 对 response token 的 logprob
```
数学上:
```text
old_resp_logp[b, t] = log pi_old(a_{b,t} | s_{b,t})
```
`mb_resp_logp`:
```text
PPO update 时当前 actor 对同一批 response token 重新算出来的 logprob
```
数学上:
```text
mb_resp_logp[b, t] = log pi_theta(a_{b,t} | s_{b,t})
```
这里的 `a_{b,t}` 是第 `b` 条样本第 `t` 个 response token。
这里的 `s_{b,t}` 是:
```text
prompt + response 前 t-1 个 token
```
## 2. log_ratio 是什么
源码:
```python
log_ratio = mb_resp_logp - old_resp_logp[inds]
```
公式:
```text
log_ratio[b, t]
= log pi_theta(a_{b,t} | s_{b,t})
- log pi_old(a_{b,t} | s_{b,t})
```
也就是:
```text
log_ratio[b, t]
= log( pi_theta(a_{b,t} | s_{b,t}) / pi_old(a_{b,t} | s_{b,t}) )
```
所以后面:
```python
ratio = torch.exp(log_ratio)
```
就是:
```text
ratio[b, t]
= pi_theta(a_{b,t} | s_{b,t}) / pi_old(a_{b,t} | s_{b,t})
```
直观理解:
```text
ratio = 1:
当前 actor 和 old actor 对这个 token 的概率一样。
ratio > 1:
当前 actor 比 old actor 更倾向这个 token。
ratio < 1:
当前 actor 比 old actor 更不倾向这个 token。
```
## 3. approx_kl 是什么
源码:
```python
approx_kl = (
0.5 * (log_ratio ** 2) * resp_policy_mask[inds]
).sum() / resp_policy_mask[inds].sum().clamp(min=1)
```
公式:
```text
approx_kl
= sum_{b,t} m_{b,t} * 0.5 * log_ratio[b,t]^2
/ sum_{b,t} m_{b,t}
```
展开 `log_ratio`:
```text
approx_kl
= sum_{b,t} m_{b,t} * 0.5 *
(log pi_theta(a_{b,t}|s_{b,t})
- log pi_old(a_{b,t}|s_{b,t}))^2
/ sum_{b,t} m_{b,t}
```
其中:
```text
m_{b,t} = resp_policy_mask[b,t]
```
`resp_policy_mask` 的作用:
```text
只统计有效 response token;
不统计 prompt;
不统计 padding;
不统计 EOS 后面的 token。
```
## 4. 为什么叫 approx_kl
PPO 需要监控:
```text
当前 actor pi_theta 离 rollout 时的 old actor pi_old 有多远。
```
KL 本来是衡量两个分布距离的量。
当新旧策略很接近时,可以用二阶近似:
```text
KL(pi_old || pi_theta)
approx 0.5 * (log pi_theta - log pi_old)^2
```
所以源码用:
```python
0.5 * (log_ratio ** 2)
```
来近似新旧策略的 KL。
直观判断:
```text
log_ratio 接近 0:
new actor 和 old actor 很接近,approx_kl 小。
log_ratio 绝对值变大:
new actor 和 old actor 差得更远,approx_kl 变大。
```
## 5. 它是不是训练 loss
这段 `approx_kl` 不是 PPO 的主训练 loss。
PPO 的 actor loss 在后面:
```python
policy_loss = (
clipped_policy_loss
+ args.kl_coef * kl_ref_penalty
)
```
`approx_kl` 的主要用途是 early stop:
```python
if approx_kl_val > args.early_stop_kl:
stop_ppo = True
```
意思是:
```text
如果当前 actor 相对 old actor 改得太多,
就停止继续用这批 rollout 做 PPO 更新。
```
因为 PPO 是 on-policy 算法。
这批 response 是 old actor 生成的,如果当前 actor 已经离 old actor 太远,再继续拿这批旧 rollout 训练就不稳定。
## 6. 和 kl_ref_penalty 的区别
这两个很容易混:
```text
approx_kl:
比较当前 actor 和 rollout 时的 old actor。
用途是 early stop / 监控 PPO 更新幅度。
源码位置: train_ppo.py:180-189
kl_ref_penalty:
比较当前 actor 和 frozen reference model。
用途是放进 actor loss,防止模型偏离 SFT reference。
源码位置: train_ppo.py:194-199
```
一句话:
```text
approx_kl 控制“别离 old actor 太远”。
kl_ref_penalty 控制“别离 SFT reference 太远”。
```
## 7. 最小例子
假设某个 token:
```text
old_resp_logp = -2.0
mb_resp_logp = -1.8
```
那么:
```text
log_ratio = -1.8 - (-2.0) = 0.2
ratio = exp(0.2) = 1.221
approx_kl_token = 0.5 * 0.2^2 = 0.02
```
含义:
```text
当前 actor 比 old actor 更倾向这个 token;
但变化不大,approx_kl_token 只有 0.02。
```
如果:
```text
old_resp_logp = -2.0
mb_resp_logp = -0.5
```
那么:
```text
log_ratio = 1.5
ratio = exp(1.5) = 4.48
approx_kl_token = 0.5 * 1.5^2 = 1.125
```
含义:
```text
当前 actor 对这个 token 的概率提高太多;
如果 batch 平均也很大,就可能触发 early stop。
```
## 8. 记忆方式
```text
old_resp_logp:
rollout 时候的概率,固定不变。
mb_resp_logp:
当前 actor 重新 forward 的概率,会随着训练变化。
log_ratio:
当前 actor 相对 old actor 的变化量。
approx_kl:
用 log_ratio 的平方估计新旧 actor 的距离。
early_stop_kl:
如果距离太大,就不要继续拿这批 old rollout 训练。
```