verl 中的 SPPO 自博弈偏好优化配方:从算法原理到 MATH 复现实践
【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl
Self-Play Preference Optimization(SPPO)是一种不依赖 GPT-4 等强外部信号、仅靠模型与自身历史策略对弈即可完成对齐的偏好优化方法。本文基于 verl 官方社区配方文档 docs/algo/sppo.md,完整讲解 SPPO 的核心思想、在 verl 框架中的复现步骤(含 MATH 数据集预处理、依赖安装、训练脚本执行),并结合仓库源码(数据脚本、规则奖励、KL 控制与 PPO 配置)给出源码级佐证,帮助你从零复现"Qwen2.5-7B-Instruct 在 MATH 上从 46.6 分提升至 65.6 分"的完整实验。
SPPO 算法背景与核心思想
SPPO 对应论文Self-Play Preference Optimization for Language Model Alignment(arXiv: 2405.00675),由 Yue Wu、Zhiqing Sun、Huizhuo Yuan 等学者提出。verl 在 docs/algo/sppo.md 中提供了该论文的社区配方(recipe)实现,实现作者为 Yuhao Yang 与 Chenyang Zhao。
该方法的核心动机是:传统对齐方法(如基于 GPT-4 生成偏好对或响应的 RLHF/DPO 流程)依赖昂贵且不稳定的外部信号。SPPO 则通过**自博弈(self-play)**机制,让当前策略与自身(或其历史版本)生成的数据进行偏好优化,从而在不借助强外部模型的情况下显著提升 LLM 性能。文档明确指出,SPPO 可以超越采用迭代式直接偏好优化(iterative DPO)训练的模型。
SPPO 具备两个层面的保证:
- 理论层面:算法有理论根基,能够保证 LLM 在一般的、甚至不可传递(intransitive)的偏好关系下,收敛到 von Neumann winner,即纳什均衡(Nash equilibrium)意义上的最优策略;
- 实证层面:论文通过多个数据集的广泛评测验证了有效性,verl 配方则进一步在 MATH 数据集上给出了可复现的工程验证。
verl 中的 SPPO 配方:定位与组织
在 verl 仓库中,算法配方文档统一组织在 docs/algo/ 目录下(如 ppo.md、dpo_extension.rst、sppo.md、spin.md 等),并在 docs/index.rst 中登记索引。项目 README 也将 SPPO 列为 LLM 对齐配方之一(见 README.md)。
需要说明的是:SPPO 的训练脚本(即文档中调用的recipe/sppo/run_qwen2.5-7b_rm.sh)由 verl-recipe 社区配方仓库维护,当前仓库的recipe/目录下未内置该脚本;本文后续章节将给出完整的复现命令与每一步的仓库内依据,确保即便脚本不在当前仓库,你也能按照 verl 的标准 PPO 训练流程跑通实验。
环境准备:安装 verl 与 SGLang 依赖
SPPO 实验基于 verl 的标准训练框架,官方推荐的安装方式如下:
git clone git@github.com:verl-project/verl.git cd verl python3 -m uv pip install -e ".[sglang]"其中-e表示可编辑安装,[sglang]表示安装 SGLang 推理后端对应的 extras 依赖。从仓库的 setup.py 可以看到,sglangextras 具体包含:
sglang[srt,openai]==0.5.8:SGLang 推理服务端与 OpenAI 兼容接口;torch==2.9.1:与 SGLang 配套的 PyTorch 版本;tensordict>=0.8.0,<=0.10.0,!=0.9.0:verl 数据传输所需的张量字典库。
setup.py中还定义了gpu(liger-kernel、flash-attn)、vllm、math(math-verify)、test等可选依赖组,SPPO 实验按文档使用[sglang]即可。
flash-attn 安装失败的兜底方案
文档特别提示:安装过程偶尔会失败在 flash-attn 上。若遇到该情况,可手动分步安装:
python3 -m uv pip install wheel python3 -m uv pip install packaging python3 -m uv pip install flash-attn --no-build-isolation --no-deps--no-build-isolation表示复用当前环境已安装的 wheel/packaging 进行构建,--no-deps跳过依赖自动解析,从而绕开常见的构建环境问题。
数据准备:预处理 MATH 数据集
实验使用 MATH 数据集(对应 HuggingFace 上的DigitalLearningGmbH/MATH-lighteval镜像仓库),预处理脚本为仓库内的 examples/data_preprocess/math_dataset.py:
python3 examples/data_preprocess/math_dataset.py --local_dir ~/data/math从脚本源码(examples/data_preprocess/math_dataset.py)可以看到其核心逻辑:
- 加载数据源:默认从 HuggingFace 加载
DigitalLearningGmbH/MATH-lighteval,也可通过--local_dataset_path指向本地原始数据; - 构造指令:在每个 problem 后拼接指令
Let's think step by step and output the final answer within \boxed{}.,引导模型逐步思考并以\boxed{}输出最终答案; - 提取标准答案:通过
last_boxed_only_string与remove_boxed(来自 verl/utils/reward_score/math_reward.py)从官方 solution 中提取\boxed{}内的 ground truth; - 组织数据字段:每条数据包含
data_source、prompt(OpenAI 风格的{"role": "user", "content": ...})、ability: "math"、reward_model: {"style": "rule", "ground_truth": solution}以及extra_info,其中reward_model字段声明了该任务使用规则奖励(rule-based reward); - 输出格式:在
--local_dir指定目录下生成train.parquet、test.parquet,并额外保存train_example.json、test_example.json各一条样本便于检查;若传入--hdfs_dir还会同步到 HDFS。
对应的规则奖励实现在 verl/utils/reward_score/math_reward.py:compute_score从模型输出中提取最后一个\boxed{}内的答案,经字符串规范化(is_equiv/strip_string)与 ground truth 比对,相等得 1.0,否则为 0.0。这套"抽取 boxed 答案 + 规范化比对"的机制,正是 MATH 上 SPPO 训练奖励信号的来源。
下载基座模型并运行训练
下载 Qwen2.5-7B-Instruct
hf download Qwen/Qwen2.5-7B-Instruct --local-dir $HOME/models/Qwen2.5-7B-Instruct使用 HuggingFace CLI 的hf download将基座模型下载到本地$HOME/models/Qwen2.5-7B-Instruct。
运行 SPPO 训练脚本
export WANDB_API_KEY=<YOUR_WANDB_API_KEY> export CUDA_VISIBLE_DEVICES=0,1,2,3 bash recipe/sppo/run_qwen2.5-7b_rm.shWANDB_API_KEY:实验跟踪(wandb)所需,verl 默认 logger 包含console与wandb(见 verl/trainer/config/ppo_trainer.yaml),不配置该环境变量会导致 wandb 上报失败;CUDA_VISIBLE_DEVICES=0,1,2,3:指定 4 张 GPU 用于训练,对应 verl 的 Ray worker 资源分配;run_qwen2.5-7b_rm.sh:SPPO 社区配方脚本,内部会以 verl 的标准 PPO 训练入口(verl/trainer/main_ppo.py)启动 20 个 epoch 的 SPPO 训练,并启用参考模型(ref)以计算 KL 惩罚。
训练入口与配置落点
从 verl 的 PPO 训练配置 verl/trainer/config/ppo_trainer.yaml 可以看出,这类配方脚本实际控制的关键配置包括:
trainer.total_epochs:训练轮数,SPPO 实验为 20 轮;trainer.project_name/trainer.experiment_name:实验跟踪与 checkpoint 目录命名(默认落盘到checkpoints/${trainer.project_name}/${trainer.experiment_name});actor_rollout_ref.hybrid_engine: true:actor 与 rollout 共享引擎,是 verl 默认的高效混合引擎模式;algorithm.gamma: 1.0、algorithm.lam: 1.0:GAE 折扣与 bias-variance 权衡参数,SPPO 这类 token 级奖励场景通常取 1.0(配置类定义见 verl/trainer/config/algorithm.py)。
实验结果:MATH 上的 20 轮提升
文档给出的核心实验结果是:
- 起点:Qwen2.5-7B-Instruct 初始 MATH 得分46.6;
- 终点:经过20 个 epoch的 SPPO 训练后得分65.6;
- 该成绩使模型大约跻身 [MATH leaderboard] 前 20 名。
文档同时给出了重要的评估口径说明:verl 内部的评估指标可能与 Qwen2.5-7B-Instruct 的官方评测方法不完全一致,因此为了口径统一与公平比较,只报告基于 verl 评估框架的结果。这意味着复现时若与你看到的第三方基准分数存在偏差,属于评估方法差异,而非训练失效。
结合仓库源码,这一评估口径的实现依据在于:verl 的 MATH 规则奖励(verl/utils/reward_score/math_reward.py)与 EleutherAI lm-evaluation-harness 的 hendrycks_math 任务同源(文件头部注明了改编来源),但具体字符串规范化、\boxed{}解析策略与官方评测管道仍有细节差异,这正是文档提醒"内部评估指标可能不完全对齐"的原因。
源码级原理支撑:SPPO 训练框架中的 KL 控制与奖励机制
SPPO 属于偏好优化类算法,其 verl 实现运行在通用 PPO 训练框架之上,理解框架的 KL 惩罚与奖励注入机制,有助于深入理解训练行为。
参考模型与 in-reward KL 惩罚
verl 的 PPO 配置支持两种 KL 惩罚路径:actor loss 内置的use_kl_loss,以及将 KL 直接计入 token 级奖励的use_kl_in_reward(配置项见 verl/trainer/config/ppo_trainer.yaml,类型定义见 verl/trainer/config/algorithm.py)。当启用 in-reward KL 时,参考模型(ref)会被加载,训练流程调用 verl/trainer/ppo/ray_trainer.py 中的apply_kl_penalty,依据当前策略与参考策略的对数概率差计算 KL 估计并施加到 token 级奖励上——这正是 SPPO 这类"以自身为参照"对齐方法在工程上落地的关键机制。
KL 估计方式与自适应控制器
algorithm.kl_penalty决定 KL 的估计方式,verl 支持kl(默认)、abs、mse、low_var_kl、full等选项,其具体数学形式实现在 verl/trainer/ppo/core_algos.py 中:
kl/k1:直接取logprob - ref_logprob(正向 KL 估计);abs:取绝对差值;mse/k2:取0.5 * (logprob - ref_logprob)^2;low_var_kl/k3:Schulman 的低方差 KL 近似ratio - kl - 1,并对中间量做数值稳定截断;full:需要逐 token 词汇表 logits,当前实现抛NotImplementedError。
KL 系数既可固定(FixedKLController),也可使用自适应控制器(verl/trainer/ppo/core_algos.py):AdaptiveKLController依据current_kl / target_kl的比例误差(截断在 ±0.2)按horizon步长动态调整kl_coef。对应配置项为 verl/trainer/config/algorithm.py 中的KLControlConfig(type、kl_coef、horizon、target_kl)。配方脚本可按需在algorithm.kl_ctrl下覆盖这些字段。
优势估计
SPPO 配方基于 token 级奖励,verl 通过algorithm.adv_estimator选择优势估计器(gae、grpo、reinforce_plus_plus等,见 verl/trainer/config/algorithm.py),配合gamma、lam完成 GAE 计算。从源码结构看,SPPO 与 DPO 类方法共享同一套 reward + advantage + policy update 流水线,区别主要在于训练数据由自博弈生成,这正体现了 verl "算法配方与训练框架解耦"的设计理念。
常见问题与注意事项
- flash-attn 编译失败:按上文兜底方案先装
wheel、packaging,再以--no-build-isolation --no-deps安装 flash-attn; - wandb 无法上报:确认
WANDB_API_KEY已正确导出;若无需外部跟踪,可在配置中将trainer.logger收敛为["console"]; - GPU 资源:文档示例使用 4 张 GPU(
CUDA_VISIBLE_DEVICES=0,1,2,3),需要根据实际显存调整trainer.n_gpus_per_node与 batch 相关参数(train_batch_size、max_prompt_length、max_response_length等,均在 verl/trainer/config/ppo_trainer.yaml 中可查); - 结果可比性:verl 内部评估口径与官方评测存在差异,对比第三方分数时需保持同一框架内比较;
- 数据下载:
lighteval/MATH原仓库在 HuggingFace 已不可用,脚本已切换至DigitalLearningGmbH/MATH-lighteval镜像(见 examples/data_preprocess/math_dataset.py),无需手动处理。
总结
SPPO 为 LLM 对齐提供了一条不依赖 GPT-4 等强外部信号的路径:通过自博弈与理论上的纳什均衡收敛保证,在 MATH 等基准上取得了超越迭代 DPO 的效果。verl 社区配方完整覆盖了从数据预处理(examples/data_preprocess/math_dataset.py)、规则奖励(verl/utils/reward_score/math_reward.py)到训练框架(verl/trainer/config/ppo_trainer.yaml、verl/trainer/ppo/ray_trainer.py)的全链路,使得"Qwen2.5-7B-Instruct 在 MATH 上 46.6 → 65.6"的复现只需寥寥几条命令即可完成。若想进一步了解 SPPO 与 SPIN 等其他自博弈配方的异同,可继续阅读仓库中的 spin.md 与 baseline.md。
【免费下载链接】verlverl/HybridFlow: A Flexible and Efficient RL Post-Training Framework项目地址: https://gitcode.com/GitHub_Trending/ve/verl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考