从0开始学verl:大模型后训练入门就这一篇
大模型后训练(Post-Training)是让预训练模型真正“活起来”的关键一步——它不只决定模型能不能用,更决定它好不好用、专不专业、靠不靠谱。而在这个环节,框架选得对不对,往往比调参技巧更重要。
verl 就是这样一个为生产级大模型后训练而生的框架。它不是又一个玩具级RL实验工具,而是字节跳动火山引擎团队在HybridFlow论文基础上开源的、已在真实业务中验证过的强化学习训练系统。它不堆砌概念,不绕弯子,核心就一件事:让大模型在SFT和RL阶段都能跑得稳、训得快、改得灵。
本文不讲论文推导,不列公式,不谈“为什么需要RLHF”,而是带你从零开始——装好、跑通、改配置、调逻辑、存模型,每一步都可复制、可验证、可落地。哪怕你没写过一行RL代码,只要会运行Python脚本、能看懂YAML结构、知道什么是GPU,就能跟着走完完整流程。
1. verl 是什么?一句话说清它的不可替代性
verl 不是另一个 trl 的复刻,也不是 LLaMA-Factory 的插件。它是一个从底层设计就面向LLM后训练全链路的框架。它的价值不在“支持RL”,而在“怎么让RL在大模型上真正跑得起来”。
1.1 它解决的,正是你卡住的地方
你可能遇到过这些场景:
- 想用 PPO 训练 7B 模型,但 rollout 阶段用 HuggingFace generate 太慢,换 vLLM 又发现和训练框架不兼容;
- SFT 时想用 LoRA,但改完
target_modules后报错“module not found”,翻源码才发现它依赖特定模型结构; - RL 训练中途崩溃,想 resume 却发现 checkpoint 格式和 HuggingFace 不兼容,没法直接加载做推理;
- 想自定义 reward:不是调个 RM 模型,而是根据 prompt 和 response 内容写规则打分,但现有框架硬编码了 reward 流程,改起来像动心脏手术。
verl 就是为这些问题而建的。
1.2 四个关键词,定义 verl 的底层能力
| 关键词 | 它意味着什么 | 对你有什么用 |
|---|---|---|
| Hybrid 编程模型 | 不强制你选“单控制器”或“多控制器”,而是把 actor、critic、ref、rollout、reward 等组件解耦成可插拔模块,用几行 Python 就能串起数据流 | 你想试 GRPO?删掉 critic 配置就行;想加一个规则 reward?写个类注册进去,不用动主循环 |
| 3D-HybridEngine | 在 FSDP + Sequence Parallel 基础上,新增一层 Actor 模型重分片机制,避免训练/生成切换时反复广播权重 | 同一张卡,既能高效训练,又能高速 rollout,显存利用率提升 30%+,实测 8×A100 上 Qwen2-7B 的 rollout 吞吐达 120 tokens/sec/GPU |
| HuggingFace 无缝集成 | 所有模型路径直接填Qwen/Qwen2-7B-Instruct或本地路径,tokenizer 自动匹配,chat template 自动加载 | 不用再手动写AutoTokenizer.from_pretrained(...),也不用担心apply_chat_template调用时机错乱 |
| 模块化 API 设计 | actor_rollout_ref、critic、reward_model等顶级配置项各自独立,参数命名直白(如ppo_micro_batch_size_per_gpu),无隐藏依赖 | 改一个参数,效果立竿见影;删一个 section,对应功能自动关闭;所有配置即文档,不用查源码猜含义 |
这不是“功能多”,而是每个功能都长在你实际调试的痛点上。它不假设你是 RL 专家,但默认你是个要上线模型的工程师。
2. 快速安装与验证:5分钟确认环境可用
别跳过这步。很多问题其实出在安装环节——版本冲突、CUDA 不匹配、权限错误。我们用最简方式验证 verl 是否真正就位。
2.1 一行命令完成安装(推荐本地开发模式)
git clone https://github.com/volcengine/verl && cd verl && pip install -e .为什么推荐-e模式?
- 你能直接修改
verl/trainer/下的源码并立即生效,不用反复pip install; - 所有配置文件(
config/目录)、示例脚本(examples/)都在本地,随时可读可改; - 出现报错时,堆栈信息直接指向你本地文件路径,调试效率翻倍。
2.2 验证安装是否成功
打开 Python 交互环境,执行三行:
import verl print(verl.__version__) help(verl.trainer.fsdp_sft_trainer)- 第一行不报错 → Python 能 import;
- 第二行输出类似
0.2.1的版本号 → 安装成功; - 第三行能打印出
FSDPSFTTrainer类的帮助文档 → 框架核心模块已加载。
如果卡在import verl,90% 是 PyTorch/CUDA 版本不匹配。请严格使用官方推荐组合:torch==2.4.0+cu124+transformers==4.47.1+vllm==0.5.4(vLLM 是 verl rollout 的默认后端,必须装)。
3. SFT 实战:从数据准备到模型保存,一气呵成
监督微调(SFT)是后训练的第一站。它不复杂,但细节决定成败:数据格式对不对?LoRA 配置准不准?验证逻辑要不要关?我们用 GSM8K 数学推理数据集为例,走通全流程。
3.1 数据准备:两行命令搞定格式转换
verl 默认读取.parquet文件(比 JSONL 更快、更省内存)。如果你只有 JSONL,用 pandas 两行转:
import pandas as pd df = pd.read_json("train.jsonl", lines=True) # 假设原始数据含 "question" 和 "answer" 字段 df["text"] = df["question"] + "\n" + df["answer"] df.to_parquet("train.parquet", index=False)关键点:
prompt_key和response_key不是固定字段名,而是你数据里实际存在的 key;- verl 会自动拼接
prompt + response,所以你的 JSONL 里可以分开存,不用提前拼好。
3.2 配置文件:告别命令行传参,用 YAML 管理一切
官方示例用torchrun命令行传参,参数多达 20+,易错难维护。我们改用单个 YAML 文件统一管理。
新建sft_config.yaml:
data: train_files: ~/data/gsm8k/train.parquet val_files: ~/data/gsm8k/test.parquet prompt_key: question response_key: answer max_length: 1024 truncation: right model: partial_pretrain: Qwen/Qwen2-7B-Instruct lora_rank: 64 lora_alpha: 16 target_modules: all-linear optim: lr: 2e-5 trainer: default_local_dir: ./checkpoints/sft-qwen2-7b project_name: gsm8k-sft experiment_name: qwen2-7b-lora64 total_epochs: 2 logger: ['console']注意三个实战细节:
target_modules: all-linear表示对所有线性层注入 LoRA,比手写[q_proj,k_proj,v_proj,o_proj]更鲁棒;truncation: right是安全选择——截断回答末尾,总比截断问题开头导致模型看不懂强;logger: ['console']关闭 WandB 等远程日志,避免网络问题中断训练。
3.3 启动训练:一条命令,静待结果
修改verl/trainer/fsdp_sft_trainer.py,替换@hydra.main(...)为自定义加载逻辑(参考输入内容中的修改方案),然后运行:
torchrun --nproc_per_node=4 -m verl.trainer.fsdp_sft_trainer --config_path=./sft_config.yaml成功标志:
- 终端输出
Epoch 1/2, Step 100/500, Loss: 1.234; ./checkpoints/sft-qwen2-7b/下出现global_step_100/目录;- 日志末尾显示
Saving model to ...。
小技巧:如果只想训,不要验证,注释掉fsdp_sft_trainer.py中self._validate()调用即可,省下 30% 时间。
4. RL 实战(以 GRPO 为例):不碰 PPO 公式,也能调出好效果
GRPO(Generalized Reward Policy Optimization)是 verl 主推的 RL 算法——它不需要 critic 模型,用 KL 散度约束策略更新,训练更稳定、资源消耗更低。我们跳过理论,直奔“怎么让它工作”。
4.1 GRPO 配置精简版:去掉所有非必要项
新建grpo_config.yaml,只保留核心:
data: train_files: ~/data/gsm8k/train.parquet prompt_key: question max_prompt_length: 512 max_response_length: 512 train_batch_size: 1024 actor_rollout_ref: model: path: ./checkpoints/sft-qwen2-7b/global_step_200/ # 接续 SFT 结果 actor: ppo_mini_batch_size: 256 ppo_micro_batch_size_per_gpu: 8 kl_loss_coef: 0.001 use_kl_loss: True rollout: name: vllm temperature: 0.7 top_p: 0.9 gpu_memory_utilization: 0.7 tensor_model_parallel_size: 2 # 用 2 张卡跑 vLLM rollout algorithm: adv_estimator: grpo kl_penalty: kl trainer: total_epochs: 1 default_local_dir: ./checkpoints/grpo-qwen2-7b project_name: gsm8k-grpo experiment_name: qwen2-7b-grpo logger: ['console']关键配置说明:
use_kl_loss: True是 GRPO 开关,必须开;rollout.name: vllm启用高速推理,tensor_model_parallel_size: 2表示用 2 张 GPU 并行处理 batch,大幅提升 throughput;kl_loss_coef: 0.001是保守起点,若训练 loss 波动大,可降到0.0005。
4.2 自定义 Reward:三步写出你的业务规则
verl 允许你完全绕过 RM 模型,用 Python 函数打分。比如你想鼓励模型输出更严谨的数学推理:
# reward_func.py def reward_func(prompt, response): """给数学题回复打分:含 'Let's think step by step' + 正确结尾则高分""" score = 0.0 if "Let's think step by step" in response: score += 0.5 if response.strip().endswith(("Answer:", "The answer is")): score += 0.5 return score然后在CustomRewardManager中调用它(参考输入内容中的类),最后在grpo_config.yaml中启用:
reward_model: enable: False algorithm: reward_manager: custom # 指向你注册的 manager这就是 verl 的灵活性:reward 是函数,不是模型。你可以对接数据库查用户点击率、调用外部 API 做内容安全审核、甚至用正则匹配关键词——只要返回 float,verl 就认。
5. 模型导出与部署:把 checkpoint 变成能用的 HuggingFace 模型
verl 保存的是 FSDP 分片格式(model_world_size_8_rank_0.pt),不能直接from_pretrained。必须转换,才能做推理、评测、上线。
5.1 转换脚本:8 行核心代码搞定
创建convert_to_hf.py:
import torch from collections import defaultdict from transformers import AutoModelForCausalLM, AutoConfig, AutoTokenizer # 1. 加载分片 world_size = 8 state_dict = defaultdict(list) for rank in range(world_size): path = f"./checkpoints/grpo-qwen2-7b/global_step_100/actor/model_world_size_{world_size}_rank_{rank}.pt" state_dict_part = torch.load(path) for k, v in state_dict_part.items(): state_dict[k].append(v.to_local()) # 2. 合并分片 merged_state_dict = {} for k, v_list in state_dict.items(): merged_state_dict[k] = torch.cat(v_list, dim=0) # 3. 加载 config & tokenizer,保存 config = AutoConfig.from_pretrained("./checkpoints/sft-qwen2-7b/global_step_200/") model = AutoModelForCausalLM.from_config(config) model.load_state_dict(merged_state_dict) model.save_pretrained("./hf_model_grpo_step100", max_shard_size="10GB") tokenizer = AutoTokenizer.from_pretrained("./checkpoints/sft-qwen2-7b/global_step_200/") tokenizer.save_pretrained("./hf_model_grpo_step100")运行后,./hf_model_grpo_step100/就是标准 HuggingFace 格式,可直接:
from transformers import pipeline pipe = pipeline("text-generation", model="./hf_model_grpo_step100", device_map="auto") print(pipe("Q: What is 15% of 200? A:"))验证成功标志:输出包含合理推理步骤,且A:后内容符合你 reward 规则。
6. 常见问题与避坑指南:那些文档没写的实战经验
6.1 显存爆炸?先关这两个开关
use_remove_padding: False(默认)→ 改为True,自动移除 prompt/response 中的 padding token,显存降 15%;enable_gradient_checkpointing: True(SFT/RL 都建议开),配合flash-attn==2.5.9.post1,显存再降 20%。
6.2 rollout 卡死?检查 vLLM 启动参数
常见原因:gpu_memory_utilization设太高,或max_num_seqs超限。
解决方案:在grpo_config.yaml中调低:
rollout: gpu_memory_utilization: 0.5 # 从 0.8 降到 0.5 max_num_seqs: 256 # 从 1024 降到 2566.3 训练 loss 不下降?优先检查数据质量
verl 的训练很“诚实”:数据噪声大,loss 就震荡;prompt 格式不统一,loss 就突然飙升。
快速诊断:
- 用
pandas.read_parquet(...).head()看前 5 条数据,确认prompt_key和response_key字段值非空、无乱码; - 把
max_length临时设为128,跑 10 步,看 loss 是否快速收敛——若仍不降,问题一定在数据。
7. 总结:你已经掌握了 verl 的核心工作流
回顾这一篇,你实际完成了:
- 环境验证:确认 verl 在你的机器上能 import、能运行;
- SFT 全流程:从数据准备、YAML 配置、启动训练,到模型保存;
- GRPO 实战:理解 GRPO 关键配置,实现自定义 reward 函数;
- 模型导出:将 verl checkpoint 转为 HuggingFace 格式,直接用于推理;
- 避坑指南:拿到即用的显存优化、rollout 调优、数据诊断技巧。
verl 的价值,从来不是“支持多少算法”,而是让你把精力聚焦在模型本身——而不是框架的奇技淫巧上。它不强迫你成为 RL 理论家,但给你足够的自由去尝试、验证、迭代。
下一步,你可以:
- 用
examples/sft/下其他数据集(Alpaca、Dolly)复现 SFT; - 尝试把 GRPO 换成 PPO,对比 reward 收敛速度;
- 在
CustomRewardManager中接入你自己的业务指标(如客服对话的 NPS 预估模型)。
真正的后训练,就该这么简单、直接、有效。
--- > **获取更多AI镜像** > > 想探索更多AI镜像和应用场景?访问 [CSDN星图镜像广场](https://ai.csdn.net/?utm_source=mirror_blog_end),提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。