Files
ray-project--ray/python/ray/train/v2/tests/test_accelerator_utils.py
T
2026-07-13 13:17:40 +08:00

144 lines
4.1 KiB
Python

import collections
import os
import pytest
import ray
from ray.cluster_utils import Cluster
from ray.train import BackendConfig
from ray.train.backend import Backend
from ray.train.v2._internal.callbacks.accelerators import (
AcceleratorSetupCallback,
_get_visible_accelerator_ids_per_worker,
)
from ray.train.v2._internal.execution.worker_group import ActorMetadata, WorkerGroup
from ray.train.v2._internal.execution.worker_group.worker_group import (
WorkerGroupContext,
)
from ray.train.v2._internal.util import ObjectRefWrapper
from ray.train.v2.api.config import ScalingConfig
from ray.train.v2.tests.util import create_dummy_run_context
@pytest.fixture
def mock_gpu_cluster():
"""Yields a GPU cluster with 3 nodes (4 GPU, 1 GPU, 1 GPU)."""
cluster = Cluster()
cluster.add_node(num_gpus=4)
cluster.add_node(num_gpus=1)
cluster.add_node(num_gpus=1)
cluster.wait_for_nodes()
cluster.connect()
yield cluster
ray.shutdown()
cluster.shutdown()
@pytest.mark.parametrize(
"node_ids, accelerator_ids_per_worker, expected",
[
(["0"], [[0]], ["0"]),
(
["0", "0", "1"],
[[0, 1], [2, 3], [0, 1]],
["0,1,2,3", "0,1,2,3", "0,1"],
),
(
["0", "0", "1", "1", "1", "1"],
[["1"], ["3"], ["3"], ["0"], ["1"], ["2"]],
["1,3", "1,3", "0,1,2,3", "0,1,2,3", "0,1,2,3", "0,1,2,3"],
),
],
)
def test_get_visible_accelerator_ids_per_worker(
node_ids, accelerator_ids_per_worker, expected
):
worker_metadatas = [
ActorMetadata(
hostname=node_id,
node_id=node_id,
node_ip=node_id,
pid=0,
accelerator_ids={"GPU": accelerator_ids},
)
for node_id, accelerator_ids in zip(node_ids, accelerator_ids_per_worker)
]
assert (
_get_visible_accelerator_ids_per_worker(
worker_metadatas=worker_metadatas, accelerator_name="GPU"
)
== expected
)
def test_missing_accelerator():
"""Trying to share accelerator ids on a heterogeneous worker group
(where some workers do not have access to certain accelerators)
should raise an error."""
with pytest.raises(ValueError):
_get_visible_accelerator_ids_per_worker(
worker_metadatas=[
ActorMetadata(
hostname="0",
node_id="0",
node_ip="0",
pid=0,
accelerator_ids={"GPU": [0]},
),
ActorMetadata(
hostname="0",
node_id="0",
node_ip="0",
pid=0,
accelerator_ids={},
),
],
accelerator_name="GPU",
)
def test_accelerator_setup_callback(mock_gpu_cluster, mock_runtime_context):
"""The accelerator setup callback should set the CUDA_VISIBLE_DEVICES
on each worker properly."""
class DummyBackendConfig(BackendConfig):
def backend_cls(self):
return DummyBackend
class DummyBackend(Backend):
share_cuda_visible_devices = True
scaling_config = ScalingConfig(num_workers=6, use_gpu=True)
setup_callback = AcceleratorSetupCallback(
backend_config=DummyBackendConfig(),
scaling_config=scaling_config,
)
worker_group_context = WorkerGroupContext(
run_attempt_id="attempt_1",
train_fn_ref=ObjectRefWrapper(lambda: None),
num_workers=scaling_config.num_workers,
resources_per_worker=scaling_config._resources_per_worker_not_none,
)
worker_group = WorkerGroup(
train_run_context=create_dummy_run_context(),
worker_group_context=worker_group_context,
)
worker_group._start()
setup_callback.before_init_train_context(worker_group.get_workers())
visible_devices_per_worker = worker_group.execute(
lambda: os.environ["CUDA_VISIBLE_DEVICES"]
)
assert collections.Counter(visible_devices_per_worker) == {"0,1,2,3": 4, "0": 2}
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", "-x", __file__]))