首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >22-mini-batch-micro-batch-dynamic-batch-size的区别

22-mini-batch-micro-batch-dynamic-batch-size的区别

作者头像
anzhsoft
发布2026-07-16 21:30:31
发布2026-07-16 21:30:31
80
举报

mini-batch 决定算法用哪些样本更新,micro-batch 决定 GPU 一次承受多少工作,dynamic batch size 决定这些工作如何按 token 重新装箱。 第22篇拆清mini-batch、micro-batch与dynamic batch size三层语义:前者决定PPO/GRPO算法窗口,后者决定GPU执行切片,dynamic则按token预算重 新装箱。文章从verl源码追到loss归一化,解释batch配置为何直接影响显存、吞吐和训练语义。

第 21 篇解释了 EngineRegistryBaseEngine如何把不同训练后端收进同一组 worker 合同。接下来最容易混淆的不是后端名字,而是 batch 名字:ppo_mini_batch_sizeppo_micro_batch_size_per_gpuuse_dynamic_bszppo_max_token_len_per_gpuglobal_batch_size同时出现时,究竟哪个在定义算法,哪个在定义显存和吞吐?

本文的核心判断是:verl 里至少有三层 batch。第一层是算法 mini-batch:controller 决定一次 PPO/GRPO update 用多少样本、跑几轮 epoch、是否 shuffle。第二层是 engine micro-batch:worker 和 engine 把每个 mini-batch 切成能放进 GPU、能喂给 FSDP 或 Megatron schedule 的执行切片。第三层是 dynamic batch size:当样本长度差异很大时,不再按固定样本数切 micro-batch,而是按 token 上限和近似计算量重新装箱。把这三层分开,才能解释 loss 归一化、显存峰值、pipeline bubble 和训练语义之间的关系。

先看第 22 篇的整体地图。读图时注意:balance_batch发生在 controller 分发前,mini-batch 发生在 worker 的算法迭代里,micro-batch 和 dynamic batch 发生在 engine 执行里。

batch 名词在一次 update 里的位置

源码路径对应这条链路:RayPPOTrainer._update_actor()_update_critic()写入 mini_batch_sizeepochsseeddataloader_kwargsglobal_batch_sizeTrainingWorker.train_mini_batch()再按 data parallel size 计算每个 rank 的 mini-batch;TrainingWorker.train_batch()注入 use_dynamic_bszmax_token_len_per_gpumicro_batch_size_per_gpu;最后各 engine 在 forward_backward_batch()里调用 prepare_micro_batches()verl/trainer/ppo/ray_trainer.py:1205-1265verl/workers/engine_workers.py:238-382verl/workers/engine/utils.py:58-96)。

1. mini-batch 是算法更新窗口

mini-batch 首先是算法概念。它回答的是:这一轮 PPO/GRPO update 中,多少条样本组成一个更新窗口,要重复训练几个 epoch,是否打乱顺序。它不直接回答 GPU 一次能放多少 token。

下面这张图把 controller 到 worker 的 mini-batch 路径画出来。看图时注意:rollout.n会放大 actor/critic 的 PPO mini-batch,因为同一个 prompt 可能对应多条 response。

PPO mini-batch 如何进入 TrainingWorker

actor 更新里,_update_actor()先把 DataProto 转成 TensorDict,再把 actor.ppo_mini_batch_size * rollout.n写成 mini_batch_sizeglobal_batch_size,同时写入 ppo_epochsdata_loader_seedshuffle;critic 更新走类似路径(verl/trainer/ppo/ray_trainer.py:1211-12381247-1265)。配置层也对应这个语义:actor 的 ppo_mini_batch_size注释是“Split each sample into sub-batches of this size for PPO”,critic 也有自己的 ppo_mini_batch_sizeppo_epochsverl/trainer/config/actor/actor.yaml:17-33verl/trainer/config/critic/critic.yaml:36-52)。

进入 worker 后,train_mini_batch()用当前 engine 的 DP size 把全局 mini_batch_size换算成 mini_batch_size_per_gpu,并用 seed + data_parallel_rank构造 dataloader;tu.make_iterator()要求 TensorDict batch size 能被 mini-batch size 整除,并按 epoch 反复 yield mini-batch(verl/workers/engine_workers.py:248-275verl/utils/tensordict_utils.py:559-612)。所以 mini-batch 的作用是让算法按样本窗口迭代,DP rank 只是把这个窗口分到不同设备上。

2. micro-batch 是引擎执行切片

一个 mini-batch 进入 engine 后,往往仍然太大。micro-batch 才是模型 forward/backward 实际承受的执行单位。它回答的是:每张 GPU 一次处理多少样本,或者在 dynamic batch size 下处理多少 token。

下面这张图展示 mini-batch 到 micro-batch 的边界。看图时注意:BaseEngine.train_batch()看起来只有一次调用,但 forward_backward_batch()内部可能已经切成多个 micro-batch。

mini-batch 如何被切成 engine micro-batch

