Define a resource pool specification. Resource pool will be initialized first.
| 166 | |
| 167 | @dataclass |
| 168 | class ResourcePoolManager: |
| 169 | """ |
| 170 | Define a resource pool specification. Resource pool will be initialized first. |
| 171 | """ |
| 172 | |
| 173 | resource_pool_spec: dict[str, list[int]] |
| 174 | mapping: dict[int, str] |
| 175 | resource_pool_dict: dict[str, RayResourcePool] = field(default_factory=dict) |
| 176 | |
| 177 | def create_resource_pool(self): |
| 178 | """Create Ray resource pools for distributed training. |
| 179 | |
| 180 | Initializes resource pools based on the resource pool specification, |
| 181 | with each pool managing GPU resources across multiple nodes. |
| 182 | For FSDP backend, uses max_colocate_count=1 to merge WorkerGroups. |
| 183 | For Megatron backend, uses max_colocate_count>1 for different models. |
| 184 | """ |
| 185 | for resource_pool_name, process_on_nodes in self.resource_pool_spec.items(): |
| 186 | # max_colocate_count means the number of WorkerGroups (i.e. processes) in each RayResourcePool |
| 187 | # For FSDP backend, using max_colocate_count=3: actor_critic_ref, rollout, reward model (optional) |
| 188 | # For Megatron backend, we recommend using max_colocate_count>1 |
| 189 | # that can utilize different WorkerGroup for differnt models |
| 190 | resource_pool = RayResourcePool( |
| 191 | process_on_nodes=process_on_nodes, use_gpu=True, max_colocate_count=3, name_prefix=resource_pool_name |
| 192 | ) |
| 193 | self.resource_pool_dict[resource_pool_name] = resource_pool |
| 194 | |
| 195 | self._check_resource_available() |
| 196 | |
| 197 | def get_resource_pool(self, role) -> RayResourcePool: |
| 198 | """Get the resource pool of the worker_cls""" |
| 199 | return self.resource_pool_dict[self.mapping[role]] |
| 200 | |
| 201 | def get_n_gpus(self) -> int: |
| 202 | """Get the number of gpus in this cluster.""" |
| 203 | return sum([n_gpus for process_on_nodes in self.resource_pool_spec.values() for n_gpus in process_on_nodes]) |
| 204 | |
| 205 | def _check_resource_available(self): |
| 206 | """Check if the resource pool can be satisfied in this ray cluster.""" |
| 207 | node_available_resources = ray._private.state.available_resources_per_node() |
| 208 | node_available_gpus = { |
| 209 | node: node_info.get("GPU", 0) if "GPU" in node_info else node_info.get("NPU", 0) |
| 210 | for node, node_info in node_available_resources.items() |
| 211 | } |
| 212 | |
| 213 | # check total required gpus can be satisfied |
| 214 | total_available_gpus = sum(node_available_gpus.values()) |
| 215 | total_required_gpus = sum( |
| 216 | [n_gpus for process_on_nodes in self.resource_pool_spec.values() for n_gpus in process_on_nodes] |
| 217 | ) |
| 218 | if total_available_gpus < total_required_gpus: |
| 219 | raise ValueError( |
| 220 | f"Total available GPUs {total_available_gpus} is less than total desired GPUs {total_required_gpus}" |
| 221 | ) |
| 222 | |
| 223 | |
| 224 | def extract_pg_from_exist( |
no outgoing calls