verl sharding manager原理:初学者友好解释
在大型语言模型(LLM)的强化学习后训练中,如何高效调度计算资源、协调多个GPU协同工作,是决定训练速度和稳定性的关键。verl 作为专为 LLM 后训练设计的 RL 框架,其核心创新之一正是Sharding Manager(分片管理器)——它不是简单的模型并行工具,而是一套动态适配不同计算角色(Actor、Rollout、Reference)、支持混合并行策略的智能资源编排系统。
很多初学者看到FSDPUlyssesShardingManager、FSDPVLLMShardingManager这类名称时容易困惑:这到底是在“切模型”还是“切数据”?为什么同一个模型要在不同阶段被反复重分片?本文不讲论文公式,不堆术语,而是用你日常能感知的方式,把 sharding manager 的设计逻辑、运行机制和实际作用,一层层拆开讲清楚。
我们不预设你熟悉 FSDP、vLLM 或张量并行,只假设你了解“训练一个大模型需要很多 GPU”这个基本事实。接下来的内容,会像带朋友调试一段代码那样,边看配置、边读源码、边画图解释——目标很明确:让你合上这篇文章后,再看到sharding_manager.preprocess_data()就知道它在干什么,再遇到device_mesh就明白它为什么长那样。
1. 为什么需要 Sharding Manager?——从一个真实问题说起
想象你在用 6 张 GPU 训练一个 7B 参数的 LLM,采用 PPO 或 GRPO 算法做后训练。整个流程不是“喂数据→算梯度→更新模型”这么简单,而是包含多个异构计算阶段:
- Actor 阶段:用当前策略模型生成新文本(rollout),要快、要高吞吐;
- Rollout 推理阶段:对每条 prompt 采样 12 次,产生 12 条响应,需低延迟、高并发;
- Reference 模型阶段:用冻结的旧策略模型重新计算 log prob,要求精度一致、无扰动;
- Critic / Reward 计算阶段:评估生成质量(GRPO 中直接用规则打分,但逻辑仍需同步)。
问题来了:
Actor 模型需要全参数参与前向/反向,适合用FSDP 做参数分片;
Rollout 推理却更看重吞吐,vLLM 的 PagedAttention + 张量并行(TP)才是最优解;
Reference 模型只需前向,甚至可以 CPU 卸载,但必须和 Actor 共享同一份权重初始化。
如果所有阶段都用同一套并行方式——比如强行让 vLLM 也走 FSDP——要么显存炸掉,要么速度慢到无法接受。
反过来,如果每个阶段各自为政、独立加载模型——那 6 张卡上就要存 6 份重复权重,显存浪费严重,参数同步还容易出错。
Sharding Manager 就是为解决这个矛盾而生的:它不绑定某一种并行范式,而是根据当前任务角色(role)和硬件拓扑(device mesh),动态选择最合适的分片策略,并在阶段切换时自动完成权重映射、数据重分布和 KV 缓存清理。
你可以把它理解成一个“GPU 调度交通指挥中心”:
- 它不自己开车(不实现底层通信),但清楚每条路(NCCL channel)怎么走、每辆车(tensor)该上哪条道(device);
- 它不管红绿灯(CUDA kernel),但知道什么时候该让 Actor 车队先过、什么时候该给 Rollout 列车让出轨道;
- 它甚至能临时拆掉一座桥(offload 参数到 CPU),等列车通过后再重建(load 回 GPU)。
下面我们就从最常接触的FSDPUlyssesShardingManager和FSDPVLLMShardingManager入手,看看它是怎么指挥的。
2. FSDPUlyssesShardingManager:当 Actor 需要“既快又省”
2.1 它出现的场景:Actor 模型训练阶段
在ActorRolloutRefWorker.__init__()中,你会看到这段关键初始化:
self.ulysses_device_mesh = init_device_mesh('cuda', mesh_shape=(dp, self.ulysses_sequence_parallel_size), mesh_dim_names=['dp', 'sp']) self.ulysses_sharding_manager = FSDPUlyssesShardingManager(self.ulysses_device_mesh)这里没有魔法,只有两个明确的输入:
dp:数据并行组大小(例如 6 张卡 / 1 = 6,若启用了 SP 则为6 // sp_size);sp_size:序列并行尺寸(默认为 1,即不启用)。
初学者注意:
ulysses_sequence_parallel_size=1并不意味着“没用”,而是表示当前不拆分序列维度,所有 GPU 一起处理整条 prompt。这是为了兼容 HuggingFace 模型和简化调试——它保留了扩展能力,但默认走最稳妥路径。
那么FSDPUlyssesShardingManager到底做了什么?一句话回答:
它让 FSDP 的参数分片逻辑,和 Ulysses 序列并行的数据切分逻辑,在同一套 device mesh 上协同工作,避免冗余通信。
我们用一个具体例子说明:
假设你有 6 张 GPU,配置如下:
data.train_batch_size: 60 trainer.n_gpus_per_node: 6 actor_rollout_ref.actor.ppo_mini_batch_size: 60 actor_rollout_ref.rollout.n: 12按常规理解,Actor 一次要处理 60 条 prompt。但注意:ppo_mini_batch_size在__init__中会被重写:
self.config.actor.ppo_mini_batch_size *= self.config.rollout.n # 60 → 720 self.config.actor.ppo_mini_batch_size //= (self.device_mesh.size() // self.ulysses_sequence_parallel_size) # 720 // 6 = 120结果是:每个 GPU 实际处理 120 个样本的 mini-batch。
为什么是 120?因为 rollout 要对每条 prompt 生成 12 条响应,60 × 12 = 720;而这 720 条响应,要均分给 6 张卡,所以每卡 120。
FSDPUlyssesShardingManager的作用,就是确保:
- 这 120 个样本的 forward/backward 计算,能正确路由到对应 GPU 的 FSDP 分片上;
- 如果未来开启
sp_size=2,它还能自动把一条长 prompt 的 attention 计算拆到 2 张卡上,同时保持梯度聚合正确; - 它不改变模型结构,只改 tensor 的 placement 和 all-gather/ reduce-scatter 的触发时机。
关键认知:
FSDPUlyssesShardingManager不是“让模型变小”,而是“让大模型在多卡上跑得更聪明”。它解决的核心问题是:如何在不增加通信开销的前提下,让每个 GPU 都有事干、且干得精准。
3. FSDPVLLMShardingManager:当 Rollout 需要“又快又稳”
3.1 它出现的场景:Rollout 推理阶段
Rollout 是 RL 训练中最耗时的环节之一。如果你用原始 PyTorch 写 rollout,6 张卡可能只能串行跑 6 条 prompt;而 vLLM 通过 PagedAttention + Block Manager,能让单卡同时服务上百请求。
但问题来了:vLLM 默认假设模型已完整加载到单卡,而 verl 的 Actor 模型是用 FSDP 分片加载的——你怎么把一个跨 6 卡的分片模型,“瞬间”变成 vLLM 能识别的单卡模型?
答案就是FSDPVLLMShardingManager。
它出现在_build_rollout()方法中:
rollout_device_mesh = init_device_mesh('cuda', mesh_shape=(dp, infer_tp), mesh_dim_names=['dp', 'infer_tp']) # dp = 3, infer_tp = 2 → DeviceMesh([[0,1], [2,3], [4,5]]) rollout_sharding_manager = FSDPVLLMShardingManager( module=self.actor_module_fsdp, inference_engine=rollout.inference_engine, device_mesh=rollout_device_mesh)这里定义了一个2D device mesh:3 行(DP 组)× 2 列(TP 组)。每一行是一个数据并行组(负责处理一批 prompt),每一列是一个张量并行组(共同完成一次 attention 计算)。
FSDPVLLMShardingManager的核心职责有三:
(1)preprocess_data:把原始 batch “掰开揉碎”,适配 vLLM 输入格式
原始gen_batch是一个含 60 条 prompt 的 DataProto 对象。preprocess_data()会:
- 按
dp=3把 60 条 prompt 均分为 3 组(每组 20 条); - 每组发往一个 DP 组(即
[0,1]、[2,3]、[4,5]); - 在组内,vLLM 的 TP 引擎自动将每条 prompt 的 Q/K/V 投影矩阵拆到两张卡上计算。
(2)postprocess_data:把 vLLM 输出“拼回去”,恢复全局语义
vLLM 返回的是每个 DP 组本地生成的 20×12=240 条响应。postprocess_data()会:
- 收集全部 3 个组的结果(共 720 条);
- 按原始 prompt ID 排序,保证顺序一致;
- 补充 log_prob、token_ids 等元信息,构建成统一的
DataProto。
(3)权重同步:确保 vLLM 使用的模型参数,和 Actor 训练时完全一致
这是最容易被忽略、却最关键的一点。FSDPVLLMShardingManager在__enter__时会:
- 调用
self.module._fsdp_wrapped_module的all_gather,把分片参数临时聚合; - 将聚合后的完整权重,拷贝到 vLLM 的 model runner 中;
- 在
__exit__时,自动清理 vLLM 占用的显存,并可选地将参数 offload 回 CPU。
效果:vLLM 看到的是“完整模型”,Actor 训练看到的是“分片模型”,两者共享同一套参数更新逻辑,零偏差。
关键认知:
FSDPVLLMShardingManager不是“把 FSDP 和 vLLM 硬凑在一起”,而是构建了一座双向桥梁:一边承接 FSDP 的分片状态,一边输出 vLLM 所需的完整模型视图。它让两种范式不再互斥,而是互补。
4. Sharding Manager 如何协作?——看一次完整的 rollout 流程
现在我们把前面两节串起来,用一次真实的generate_sequences()调用,还原 sharding manager 是如何协同工作的。
4.1 步骤分解(附代码位置与行为说明)
| 步骤 | 代码位置 | 发生位置 | Sharding Manager 行为 | 你该关注的重点 |
|---|---|---|---|---|
| ① 初始化 worker | ActorRolloutRefWorker.__init__() | 主进程 | 创建ulysses_device_mesh和FSDPUlyssesShardingManager | device_mesh.size()= 6,sp_size=1→ 当前纯 DP |
| ② 构建 rollout 引擎 | _build_rollout() | 主进程 | 创建rollout_device_mesh(3×2)和FSDPVLLMShardingManager | dp=3,infer_tp=2→ 3 个 vLLM 实例,每实例用 2 卡 |
| ③ 进入 rollout 阶段 | generate_sequences() | 所有 GPU | with self.rollout_sharding_manager:触发上下文管理 | preprocess_data()拆 batch,postprocess_data()拼结果 |
| ④ vLLM 执行推理 | rollout.generate_sequences() | 每个 DP 组内 | vLLM 自动使用 TP 加速,无需 sharding manager 干预 | 你只需确认:每组 20 条 prompt × 12 次 rollout = 240 条输出 |
| ⑤ 汇总结果 | generate_sequences()末尾 | 主进程 | output.to('cpu')+return output | 最终得到 3 × 240 =720 条响应,和ray_trainer.py中打印完全一致 |
4.2 一张图看懂数据流向
原始 batch(60 条 prompt) ↓ FSDPVLLMShardingManager.preprocess_data() ↓ [Group 0: GPUs 0+1] → vLLM 推理 → 20×12 = 240 条响应 [Group 1: GPUs 2+3] → vLLM 推理 → 20×12 = 240 条响应 [Group 2: GPUs 4+5] → vLLM 推理 → 20×12 = 240 条响应 ↓ FSDPVLLMShardingManager.postprocess_data() ↓ 合并排序 → 720 条响应(按 prompt_id 有序) ↓ 返回给 trainer,进入 log_prob 计算、advantage 估计、actor 更新...这个过程之所以高效,是因为:
- 没有重复加载模型:Actor 的 FSDP 分片权重,被复用为 vLLM 的推理权重;
- 没有跨组通信瓶颈:3 个 DP 组完全独立,仅在最后汇总时做一次 gather;
- 没有精度损失:所有计算都在 FP16/BF16 下完成,权重同步是 bit-wise 精确的。
5. 初学者常见疑问直答
Q1:Sharding Manager 和 FSDP 本身有什么区别?
FSDP 是一个静态分片框架:你声明FullyShardedDataParallel(model),它就按固定策略(如HYBRID_SHARD)把参数切开,全程不变。
Sharding Manager 是一个动态调度层:它不替代 FSDP,而是在 FSDP 之上加了一层“策略路由器”。它可以根据当前任务(train / rollout / ref),决定:
- 是否启用 all-gather(rollout 需要,training 可能不需要);
- 是否 offload 到 CPU(ref 阶段常用);
- 是否重映射 device mesh(从 6 卡 DP 切换到 3×2 DP+TP)。
类比:FSDP 是一辆卡车(负责运货),Sharding Manager 是物流调度中心(决定哪批货走哪条路、何时装车、是否中转)。
Q2:我该怎么修改配置,让 sharding manager 发挥更大作用?
记住三个黄金配置项(都在ppo_trainer.yaml中):
| 配置项 | 默认值 | 修改建议 | 效果 |
|---|---|---|---|
actor_rollout_ref.rollout.tensor_model_parallel_size | 2 | 设为 GPU 总数的因数(如 6 卡可设 1/2/3/6) | 控制每个 vLLM 实例用几张卡,值越大单实例吞吐越高,但实例数越少 |
actor_rollout_ref.actor.ulysses_sequence_parallel_size | 1 | 实验性开启(如设为 2),需配合模型支持 | 启用序列并行,降低单卡显存压力,适合超长 context |
actor_rollout_ref.fsdp_config.fsdp_size | -1 | 显式设为2或3,强制 FSDP 分组 | 避免 FSDP 自动分组与 rollout 的 DP 组冲突,提升稳定性 |
提示:所有修改后,务必验证self.device_mesh.size()和rollout_device_mesh的 shape 是否匹配,否则会报mesh not divisible错误。
Q3:如果我只用 1 张卡,sharding manager 还有必要吗?
有必要,而且更简单。
当world_size == 1时:
device_mesh变成DeviceMesh('cuda', [0]);rollout_device_mesh变成DeviceMesh('cuda', [[0]]);FSDPVLLMShardingManager的preprocess_data()变成恒等变换(不拆 batch);postprocess_data()也不做 gather,直接返回。
本质:sharding manager 是面向多卡设计、但对单卡完全透明的。你不用写 if-else,一套代码通吃。
6. 总结:Sharding Manager 的本质是什么?
回到最初的问题:verl sharding manager到底是什么?
它不是一段炫技的算法,而是一个工程级的抽象决策:
- 它承认:RL 训练天然包含多个计算角色,每个角色对硬件的需求不同;
- 它拒绝:用单一并行范式硬套所有阶段,导致性能妥协或显存浪费;
- 它选择:用清晰的 device mesh 描述硬件拓扑,用可插拔的 sharding manager 实现角色适配;
- 它交付:开发者只需关注“我要做什么”(rollout / train / ref),不用操心“GPU 怎么分”。
对初学者来说,理解 sharding manager 的意义,不在于记住FSDPUlyssesShardingManager的 17 个方法,而在于建立一个关键心智模型:
模型不是固定在 GPU 上的“物体”,而是可以按需投影、动态重组的“计算图谱”。
Sharding Manager,就是这张图谱的导航仪。
当你下次看到with self.sharding_manager:,请想到:
这不是一段装饰器语法,而是一次郑重的“计算上下文切换”——
Actor 的权重即将以完整形态服务于 vLLM,
vLLM 的输出即将以结构化形式回归 RL 流水线,
而你写的每一行配置,都在悄悄指挥这场千卡协奏。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。