在当前的AI领域,强化学习(RL)已经成为提升大语言模型(LLM)推理能力的关键技术。然而,传统的PPO(Proximal Policy Optimization)方法存在内存占用高、计算开销大的问题。GRPO(Group Relative Policy Optimization)通过创新的基线估计方式,显著降低了资源消耗。本文将详细介绍如何在多GPU环境下,使用Verl框架结合LoRA技术高效训练Qwen2.5-3B-Instruct模型。
传统PPO需要同时加载四个组件到VRAM:Actor(策略模型)、Critic(价值函数)、Reference(参考模型)和Reward模型。其中Critic通常是主要瓶颈,其大小通常与Actor相当,这相当于使内存使用量翻倍。GRPO通过改变基线估计方式解决了这个问题:
这种方法的优势显而易见:
我们选择Qwen 2.5 3B Instruct作为基础模型,主要基于以下考虑:
3B参数的效率优势:
Instruct版本的稳定性:
RL后训练是一种混合工作负载,需要在生成(内存带宽受限)和训练(计算受限)之间交替进行。我们选择的框架组合解决了这一挑战:
这种组合特别适合生产环境,已被字节跳动等顶级实验室的团队广泛采用。然而,企业级框架的强大功能往往伴随着陡峭的学习曲线,这也是本文旨在解决的问题之一。
我们在RunPod实例上进行部署,具体配置如下:
提示:在实际操作中,确保你的云实例有足够的临时存储空间(至少200GB),以容纳大型检查点和数据集。
首先安装Miniconda,将我们的环境与系统Python解耦,防止版本冲突:
bash复制# 1. 安装Miniconda(如果尚未安装)
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash ./Miniconda3-latest-Linux-x86_64.sh
我们使用Python 3.12以确保与最新框架的完全兼容:
bash复制# 2. 创建环境
conda create -n verl python=3.12 -y
conda activate verl
获取Verl框架源代码:
bash复制# 3. 克隆仓库
git clone https://github.com/volcengine/verl
cd verl
Verl提供了一个便捷脚本(install_vllm_sglang_mcore.sh)来处理复杂的依赖关系树:
bash复制# 1. 运行官方脚本安装基础依赖
chmod +x ./scripts/install_vllm_sglang_mcore.sh
bash ./scripts/install_vllm_sglang_mcore.sh
注意:在"Building Megatron-LM"阶段可能会挂起15分钟以上,这属于正常现象。
基础架构就绪后,以可编辑模式完成Verl安装:
bash复制# 2. 安装Verl
pip install --no-deps -e .
专业提示:如果遇到Python 3.12导致的递归或导入错误,可以回退到Python 3.11。在这种情况下,可能需要手动安装预编译的Flash Attention wheel文件。
强化学习对数据格式非常敏感。常见的陷阱是在没有将提示结构与奖励函数对齐的情况下将原始数据集输入训练器。对于本实验,我们使用标准的GSM8K数据集。
运行预处理器以构建训练/测试集的parquet文件:
bash复制# 生成'train.parquet'和'test.parquet'到./data/gsm8k
python3 examples/data_preprocess/gsm8k.py --local_dir "./data/gsm8k"
关于"Think Tags"的说明:与DeepSeek-R1不同,标准Verl预处理器使用通用的思维链提示("Let's think step by step"),默认不注入
为提高训练效率,我们进行了以下数据加载优化:
这些优化显著减少了训练迭代间的等待时间,使GPU保持高利用率。
我们使用LoRA+GRPO工作流,Verl提供了参考脚本:examples/grpo_trainer/run_qwen2_5-3b_gsm8k_grpo_lora.sh。默认配置如下:
bash复制set -x
python3 -m verl.trainer.main_ppo \
algorithm.adv_estimator=grpo \
trainer.val_before_train=False \
data.train_files=$HOME/data/gsm8k/train.parquet \
data.val_files=$HOME/data/gsm8k/test.parquet \
data.train_batch_size=16 \
data.max_prompt_length=512 \
data.max_response_length=1024 \
data.filter_overlong_prompts=True \
data.truncation='error' \
data.shuffle=False \
actor_rollout_ref.model.path=Qwen/Qwen2.5-3B-Instruct \
actor_rollout_ref.model.lora_rank=64 \
actor_rollout_ref.model.lora_alpha=32 \
actor_rollout_ref.actor.optim.lr=3e-6 \
actor_rollout_ref.model.use_remove_padding=True \
actor_rollout_ref.actor.ppo_mini_batch_size=16 \
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=40 \
actor_rollout_ref.actor.use_kl_loss=True \
actor_rollout_ref.actor.kl_loss_coef=0.001 \
actor_rollout_ref.actor.kl_loss_type=low_var_kl \
actor_rollout_ref.actor.entropy_coeff=0 \
actor_rollout_ref.model.enable_gradient_checkpointing=True \
actor_rollout_ref.actor.fsdp_config.param_offload=False \
actor_rollout_ref.actor.fsdp_config.optimizer_offload=False \
actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=40 \
actor_rollout_ref.rollout.tensor_model_parallel_size=2 \
actor_rollout_ref.rollout.name=vllm \
actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \
actor_rollout_ref.rollout.n=5 \
actor_rollout_ref.rollout.load_format=safetensors \
actor_rollout_ref.rollout.layered_summon=True \
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=40 \
actor_rollout_ref.ref.fsdp_config.param_offload=True \
algorithm.use_kl_in_reward=False \
trainer.critic_warmup=0 \
trainer.logger='["console","wandb"]' \
trainer.project_name='verl_grpo_example_gsm8k' \
trainer.experiment_name='qwen2.5_3b_grpo_lora' \
trainer.n_gpus_per_node=2 \
trainer.nnodes=1 \
trainer.save_freq=20 \
trainer.test_freq=5 \
trainer.total_epochs=15 $@
默认值对于4×A100 80GB配置来说效率极低。我们识别了以下瓶颈并进行优化:
在启动训练运行前,必须完成以下准备工作:
WandB登录:
bash复制wandb login
避免训练脚本运行几分钟后因未认证而崩溃。
监控工具安装:
bash复制pip3 install nvitop
nvitop
实时监控GPU利用率和内存使用情况。
脚本修改:根据上述超参数优化建议调整训练脚本。
执行修改后的训练脚本:
bash复制chmod +x ./examples/grpo_trainer/run_qwen2_5-3b_gsm8k_grpo_lora.sh
./examples/grpo_trainer/run_qwen2_5-3b_gsm8k_grpo_lora.sh
初始配置使用tensor_model_parallel_size=4,将模型权重分片到所有4个GPU。但对于3B参数的小模型(约6GB),这种过度分片会导致GPU花费大量时间等待通信。
优化方案:
效果:
尽管改为数据并行后性能提升,但VRAM使用率仍只有约78%,意味着每个A100有约20GB内存未被利用。
调整参数:rollout.gpu_memory_utilization
效果:
理论上,增加rollout.n(组响应数量)可以减少基线估计的方差,带来更稳定的训练更新。
测试:
决策:保持n=5,因为3B模型在GSM8K上已经能很好收敛,额外的计算成本不划算。
系统健康度:
学习曲线:
响应长度:
现象:训练在Step 80/105时因磁盘满而崩溃。
原因:检查点文件过大,填满容器根卷。
解决方案:
bash复制rm -rf /workspace/verl/checkpoints/verl_grpo_example_gsm8k/qwen2.5_3b_grpo_lora/global_step_80
bash复制rm -rf /workspace/verl/checkpoints/verl_grpo_example_gsm8k/qwen2.5_3b_grpo_lora/global_step_20
rm -rf /workspace/verl/checkpoints/verl_grpo_example_gsm8k/qwen2.5_3b_grpo_lora/global_step_40
rm -rf /workspace/verl/checkpoints/verl_grpo_example_gsm8k/qwen2.5_3b_grpo_lora/global_step_60
bash复制trainer.resume_mode="auto"
预防措施:
现象:训练结束时出现"Signal Killed"或"Process Terminated"错误。
原因:WandB后台进程在关闭前被终止。
解决方案:无需处理,只要最终检查点存在即可。
在训练过程中,模型在保留测试集上的表现:
建议:对于类似配置的小模型,可将trainer.total_epochs从15减少到5,节省约60%计算资源。
使用LM Evaluation Harness在标准GSM8K和GSM8K-CoT任务上评估:
| 指标 | 基础模型 | 微调模型 | 提升 |
|---|---|---|---|
| GSM8K严格匹配 | 52.3% | 56.0% | +3.7% |
| GSM8K-CoT严格匹配 | 54.1% | 57.0% | +2.9% |
| GSM8K宽松匹配 | 58.2% | 59.5% | +1.3% |
分析:
GRPO的实际优势:
3B模型的特性:
Verl框架使用心得:
奖励函数工程:
数据多样性增强:
超参数优化:
架构扩展:
通过本项目的实践,我们建立了一个稳定、高效的GRPO训练管道,为后续更复杂的RLHF实验奠定了基础。最重要的是,我们验证了即使在小规模模型上,通过适当的工程优化和算法选择,也能实现显著的性能提升。