|
21 | 21 | from ray.util.placement_group import ( |
22 | 22 | PlacementGroup, |
23 | 23 | placement_group, |
| 24 | + placement_group_table, |
24 | 25 | remove_placement_group, |
25 | 26 | ) |
26 | 27 | from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy |
@@ -170,6 +171,14 @@ def init_ray(log_dir: Optional[str] = None) -> None: |
170 | 171 | ) |
171 | 172 |
|
172 | 173 |
|
| 174 | +@ray.remote(num_gpus=1) |
| 175 | +class GetGPUIDActor: # pragma: no cover |
| 176 | + """Util actor class to return GPU id of the current worker.""" |
| 177 | + |
| 178 | + def get_gpu_id(self): |
| 179 | + return ray.get_gpu_ids()[0] |
| 180 | + |
| 181 | + |
173 | 182 | class ResourceInsufficientError(Exception): |
174 | 183 | """Exception raised when the cluster does not have enough resources to satisfy the requested configuration.""" |
175 | 184 |
|
@@ -210,6 +219,7 @@ def __init__( |
210 | 219 | self._bundle_ct_per_node_list = bundle_ct_per_node_list |
211 | 220 | self._world_size = sum(self._bundle_ct_per_node_list) |
212 | 221 | self._node_placement_groups: Optional[list[PlacementGroup]] = None |
| 222 | + self._sorted_bundle_indices: Optional[list[int]] = None |
213 | 223 |
|
214 | 224 | self.num_gpus_per_node = num_gpus_per_node |
215 | 225 | self.use_gpus = use_gpus |
@@ -251,6 +261,8 @@ def _init_placement_groups( |
251 | 261 | self._node_placement_groups = self._create_placement_groups_internal( |
252 | 262 | strategy, use_unified_pg |
253 | 263 | ) |
| 264 | + if use_unified_pg and self.use_gpus: |
| 265 | + self._sorted_bundle_indices = self._get_sorted_bundle_indices() |
254 | 266 | return self._node_placement_groups |
255 | 267 | except ResourceInsufficientError as e: |
256 | 268 | print(e) |
@@ -402,8 +414,66 @@ def get_master_address_and_port(self) -> tuple[str, int]: |
402 | 414 | Returns: |
403 | 415 | Tuple of (address, port) |
404 | 416 | """ |
| 417 | + # Get placement groups if not already created |
| 418 | + if not self._node_placement_groups: |
| 419 | + self.get_placement_groups() |
| 420 | + |
| 421 | + # If sorted bundle indices are available, get the address and port for the first bundle index |
| 422 | + if self._sorted_bundle_indices is not None: |
| 423 | + return self.get_available_address_and_port( |
| 424 | + pg_idx=0, bundle_idx=self._sorted_bundle_indices[0] |
| 425 | + ) |
| 426 | + |
| 427 | + # Otherwise, get the address and port for bundle index 0 |
405 | 428 | return self.get_available_address_and_port(pg_idx=0, bundle_idx=0) |
406 | 429 |
|
| 430 | + def _get_sorted_bundle_indices(self) -> Optional[list[int]]: |
| 431 | + """Gets the sorted bundle indices for the placement groups.""" |
| 432 | + if self._node_placement_groups is None: |
| 433 | + raise ValueError( |
| 434 | + "Placement groups must be initialized before calling _get_sorted_bundle_indices" |
| 435 | + ) |
| 436 | + |
| 437 | + if not self.use_gpus: |
| 438 | + return None |
| 439 | + |
| 440 | + if len(self._node_placement_groups) != 1: |
| 441 | + return None |
| 442 | + |
| 443 | + pg = self._node_placement_groups[0] |
| 444 | + pg_data = placement_group_table(pg) |
| 445 | + num_bundles = len(pg_data["bundles"]) |
| 446 | + bundle_to_node_ids = pg_data["bundles_to_node_id"] |
| 447 | + |
| 448 | + # use info actor to get the GPU id |
| 449 | + info_actors = [] |
| 450 | + for i in range(num_bundles): |
| 451 | + info_actors.append( |
| 452 | + GetGPUIDActor.options( |
| 453 | + num_cpus=0.01, # set both num_cpus and num_gpus to be small values to enable assignment in colocated case |
| 454 | + num_gpus=0.01, |
| 455 | + resources=None, |
| 456 | + scheduling_strategy=PlacementGroupSchedulingStrategy( |
| 457 | + placement_group=pg, |
| 458 | + placement_group_bundle_index=i, |
| 459 | + ), |
| 460 | + ).remote() |
| 461 | + ) |
| 462 | + |
| 463 | + gpu_ids = ray.get([actor.get_gpu_id.remote() for actor in info_actors]) |
| 464 | + for actor in info_actors: |
| 465 | + ray.kill(actor) |
| 466 | + |
| 467 | + # original index, node_id, gpu_id |
| 468 | + bundle_infos = [ |
| 469 | + (i, bundle_to_node_ids[i], gpu_ids[i]) for i in range(num_bundles) |
| 470 | + ] |
| 471 | + pg_reordered_bundle_indices = [ |
| 472 | + bundle_info[0] |
| 473 | + for bundle_info in sorted(bundle_infos, key=lambda x: (x[1], x[2])) |
| 474 | + ] # sort by node_id, then gpu_id |
| 475 | + return pg_reordered_bundle_indices |
| 476 | + |
407 | 477 | def shutdown(self) -> bool: |
408 | 478 | """Cleans up and releases all resources associated with this virtual cluster. |
409 | 479 |
|
|
0 commit comments