PPO(Proximal Policy Optimization)是 RLHF(Reinforcement Learning from Human Feedback)流水线里最常用的强化学习算法:ChatGPT、Claude 早期的对齐训练都用它把一个已经会说话的模型,调教成一个”说人话更讨喜”的模型。这篇文章按”为什么需要它 → 它怎么推导出来 → 怎么落到 LLM 训练里”的顺序讲清楚 PPO,重点放在它在 LLM RLHF 场景下的具体形态,而不是经典的 Atari/MuJoCo 场景。

1. RLHF 全景与 MDP 建模

一个典型的 RLHF 流程分三步:SFT——用人工标注的高质量问答对,对预训练模型做监督微调,得到一个”听得懂指令”的基础策略;训练 Reward Model(RM)——让标注员对同一个 prompt 的多个模型输出排序,训练一个打分模型 rϕ(x,y)r_\phi(x, y),输入 prompt xx 和回复 yy,输出一个标量分数,代表”人类有多喜欢这个回复”;PPO 阶段——把 SFT 模型当作初始策略,用 RM 的打分作为强化学习的 reward 信号,继续优化这个策略。PPO 只负责第三步,但效果很大程度上取决于前两步的质量——RM 学得不准,PPO 会很忠实地把策略优化到 RM 的漏洞上(这就是后面要讲的 reward hacking)。

要用 RL 的框架处理语言生成,首先要把生成过程翻译成 MDP(马尔可夫决策过程)的语言:状态 sts_t 是 prompt 加上目前已经生成的 token,即 st=(x,y<t)s_t = (x, y_{<t})动作 ata_t 是下一个要生成的 token,动作空间就是整个词表(通常几万到十几万);策略 πθ\pi_\theta 就是 LLM 本身,πθ(atst)\pi_\theta(a_t \mid s_t) 是模型在给定上下文时对下一个 token 的概率分布;一个 episode 是一次完整的生成,从 prompt 开始到 EOS 或截断为止。reward 是和经典 RL 最不一样的地方:RM 通常只在整个回复生成完毕后对完整序列打一个分,中间每个 token 没有”环境”给出的即时奖励,原始 reward 是序列级、稀疏的,只落在最后一个 token 上。这个”每个 token 是一步决策,但奖励只在最后才出现”的结构,决定了后面 GAE 和 KL 惩罚要怎么设计。

2. 从策略梯度到 Clipped Objective

强化学习最直接的做法是策略梯度:直接对期望回报 J(θ)=Eτπθ[R(τ)]J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta}[R(\tau)] 求梯度,REINFORCE 给出的估计是:

θJ(θ)=Et[θlogπθ(atst)Rt]\nabla_\theta J(\theta) = \mathbb{E}_t\big[\nabla_\theta \log \pi_\theta(a_t \mid s_t) \cdot R_t\big]

直觉很简单:某次采样的轨迹回报 RtR_t 是正的就调高这次采样到的动作的概率,是负的就调低。问题在于 RtR_t 是蒙特卡洛采样得到的,方差很大,尤其在长序列(LLM 生成动辄几百个 token)和巨大动作空间(几万词表)下会被进一步放大。标准做法是引入一个 baseline(通常是状态价值函数 V(st)V(s_t)),用优势函数 At=RtV(st)A_t = R_t - V(s_t) 替代原始回报——它衡量”这个动作比这个状态下的平均水平好多少”,均值为零,方差显著更小:

θJ(θ)=Et[θlogπθ(atst)At]\nabla_\theta J(\theta) = \mathbb{E}_t\big[\nabla_\theta \log \pi_\theta(a_t \mid s_t) \cdot A_t\big]

策略梯度还有一个更棘手的问题:更新幅度不好控制。学习率稍大,一次梯度更新就可能让策略的输出分布剧烈偏移——对 LLM 来说,意味着模型可能几步之内就开始输出乱码,或坍缩到能骗过 RM 但语言质量很差的退化模式,而且这种偏移一旦发生很难恢复,因为下一批 rollout 已经是从被破坏的策略里采出来的。TRPO(Trust Region Policy Optimization)的方案是显式约束新旧策略之间的 KL 散度,把每一步更新限制在一个”信赖域”内,但这需要求解带约束的二阶优化问题,实现复杂、代价高。PPO 的出发点是:能不能用一个更简单的一阶方法,达到类似的效果?

PPO 的答案是用一个概率比来衡量新旧策略的差异:rt(θ)=πθ(atst)/πθold(atst)r_t(\theta) = \pi_\theta(a_t \mid s_t) / \pi_{\theta_{\text{old}}}(a_t \mid s_t)。如果直接优化 rt(θ)Atr_t(\theta) A_t,当 AtA_t 很大时优化过程会疯狂拉高 rt(θ)r_t(\theta),导致策略一步走出信赖域。PPO 的 clipped objective 把这个比值夹在一个区间内:

LCLIP(θ)=Et[min(rt(θ)At, clip(rt(θ),1ϵ,1+ϵ)At)]L^{\text{CLIP}}(\theta) = \mathbb{E}_t\Big[\min\big(r_t(\theta)\, A_t,\ \text{clip}(r_t(\theta),\, 1-\epsilon,\, 1+\epsilon)\, A_t\big)\Big]

ϵ\epsilon 通常取 0.1~0.2。当 At>0A_t > 0(这个动作比平均水平好)时,rt(θ)r_t(\theta) 一旦超过 1+ϵ1+\epsilon,clip 后的项就不再增长,梯度也就没有动力继续把 rtr_t 推得更高;当 At<0A_t < 0 时同理,rt(θ)r_t(\theta) 一旦跌破 1ϵ1-\epsilon,目标就不再因为继续降低 rtr_t 而变得更”好”。取 min\min 而不是直接用 clip 后的值,是为了保证这个目标始终是真实目标的一个悲观下界——无论 AtA_t 正负,最终优化的都是更保守的那个估计,防止模型基于单次采样的优势估计做出过度自信、不可逆的更新。这就是”proximal”(邻近)这个名字的来源:用一个廉价的一阶方法,达到和 TRPO 类似的”每步更新幅度受限”的效果。

3. GAE 与完整损失函数

Clipped objective 里的 AtA_t 需要一个价值函数 Vϕ(st)V_\phi(s_t) 来估计。PPO 通常配合 GAE(Generalized Advantage Estimation)计算优势:

δt=rt+γVϕ(st+1)Vϕ(st),AtGAE(γ,λ)=l=0(γλ)lδt+l\delta_t = r_t + \gamma V_\phi(s_{t+1}) - V_\phi(s_t), \qquad A_t^{\text{GAE}(\gamma,\lambda)} = \sum_{l=0}^{\infty} (\gamma\lambda)^l\, \delta_{t+l}

δt\delta_t 是单步 TD 误差,λ\lambda 控制”往后看多远”:λ=0\lambda=0 时 GAE 退化成只用单步 TD 误差(低方差、高偏差),λ=1\lambda=1 时退化成蒙特卡洛优势估计(高方差、无偏),常见取值在 0.9~0.97 之间权衡。在 LLM RLHF 场景里,由于原始 reward 只落在序列最后一个 token,Vϕ(st)V_\phi(s_t) 需要学会”预测从当前生成到结尾最终能拿到的 RM 分数”,这也是为什么需要单独训练一个 Critic(价值模型),通常从 SFT 模型或 RM 初始化,输出维度改成每个 token 位置一个标量。

实际实现中,PPO 的总损失是三项的组合:

L(θ,ϕ)=LCLIP(θ)c1LVF(ϕ)+c2S[πθ](<st>)L(\theta, \phi) = L^{\text{CLIP}}(\theta) - c_1 \, L^{\text{VF}}(\phi) + c_2 \, S[\pi_\theta](<s_t>)

LCLIP(θ)L^{\text{CLIP}}(\theta) 是上面推导的 clipped policy objective,训练 Actor;LVF(ϕ)=(Vϕ(st)Vttarget)2L^{\text{VF}}(\phi) = (V_\phi(s_t) - V_t^{\text{target}})^2 是 Critic 的回归损失(Vttarget=AtGAE+Vϕ(st)V_t^{\text{target}} = A_t^{\text{GAE}} + V_\phi(s_t));S[πθ](<st>)S[\pi_\theta](<s_t>) 是策略的熵,加一个熵奖励鼓励策略保持一定的随机性,防止过早收敛到确定性输出。c1,c2c_1, c_2 分别控制价值损失和熵奖励的权重。

4. LLM RLHF 里的 PPO:四个模型与 KL 惩罚

经典 RL 场景(比如 MuJoCo)里 PPO 只需要 Actor 和 Critic 两个网络。LLM RLHF 的 PPO 阶段要同时在显存里放四个模型,这是它区别于经典 PPO 最大的工程挑战:

模型是否训练作用
Policy / Actor训练当前正在优化的 LLM,从 SFT 模型初始化,负责生成回复
Reference Model冻结SFT 模型的一份只读副本,只用来计算 KL 惩罚,防止 Policy 跑得太远
Reward Model冻结单独训练好的打分模型,只在序列末尾给一个标量分数
Critic / Value Model训练预测每个 token 位置的价值 Vϕ(st)V_\phi(s_t),为 GAE 提供优势估计

四个模型往往参数规模相近(Critic 和 Reward Model 有时共享底座,只是输出头不同),意味着 PPO 阶段的显存和计算开销大约是纯推理的数倍——这也是后面 GRPO 等变体想要砍掉 Critic 的直接动机。

只靠 RM 的打分做 reward,策略很容易朝着 RM 的漏洞优化过去,而不是真正变得更好。RLHF 的标准做法是把 Policy 和 Reference Model 之间的 KL 散度作为惩罚项加进 reward:

rt=1[t=T]rϕ(x,y)只在最后一个 token 给出    βlogπθ(atst)πref(atst)r_t = \underbrace{\mathbb{1}[t = T] \cdot r_\phi(x, y)}_{\text{只在最后一个 token 给出}} \;-\; \beta \log\frac{\pi_\theta(a_t \mid s_t)}{\pi_{\text{ref}}(a_t \mid s_t)}

这个 KL 惩罚是逐 token计算并加到每一步的 reward 上,而 RM 的分数只在最后一个 token 出现——GAE 会自动把末尾的 RM 分数按时间差分传播回前面的 token(credit assignment),每一步自身的 KL 偏移也被实时计入 reward,两件事互不冲突。β\beta 的取值很敏感:太小则约束不住策略,容易 reward hacking;太大则策略几乎不敢偏离 SFT 模型,学不到 RM 想要的偏好。InstructGPT 等工作采用自适应 KL 控制:持续监控实际 KL 值和目标 KL 的偏差,用一个简单的比例控制器动态调整 β\beta,而不是固定一个常数。

5. 实践中的坑、PPO 之外的变体

工程实践里几个常见的坑:Reward hacking——策略找到 RM 打分函数里的漏洞而不是真正提升质量,常见表现是回复异常啰嗦(RM 对长回复有偏好)、堆砌 RM 喜欢的措辞,缓解手段包括 KL 惩罚、RM 集成、定期用新数据重新训练 RM;PPO epoch 数不能太多——同一批 rollout 数据重复做太多次梯度更新会让策略过拟合到这批样本、KL 迅速失控,LLM RLHF 里通常只做 1~2 个 epoch,比经典 RL 场景保守得多;Value Model 训练不稳定——Critic 从头学习预测最终 RM 分数本身很难,训练早期价值估计噪声大会污染优势估计,一些实践会先单独 warm-up Critic;Reward 归一化——不同 batch 之间 RM 分数尺度可能漂移,常用 running mean/std 归一化(whitening);采样参数——rollout 阶段的 temperature/top-p 直接决定探索到的动作分布,进而影响优势估计的方差,是个容易被忽略但影响很大的超参数。

PPO 的主要代价是”重”:需要单独训练 RM、维护 Critic、同时跑四个模型。近两年的变体分别砍掉了一部分复杂度:**DPO(Direct Preference Optimization)**跳过显式的 RM 和 PPO 采样-更新循环,直接从人类偏好对 (yw,yl)(y_w, y_l) 构造对比式损失函数,用监督学习的方式优化策略,代价是假设了 Bradley-Terry 偏好模型,且失去了在线探索的灵活性;**GRPO(Group Relative Policy Optimization,DeepSeekMath 提出)**保留 PPO 的 clipped objective 和在线 rollout,但去掉 Critic——对同一个 prompt 采样一组输出,用组内奖励的均值和方差把每个输出的优势标准化,直接省掉一个和 Policy 同等规模的模型,是目前 LLM 推理能力训练中很受欢迎的简化方向。这两者都是在”PPO 的通用性”和”更少的模型、更简单的流程”之间做取舍——PPO 仍是最成熟、最通用的基线,但如果偏好数据结构足够规整,或能接受组内相对奖励的近似,更轻量的变体往往性价比更高。

归根结底,PPO 在 LLM RLHF 里做的事情,是把”用 RM 的打分改进模型”这件事,约束在”每一步更新都不能偏离参考模型太远”的框架里完成:clipped surrogate objective 保证单次更新幅度有界,GAE 把稀疏的序列级奖励合理分摊到每个 token 上,KL 惩罚从 reward 层面再加一道防线。理解这三件事分别在解决什么问题,比记住最终的目标函数公式更重要。