【大模型】从强化学习到PPO训练
第一部分:前言
笔者研究大模型和强化学习相关领域已有一段时日。大语言模型(Large Language Model, LLM)在近几年迎来爆炸式发展,为自然语言处理、对话系统、自动代码生成等领域带来了全新的机遇。特别是在有了人类偏好训练(RLHF:Reinforcement Learning from Human Feedback) 这一思路之后,如何高效地将人类反馈与强化学习的范式相结合,就成了下一代大模型的“核心暗器”。
要想领悟 RLHF 的精髓,其中不可回避的算法之一就是PPO(Proximal Policy Optimization,近端策略优化)。它能在保证一定训练稳定性的同时,最大限度地让大模型根据人类的打分或偏好进行更新。 Hugging Face 团队开源的 TRL (Transformers Reinforcement Learning) 库,让大家可以在自己的语言模型之上跑 PPO、训练大语言模型的对话质量或者某些特定任务的完成质量,乃至让模型生成更优雅、更高效的代码。
本博文将分为若干个层层递进的章节,从初步概念到原理分析、从 PPO 到 RLHF 再到大模型代码生成,并包含部分关键代码的示例。
第二部分:强化学习的核心思路与 RLHF 概念
2.1 强化学习的要素
为了让后续的 PPO 讲解更有意义,我们先来简单回顾一下强化学习(Reinforcement Learning, RL)的核心构架。
- Agent 与 Environment: 在 RL 中,智能体(Agent)与环境(Environment)对话。Agent 依据环境状态(state)采取行动(action),从环境获得奖励(reward)以及新的状态。
- 策略(Policy): Agent 选择行动的“法则”或“函数”称为策略。策略可以是确定性的,也可以是随机的(例如 π ( a ∣ s ) \pi(a|s) π(a∣s))。
- 价值函数(Value Function): 衡量某个状态(或状态-动作对)在长期内能获得奖励的期望值。常见的如 V ( s ) V(s) V(s)或 (Q(s, a)) 。
- 目标: 最大化总回报(cumulative reward),或期望回报。
而在自然语言处理的大模型场景下,Agent 就是我们的语言模型,Environment 则是“人类反馈打分”或“训练脚本里自定义的一套奖励函数”。我们希望让语言模型的回答越来越符合人类的期望,让这条“策略”持续地被强化。
2.2 人类偏好训练(RLHF)
当大语言模型出现之后,如何让模型“更懂人意”,便催生了RLHF(Reinforcement Learning from Human Feedback) 的想法。它的逻辑大体是:
- 先训练一个监督微调(SFT, Supervised Fine-tuning) 的模型(或许最早只是预训练的模型加上人工标注数据),让模型初步学会如何回答问句、完成摘要或生成代码等;
- 然后训练一个奖励模型(Reward Model),这个奖励模型可以把生成的内容与人类偏好对应起来,比如给“安全、有用”的回答更高分,给“胡说八道、有害”的回答更低分;
- 接着再使用强化学习的方法,让语言模型(即 Agent)在生成内容后,奖励模型进行打分,把这个分数当做 reward,去更新策略。
其中最关键的一点就是如何在大规模语言模型上执行强化学习。市面上出现了许多做法,包括 PPO、DPO、RLAIF、自己写一个 REINFORCE、基于 Actor-Critic、甚至基于鞅逼近(SGD + 边界)的方式,等等。
在 Hugging Face 的 TRL 库 中,他们主要选择了PPO 这一方法,因此也就成了大家接触 RLHF 时的首选之一。
第三部分:PPO(近端策略优化)的原理
3.1 PPO 的魅力
PPO 之所以广受欢迎,是因为它对策略梯度算法做了一个“裁剪”更新,保证每一次更新不会太激进,从而提高训练的稳定性。在经典的策略梯度(Policy Gradient)或 REINFORCE 框架里,更新过大会让训练非常不稳定;而 PPO 引入了一个目标函数剪裁(Clipped Objective),在保持一定更新步幅的同时,避免过度离开旧策略。
PPO 的核心损失可表述为:
L C L I P ( θ ) = E t [ min ( r t ( θ ) A ^ t , clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] L^{CLIP}(\theta) = \mathbb{E}_t \left[ \min(r_t(\theta) \hat{A}_t, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\hat{A}_t)\right] LCLIP(θ)=Et[min(rt(θ)A^t,clip(rt(θ),1−ϵ,1+ϵ)A^t)]
其中:
- r t ( θ ) = π θ ( a t ∣ s t ) π θ old ( a t ∣ s t ) r_t(\theta) = \frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\text{old}}}(a_t|s_t)} rt(θ)=πθold(at∣st)πθ(at∣st)表示新旧策略在同一动作下的概率比;
- A ^ t \hat{A}_t A^t表示优势函数(Advantage Function),衡量当前动作比平均策略好多少;
- ϵ \epsilon ϵ是一个超参数,一般取 0.1 或 0.2 之类;
- clip ( r t ( θ ) , 1 − ϵ , 1 + ϵ ) \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) clip(rt(θ),1−ϵ,1+ϵ)用于约束更新幅度。
如果这个概率比 r t ( θ ) r_t(\theta) rt(θ)离 1 太远,就会被裁剪。这样做的好处是:不至于在某一轮训练中让策略出现极端跳跃,稳定性大大增强,也更易收敛。
3.2 从 RL 到自然语言生成
在传统的强化学习环境中,状态(state)和动作(action)通常是离散或连续的数值,而在大语言模型中:
- 状态: 可以理解为“当前对话上下文”或“已经生成的前缀文本”;
- 动作: 可以理解为“下一步生成的 token(或一段文本)”;
- 奖励: 可以来自一个外部的“Reward Model”或“人工打分”。
PPO 在这其中扮演的角色非常适合,因为它通过“近端优化”的思路,一次性更新大量参数,却避免走得太远。此外,语言模型往往很庞大,包含数亿乃至千亿参数,PPO 强调小步前进+裁剪更新,就显得十分友好。
在 Hugging Face 的实现中,他们还在策略模型(Policy)旁边保留了一个“参考模型(Reference Model)”,用来计算 KL 散度,使得生成的内容不要偏离初始 SFT 模型太远。这也能理解为某种形式的正则化/惩罚项,为了保证不要“破坏”模型本就学到的知识。
第四部分:一个最基础的 PPO 训练脚本
4.1 Hugging Face TRL 的安装
首先,我们需要安装 trl
库(以及其依赖)。常见做法是直接:
pip install trl
官方库地址在这里,可以自行查阅里面的示例脚本。
4.2 Minimal PPO 训练示例
在 TRL 的官方示例中,有一个极简的 PPO 训练脚本 ppo.py
。它为了验证训练器是否正常工作,往往会用一个 “虚拟的奖励模型” 作为打分依据(例如随便定义一个评分函数),然后尝试用 PPO 更新模型。
示例命令行可以是:
python examples/scripts/ppo/ppo.py \
--dataset_name trl-internal-testing/descriptiveness-sentiment-trl-style \
--dataset_train_split descriptiveness \
--learning_rate 3e-6 \
--num_ppo_epochs 1 \
--num_mini_batches 1 \
--output_dir models/minimal/ppo \
--per_device_train_batch_size 64 \
--gradient_accumulation_steps 1 \
--total_episodes 10000 \
--model_name_or_path EleutherAI/pythia-1b-deduped \
--missing_eos_penalty 1.0
这里用到的参数说明大致如下:
--dataset_name
:指定了某个数据集,比如可以是 huggingface 上的一个包含文本的 dataset;--learning_rate
:学习率;--num_ppo_epochs
:我们打算跑几轮 PPO 迭代;--per_device_train_batch_size
:单卡上的 batch size;--total_episodes
:总的 episodes 数量,这里等价于 RL 里的轨迹(trajectories)个数;--model_name_or_path
:选择一个基础模型,比如EleutherAI/pythia-1b-deduped
;--missing_eos_penalty
:一种“EOS 技巧”,会对没有生成EOS token的序列做一个惩罚,鼓励模型学会收尾。
当然,这个脚本本身只是一种最简单的演示,让大家可以先确认“哦,PPO 训练流程能跑起来啦。”再往后我们就可以把这个流程扩展到更真实的 RLHF 应用。
4.3 Weights & Biases 或其它可视化
在训练 PPO 时,我们会跟踪许多指标,可以通过 Weights & Biases 或类似工具可视化,这包括:
eps
:每秒的 episodes 数;objective/kl
:当前策略和参考策略之间的平均 KL 散度;objective/entropy
:策略的平均熵值,代表模型生成结果的多样性;objective/rlhf_reward
:平均 RLHF 奖励分数;objective/scores
:奖励模型的原始评分;policy/approxkl_avg
:策略之间的近似 KL;policy/clipfrac_avg
:策略更新被剪裁的平均比例;loss/policy_avg
、loss/value_avg
:策略和价值函数的损失;- 以及学习率、episode 计数等等。
如果发现某些指标(比如 KL 一路狂飙到 1000,或者 reward 就不涨)就要去排查可能是训练超参数不合适、数据分布有问题、或者出现数值爆炸等等。
第五部分:PPO 与大模型代码生成
5.1 为什么要让大模型生成更高效的代码?
既然 NLP 大模型能写博客、能写论文摘要、当然也能写代码。那么为什么要给它做 PPO 呢?最常见的出发点无外乎:
- 质量:我们想让模型生成的代码正确率更高、效率更高、对边界情况处理更完善;
- 风格:有些场景下我们需要统一的命名风格、注释风格、或者符合某种编程规范;
- 安全:某些代码生成场景需要过滤掉安全风险,如泄露隐私、无权操作文件系统等。
若只用“简单的监督学习”去教模型写代码,模型也许能学到基础的语法和常见库的调用方式,但当需要让它“更进一步”符合开发者需求,或者在多种潜在实现中选择效率更高的实现,就需要引入一个“reward”或“人类偏好打分”。例如:
- 我们可以给同一个功能的多种代码实现,通过某种自动化测试或性能 Benchmark 给出评分,跑得越快得分越高;
- 又或者在人类代码审查后给评分,让 PPO 优化朝向“可读性更好”“低耦合度”“高内聚度”的目标。
5.2 构建一个奖励模型(举例说明)
在实际项目中,如何构建Reward Model并非完全固定。大致有几种思路:
- 自动评分:比如让生成的代码自动编译并运行特定用例测试,通过测试覆盖率、执行速度等指标给出 reward;
- 人类评分:请程序员给一些模型生成的代码打分,然后训练出一个打分模型;
- 混合方式:先用人类打分数据训练一个基础的 reward model,然后再加一些自动化脚本辅助。
下面就设想一个简化场景:假如我们要让模型生成某个函数功能的 Python 代码,然后运行后测一下执行时间,再做一个简单的打分(越快得分越高)。理论上我们可以这样伪代码:
def code_quality_reward(code_str):
# 1. 写到本地一个 temp.py 文件
with open("temp.py", "w") as f:
f.write(code_str)
# 2. 尝试运行并计时
start = time.time()
run_success = run_the_code("temp.py") # 这可能是subprocess调用
end = time.time()
if not run_success:
return -10 # 运行失败就扣分
elapsed = end - start
# 假设我们希望越快越好,就可以做一些简单转换
# 例如: reward = 10 - elapsed (大约跑1秒返回9分, 跑5秒返回5分等)
reward = 10 - elapsed
return reward
这个 reward 可以非常粗糙,但能表达“运行速度”这个偏好。然后我们就把这个函数替换到 PPO 训练中,让生成的代码自动被打分,分数高就让策略更新的优势大。训练若能稳定收敛,模型就能学会如何产生更高效的代码结构(至少在它的训练范式下)。
5.3 训练流程概览
那么在实际操作中,我们可能会做以下流程:
-
准备初始模型:加载一个支持文本生成的模型(如 LlaMA / Qwen / Gemma 等);
-
数据集准备:准备一堆自然语言指令或函数描述,比如“实现一个快速排序函数”,“实现一个斐波那契计算并行版本”,等等;
-
编写奖励函数:如上所述,或者基于自动测试、或者人类打分;
-
PPOTrainer:用 TRL 里的
PPOTrainer
来跑强化学习迭代,每一轮:- 从模型中根据指令采样几段代码;
- 送到 reward 函数里打分;
- 计算优势函数并更新策略(模型权重);
-
迭代:多轮下来,希望reward 能越来越高。
接下来,我们给一个相对完整的示例(伪代码),帮助大家更直观地看到“在大模型上跑 PPO 做代码生成”的样子。
第六部分:示例——训练大模型生成高效的代码
下面的示例不一定能直接跑通(因为省略了大量工程细节),但是能帮助你理解核心流程。如果想要一个更完整、更可运行的脚本,可参考 Hugging Face 的 TRL 官方示例,或根据本示例做适当扩展。
6.1 主要依赖
import time
import subprocess
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from trl import PPOTrainer, PPOConfig
确保你已经安装了 trl
库,并且能正常导入。
6.2 准备初始模型
model_name = "EleutherAI/pythia-1b-deduped"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 注意:有的模型需要添加特殊tokens;也要留意pad_token, eos_token的处理
base_model = AutoModelForCausalLM.from_pretrained(model_name)
base_model_ref = AutoModelForCausalLM.from_pretrained(model_name)
# 用base_model_ref作为参考模型(计算KL散度时用),保持不训练的状态
base_model_ref.requires_grad_(False)
6.3 构建奖励函数
我们这里简单举例:让生成的 Python 代码可以被成功运行,并计时。再将运行时间转化成一个 reward(越快越好)。注意这纯粹是示例,真实项目可能要更复杂,如测试 correctness。
def measure_time_reward(generated_code):
# 1. 写到文件
with open("temp_code.py", "w", encoding="utf-8") as f:
f.write(generated_code)
# 2. 运行
start = time.time()
try:
subprocess.run(["python", "temp_code.py"], check=True, timeout=5)
run_success = True
except subprocess.CalledProcessError:
run_success = False
except subprocess.TimeoutExpired:
run_success = False
end = time.time()
# 3. 打分
if not run_success:
return -5.0 # 运行失败直接负分
elapsed = end - start
# 简单的线性打分
# 跑得越快 -> reward 越高. 一般可以再做一些平滑或截断
reward = 5.0 - elapsed
return reward
6.4 准备训练数据
我们需要一些“指令”或“题目”告诉模型要实现什么功能。可以是一个简单的 list:
train_prompts = [
"请实现一个快速排序函数,要求输入一个列表,输出排序后的列表。",
"编写一段代码,计算前 30 个斐波那契数并打印出来。",
"用 Python 实现一个二分查找函数。",
"写一个脚本,将文件夹下所有 .txt 文件的内容合并成一个新文件。",
# ... 可以自己添加
]
在实际项目中,这些“指令”可能来自真实世界的代码需求,或者一些开源的指令数据集。
6.5 配置并初始化 PPOTrainer
ppo_config = PPOConfig(
model_name_or_path=model_name,
learning_rate=3e-6,
num_ppo_epochs=1,
# 其它可选的配置...
)
ppo_trainer = PPOTrainer(
config=ppo_config,
model=base_model,
ref_model=base_model_ref,
tokenizer=tokenizer,
# reward_model 可以不在此处指定(若已经自定义了reward计算),
train_dataset=None, # 后面我们手动喂数据
)
这里我们直接把 train_dataset
设置成 None
,因为我们会自行在循环中处理 prompt -> 生成 -> 计算 reward -> 更新的过程。当然你也可以给定一个 dataset,让 trainer 自动完成采样。
6.6 PPO 训练循环
下面是一个非常简单的训练循环逻辑示例:
num_episodes = 10000
batch_size = 4 # 每个训练step从多少prompt里采样
for episode_i in range(num_episodes):
# 1. 随机采样一些 prompts
prompts = random.sample(train_prompts, batch_size)
# 2. 让模型生成代码
# 这里要注意 max_new_tokens, temperature, top_k, eos_token, 等等
tokenized_prompts = tokenizer(prompts, return_tensors="pt", padding=True).to(base_model.device)
with torch.no_grad():
gen_outputs = base_model.generate(**tokenized_prompts, max_new_tokens=100)
# 3. 对生成结果做后处理,并计算 reward
# PPO 一般需要分词后的 token 来计算 log_probs
gen_texts = [tokenizer.decode(g, skip_special_tokens=True) for g in gen_outputs]
rewards = []
for text in gen_texts:
# 只取模型新生成的部分作为代码
# 当然这可能要更精细的方式去定位
generated_code = text[len(prompts[0]):]
r = measure_time_reward(generated_code)
rewards.append(r)
# 4. 计算 PPO 的 loss 并进行更新
# PPO 需要把 query (prompt) 与 response (生成) 封装成 rollout 信息
# 这里使用 trl 的 gather-then-update 接口
# (注意:实际中需要的格式更多,比如logprobs, values等,需要仔细看官方PPOTrainer用法)
stats = ppo_trainer.step(prompts, gen_texts, rewards)
# 5. 日志输出
if episode_i % 100 == 0:
avg_reward = sum(rewards) / len(rewards)
print(f"Episode: {episode_i} AvgReward: {avg_reward:.4f} Stats: {stats}")
这段循环大概体现了 PPO 训练的精髓:“让模型先做出动作,再计算奖励,最后更新策略”。具体在 TRL 中,需要把 prompts (query)和 responses 搭配在一起计算 log_probs、价值函数值,然后用奖励进行优势估计(advantage calculation),再通过 PPO 的裁剪目标函数做梯度下降。
在真实的项目中,还需要把“参考模型”ref_model 与 base_model 的输出都计算一下,以便拿到 KL 惩罚等。TRL 库会替你做很多底层操作,但你需要给它喂正确的东西并配置对的超参数。
这样一来,随着训练进行,reward 应该逐渐变高,也就是我们让模型学会了在有限的时间内给出可运行、且更高效的代码实现。
第七部分:PPO 训练过程中的关键指标与调试
7.1 RLHF 指标
正如前文所提,objective/rlhf_reward
一般是我们最关心的指标。如果它一直在涨,说明训练大概率往对的方向走;如果它不涨甚至发散,就需要看看是不是下面问题:
- 学习率过大导致梯度爆炸或 KL 爆炸;
cliprange
设置不当;- reward 函数设计不合理(随便让 model 得到高 reward,却没有真正学到可行输出);
- 训练脚本本身的 BUG 等。
7.2 KL 散度
很多 RLHF 实现都会把 KL 当作“惩罚项”,以免模型输出离原先 SFT 模型差距过大。因为大模型通常在指令任务上已经很强了,我们并不想让它推翻先前的所有学习成果,而是做适度微调。
如果观测到 KL 爆炸增长,则说明新策略与旧策略差距太大。可以适度调小学习率,或者加大 KL 系数、减少梯度步数等等。
7.3 如何查看生成示例
在实际操作中,我们往往还会在每隔 N 步的时候采样一些生成结果看看,确认模型有没有变得“奇怪”。比如在 Weights & Biases 的可视化中,可以把 prompt、model response、reward、参考 model response 等都 log 进去,方便查看训练进程中语言模型的输出变化。
第八部分:将 PPO 扩展到更大规模模型
8.1 模型规模与分布式训练
在小规模(几亿参数)模型上跑 PPO 通常还比较容易,但若是到百亿、千亿级别就要考虑分布式训练(GPU 多卡并行、甚至多机多卡)以及优化器的内存占用。Hugging Face 推出的 Accelerate 与 DeepSpeed 深度结合,就能让这些大模型在多 GPU 环境下进行扩展式训练。
例如,你可以用如下方式启动训练脚本,实现 DeepSpeed stage-3 的并行:
accelerate launch --config_file examples/accelerate_configs/deepspeed_zero3.yaml \
ppo_training_script.py \
--model_name_or_path EleutherAI/pythia-1b-deduped \
--per_device_train_batch_size 16 \
--gradient_accumulation_steps 4 \
--num_ppo_epochs 4 \
...
这样就能在有限 GPU 的前提下更好地利用内存进行大模型的微调。
8.2 Checkpoint 与在线评估
PPO 训练往往要做很长时间,因此每隔一段里程碑(例如 1000 episodes)最好做一次 checkpoint,保存模型权重。这样不仅可以预防训练崩溃后无处恢复,也可以让你在不同时间点比较一下生成质量。在大规模项目中,也会有在线评估的需求,比如设置一批验证指令,每隔 N 步去看看模型表现是否有提升或退化。
第九部分:PPO 和 RLHF 的那些“实现细节”
在 Hugging Face 官方的资料中,提到了一些在使用 PPO 做 RLHF 时的要点,这些也值得我们注意:
- KL 惩罚系数:这往往是超参数,需要根据实验效果调节。如果 KL 太小,模型可能会快速走偏;若 KL 太大,模型又可能束手束脚不敢更新。
- 价值函数的初始化:在 TRL 的实现中,可以选择从策略模型复制一份当作初始价值函数网络,也可以单独训练一个价值网络。
- 白化奖励:有些做法会对奖励进行白化(Whitening),即减去平均值后除以标准差,以让梯度更稳定。
- EOS 技巧:给不生成 EOS 的序列一个负面奖励,以鼓励模型尽快收尾,不要无限滥用上下文长度。
- 多 batch、GAE 优势估计:在 PPO 里,常常会用广义优势估计(Generalized Advantage Estimator, GAE)来平滑优势函数,减少方差,提高训练稳定性。
所有这些小技巧都能在 TRL 里配置或调参,所以实际应用时务必在超参数表上多做实验。
第十部分:基准实验与结果验证
10.1 “TL;DR” 摘要任务的案例
Hugging Face 也给出了一个关于 “TL;DR” 生成任务的案例研究。他们训练了一个能给 Reddit 贴子生成简短摘要的模型,并进行大量对比:
- SFT(监督微调)模型的摘要质量;
- PPO 训练过的模型的摘要质量(通过人类打分);
- 以及 PPO 不同配置下的性能差异。
他们观察到,使用 PPO 之后,对人类来说更满意的“TL;DR”摘要概率从 33% 提升到了 64.7%。也就是说,RLHF 确实显著提升了模型的表现。
在你自己的项目中,也可以做类似对比:“纯监督 vs RLHF”,看究竟有没有质的飞跃。
10.2 代码生成的案例
虽然 Hugging Face 的官方示例主要关注文本摘要或对话任务,但用 PPO 训练代码生成模型同样大有可为。具体的实验细节包括:
- 使用 LLaMA 或 CodeGen 之类的基础代码模型做 SFT;
- 准备一批合成任务或真实项目的函数需求作为 prompt;
- 写一个或多个自动测试脚本衡量 correctness、效率、代码规范等;
- 用 PPO 做微调,以提升相应指标。
最终的模型往往能在典型的开发任务上给出更可靠或更高效的实现,从而为实际的开发流程带来时间/人力节省。当然,中间需要投入不少工程精力去设计数据集、reward 函数和基础架构。
第十一部分:在实践中可能遇到的“坑”
最后,分享一些我在使用 PPO + 大模型时踩过的“坑”,也供大家参考或避免:
- 梯度爆炸和数值不稳定:在大模型上做 RL,很容易出现梯度爆炸。常用对策:降低学习率;增大 batch_size;或使用梯度裁剪。
- KL 爆炸:如果 KL 系数太小,模型更新就“肆无忌惮”地走向未知分布,导致 KL 爆炸,训练崩溃。要么提升 KL 惩罚,要么用 more cautious 的超参数。
- Reward 漏洞:有时模型会学到“投机取巧”的方式来获取高 reward,却没真正完成任务。这时要么改进 reward 函数,要么在 reward 中增加多重约束。
- Prompt 设计不够多样:如果只给模型很少或单一类型的指令,模型也许在这个小范围内得到了极高分数,但泛化能力不足。需要更多样化的 prompt 。
- 训练时间和算力消耗:PPO 在大模型上迭代效率不算很高,特别是如果 reward 函数包含外部编译/运行测试环节,会极大拉长训练时间。要做好算力和时间的计划。
第十二部分:总结与展望
从最早的 AlphaGo 深度强化学习,到现在对话系统、代码生成、内容过滤等等,无不在用 RLHF 来让大模型更好地对齐人类需求。而其中,PPO 因为其相对稳定易用的特性,在众多 RL 算法中依然扮演着重要角色。
在本篇幅较长的博客里,我从基础强化学习概念,到 PPO 原理,再到如何用 Hugging Face TRL 在大模型上跑 PPO,然后结合一个“生成更高效 Python 代码”的场景示例,还穿插了各种落地细节与踩坑心得。
希望大家读完能够对以下几个问题有更清晰的答案:
- 为什么要把 PPO 用到语言模型或代码生成? 因为我们可以构造任意我们关心的 reward,指导模型做更对人意、更有效的输出。
- 如何把 PPO 与大模型结合? 通过 RLHF 的范式:SFT 模型作为初始化,用一个 reward model(或自定义打分逻辑)对生成结果给分,PPO 算法则保证策略在收益和稳定性之间取得平衡。
- 训练大模型过程中需要注意什么? 需考虑 KL 惩罚、batch_size、reward 设计、分布式训练,以及无数工程上的小坑。
- 实际效果如何? 在文本总结、对话安全过滤、代码效率提升等方面都有显著的改进,当然前提是要有合适的数据和合理的 reward。
下一个阶段,我们或许能见到更多新奇的 RLHF 技术,比如 DPO、RLAIF、或者把人类偏好直接融入对比学习等。无论如何,PPO 仍然值得我们深入掌握,因为它相对简洁、易上手,而且社区资源丰富。借助它,你完全可以让你的语言模型在各种你能想到的“奖励函数”上开疆拓土,构建定制化的强力 LLM。