一、前言:为什么需要 PPO?
在大语言模型(LLM)的后训练阶段,监督微调(SFT) 只能让模型学会"模仿"人类的表达形式,但无法真正理解人类的偏好 和价值观 。这就是 RLHF(Reinforcement Learning from Human Feedback) 的用武之地。
PPO(Proximal Policy Optimization)作为 RLHF 的核心算法,通过奖励模型(Reward Model)的反馈来优化策略,使模型生成更符合人类期望的回复。
本文将基于 LLaMA-Factory 框架,完整演示从 SFT → Reward Model → PPO 的三阶段训练流程。
二、训练流程架构
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 预训练模型 │ --> │ SFT 训练 │ --> │ SFT 模型 │
│ (Qwen2.5-7B) │ │ 学习指令遵循 │ │ (有对话能力) │
└─────────────────┘ └──────────────────┘ └────────┬────────┘
│
┌──────────────────────────┘
▼
┌──────────────────┐
│ 构建偏好数据 │
│ (chosen/rejected) │
└────────┬─────────┘
▼
┌──────────────────┐ ┌─────────────────┐
│ Reward Model │ --> │ 奖励模型 │
│ 训练阶段 │ │ (学会打分) │
└──────────────────┘ └────────┬────────┘
│
┌────────────────────────┘
▼
┌──────────────────┐ ┌─────────────────┐
│ PPO 训练 │ --> │ 最终模型 │
│ 强化学习优化 │ │ (符合人类偏好) │
└──────────────────┘ └─────────────────┘
三、环境准备
3.1 安装 LLaMA-Factory
bash
# 克隆仓库
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
# 创建虚拟环境
conda create -n llama-factory python=3.10 -y
conda activate llama-factory
# 安装依赖(推荐使用最新版本)
pip install -e ".[torch,metrics]"
3.2 验证安装
bash
llamafactory-cli version
# 预期输出:显示 LLaMA-Factory 版本信息
四、阶段一:SFT 监督微调
SFT 是 RLHF 的基础,目标是让模型具备基本的指令遵循能力。
4.1 配置文件 sft_config.yaml
yaml
### 模型配置
model_name_or_path: Qwen/Qwen3-4B # 基础模型
trust_remote_code: true
### 训练方法
stage: sft
do_train: true
finetuning_type: lora # 使用 LoRA 节省显存
lora_target: all
lora_rank: 8
lora_alpha: 16
lora_dropout: 0.05
### 数据集配置
dataset: alpaca_gpt4_zh,sharegpt_gpt4 # 混合多个数据集
template: qwen
cutoff_len: 2048
max_samples: 100000
overwrite_cache: true
preprocessing_num_workers: 16
### 输出配置
output_dir: saves/qwen-sft-lora
logging_steps: 10
save_steps: 500
plot_loss: true
overwrite_output_dir: true
### 训练参数
per_device_train_batch_size: 1
gradient_accumulation_steps: 8 # 有效 batch size = 8
learning_rate: 5.0e-5
num_train_epochs: 3.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true # A100/V100 启用
# fp16: true # 旧显卡启用
### 优化器配置
optim: adamw_torch
weight_decay: 0.1
max_grad_norm: 1.0
### 系统配置
ddp_timeout: 180000000
seed: 42
4.2 启动训练
bash
# 单卡训练
llamafactory-cli train configs/sft_config.yaml
# 多卡训练(DeepSpeed)
CUDA_VISIBLE_DEVICES=0,1,2,3 llamafactory-cli train configs/sft_config.yaml \
--deepspeed configs/deepspeed/ds_z2_config.json
4.3 合并 LoRA 权重(关键步骤)
PPO 训练需要加载完整的 SFT 模型,因此必须先合并:
bash
llamafactory-cli export \
--model_name_or_path saves/qwen-sft-lora \
--adapter_name_or_path saves/qwen-sft-lora \
--template qwen \
--finetuning_type lora \
--export_dir saves/qwen-sft-merged \
--export_size 2 \
--export_device cpu \
--export_legacy_format false
注意: 合并后的模型目录应包含 config.json、pytorch_model.bin 或 model.safetensors 等文件。
五、阶段二:Reward Model 训练
奖励模型的作用是给模型的输出"打分",学习人类偏好。
5.1 准备偏好数据
数据格式(JSON):
json
[
{
"instruction": "请解释机器学习中的过拟合现象",
"input": "",
"output": [
"过拟合是指模型在训练数据上表现很好,但在新数据上表现差的现象。这通常是因为模型过于复杂,记住了训练数据的噪声而不是学习通用规律。解决方法包括增加数据、正则化、早停等。",
"过拟合就是模型训练得太好了,所以有问题。"
]
}
]
关键: output 字段是一个数组,第一个元素是 chosen (优选),第二个是 rejected(劣选)。
5.2 配置文件 reward_config.yaml
yaml
### 模型配置
model_name_or_path: saves/qwen-sft-merged # 使用合并后的 SFT 模型
### 训练方法
stage: rm # reward modeling
do_train: true
finetuning_type: lora
lora_target: all
lora_rank: 8
lora_alpha: 16
### 数据集配置
dataset: comparison_gpt4_zh # 偏好数据集
template: qwen
cutoff_len: 2048
max_samples: 50000
### 输出配置
output_dir: saves/qwen-reward-lora
logging_steps: 10
save_steps: 300
plot_loss: true
overwrite_output_dir: true
### 训练参数
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 1.0e-5 # RM 使用较小学习率
num_train_epochs: 2.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
### RM 特定配置
# 奖励模型使用回归头,自动添加 value_head
5.3 启动训练与合并
bash
# 训练奖励模型
llamafactory-cli train configs/reward_config.yaml
# 合并奖励模型
llamafactory-cli export \
--model_name_or_path saves/qwen-reward-lora \
--adapter_name_or_path saves/qwen-reward-lora \
--template qwen \
--finetuning_type lora \
--export_dir saves/qwen-reward-merged \
--export_size 2 \
--export_device cpu
验证奖励模型:
python
from transformers import AutoModelForSequenceClassification, AutoTokenizer
model = AutoModelForSequenceClassification.from_pretrained(
"saves/qwen-reward-merged",
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained("saves/qwen-reward-merged")
# 测试打分
text = "这是一个很好的回答!"
inputs = tokenizer(text, return_tensors="pt")
score = model(**inputs).logits
print(f"Reward score: {score.item()}")
六、阶段三:PPO 强化学习训练
这是 RLHF 的核心阶段,使用 PPO 算法优化策略。
6.1 准备 Prompt 数据
PPO 只需要 prompts,模型会自己生成 responses:
json
[
{
"instruction": "请用简单的语言解释什么是区块链",
"input": ""
},
{
"instruction": "写一段 Python 代码计算斐波那契数列",
"input": ""
}
]
6.2 配置文件 ppo_config.yaml
yaml
### 模型配置
model_name_or_path: saves/qwen-sft-merged # Actor 模型(SFT 模型)
reward_model: saves/qwen-reward-merged # Critic/Reward 模型
### 训练方法
stage: ppo
do_train: true
finetuning_type: lora
lora_target: all
lora_rank: 8
lora_alpha: 16
### 数据集配置
dataset: ppo_prompts_zh # 只有 prompts 的数据集
template: qwen
cutoff_len: 2048
max_samples: 20000
### PPO 核心参数
ppo_epochs: 1 # 每次数据迭代中 PPO 更新次数
ppo_buffer_size: 1 # 经验回放缓冲区大小
ppo_target_kl: 0.1 # KL 散度目标值(防止策略偏离太远)
ppo_clip_eps: 0.2 # PPO 裁剪参数
ppo_score_norm: true # 奖励分数归一化(重要!)
ppo_score_clip: 10.0 # 奖励分数裁剪范围
ppo_temperature: 1.0 # 采样温度
### 生成配置(影响采样质量)
top_k: 0 # 0 表示禁用 top-k
top_p: 0.9 # nucleus sampling
temperature: 0.7 # 生成多样性
max_new_tokens: 512 # 每次生成长度
do_sample: true # 必须启用采样
### 输出配置
output_dir: saves/qwen-ppo-lora
logging_steps: 5
save_steps: 100
plot_loss: true
overwrite_output_dir: true
### 训练参数
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 1.0e-5 # PPO 通常需要较小学习率
num_train_epochs: 1.0 # PPO 通常 1-3 个 epoch 即可
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
### 优化器
optim: adamw_torch
max_grad_norm: 1.0
6.3 PPO 参数详解
| 参数 | 作用 | 调参建议 |
|---|---|---|
ppo_target_kl |
控制新旧策略的 KL 散度 | 0.05-0.1,越大允许偏离越多 |
ppo_score_norm |
对奖励分数进行归一化 | 强烈建议开启,稳定训练 |
ppo_score_clip |
限制奖励分数范围 | 5-10,防止极端值影响 |
temperature |
生成采样的温度 | 0.5-1.0,影响探索能力 |
ppo_buffer_size |
经验缓冲区大小 | 根据显存调整,1-4 |
6.4 启动 PPO 训练
bash
# 单卡训练(需要 24GB+ 显存)
llamafactory-cli train configs/ppo_config.yaml
# 多卡训练(推荐)
CUDA_VISIBLE_DEVICES=0,1,2,3 llamafactory-cli train configs/ppo_config.yaml \
--deepspeed configs/deepspeed/ds_z3_config.json
七、关键问题与解决方案
7.1 网络连接错误(Hugging Face)
现象:
MaxRetryError: HTTPSConnectionPool(host='huggingface.co', port=443):
Max retries exceeded with url: ... Network is unreachable
解决:
yaml
# 使用本地绝对路径,而非 Hugging Face Hub 路径
model_name_or_path: /absolute/path/to/saves/qwen-sft-merged # ✅
# model_name_or_path: saves/qwen-sft-merged # ❌ 会被识别为 HF Hub
或设置镜像:
bash
export HF_ENDPOINT=https://hf-mirror.com
7.2 显存不足(OOM)
方案 A:启用 QLoRA
yaml
quantization_bit: 4 # 4-bit 量化
quantization_method: bitsandbytes
方案 B:减小生成长度
yaml
max_new_tokens: 256 # 从 512 减小
cutoff_len: 1024 # 从 2048 减小
方案 C:使用 DeepSpeed ZeRO-3
bash
# configs/deepspeed/ds_z3_config.json
{
"train_batch_size": "auto",
"train_micro_batch_size_per_gpu": "auto",
"gradient_accumulation_steps": "auto",
"zero_optimization": {
"stage": 3,
"offload_optimizer": {
"device": "cpu",
"pin_memory": true
},
"offload_param": {
"device": "cpu",
"pin_memory": true
}
}
}
7.3 KL 散度过大(训练不稳定)
现象: kl 值迅速增大,奖励分数震荡
解决:
yaml
ppo_target_kl: 0.05 # 从 0.1 减小
learning_rate: 5.0e-6 # 从 1.0e-5 减小
ppo_score_clip: 5.0 # 从 10.0 减小
7.4 奖励模型分数异常
现象: 所有样本奖励分数接近,无法区分好坏
解决:
yaml
ppo_score_norm: true # 确保开启归一化
# 或检查奖励模型是否训练充分
八、完整训练脚本
bash
#!/bin/bash
set -e
# ========== 配置 ==========
BASE_MODEL="Qwen/Qwen3-4B"
PROJECT_NAME="qwen-rlhf"
STEPS=("sft" "rm" "ppo")
# ========== 阶段 1: SFT ==========
echo ">>> Stage 1: SFT Training"
llamafactory-cli train configs/sft_config.yaml
echo ">>> Merging SFT LoRA"
llamafactory-cli export \
--model_name_or_path saves/${PROJECT_NAME}-sft-lora \
--adapter_name_or_path saves/${PROJECT_NAME}-sft-lora \
--template qwen \
--finetuning_type lora \
--export_dir saves/${PROJECT_NAME}-sft-merged \
--export_device cpu
# ========== 阶段 2: Reward Model ==========
echo ">>> Stage 2: Reward Model Training"
llamafactory-cli train configs/reward_config.yaml
echo ">>> Merging Reward Model"
llamafactory-cli export \
--model_name_or_path saves/${PROJECT_NAME}-reward-lora \
--adapter_name_or_path saves/${PROJECT_NAME}-reward-lora \
--template qwen \
--finetuning_type lora \
--export_dir saves/${PROJECT_NAME}-reward-merged \
--export_device cpu
# ========== 阶段 3: PPO ==========
echo ">>> Stage 3: PPO Training"
llamafactory-cli train configs/ppo_config.yaml
echo ">>> Merging Final PPO Model"
llamafactory-cli export \
--model_name_or_path saves/${PROJECT_NAME}-ppo-lora \
--adapter_name_or_path saves/${PROJECT_NAME}-ppo-lora \
--template qwen \
--finetuning_type lora \
--export_dir saves/${PROJECT_NAME}-ppo-merged \
--export_device cpu
echo ">>> RLHF Training Complete!"
echo "Final model: saves/${PROJECT_NAME}-ppo-merged"
九、模型评估与部署
9.1 快速测试
bash
# 启动 Web UI
llamafactory-cli webui
# 或使用命令行推理
llamafactory-cli chat saves/qwen-ppo-merged --template qwen
9.2 部署为 API
bash
llamafactory-cli api \
--model_name_or_path saves/qwen-ppo-merged \
--template qwen \
--infer_backend vllm \
--vllm_maxlen 4096
十、总结与最佳实践
| 阶段 | 关键要点 | 常见陷阱 |
|---|---|---|
| SFT | 数据质量 > 数量,多轮对话需正确处理 | 忘记合并 LoRA 直接用于下一阶段 |
| RM | 偏好对要清晰可区分,数据平衡 | 奖励模型过拟合,分数饱和 |
| PPO | 小学习率、KL 约束、分数归一化 | KL 爆炸、训练不稳定、生成质量下降 |
核心建议:
- 渐进式训练:每个阶段都保存 checkpoint,便于回滚调试
- 监控指标 :重点关注
kl、reward、loss三个指标的变化曲线 - 数据质量:RLHF 的效果上限由数据质量决定,算法只是放大器
- 充分验证:PPO 训练后务必进行人工评估,奖励分数高 ≠ 实际效果好
参考资源
- LLaMA-Factory 官方文档: https://github.com/hiyouga/LLaMA-Factory
- PPO 原始论文: Proximal Policy Optimization Algorithms
- InstructGPT: Training language models to follow instructions
本文基于 LLaMA-Factory v0.9.0 版本编写,不同版本配置可能略有差异。