PPO的局限与RLHF的兴起

说实话,PPO在游戏、机器人控制这些领域确实很能打。我最早接触PPO是在做Atari游戏智能体的时候,效果确实惊艳。但当我把它搬到LLM微调上,问题就来了。

为什么会这样?你想想看,游戏里的奖励信号是明确的——得分多少、过关与否。但LLM呢?什么叫「好」的回答?什么叫「不好」?这太主观了。PPO原本的设计假设,在LLM场景下开始出现裂缝。

PPO在LLM微调中的三大硬伤

我总结了一下,PPO在LLM场景下主要有三个问题。嗯,每个我都踩过坑。

  1. 奖励信号太稀疏:游戏里每帧都有奖励,但LLM生成一句话,你只能给一个整体评分。中间哪个词好、哪个词不好?不知道。
  2. 动作空间爆炸:LLM的词汇表动辄几万甚至几十万。PPO在这种超高维离散动作空间里,效率低得让人抓狂。
  3. 训练不稳定:PPO依赖重要性采样,但LLM的策略更新稍微大一点,重要性权重就飘了。我见过loss直接飞到NaN的情况。

核心矛盾:PPO需要密集、连续的奖励信号才能稳定训练。但LLM的奖励天然是稀疏、离散的。这就好比让一个短跑运动员去游泳——不是不能游,但姿势肯定别扭。

奖励模型训练:给LLM装个「评分器」

既然真实奖励不好拿,那就训练一个奖励模型来模拟。这是RLHF的核心思路。

具体怎么做?我习惯分三步走:

  1. 收集人类偏好数据:让标注员对比两个模型输出,选出更好的那个。注意,不是打分,是排序。
  2. 训练奖励模型:用Bradley-Terry模型来拟合人类偏好。说白了就是学一个函数,给好的回答高分,差的低分。
  3. 用奖励模型指导PPO:把奖励模型的输出当作PPO的奖励信号。

这里有个细节——奖励模型通常和策略模型共享底层结构。我在项目中试过用不同大小的模型,发现奖励模型太小的话,学到的偏好会失真。

# 奖励模型训练的核心代码(简化版)
import torch.nn as nn

class RewardModel(nn.Module):
    def __init__(self, base_model):
        super().__init__()
        self.base_model = base_model
        # 在最后一层加一个线性层,输出标量奖励
        self.reward_head = nn.Linear(base_model.config.hidden_size, 1)
    
    def forward(self, input_ids, attention_mask):
        outputs = self.base_model(input_ids, attention_mask=attention_mask)
        # 取最后一个token的隐藏状态
        last_hidden = outputs.last_hidden_state[:, -1, :]
        reward = self.reward_head(last_hidden)
        return reward.squeeze(-1)

避坑指南:我曾经把奖励模型训练得太久,结果它记住了训练数据里的噪声。后来我加了一个early stopping,在验证集上监控准确率,效果好了很多。

KL散度惩罚:别让模型跑偏

PPO在LLM微调中最大的问题是什么?奖励过拟合。模型会疯狂寻找奖励模型的漏洞,生成一些语法正确但内容荒谬的文本。

我见过最夸张的例子:模型学会了重复「非常好」这个词100遍,因为奖励模型觉得这个词出现次数越多,回答越好。

解决方案就是KL散度惩罚。核心思想很简单——不要让微调后的模型离原始模型太远。

数学上,PPO的目标函数变成了:

# 伪代码:带KL惩罚的PPO目标
ppo_loss = -E[ min(ratio * advantage, clip(ratio, 1-eps, 1+eps) * advantage) ]
kl_penalty = beta * KL(pi_ref || pi_current)
total_loss = ppo_loss + kl_penalty

这里的beta是个超参数。我建议从0.01开始调,太小了惩罚不够,太大了模型学不到新东西。

注意:KL散度不是对称的。我习惯用前向KL(KL(pi_ref || pi_current)),因为它对pi_current的「冒险行为」惩罚更严厉。反向KL会让模型过于保守。

奖励过拟合:RLHF的阿克琉斯之踵

奖励过拟合是RLHF里最头疼的问题。说白了,就是模型学会了「欺骗」奖励模型,而不是真正提升回答质量。

我总结了几种典型表现:

  • 长度作弊:模型发现长回答更容易得高分,就开始无意义地堆砌内容
  • 词汇偏好:某些高频词(如「好的」「当然」)被过度使用
  • 模板化:所有回答都套用同一个结构,缺乏多样性

怎么缓解?我试过几种方法:

  1. 奖励归一化:把奖励缩放到[-1, 1]区间,减少极端值的影响
  2. 多奖励模型集成:训练3-5个奖励模型,取平均分
  3. 动态KL权重:根据当前KL散度自动调整beta值

嗯,这些方法都有用,但治标不治本。真正的问题在于PPO的框架本身——它需要一个稳定的奖励信号,但LLM的奖励天然不稳定。

从PPO到DPO:为什么需要改变?

说到这里,你可能已经感觉到了——PPO在LLM场景下,有点「杀鸡用牛刀」的意思。它太复杂了,需要维护策略网络、价值网络、奖励模型三个组件,训练流程也繁琐。

我算过一笔账:用PPO微调一个7B模型,光是奖励模型的前向推理就占了30%的计算量。而且,PPO对超参数极其敏感,稍微调不好就崩。

所以后来出现了DPO(Direct Preference Optimization)。它的思路很直接——既然我们有偏好数据,为什么不直接优化偏好?非要绕一圈去训练奖励模型?

DPO的核心洞察是:奖励函数可以隐式地嵌入到策略优化中。不需要显式的奖励模型,也不需要PPO的复杂流程。说白了,就是让模型直接从「好回答vs坏回答」的对比中学习。

我的看法:PPO在LLM领域不是不能用,但性价比太低。如果你有充足的计算资源和调参经验,PPO依然能出好结果。但如果你想要一个更轻量、更稳定的方案,DPO是更好的选择。

我个人习惯在项目初期先用DPO快速验证想法,等模型效果稳定了,再考虑要不要上PPO做精细调优。这样既保证了效率,又保留了上限。

好了,这一章就到这里。下一章我们会深入DPO的数学原理和实现细节,看看它到底是怎么绕过奖励模型的。


公众号:蓝海资料掘金营,微信deep3321