Ray Placement Group 本节详细说明 slime 在 Ray 上如何进行 GPU 资源编排,包括: 如何创建并重排 Placement Group(PG)以实现稳定的 GPU排序 训练 Actor 与 Rollout Engine 如何在 PG 上调度 两种部署形态:colocate与 dis-agg High Level Concepts :在集群中预留一组 bundle(每个包含 1GPU+1CPU),并将后续 actor 固定绑定到这些 bundle 上,实现可控、稳定的资源放置。 :训练侧“同构 actor 组”的管理器。 按稳定顺序为每个 rank 创建训练 actor,并提供并发的 init/train/eval/save/update/offload 接口。
本节详细说明 slime 在 Ray 上如何进行 GPU 资源编排,包括:
Ray Placement Group:在集群中预留一组 bundle(每个包含 1GPU+1CPU),并将后续 actor 固定绑定到这些 bundle 上,实现可控、稳定的资源放置。RayTrainGroup:训练侧“同构 actor 组”的管理器。
self._actor_handlers:保存所有训练 actor 列表,长度等于 world_size;后续所有并发操作都是对这个列表做映射调用. Created by _allocate_gpus_for_actor。RolloutManager:推理/数据编排器,负责创建 Rollout Engine, Data Buffer、Lock and Router;其细节放在 Part 2。InfoActor:临时探测 actor,用于获知“bundle 实际落点的 (node_ip, gpu_id)”,从而对 bundle 进行稳定排序。入口位于Ray Placement Group 的 create_placement_groups
def create_placement_groups(args): """Create placement groups for actor and rollout engines.""" num_gpus = 0 if args.debug_train_only: num_gpus = args.actor_num_nodes * args.actor_num_gpus_per_node rollout_offset = 0 elif args.debug_rollout_only: num_gpus = args.rollout_num_gpus rollout_offset = 0 elif args.colocate: num_gpus = args.actor_num_nodes * args.actor_num_gpus_per_node rollout_offset = 0 else: num_gpus = args.actor_num_nodes * args.actor_num_gpus_per_node + args.rollout_num_gpus rollout_offset = args.actor_num_nodes * args.actor_num_gpus_per_node print(f"Creating placement group with {num_gpus} GPUs...") pg, actor_pg_reordered_bundle_indices = _create_placement_group(num_gpus) rollout_pg_reordered_bundle_indices = actor_pg_reordered_bundle_indices[rollout_offset:] return { "actor": (pg, actor_pg_reordered_bundle_indices), "rollout": (pg, rollout_pg_reordered_bundle_indices), }
要点:
num_gpus。num_gpus 个 bundle 的 PG,每个 bundle 需要 {"GPU": 1, "CPU": 1}。rollout_offset 将 PG 的索引划分给训练 Actor 与 Rollout 引擎。创建 PG 后,slime 用一个临时 InfoActor 在每个 bundle 上运行一次,探测该 bundle 实际分配到的 (Node IP, GPU ID),随后按“节点 IP 数值化”与 “GPU ID”排序,得到稳定序列。
@ray.remote(num_gpus=1) class InfoActor: def get_ip_and_gpu_id(self): return ray.util.get_node_ip_address(), ray.get_gpu_ids()[0] def sort_key(x): index, node_identifier, gpu_id = x # Sort by node IP number and then by GPU ID try: # try to parse it as an IP address. ip_address = node_identifier node_ip_parts = list(map(int, ip_address.split("."))) except ValueError: # Try to resolve the hostname to an IP address. try: ip_address = socket.gethostbyname(node_identifier) node_ip_parts = list(map(int, ip_address.split("."))) except (socket.gaierror, TypeError): # Instead, we convert each character of the original identifier string # to its ASCII value. This provides a stable and consistent numerical # representation that allows for sorting. node_ip_parts = [ord(c) for c in node_identifier] return (node_ip_parts, gpu_id)
sort_key 的策略要点:
node_identifier 解析成 IPv4 地址,转成 4 个整型并据此排序;gpu_id 升序排序。这样可以获得跨多机的稳定 bundle 顺序,避免由于 Ray 的内部调度差异导致“训练 rank 对应 GPU”与“rollout 分片对 GPU”的映射不稳定。
场景:训练 Actor 与 Rollout 引擎共用同一批 GPU 资源(例如 8 张卡上既跑训练也跑推理)。
num_gpus = actor_num_nodes * actor_num_gpus_per_node;rollout_offset = 0。num_gpus_per_actor = 0.8(一张卡上可并存 1 个 Actor + 若干轻量进程)。num_gpus_per_engine = 0.2(见后文),让推理与训练在同卡共存。适用:小规模单/多机、节省节点数,总吞吐优先,但需注意推理与训练争抢显存与算力。
场景:训练 Actor 与 Rollout 引擎使用各自独立的 GPU 池(例如训练占 8 卡,rollout 占 4 卡)。
num_gpus = actor_total + rollout_total,rollout_offset = actor_total。actor_total 个稳定 bundle 分给训练 Actor,后 rollout_total 个分给 Rollout 引擎。Train(RayTrainGroup):
MASTER_ADDR/PORT。num_gpus_per_actor(默认 0.8)允许与 rollout 同卡共享 (when co-locate)。self._actor_handlers 批量并发 init/train/eval/save/update。Rollout(create_rollout_engines):
num_gpus=0.2,placement_group_capture_child_tasks=True 使子任务也受 PG 约束。dist_init_addr。在多节点/多卡下,create_rollout_engines 会通过 RayActor._get_current_node_ip_and_free_port 在目标节点上寻找一段连续可用端口,并将Node 0的 dist_init_addr 扩散到同一引擎的其他节点,以保证跨机的进程组一致性。
def _get_current_node_ip_and_free_port(start_port=10000, consecutive=1): address = ray._private.services.get_node_ip_address() address = address.strip("[]") port = start_port while not all(is_port_available(port + i) for i in range(consecutive)): port += 1 return address, port
选择 colocate,当:
选择 dis-agg,当:
绑定 InfoActor 的原因:
InfoActor 以 num_gpus=1 运行,ray.get_gpu_ids() 才会返回明确的本地 GPU ID。placement_group_bundle_index 绑定到这些 bundle,先探明映射便于稳定放置与核对。需要 sort_key 的原因:
gpu_id 升序。不能直接用“分配顺序”的原因: