一个GRPO的例子
🚀 分布式训练参数 (torchrun)
bash
--nproc_per_node="4" # 每个节点使用4个GPU
--nnodes="1" # 使用1个计算节点
--node_rank="0" # 当前节点的编号(单节点时为0)
--master_addr="127.0.0.1" # 主节点的IP地址(本地训练)
--master_port="12362" # 主节点通信端口
📁 路径和配置
bash
--deepspeed # DeepSpeed配置文件(ZeRO-3优化,显存优化)
--output_dir # 模型检查点和日志的输出目录
--model_name_or_path # 预训练模型或checkpoint的路径
--train_data_path # 训练数据JSON文件
--eval_data_path # 验证数据JSON文件
--video_folder # 视频文件存放目录
--dataset_name charades # 数据集名称(Charades视频理解数据集)
🎯 GRPO核心参数
bash
--max_prompt_length 8192 # 输入提示的最大token长度
--max_completion_length 1024 # 模型生成回复的最大token长度
--num_generations 8 # 每个提示生成8个候选回复
# GRPO会从这些候选中学习相对优劣
GRPO原理:通过生成多个候选答案,基于奖励函数对它们进行排序,学习相对优势而非绝对分数,这样更稳定。
⚙️ 训练超参数
bash
--per_device_train_batch_size 4 # 每个GPU的batch size
--gradient_accumulation_steps 2 # 梯度累积步数
# 实际batch size = 4 GPU × 4 × 2 = 32
--num_train_epochs 1 # 训练轮数
--logging_steps 1 # 每1步记录一次日志
--save_steps 100 # 每100步保存一次checkpoint
--save_total_limit 3 # 最多保留3个checkpoint
💾 内存和精度优化
bash
--bf16 # 使用bfloat16混合精度训练
--torch_dtype bfloat16 # 模型权重使用bfloat16格式
--gradient_checkpointing True # 开启梯度检查点(节省显存,略降速度)
优势:bf16比fp16数值范围更大,训练更稳定;梯度检查点可大幅减少显存占用。
🔧 其他设置
bash
--data_seed 42 # 数据随机种子(确保可复现)
--report_to tensorboard # 使用TensorBoard记录训练过程
--run_name qwen2vl-baseline # 实验运行名称
更加详细的参数设置例子
VERL GRPO训练参数详解
📊 完整参数表格
| 参数类别 | 参数名 | 值 | 含义说明 |
|---|---|---|---|
| 🔧 算法配置 | |||
algorithm.adv_estimator |
grpo |
优势估计器类型:GRPO(组相对策略优化) | |
algorithm.use_kl_in_reward |
False |
是否在奖励函数中加入KL散度惩罚 | |
| 📁 数据配置 | |||
data.train_files |
~/data/geo3k/train.parquet |
训练数据文件路径(Parquet格式) | |
data.val_files |
~/data/geo3k/test.parquet |
验证数据文件路径 | |
data.train_batch_size |
512 |
训练批次大小(rollout阶段生成的样本数) | |
data.max_prompt_length |
1024 |
输入提示的最大token长度 | |
data.max_response_length |
2048 |
模型生成回复的最大token长度 | |
data.filter_overlong_prompts |
True |
是否过滤超长提示(超过max_prompt_length) | |
data.truncation |
error |
截断策略:遇到超长序列直接报错 | |
data.image_key |
images |
数据中图像字段的键名(VLM需要) | |
| 🤖 模型配置 | |||
actor_rollout_ref.model.path |
/path/to/Qwen2.5-VL-7B-Instruct |
预训练模型路径 | |
actor_rollout_ref.model.use_remove_padding |
True |
移除padding提高效率(适用于变长序列) | |
actor_rollout_ref.model.lora_rank |
64 |
LoRA秩(低秩适配矩阵的维度) | |
actor_rollout_ref.model.lora_alpha |
32 |
LoRA缩放系数(实际学习率 = lr × alpha/rank) | |
actor_rollout_ref.model.target_modules |
all-linear |
LoRA应用的模块:所有线性层 | |
actor_rollout_ref.model.exclude_modules |
'.*visual.*' |
排除视觉编码器,仅训练语言部分 | |
actor_rollout_ref.model.enable_gradient_checkpointing |
True |
启用梯度检查点节省显存 | |
| 🎯 Actor训练配置 | |||
actor_rollout_ref.actor.optim.lr |
3e-6 |
学习率(0.000003) | |
actor_rollout_ref.actor.ppo_mini_batch_size |
128 |
PPO更新时的小批次大小 | |
actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu |
10 |
每个GPU的微批次大小(实际前向传播单位) | |
actor_rollout_ref.actor.use_kl_loss |
True |
在Actor损失中加入KL散度损失 | |
actor_rollout_ref.actor.kl_loss_coef |
0.01 |
KL损失系数(防止策略偏离参考模型太远) | |
actor_rollout_ref.actor.kl_loss_type |
low_var_kl |
KL散度计算方式:低方差估计 | |
actor_rollout_ref.actor.entropy_coeff |
0 |
熵正则化系数(0表示不鼓励探索) | |
actor_rollout_ref.actor.fsdp_config.param_offload |
False |
FSDP参数卸载到CPU(False=全在GPU) | |
actor_rollout_ref.actor.fsdp_config.optimizer_offload |
False |
FSDP优化器状态卸载到CPU | |
| 🎲 Rollout配置 | |||
actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu |
20 |
计算log概率时的微批次大小 | |
actor_rollout_ref.rollout.tensor_model_parallel_size |
2 |
张量模型并行度(模型切分到2个GPU) | |
actor_rollout_ref.rollout.name |
vllm |
推理引擎:vLLM(高性能推理) | |
+actor_rollout_ref.rollout.engine_kwargs.vllm.disable_mm_preprocessor_cache |
True |
禁用多模态预处理缓存 | |
actor_rollout_ref.rollout.gpu_memory_utilization |
0.6 |
GPU显存利用率(为其他组件预留40%) | |
actor_rollout_ref.rollout.enable_chunked_prefill |
False |
禁用分块预填充(可能影响长序列性能) | |
actor_rollout_ref.rollout.enforce_eager |
False |
不强制eager模式(允许CUDA图优化) | |
actor_rollout_ref.rollout.free_cache_engine |
False |
不在rollout后释放缓存引擎 | |
actor_rollout_ref.rollout.n |
5 |
每个提示生成5个候选回复(GRPO核心) | |
| 🔄 Reference模型配置 | |||
actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu |
20 |
参考模型计算log概率的微批次大小 | |
actor_rollout_ref.ref.fsdp_config.param_offload |
True |
参考模型参数卸载到CPU节省显存 | |
| 📈 Trainer配置 | |||
trainer.critic_warmup |
0 |
Critic预热步数(GRPO不使用Critic) | |
trainer.logger |
["console","wandb"] |
日志记录器:控制台+Weights & Biases | |
trainer.project_name |
verl_grpo_example_geo3k |
WandB项目名称 | |
trainer.experiment_name |
qwen2_5_vl_7b_function_rm |
实验名称 | |
trainer.n_gpus_per_node |
4 |
每个节点使用4个GPU | |
trainer.nnodes |
1 |
使用1个计算节点 | |
trainer.save_freq |
20 |
每20次迭代保存一次checkpoint | |
trainer.test_freq |
5 |
每5次迭代进行一次验证 | |
trainer.total_epochs |
15 |
总训练轮数 |
🔑 关键概念解析
1️⃣ 批次大小关系
总样本数 = data.train_batch_size (512)
↓ (每个提示生成n个候选)
生成候选数 = 512 × 5 = 2560个回复
↓ (分批更新策略)
Mini batch = ppo_mini_batch_size (128)
↓ (每GPU实际处理)
Micro batch/GPU = ppo_micro_batch_size_per_gpu (10)
实际训练:
- 每次迭代从512个提示生成2560个候选
- 策略更新时分为 2560/128 = 20 个mini-batch
- 每个GPU每次处理10个样本
2️⃣ GRPO vs PPO
| 特性 | GRPO | PPO |
|---|---|---|
| 优势计算 | 相对排序(组内比较) | 绝对值估计(需Critic) |
| 是否需要Critic | ❌ 不需要 | ✅ 需要 |
| 采样效率 | 高(每提示生成多个) | 低(每提示1个) |
| 稳定性 | 高(相对比较更稳定) | 中等 |
3️⃣ LoRA参数高效训练
python
实际更新参数量 = lora_rank × lora_alpha / total_params
= 64 × 32 / 7B ≈ 0.03%
仅训练0.03%的参数,大幅降低显存和计算需求。
4️⃣ KL散度控制
use_kl_loss=True:在Actor损失中加入KL项- 防止新策略偏离参考模型太远
- 系数0.01意味着1%的权重用于KL约束
use_kl_in_reward=False:不在奖励函数中加KL- 两种方式任选其一即可
5️⃣ 显存优化策略
| 技术 | 参数 | 节省显存 | 速度影响 |
|---|---|---|---|
| Gradient Checkpointing | enable_gradient_checkpointing=True |
⭐⭐⭐⭐ | 🐌 慢15-20% |
| LoRA | lora_rank=64 |
⭐⭐⭐⭐⭐ | ✅ 几乎无 |
| Reference卸载 | ref.fsdp_config.param_offload=True |
⭐⭐⭐ | 🐌 慢10% |
| 张量并行 | tensor_model_parallel_size=2 |
⭐⭐ | ✅ 几乎无 |
-
显存不足时调整优先级:
bash降低 gpu_memory_utilization: 0.6 → 0.5 增加 tensor_model_parallel_size: 2 → 4 开启 param_offload: False → True -
加速训练:
bash减少候选数 n: 5 → 3 增大 micro_batch_size: 10 → 16 关闭梯度检查点(如显存够用) -
提高采样多样性:
bash增加 entropy_coeff: 0 → 0.01 增加候选数 n: 5 → 8
要注意的一些参数
1.设置 actor_rollout_ref.model.enable_gradient_checkpointing=True是一项关键的显存优化技术。它通过以计算时间换取显存空间的方式。
2.--per_device_train_batch_size 4 # 每个GPU的batch size
--gradient_accumulation_steps 2 # 梯度累积步数
实际batch size = 4 GPU × 4 × 2 = 32
3.Mini Batch Size和Micro Batch Size per GPU
📋 配置说明
python
总样本数: 2560 个
Mini Batch Size: 128
Micro Batch Size per GPU: 10
GPU数量: 4
🚀 完整训练流程(第1个Mini Batch)
第1步:分配样本到4个GPU
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Mini Batch 1: 从2560个样本中取出前128个
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
分配给4个GPU:
GPU-0: 样本1-32 (32个样本)
GPU-1: 样本33-64 (32个样本)
GPU-2: 样本65-96 (32个样本)
GPU-3: 样本97-128 (32个样本)
每个GPU分到:128 ÷ 4 = 32个样本
第2步:每个GPU分批处理(Micro Batch)
❗ 关键:因为显存限制,每个GPU无法一次处理32个样本
GPU-0 的详细处理过程:
python
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GPU-0 处理它的32个样本(分4次Micro Batch)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔸 Micro Batch 1(样本1-10)
───────────────────────────────────────────────
样本1: "1+1=?" → "等于2" | loss₁ = 0.5
样本2: "1+1=?" → "等于2,因为..." | loss₂ = 0.3
样本3: "1+1=?" → "等于3" | loss₃ = 2.1
样本4: "2×3=?" → "等于6" | loss₄ = 0.4
样本5: "2×3=?" → "是6" | loss₅ = 0.6
样本6: "2×3=?" → "等于5" | loss₆ = 1.8
样本7: "x²=16?" → "x=4" | loss₇ = 1.2
样本8: "x²=16?" → "x=±4" | loss₈ = 0.2
样本9: "5+3=?" → "等于8" | loss₉ = 0.5
样本10: "5+3=?" → "8" | loss₁₀ = 0.7
① 前向传播:计算10个样本的输出
② 计算loss:loss_micro1 = (0.5+0.3+2.1+...+0.7)/10 = 0.83
③ 反向传播:计算梯度 → grad_micro1
④ ⚠️ 不更新参数!只是把梯度存起来
───────────────────────────────────────────────
🔸 Micro Batch 2(样本11-20)
───────────────────────────────────────────────
样本11: "10-5=?" → "等于5" | loss₁₁ = 0.4
样本12: "10-5=?" → "5" | loss₁₂ = 0.6
...
样本20: "7×2=?" → "14" | loss₂₀ = 0.8
① 前向传播:计算10个样本的输出
② 计算loss:loss_micro2 = 0.71
③ 反向传播:计算梯度 → grad_micro2
④ ⚠️ 不更新参数!继续累积梯度
───────────────────────────────────────────────
🔸 Micro Batch 3(样本21-30)
───────────────────────────────────────────────
① 前向传播 → ② 计算loss:loss_micro3 = 0.65
③ 反向传播 → grad_micro3
④ ⚠️ 不更新参数!继续累积梯度
───────────────────────────────────────────────
🔸 Micro Batch 4(样本31-32,只有2个)
───────────────────────────────────────────────
样本31: "9+1=?" → "等于10" | loss₃₁ = 0.3
样本32: "9+1=?" → "10" | loss₃₂ = 0.5
① 前向传播 → ② 计算loss:loss_micro4 = (0.3+0.5)/2 = 0.4
③ 反向传播 → grad_micro4
④ ⚠️ 不更新参数!梯度累积完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GPU-0 完成!现在有4个梯度:
grad_micro1, grad_micro2, grad_micro3, grad_micro4
GPU-0的总梯度 = (grad_micro1 + grad_micro2 + grad_micro3 + grad_micro4) / 4
GPU-0的平均loss = (0.83 + 0.71 + 0.65 + 0.4) / 4 = 0.647
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
其他3个GPU同时进行:
python
GPU-1 处理样本33-64:
→ Micro Batch 1: 样本33-42 → loss=0.55, grad累积
→ Micro Batch 2: 样本43-52 → loss=0.68, grad累积
→ Micro Batch 3: 样本53-62 → loss=0.72, grad累积
→ Micro Batch 4: 样本63-64 → loss=0.45, grad累积
→ GPU-1总梯度 = 累积平均
→ GPU-1平均loss = 0.600
GPU-2 处理样本65-96:
→ ... (同样的过程)
→ GPU-2平均loss = 0.710
GPU-3 处理样本97-128:
→ ... (同样的过程)
→ GPU-3平均loss = 0.620
第3步:4个GPU同步,更新参数 ✅
python
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
所有GPU完成Micro Batch处理后
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
① 计算Mini Batch的总loss:
Mini_Batch_Loss = (GPU-0_loss + GPU-1_loss + GPU-2_loss + GPU-3_loss) / 4
= (0.647 + 0.600 + 0.710 + 0.620) / 4
= 0.644
② 聚合4个GPU的梯度:
Final_Gradient = (GPU-0_grad + GPU-1_grad + GPU-2_grad + GPU-3_grad) / 4
③ 🎯 更新模型参数:
θ_new = θ_old - learning_rate × Final_Gradient
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 第1次参数更新完成!
使用了128个样本(Mini Batch 1)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
第4步:继续处理剩余的Mini Batch
python
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Mini Batch 2: 样本129-256
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
分配到4个GPU → 每个GPU分批处理 → 聚合梯度 → 更新参数 ✅
→ 第2次参数更新
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Mini Batch 3: 样本257-384
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
... → 第3次参数更新 ✅
... (总共20个Mini Batch)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Mini Batch 20: 样本2433-2560
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
... → 第20次参数更新 ✅
🎉 所有2560个样本训练完成!
总共进行了20次参数更新
📊 时间轴可视化
时间 ────────────────────────────────────────────────────────►
Mini Batch 1 (样本1-128)
├─ GPU-0: [Micro1] [Micro2] [Micro3] [Micro4] ─┐
├─ GPU-1: [Micro1] [Micro2] [Micro3] [Micro4] ─┤
├─ GPU-2: [Micro1] [Micro2] [Micro3] [Micro4] ─┤ → 聚合 → ✅更新参数
└─ GPU-3: [Micro1] [Micro2] [Micro3] [Micro4] ─┘
Mini Batch 2 (样本129-256)
├─ GPU-0: [Micro1] [Micro2] [Micro3] [Micro4] ─┐
├─ GPU-1: [Micro1] [Micro2] [Micro3] [Micro4] ─┤
├─ GPU-2: [Micro1] [Micro2] [Micro3] [Micro4] ─┤ → 聚合 → ✅更新参数
└─ GPU-3: [Micro1] [Micro2] [Micro3] [Micro4] ─┘
...
Mini Batch 20 (样本2433-2560)
├─ GPU-0: [Micro1] [Micro2] [Micro3] [Micro4] ─┐
├─ GPU-1: [Micro1] [Micro2] [Micro3] [Micro4] ─┤
├─ GPU-2: [Micro1] [Micro2] [Micro3] [Micro4] ─┤ → 聚合 → ✅更新参数
└─ GPU-3: [Micro1] [Micro2] [Micro3] [Micro4] ─┘
🎯 核心问题回答
❓ 模型什么时候更新参数?
答:每处理完1个Mini Batch后更新1次
具体条件:
- ✅ 所有GPU都完成了它们的Micro Batch处理
- ✅ 梯度已经在4个GPU之间同步求平均
- ✅ 然后才更新参数
频率:
- 你的配置:2560个样本 ÷ 128(Mini Batch)= 20次参数更新
❓ Loss是如何计算的?
第1层:Micro Batch Loss(单GPU内部)
python
# GPU-0的第1个Micro Batch(10个样本)
loss_micro1 = (loss₁ + loss₂ + ... + loss₁₀) / 10
第2层:GPU Total Loss(单GPU所有Micro Batch的平均)
python
# GPU-0处理了4个Micro Batch
loss_GPU0 = (loss_micro1 + loss_micro2 + loss_micro3 + loss_micro4) / 4
第3层:Mini Batch Loss(所有GPU的平均)
python
# 4个GPU的loss求平均
loss_MiniBatch = (loss_GPU0 + loss_GPU1 + loss_GPU2 + loss_GPU3) / 4
这个最终的loss就是你在训练日志中看到的loss!
💡 关键理解
| 概念 | 作用 | 更新参数? |
|---|---|---|
| Micro Batch | 因为显存限制,GPU分批前向传播 | ❌ 不更新,只累积梯度 |
| Mini Batch | 决定多少样本做一次参数更新 | ✅ 更新参数! |
记忆方法:
- Micro = 小步走(显存不够,慢慢算,梯度先存着)
- Mini = 检查点(走完一段路,更新一次方向)
📈 训练日志示例
bash
[Step 1] Mini Batch 1/20: Loss=0.644 (128 samples) → ✅参数更新
├─ GPU-0: loss=0.647 (32 samples, 4 micro batches)
├─ GPU-1: loss=0.600 (32 samples, 4 micro batches)
├─ GPU-2: loss=0.710 (32 samples, 4 micro batches)
└─ GPU-3: loss=0.620 (32 samples, 4 micro batches)
[Step 2] Mini Batch 2/20: Loss=0.612 (128 samples) → ✅参数更新
├─ GPU-0: loss=0.605 (32 samples, 4 micro batches)
├─ GPU-1: loss=0.590 (32 samples, 4 micro batches)
├─ GPU-2: loss=0.638 (32 samples, 4 micro batches)
└─ GPU-3: loss=0.615 (32 samples, 4 micro batches)
...
[Step 20] Mini Batch 20/20: Loss=0.421 (128 samples) → ✅参数更新
✅ Epoch 1 完成!总共更新了20次参数
🔑 总结
python
# 一句话总结:
每个Mini Batch = 一次参数更新
每个Micro Batch = 显存不够时的分批计算(不更新参数)
# 你的配置:
2560个样本 → 20个Mini Batch → 20次参数更新
每个Mini Batch包含 4个GPU × 4个Micro Batch = 16次前向传播
# 参数更新时机:
只有当1个Mini Batch的所有Micro Batch都处理完,
并且4个GPU的梯度都聚合完成后,才会更新参数!