TrainingWorker.train_batch()会把 use_dynamic_bszmax_token_len_per_gpumicro_batch_size_per_gpuuse_remove_paddinguse_fused_kernels写入 TensorDict;推理路径 infer_batch()也会注入对应的 infer micro-batch 参数(verl/workers/engine_workers.py:328-428)。BaseEngine.train_batch()只固定训练顺序:清梯度、调用 forward_backward_batch()、optimizer step;它不规定 micro-batch 怎么切(verl/workers/engine/base.py:112-148)。

真正切 micro-batch 的地方在 prepare_micro_batches()。如果 use_dynamic_bsz=False,它要求数据长度能被 force_group_size * micro_batch_size_per_gpu整除,然后用 chunk_tensordict()按固定样本数切块;这里的 micro_batch_size_per_gpu是本地每 GPU 的执行样本数,不是全局 batch size(verl/workers/engine/utils.py:88-96verl/utils/tensordict_utils.py:302-322)。

3. dynamic batch size 是按 token 重新装箱

dynamic batch size 的名字容易误导。它不是自动增大训练 batch,也不是改变 PPO mini-batch 的算法窗口;它改变的是 micro-batch 的切法:从“固定样本数”切换到“每个 micro-batch 的 token 工作量不超过上限,并尽量平衡”。

下面这张图把 prepare_micro_batches()的两条分支并排放在一起。看图时注意:dynamic 分支会返回 indices,因为后续还要把输出恢复到原始顺序。

固定 micro-batch 与 dynamic batch size 的分支

use_dynamic_bsz=True时,prepare_micro_batches()要求 batch 里有 max_token_len_per_gpu,并把 token 上限换算成 max_token_len_per_gpu * sp_size;随后调用 rearrange_micro_batches()verl/workers/engine/utils.py:69-87)。rearrange_micro_batches()先从 nested input_idsattention_mask估计每条样本的有效长度,要求 max_token_len至少覆盖最长样本;再按总 token 数估算需要多少 micro-batch,并在 DP group 上同步最大 micro-batch 数,必要时按 VPP 分组要求向上取整(verl/utils/seqlen_balancing.py:348-409)。

更关键的是重排逻辑。rearrange_micro_batches()calculate_workload()估算每条样本的注意力计算压力,再用分区算法把样本索引分给不同 micro-batch;如果开启 dynamic balance,还会把较小 micro-batch 放在两端,以减少 pipeline warm-up/cool-down 暴露的 bubble(verl/utils/seqlen_balancing.py:27-47414-468)。输出恢复则由 restore_dynamic_batch()根据 index map 逆变换完成;engine 的 postprocess_batch_func()在 dynamic 模式下会调用它恢复 model output 顺序(verl/utils/seqlen_balancing.py:528-551verl/workers/engine/utils.py:99-159)。

这就是 dynamic batch size 的真实含义:它牺牲固定样本数的简单性,换取按 token 和近似 workload 更均衡的执行切片。算法仍然看同一个 mini-batch,只是 engine 内部把样本重新装进更合适的 micro-batch。

4. FSDP 和 Megatron 对 micro-batch 的消费方式不同

第 21 篇说过,BaseEngine固定合同,不固定实现。micro-batch 正是后端差异很集中的地方:FSDP 可以逐个 micro-batch forward/backward;Megatron 需要把 micro-batch 数交给 pipeline schedule,并处理 VPP 整除和 loss 缩放。

下面这张图展示两个后端如何消费同一组 micro-batch。看图时注意:同样来自 prepare_micro_batches(),进入后端后的执行语义已经不同。

FSDP 与 Megatron 如何消费 micro-batch

FSDP 路径里,forward_backward_batch()写入 sp_size、全局 token 数和 DP size,然后调用 prepare_micro_batches();随后循环每个 micro-batch,执行 forward_step(),非 forward-only 时直接 loss.backward(),最后把所有 micro-batch 输出交给 postprocess_batch_func()verl/workers/engine/fsdp/transformer_impl.py:608-6451210-1252)。

Megatron 路径也会先计算全局 token 数和 DP size,再调用 prepare_micro_batches();但如果启用 virtual pipeline parallel,它会读取 microbatch_group_size_per_vp_stage,要求 micro-batch 数可被该值整除。随后 Megatron engine 给每个 micro-batch 写入 num_micro_batch,调用 get_forward_backward_func()make_batch_generator(),把执行交给 pipeline schedule;postprocess 中还要把 loss 乘回 num_micro_batch,因为 pipeline schedule 内部会做缩放(verl/workers/engine/megatron/transformer_impl.py:599-674963-1001)。

所以 micro-batch 不是一个纯配置数字。对 FSDP,它更像梯度累积和显存切片;对 Megatron,它还参与 pipeline 调度、VPP 分组和 loss 缩放。第 22 篇不展开每个后端的所有细节,但要把这个边界钉住。

5. loss 归一化把三层 batch 接回算法语义

如果 micro-batch 只是性能切片,为什么还要反复讨论它?因为 loss 计算不能被 micro-batch 切分改变语义。一次算法 mini-batch 的 loss 应该按全局样本或全局 token 归一化,而不是按某个本地 micro-batch 的大小随意变化。

