用 verl 实现 SFT 和拒绝采样,完整流程演示
在大模型后训练实践中,监督微调(SFT)与拒绝采样(Reject Sampling)是两个关键且常被低估的环节:前者为策略模型奠定高质量行为基线,后者则在不引入梯度更新的前提下,高效筛选优质响应,为后续强化学习(如 PPO、DPO)提供高信噪比数据。但传统实现往往依赖多套脚本拼接、手动管理模型加载/卸载、重复处理数据格式,导致流程脆弱、复现困难、资源利用率低。
verl 的出现,正是为了解决这类工程痛点。它不是另一个“从零造轮子”的 RL 框架,而是以 HybridFlow 论文思想为内核,将 SFT、Rollout、Reward、Training 等环节统一建模为可声明、可调度、可并行的数据流(DataFlow)。你不再需要写一堆torch.distributed启动逻辑或手动切分 GPU;你只需定义“做什么”,verl 自动处理“在哪做”和“怎么高效做”。
本文将完全基于 verl 镜像,不依赖任何外部框架胶水代码,手把手带你完成:
- 一个端到端可运行的 SFT 流程(含数据准备、模型加载、训练启动、检查点保存)
- 一个生产就绪的拒绝采样流程(含批量生成、奖励打分、Top-k 筛选、结果导出)
- 两个流程如何共享模型权重、复用数据管道、避免重复加载
- 所有命令均可一键复制粘贴,在标准 verl 镜像环境中直接执行
全程聚焦“能跑通、能复现、能理解”,不讲抽象架构图,不堆术语,只给真实命令、真实输出、真实避坑提示。
1. 环境准备与 verl 基础验证
在开始任何训练前,先确认 verl 已正确安装并可被 Python 调用。这一步看似简单,却是后续所有流程稳定运行的前提。
1.1 验证 verl 安装与版本
进入 Python 交互环境,导入 verl 并检查其版本号。该版本信息决定了你可使用的 API 特性(例如verl.data模块在 0.3.0+ 才全面可用):
python -c "import verl; print(f'verl version: {verl.__version__}')"预期输出应类似:
verl version: 0.3.2注意:若报错
ModuleNotFoundError: No module named 'verl',请确认你正在使用 CSDN 星图提供的 verl 镜像,或已通过pip install verl完成安装。不要尝试在非 verl 环境中硬凑流程——verl 的核心价值正在于其深度集成的调度与并行能力,脱离镜像将失去大部分优势。
1.2 快速查看 verl 提供的核心模块
verl 的设计哲学是“模块解耦、按需组合”。我们不需要一次性理解全部,但需知道关键能力所在位置:
python -c " import verl print('Available modules:') print('- verl.data: 数据集构建与预处理') print('- verl.trainer: SFT/PPO/DPO 等训练器封装') print('- verl.rollout: 高效批量生成(Rollout)') print('- verl.reward: 奖励模型(RM)调用与打分') print('- verl.utils: 工具函数(如权重同步、日志)') "这些模块不是孤立的工具包,而是 verl DataFlow 中的“可插拔节点”。例如,verl.rollout不仅能生成文本,还能自动与verl.reward协同,将生成结果无缝送入奖励模型计算,中间无需你手动保存/读取 JSON 文件。
2. 监督微调(SFT)全流程实操
SFT 是一切后训练的起点。它用高质量的人工标注数据,教会模型“什么回答是好的”。在 verl 中,SFT 不是一个黑盒训练脚本,而是一个明确定义的 DataFlow 节点:输入是数据集,输出是微调后的 Actor 模型权重。
2.1 准备 SFT 数据集
verl 原生支持 HuggingFace Datasets 格式。我们以经典的alpaca数据集为例(你可替换为自己的.jsonl文件):
# 下载并转换为 verl 兼容格式(单条记录示例) cat > alpaca_sample.jsonl << 'EOF' {"instruction": "解释量子纠缠", "input": "", "output": "量子纠缠是一种量子现象,其中一对或多对粒子相互作用后,即使相隔很远,其量子态仍会表现出关联性。"} {"instruction": "写一首关于春天的五言绝句", "input": "", "output": "春山暖日和风,阑干楼阁帘栊。杨柳秋千院中,啼莺舞燕,小桥流水飞红。"} EOF关键点:verl 的 SFT 数据处理器默认期望字段为
instruction、input、output。如果你的数据字段名不同(如prompt/response),需在配置中指定data_field_mapping,而非手动改数据——这是 verl “声明式”设计的体现。
2.2 编写 SFT 配置文件
创建sft_config.yaml,这是 verl 流程的“蓝图”。它声明了你要做什么,而不是怎么做:
# sft_config.yaml model: name_or_path: "meta-llama/Llama-2-7b-hf" # HuggingFace 模型ID,需有访问权限 load_in_4bit: false # 小模型建议设为 false,保证精度 use_flash_attention_2: true data: train_dataset: "alpaca_sample.jsonl" val_dataset: null # 可选,无验证集则设为 null max_length: 2048 packing: true # 启用 packing,提升 GPU 利用率 training: output_dir: "./sft_output" per_device_train_batch_size: 4 gradient_accumulation_steps: 4 num_train_epochs: 1 learning_rate: 2e-5 warmup_ratio: 0.03 logging_steps: 10 save_steps: 100 save_total_limit: 2 # verl 特有:声明这是一个 SFT DataFlow flow: type: "sft" trainer: "verl.trainer.SFTTrainer"为什么不用写 Trainer 类?
因为verl.trainer.SFTTrainer已内置了 LoRA 微调、梯度检查点、混合精度等最佳实践。你只需声明trainer字段,verl 会在运行时自动实例化并注入所有依赖(如模型、数据集、优化器)。
2.3 启动 SFT 训练
一行命令启动整个流程。verl 会自动:
- 加载模型(支持 FSDP/TP 多种并行)
- 构建数据集(自动 tokenize + packing)
- 初始化优化器与学习率调度器
- 启动训练循环,并按配置保存检查点
verl run --config sft_config.yaml典型输出片段:
[INFO] Loading model from meta-llama/Llama-2-7b-hf... [INFO] Building dataset from alpaca_sample.jsonl... [INFO] Using FSDP with sharding_strategy=FULL_SHARD... [INFO] Training: epoch 1/1, step 0/100, loss=2.145 [INFO] Saving checkpoint to ./sft_output/checkpoint-100...
训练完成后,./sft_output目录下将生成标准 HuggingFace 格式的模型权重,可直接用于后续 Rollout 或推理。
3. 拒绝采样(Reject Sampling)全流程实操
拒绝采样是连接 SFT 与强化学习的关键桥梁。它不更新模型参数,而是利用当前 Actor 模型批量生成多个响应,再用 Reward Model(RM)对每个响应打分,最终保留 Top-k 高分样本。这些样本质量远超随机采样,是训练更鲁棒策略的黄金数据。
3.1 准备提示数据(Prompts)
拒绝采样的输入是一批待生成的提示。格式为纯文本.txt,每行一个 prompt:
cat > prompts.txt << 'EOF' 请用通俗语言解释区块链的工作原理。 写一封向客户道歉的邮件,因订单延迟发货。 根据以下需求生成 Python 代码:计算斐波那契数列前20项。 EOF3.2 编写拒绝采样配置文件
创建rs_config.yaml。注意其与 SFT 配置的核心差异:flow.type变为reject_sampling,并新增rollout和reward配置块:
# rs_config.yaml model: actor_model: "./sft_output/checkpoint-100" # 复用上一步 SFT 结果 reward_model: "OpenAssistant/reward-model-deberta-v3-large" # 开源 RM data: prompts_file: "prompts.txt" num_samples_per_prompt: 4 # 每个 prompt 生成 4 个 response max_new_tokens: 512 rollout: batch_size: 8 temperature: 0.7 top_p: 0.9 reward: batch_size: 16 output: output_file: "./rs_results.jsonl" top_k: 2 # 每个 prompt 保留分数最高的 2 个 response flow: type: "reject_sampling" rollout_module: "verl.rollout.HFModelRollout" reward_module: "verl.reward.HFRewardModel"亮点解析:
actor_model直接指向 SFT 输出目录,verl 自动识别并加载,无需你手动from_pretrained。reward_module指定开源 RM,verl 内置了对其的适配(自动处理输入格式、输出归一化)。top_k: 2是业务关键参数:它决定了你最终获得多少高质量样本。值太小,数据量不足;值太大,噪声增加。
3.3 执行拒绝采样
同样是一行命令,verl 将自动编排整个 pipeline:
verl run --config rs_config.yaml执行过程可视化:
- Rollout 阶段:Actor 模型加载到 GPU,对
prompts.txt中每个 prompt 并行生成 4 个 response。- Reward 阶段:所有生成的 response(共 3×4=12 条)被送入 Reward Model 批量打分。
- 筛选阶段:按 prompt 分组,对每组 4 个 response 的 reward 值排序,取 Top-2。
- 输出阶段:结果以标准 JSONL 格式写入
./rs_results.jsonl,每行包含prompt、response、reward三字段。
rs_results.jsonl示例:{"prompt":"请用通俗语言解释区块链的工作原理。","response":"区块链就像一个公开的数字账本...","reward":0.92} {"prompt":"请用通俗语言解释区块链的工作原理。","response":"简单说,区块链是去中心化的数据库...","reward":0.87}
4. 流程整合与工程化建议
单独跑通 SFT 和 RS 是第一步,真正体现 verl 价值的是它们的无缝整合与生产化部署。
4.1 共享模型与避免重复加载
在上面的流程中,你可能注意到:SFT 训练完,RS 又加载了一次 Actor 模型。这在单卡调试时无感,但在多卡集群中会造成显存浪费和启动延迟。
verl 的解决方案是model_cache:在配置中启用,verl 会将首次加载的模型缓存在内存中,后续流程直接复用:
# 在 sft_config.yaml 和 rs_config.yaml 中均添加 model: # ... 其他配置 cache_model: true # 启用模型缓存启用后,RS 阶段的模型加载时间将从数秒降至毫秒级,因为权重已驻留在 GPU 显存中。
4.2 从拒绝采样结果到强化学习数据集
rs_results.jsonl是高质量数据,但还不能直接喂给 PPO。你需要将其转换为 PPO 训练所需的格式(包含prompt、response、advantage等)。
verl 提供了开箱即用的转换工具:
# 将 RS 结果转为 PPO 训练数据集 verl data convert \ --input ./rs_results.jsonl \ --output ./ppo_dataset.jsonl \ --format "ppo" \ --reward_key "reward"生成的ppo_dataset.jsonl可直接作为verl run --config ppo_config.yaml的输入,实现 SFT → RS → PPO 的全自动流水线。
4.3 生产环境关键配置建议
当从单机 demo 迈向生产集群时,以下配置至关重要:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
training.fsdp_sharding_strategy | "FULL_SHARD" | 对于 7B-13B 模型,此策略显存效率最高 |
rollout.tensor_parallel_size | 2或4 | 若有多卡,用 TP 加速生成,避免单卡瓶颈 |
reward.use_vllm | true | 对 RM 使用 vLLM 推理引擎,吞吐量提升 3-5 倍 |
flow.async_execution | true | 启用异步 DataFlow,让 Rollout 和 Reward 并行执行 |
这些配置均通过 YAML 文件声明,无需修改一行 Python 代码。
5. 常见问题与快速排查
实际操作中,你可能会遇到一些典型问题。以下是 verl 镜像环境下最常出现的场景及解决方法:
5.1 “CUDA out of memory” 错误
现象:SFT 或 RS 启动时报显存不足,即使模型参数量不大。
原因:默认配置未启用显存优化。
解决:在model配置块中添加:
model: load_in_4bit: true # 量化加载,显存减半 use_flash_attention_2: true # 减少 attention 中间态显存 torch_dtype: "bfloat16" # 比 float32 更省内存5.2 拒绝采样结果 reward 值全为 0 或 NaN
现象:rs_results.jsonl中所有reward字段为0.0或null。
原因:Reward Model 输入格式不匹配,或模型本身未正确加载。
排查:
- 运行
verl reward test --rm "OpenAssistant/reward-model-deberta-v3-large"验证 RM 是否可调用; - 检查
rs_config.yaml中reward_model路径是否拼写正确; - 确认
prompts.txt中的 prompt 不为空字符串(RM 对空输入可能返回异常值)。
5.3 训练速度远低于预期
现象:SFT 步骤耗时过长,GPU 利用率长期低于 30%。
优化方向:
- 增加
per_device_train_batch_size和gradient_accumulation_steps,填满 GPU; - 设置
data.packing: true,减少 padding 浪费; - 在
training块中添加fp16: true启用混合精度。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。