Chapter 03

快速上手:单机跑通 PPO demo

📌 commit 6c6056daa522 用 Llama-3-8B + 8 张卡跑出第一个 RLHF loop

RLHF 训练的"hello world" 是这样的:拿一个 SFT 好的模型 + 一个 reward model, 在偏好数据上跑 PPO,看到 reward 上升、KL 在控制中。这一章带你跑一次官方 demo train_ppo_ray_hybrid_engine.sh,重点不是看明白每个 flag,而是看到 loop 真的转起来

3.1OpenRLHF 提供的 5 类训练入口

OpenRLHF 把训练拆成 5 个独立 CLI,按 RLHF 的"准备 → 训练"流程组织:

CLI对应典型场景
python -m openrlhf.cli.train_sft SFT 把 base model 训成 chat model
python -m openrlhf.cli.train_rm Reward Model 用 preference 数据训打分器
python -m openrlhf.cli.train_dpo DPO / IPO / SimPO offline 偏好对齐(不需要 RL)
python -m openrlhf.cli.train_ppo_ray PPO / GRPO / RLOO / REINFORCE++在线 RLHF / 推理 RL
python -m openrlhf.cli.serve_rm Remote RM service 把 RM 单独起服务给训练调

本章主角是 train_ppo_ray,因为它是 OpenRLHF 真正"区别于其他 RLHF 框架"的核心入口。

3.2跑通的两步

步骤 1:起 Ray head

# 启动 Ray,head 在本机
ray start --head --num-gpus=8 --dashboard-host=0.0.0.0

# 看一下资源
ray status

预期输出包含 8 GPU 资源。如果是多机,所有 worker 都要 ray start --address=<head_ip>:6379 接到 head。

步骤 2:跑 hybrid engine PPO 脚本

cd /Users/zjw/Documents/LLM/train/repos/openrlhf
bash examples/scripts/train_ppo_ray_hybrid_engine.sh

这个脚本的关键参数(不用先记住,下章会拆):

python3 -m openrlhf.cli.train_ppo_ray \
   --ref.num_nodes 1 --ref.num_gpus_per_node 8 \
   --reward.num_nodes 1 --reward.num_gpus_per_node 8 \
   --critic.num_nodes 1 --critic.num_gpus_per_node 8 \
   --actor.num_nodes 1 --actor.num_gpus_per_node 8 \
   --vllm.num_engines 4 \
   --vllm.tensor_parallel_size 2 \
   --train.colocate_all \                          # ← 所有角色共享同一组 8 卡
   --vllm.gpu_memory_utilization 0.5 \
   --actor.model_name_or_path OpenRLHF/Llama-3-8b-sft-mixture \
   --reward.model_name_or_path OpenRLHF/Llama-3-8b-rm-700k \
   --ckpt.output_dir /openrlhf/examples/final/llama3-8b-rlhf \
   --train.batch_size 128 \
   --rollout.batch_size 1024 \
   --rollout.n_samples_per_prompt 1 \
   --train.max_epochs 1 \
   --data.max_len 2048 \
   --ds.zero_stage 3 \
   --ds.param_dtype bf16 \
   --actor.adam.lr 5e-7 \
   --critic.adam.lr 9e-6 \
   --algo.kl.init_coef 0.01 \
   --data.prompt_dataset OpenRLHF/prompt-collection-v0.1 \
   --data.input_key context_messages \
   --data.apply_chat_template \
   --reward.normalize_enable \
   --actor.gradient_checkpointing_enable \
   --ds.packing_samples \
   --vllm.sync_backend nccl \
   --vllm.enforce_eager \
   --vllm.enable_sleep \
   --ds.enable_sleep \
   --train.dynamic_batch_enable \
   --train.max_tokens_per_gpu 16384

3.3启动后会发生什么

flowchart TB A["bash train_ppo_ray_hybrid_engine.sh"] A --> B["openrlhf.cli.train_ppo_ray 解析参数"] B --> C["创建 PlacementGroup
(8 张卡放在一组里)"] C --> D1["RayActorGroup(Actor) × 8 卡"] C --> D2["RayActorGroup(Ref) × 8 卡
(与 Actor 共置)"] C --> D3["RayActorGroup(Critic) × 8 卡"] C --> D4["RayActorGroup(Reward) × 8 卡"] C --> D5["vLLMEngine × 4 个
(每个 TP=2)"] D1 --> E["RemoteExperienceMaker
启动主循环"] D5 --> E E --> F["第 1 个 step:
① actor 权重同步到 vLLM
② vLLM rollout 生成 response
③ ref 算 ref_logp
④ reward 算 reward
⑤ critic 算 value
⑥ actor + critic 训练"] F --> G[loss / reward / kl 写 wandb]

3.4训练时盯哪几个指标

OpenRLHF 默认把日志输出到 wandb / tensorboard。重点关注的几个:

指标正常区间异常预警
reward/mean 缓慢上升 飙升 → 检查 reward hacking
kl_coef 初值附近浮动 飙升 → policy 漂离过快
kl 0.001 – 0.1 > 1 → 策略坏了
actor/policy_loss有负值正常 振荡剧烈 → lr 太大
actor/entropy 2 – 6 < 1 → 熵坍塌
actor/clipfrac 0.05 – 0.3 > 0.5 → lr 过高 / clip 过紧
critic/value_loss逐步下降 不降 → critic 没学到
response_length/mean稳定或缓升 暴涨 → length hacking
throughput/sps 跌 → 网络异常 / vLLM 异常

3.5你看到了什么

跑了几小时后,你应该看到:

如果跑出"reward 涨但模型变啰嗦 / 答非所问",多半是 reward hacking — 见第 11 章。

3.6同一个入口怎么换算法

train_ppo_ray 不只是跑 PPO,它通过 --algo.* 系列参数能切到多种算法:

算法关键改动
PPO(默认) 需要 critic + reward 两个角色
GRPO --algo.advantage_estimator group_norm --rollout.n_samples_per_prompt 8;不需要 critic
RLOO --algo.advantage_estimator rloo --rollout.n_samples_per_prompt 4;不需要 critic
REINFORCE++ --algo.advantage_estimator reinforce_plus_plus;不需要 critic
REINFORCE++ baseline --algo.advantage_estimator reinforce_plus_plus_baseline(v0.10 新增)

第 9 章会详讲这些算法的差别与公式。

3.7不用 reward model 的 demo

如果你的任务有 "可验证 reward"(数学题答案、代码单测),可以直接用自定义函数代替 RM。 仓库自带 examples/python/math_reward_func.py 是经典例子:

def reward_func(queries, prompts, labels, **kwargs) -> dict:
    rewards = []
    for query, prompt, label in zip(queries, prompts, labels):
        response = query[len(prompt):]
        pred = extract_boxed_answer(response)
        is_correct = grade_answer(pred, label)
        rewards.append(1.0 if is_correct else 0.0)

    rewards_tensor = torch.tensor(rewards)
    return {
        "rewards": rewards_tensor,
        "scores": rewards_tensor,
        "extra_logs": {"math_accuracy": rewards_tensor.mean()},
    }

跑这种"规则 reward + GRPO"的 demo(R1 风格训练):

bash examples/scripts/train_reinforce_baseline_hybrid_engine.sh

这个脚本默认用 Qwen3-4B-Thinking-2507 + dapo-math-17k,配合 examples/python/agent_func.py 做"single-turn agent"训练。第 12 章会完整复现。

3.8这章你需要带走的

下一步 跑通这一章你只看到"训练在转",但还不知道四个角色(actor / ref / critic / reward)各自在做什么、 Ray 如何调度它们。这正是第 4 章要讲的内容。