engine 因此会在 forward_backward_batch()里计算 loss_mask.sum(),并在 data parallel group 上 all-reduce 得到 batch_num_tokens,再把 dp_size写入 batch;FSDP、Megatron、TorchTitan、Automodel 等后端都有类似动作(verl/workers/engine/fsdp/transformer_impl.py:612-618verl/workers/engine/megatron/transformer_impl.py:602-608)。ppo_loss()会把 dp_sizebatch_num_tokensglobal_batch_size写入 config.global_batch_info,后续 policy loss、entropy loss 和 KL loss 都把这组信息传给聚合函数(verl/workers/utils/losses.py:57-144)。

agg_loss()则把归一化规则说得更直接:token-mean需要全局 batch_num_tokens,seq-level 模式需要 global_batch_size,并用 dp_size补偿 data parallel 下的梯度平均;注释也明确指出 FSDP 的 loss 会直接 backward,而 Megatron 的 loss 还要按 micro-batch/pipeline schedule 缩放(verl/trainer/ppo/core_algos.py:1141-1198)。这说明 micro-batch 可以重排、拆分、走不同后端调度,但最终必须回到同一个算法 mini-batch 的 loss 语义。

还有一个相邻但不同的机制:trainer 侧的 balance_batch。它在 rollout 结果进入 update 前,根据全局 sequence length 把样本重排到不同 DP rank,目的是让各 rank 的 token 工作量更接近;它发生在 controller 分发前,而 dynamic batch size 发生在 engine 内部切 micro-batch 时(verl/trainer/ppo/ray_trainer.py:1060-11281399-1418)。把这两者混为一谈,会误判性能瓶颈在 controller 还是在 engine。

小结:算法 batch 和性能 batch 必须分开看

本文把第 22 篇要解决的 batch 名词收束成一条路径:

代码语言:javascript
复制
rollout batch
-> optional controller-side balance_batch across DP ranks
-> PPO/GRPO mini-batch with epochs and shuffle
-> per-DP-rank mini-batch iterator
-> engine micro-batches
-> fixed sample chunks or dynamic token/workload packing
-> backend-specific forward/backward schedule
-> global loss normalization

放回系列地图,第 22 篇补的是 training engine/distributed core里的执行单位:同一套 RL 目标为什么要同时管理算法 batch、GPU micro-batch 和 token 预算。下一篇会继续沿着 token 维度走下去,解释长上下文训练为什么不能只靠缩小 micro-batch,还需要 Sequence Parallel 把序列维度本身变成分布式对象。

本文源码索引

  • verl/trainer/ppo/ray_trainer.py:1205-1265:actor/critic update 如何写入 mini_batch_sizeglobal_batch_size、epoch、seed 和 shuffle。
  • verl/trainer/ppo/ray_trainer.py:1060-11281399-1418:controller 侧 balance_batch如何按 sequence length 重排 DP 分发。
  • verl/trainer/config/actor/actor.yaml:17-33verl/trainer/config/critic/critic.yaml:36-52verl/trainer/config/rollout/rollout.yaml:97-109:mini-batch、micro-batch、dynamic batch size 和 logprob batch 的配置入口。
  • verl/workers/config/actor.py:149-240verl/workers/config/critic.py:72-150:actor/critic 对 micro-batch 与 dynamic batch 的配置校验。
  • verl/workers/engine_workers.py:238-382TrainingWorker.train_mini_batch()train_batch()如何把算法 mini-batch 转成 engine 调用。
  • verl/workers/engine_workers.py:384-428:推理路径如何注入 infer micro-batch 和 dynamic batch 参数。
  • verl/utils/tensordict_utils.py:559-612make_iterator()如何按 mini-batch size 和 epoch 生成 TensorDict mini-batch。
  • verl/workers/engine/base.py:112-148BaseEngine.train_batch()infer_batch()的统一外壳。
  • verl/workers/engine/utils.py:58-159prepare_micro_batches()postprocess_batch_func()的固定切分、dynamic 重排和输出恢复。
  • verl/utils/seqlen_balancing.py:27-47348-468528-551:dynamic batch size 如何估算 workload、重排 micro-batch 并恢复原始顺序。
  • verl/workers/engine/fsdp/transformer_impl.py:608-6451210-1252:FSDP 如何逐 micro-batch forward/backward。
  • verl/workers/engine/megatron/transformer_impl.py:599-674963-1001:Megatron 如何把 micro-batch 交给 pipeline schedule,并处理 VPP 与 loss 缩放。
  • verl/workers/utils/losses.py:57-144verl/trainer/ppo/core_algos.py:1141-1198:全局 token、DP size 和 global batch size 如何进入 loss 归一化。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 训推工坊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. mini-batch 是算法更新窗口
  • 2. micro-batch 是引擎执行切片
  • 3. dynamic batch size 是按 token 重新装箱
  • 4. FSDP 和 Megatron 对 micro-batch 的消费方式不同
  • 5. loss 归一化把三层 batch 接回算法语义
  • 小结:算法 batch 和性能 batch 必须分开看
  • 本文源码索引
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档