161 lines
5.5 KiB
Python
161 lines
5.5 KiB
Python
import logging
|
|
import os
|
|
from collections import defaultdict
|
|
from typing import TYPE_CHECKING, Any, Dict, List
|
|
|
|
import ray
|
|
import ray._private.ray_constants as ray_constants
|
|
from ray._private.accelerators.nvidia_gpu import CUDA_VISIBLE_DEVICES_ENV_VAR
|
|
from ray._private.ray_constants import env_bool
|
|
from ray.train import BackendConfig
|
|
from ray.train.constants import ENABLE_SHARE_CUDA_VISIBLE_DEVICES_ENV
|
|
from ray.train.v2._internal.execution.callback import WorkerGroupCallback
|
|
from ray.train.v2._internal.execution.worker_group import ActorMetadata
|
|
from ray.train.v2.api.config import ScalingConfig
|
|
|
|
if TYPE_CHECKING:
|
|
from ray.train.v2._internal.execution.worker_group.worker import Worker
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AcceleratorSetupCallback(WorkerGroupCallback):
|
|
"""Perform accelerator setup for workers.
|
|
|
|
For example, this callback can be used to share CUDA_VISIBLE_DEVICES
|
|
among workers on the same node.
|
|
"""
|
|
|
|
def __init__(self, backend_config: BackendConfig, scaling_config: ScalingConfig):
|
|
self._backend = backend_config.backend_cls()
|
|
self._scaling_config = scaling_config
|
|
|
|
def before_init_train_context(
|
|
self, workers: List["Worker"]
|
|
) -> Dict[str, List[Any]]:
|
|
self._maybe_share_cuda_visible_devices(workers)
|
|
# TODO: Add support for sharing other accelerator resources.
|
|
|
|
return {}
|
|
|
|
def _maybe_share_cuda_visible_devices(self, workers: List["Worker"]):
|
|
"""Set CUDA visible devices environment variables on workers."""
|
|
share_cuda_visible_devices_enabled = env_bool(
|
|
ENABLE_SHARE_CUDA_VISIBLE_DEVICES_ENV,
|
|
self._backend.share_cuda_visible_devices,
|
|
)
|
|
|
|
if (
|
|
self._scaling_config._resources_per_worker_not_none.get("GPU", 0) > 0
|
|
and share_cuda_visible_devices_enabled
|
|
):
|
|
_share_cuda_visible_devices(workers)
|
|
|
|
|
|
def _share_cuda_visible_devices(workers: List["Worker"]):
|
|
"""Sets CUDA_VISIBLE_DEVICES on all workers.
|
|
For each worker, CUDA_VISIBLE_DEVICES will be set to the GPU IDs
|
|
visible to all workers on that worker's node.
|
|
This allows GPU workers on the same node to communicate with one
|
|
another.
|
|
|
|
Example:
|
|
Setup:
|
|
- Node1:
|
|
- Worker1: {0, 1}
|
|
- Worker2: {2, 3}
|
|
- Node2:
|
|
- Worker3: {0, 1}
|
|
CUDA_VISIBLE_DEVICES:
|
|
- Worker1: "0,1,2,3"
|
|
- Worker2: "0,1,2,3"
|
|
- Worker3: "0,1"
|
|
|
|
Args:
|
|
workers: List of worker objects.
|
|
"""
|
|
_share_accelerator_ids(workers, ray_constants.GPU, CUDA_VISIBLE_DEVICES_ENV_VAR)
|
|
|
|
|
|
def _share_accelerator_ids(
|
|
workers: List["Worker"], accelerator_name: str, env_var: str
|
|
):
|
|
"""Sets the given env_var on all workers.
|
|
For each worker, the cores/devices are visible to all the
|
|
workers on that worker's node. This allows workers on the
|
|
same node to communicate with one another.
|
|
|
|
Example:
|
|
Setup:
|
|
- Node1:
|
|
- Worker1: {0, 1}
|
|
- Worker2: {2, 3}
|
|
- Node2:
|
|
- Worker3: {0, 1}
|
|
NEURON_RT_VISIBLE_CORES/TPU_VISIBLE_CHIPS/...:
|
|
- Worker1: "0,1,2,3"
|
|
- Worker2: "0,1,2,3"
|
|
- Worker3: "0,1"
|
|
|
|
Args:
|
|
workers: List of worker objects.
|
|
accelerator_name: The name of the accelerator.
|
|
env_var: The name of the environment variable to set.
|
|
"""
|
|
worker_metadatas = [worker.metadata for worker in workers]
|
|
visible_accelerator_ids_per_worker = _get_visible_accelerator_ids_per_worker(
|
|
worker_metadatas=worker_metadatas, accelerator_name=accelerator_name
|
|
)
|
|
|
|
def set_accelerator_ids(accelerator_ids):
|
|
os.environ[env_var] = accelerator_ids
|
|
|
|
futures = []
|
|
for rank, visible_accelerator_ids in enumerate(visible_accelerator_ids_per_worker):
|
|
futures.append(
|
|
workers[rank].execute_async(
|
|
set_accelerator_ids, accelerator_ids=visible_accelerator_ids
|
|
)
|
|
)
|
|
ray.get(futures)
|
|
|
|
|
|
def _get_visible_accelerator_ids_per_worker(
|
|
worker_metadatas: List[ActorMetadata], accelerator_name: str
|
|
) -> List[str]:
|
|
"""Returns a list of comma-separated accelerator IDs visible to each worker.
|
|
|
|
All workers on a node should have the same set of visible accelerators,
|
|
which is the union of accelerator ids of the workers.
|
|
|
|
Args:
|
|
worker_metadatas: The actor metadata for each worker.
|
|
accelerator_name: The name of the accelerator resource to inspect.
|
|
|
|
Returns:
|
|
A list of comma-separated accelerator ID strings. This list is the
|
|
same length as the number of workers.
|
|
"""
|
|
for metadata in worker_metadatas:
|
|
if accelerator_name not in metadata.accelerator_ids:
|
|
raise ValueError(
|
|
f"Accelerator '{accelerator_name}' is not available on all workers. "
|
|
f"Got these available accelerators instead: {metadata.accelerator_ids}"
|
|
)
|
|
|
|
node_id_to_accelerator_ids = defaultdict(set)
|
|
|
|
for metadata in worker_metadatas:
|
|
node_id_to_accelerator_ids[metadata.node_id].update(
|
|
metadata.accelerator_ids[accelerator_name]
|
|
)
|
|
|
|
visible_accelerator_ids_per_worker = []
|
|
for worker_id in range(len(worker_metadatas)):
|
|
node_id = worker_metadatas[worker_id].node_id
|
|
accelerator_ids = sorted(node_id_to_accelerator_ids[node_id])
|
|
all_resource_ids = ",".join([str(id) for id in accelerator_ids])
|
|
visible_accelerator_ids_per_worker.append(all_resource_ids)
|
|
|
|
return visible_accelerator_ids_per_worker
|