chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:17:40 +08:00
commit f1825c8ceb
10096 changed files with 2364182 additions and 0 deletions
+660
View File
@@ -0,0 +1,660 @@
import math
import os
import sys
import tempfile
import time
from typing import Set
from unittest.mock import patch
import pytest
import ray
from ray import train
from ray._private.accelerators.neuron import NEURON_RT_VISIBLE_CORES_ENV_VAR
from ray.air._internal.util import StartTraceback
# Trigger pytest hook to automatically zip test cluster logs to archive dir on failure
from ray.tests.conftest import pytest_runtest_makereport # noqa
from ray.train import DataConfig
from ray.train._internal.backend_executor import (
BackendExecutor,
InactiveWorkerGroupError,
TrainBackendError,
TrainingWorkerError,
)
from ray.train._internal.storage import StorageContext
from ray.train._internal.worker_group import WorkerGroup, WorkerMetadata
from ray.train.backend import Backend, BackendConfig
from ray.train.constants import (
ENABLE_SHARE_CUDA_VISIBLE_DEVICES_ENV,
ENABLE_SHARE_NEURON_CORES_ACCELERATOR_ENV,
JAX_DISTRIBUTED_SHUTDOWN_TIMEOUT_S,
TORCH_PROCESS_GROUP_SHUTDOWN_TIMEOUT_S,
TRAIN_ENABLE_WORKER_SPREAD_ENV,
)
from ray.train.torch import TorchConfig
from ray.train.v2.jax.config import JaxConfig
from ray.util.placement_group import get_current_placement_group
from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
from ray.util.state import list_actors
@pytest.fixture
def ray_start_2_cpus():
address_info = ray.init(num_cpus=2)
yield address_info
ray.shutdown()
def _start_training(backend_executor: BackendExecutor, fn):
storage = StorageContext(
storage_path=tempfile.mkdtemp(),
experiment_dir_name="exp_name",
trial_dir_name="trial_name",
)
backend_executor.start_training(
train_func=fn,
datasets={},
metadata={},
data_config=DataConfig(),
storage=storage,
)
def gen_execute_special(special_f):
def execute_async_special(self, f):
"""Runs f on worker 0, special_f on other workers."""
futures = [
self.workers[0]
.actor._RayTrainWorker__execute.options(name=f.__name__)
.remote(f)
]
for worker in self.workers[1:]:
futures.append(
worker.actor._RayTrainWorker__execute.options(
name=special_f.__name__
).remote(special_f)
)
return futures
return execute_async_special
class TestConfig(BackendConfig):
@property
def backend_cls(self):
return TestBackend
class TestBackend(Backend):
def on_start(self, worker_group: WorkerGroup, backend_config: TestConfig):
pass
def on_shutdown(self, worker_group: WorkerGroup, backend_config: TestConfig):
pass
original_add_workers = WorkerGroup.add_workers
def mock_add_workers(self, num_workers):
original_add_workers(self, num_workers)
for i, worker in enumerate(self.workers):
metadata = WorkerMetadata(
node_id=str(i % 2),
node_ip=str(i % 2),
hostname=0,
resource_ids={"GPU": ["0"]},
pid=0,
)
worker.metadata = metadata
def mock_add_workers_to_nodes_with_same_ip(self, num_workers):
original_add_workers(self, num_workers)
for i, worker in enumerate(self.workers):
metadata = WorkerMetadata(
node_id=str(i % 2),
node_ip=0,
hostname=0,
resource_ids={"GPU": ["0"]},
pid=0,
)
worker.metadata = metadata
def test_start(ray_start_2_cpus):
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
with pytest.raises(InactiveWorkerGroupError):
_start_training(e, lambda: 1)
e.start()
assert len(e.worker_group) == 2
def test_initialization_hook(ray_start_2_cpus):
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
def init_hook():
import os
os.environ["TEST"] = "1"
e.start(initialization_hook=init_hook)
def check():
import os
return os.getenv("TEST", "0")
_start_training(e, check)
assert e.finish_training() == ["1", "1"]
def test_shutdown(ray_start_2_cpus):
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
assert len(e.worker_group) == 2
e.shutdown()
with pytest.raises(InactiveWorkerGroupError):
_start_training(e, lambda: 1)
def test_train(ray_start_2_cpus):
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
_start_training(e, lambda: 1)
assert e.finish_training() == [1, 1]
def test_local_ranks(ray_start_2_cpus):
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
def train_func():
return train.get_context().get_local_rank()
_start_training(e, train_func)
assert set(e.finish_training()) == {0, 1}
def test_local_ranks_with_same_ip_nodes(ray_2_node_2_cpu):
config = TestConfig()
e = BackendExecutor(config, num_workers=4)
e.start()
def train_func():
return train.get_context().get_local_rank()
_start_training(e, train_func)
assert list(e.finish_training()) == [0, 1, 0, 1]
def test_local_world_size(ray_2_node_2_cpu):
config = TestConfig()
with patch.object(WorkerGroup, "add_workers", mock_add_workers):
e = BackendExecutor(config, num_workers=3)
e.start()
def train_func():
return train.get_context().get_local_world_size()
_start_training(e, train_func)
assert list(e.finish_training()) == [2, 2, 1]
def test_local_world_size_with_same_ip_nodes(ray_2_node_2_cpu):
config = TestConfig()
with patch.object(
WorkerGroup, "add_workers", mock_add_workers_to_nodes_with_same_ip
):
e = BackendExecutor(config, num_workers=3)
e.start()
def train_func():
return train.get_context().get_local_world_size()
_start_training(e, train_func)
assert list(e.finish_training()) == [2, 2, 1]
def test_node_ranks(ray_2_node_2_cpu):
config = TestConfig()
with patch.object(WorkerGroup, "add_workers", mock_add_workers):
e = BackendExecutor(config, num_workers=3)
e.start()
def train_func():
return train.get_context().get_node_rank()
_start_training(e, train_func)
assert list(e.finish_training()) == [0, 0, 1]
def test_node_ranks_with_same_ip_nodes(ray_2_node_2_cpu):
config = TestConfig()
with patch.object(
WorkerGroup, "add_workers", mock_add_workers_to_nodes_with_same_ip
):
e = BackendExecutor(config, num_workers=3)
e.start()
def train_func():
return train.get_context().get_node_rank()
_start_training(e, train_func)
assert list(e.finish_training()) == [0, 0, 1]
def test_train_failure(ray_start_2_cpus):
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
with pytest.raises(StartTraceback) as exc:
e.get_next_results()
assert isinstance(exc.value.__cause__, TrainBackendError)
with pytest.raises(StartTraceback) as exc:
e.pause_reporting()
assert isinstance(exc.value.__cause__, TrainBackendError)
with pytest.raises(StartTraceback) as exc:
e.finish_training()
assert isinstance(exc.value.__cause__, TrainBackendError)
_start_training(e, lambda: 1)
with pytest.raises(StartTraceback) as exc:
_start_training(e, lambda: 2)
assert isinstance(exc.value.__cause__, TrainBackendError)
assert e.finish_training() == [1, 1]
def test_single_worker_user_failure(ray_start_2_cpus):
"""Tests if training fails immediately if one worker raises an Exception
while executing the user training code."""
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
def single_worker_user_failure():
if train.get_context().get_world_rank() == 0:
raise RuntimeError
else:
time.sleep(1000000)
_start_training(e, single_worker_user_failure)
with pytest.raises(StartTraceback) as exc:
e.get_next_results()
assert isinstance(exc.value.__cause__, RuntimeError)
def test_single_worker_actor_failure(ray_start_2_cpus):
"""Tests is training fails immediately if one worker actor dies."""
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
def single_worker_actor_failure():
if train.get_context().get_world_rank() == 0:
# Simulate actor failure
os._exit(1)
else:
time.sleep(1000)
_start_training(e, single_worker_actor_failure)
with pytest.raises(TrainingWorkerError):
e.get_next_results()
@pytest.mark.skipif(
sys.version_info >= (3, 12), reason="tensorflow is not supported in python 3.12+"
)
def test_tensorflow_start(ray_start_2_cpus):
from ray.train.tensorflow import TensorflowConfig
num_workers = 2
tensorflow_config = TensorflowConfig()
e = BackendExecutor(tensorflow_config, num_workers=num_workers)
e.start()
def get_tf_config():
import json
import os
return json.loads(os.environ["TF_CONFIG"])
_start_training(e, get_tf_config)
results = e.finish_training()
assert len(results) == num_workers
workers = [result["cluster"]["worker"] for result in results]
assert all(worker == workers[0] for worker in workers)
indexes = [result["task"]["index"] for result in results]
assert len(set(indexes)) == num_workers
@pytest.mark.parametrize("init_method", ["env", "tcp"])
def test_torch_start_shutdown(ray_start_2_cpus, init_method):
torch_config = TorchConfig(backend="gloo", init_method=init_method)
e = BackendExecutor(torch_config, num_workers=2)
e.start()
def check_process_group():
import torch
return (
torch.distributed.is_initialized()
and torch.distributed.get_world_size() == 2
)
_start_training(e, check_process_group)
assert all(e.finish_training())
e._backend.on_shutdown(e.worker_group, e._backend_config)
_start_training(e, check_process_group)
assert not any(e.finish_training())
@pytest.mark.parametrize(
"init_method, timeout_s", [("env", 5), ("tcp", 5), ("env", 0), ("tcp", 0)]
)
def test_torch_process_group_shutdown_timeout(
ray_start_2_cpus, monkeypatch, init_method, timeout_s
):
monkeypatch.setenv(TORCH_PROCESS_GROUP_SHUTDOWN_TIMEOUT_S, timeout_s)
torch_config = TorchConfig(backend="gloo", init_method=init_method)
e = BackendExecutor(torch_config, num_workers=2)
e.start()
_start_training(e, lambda: 1)
assert e.finish_training() == [1, 1]
# Verify that we do not raise an exception even if we time out
e._backend.on_shutdown(e.worker_group, e._backend_config)
@pytest.mark.parametrize(
"worker_results",
[
(1, [[0]]),
(2, [[0, 1]] * 2),
(3, [[0]] + [[0, 1]] * 2),
(4, [[0, 1]] * 4),
],
)
def test_cuda_visible_devices(ray_2_node_2_gpu, worker_results):
config = TestConfig()
def get_resources():
cuda_visible_devices = os.environ["CUDA_VISIBLE_DEVICES"]
# Sort the cuda visible devices to have exact match with expected result.
sorted_devices = [
int(device) for device in sorted(cuda_visible_devices.split(","))
]
return sorted_devices
num_workers, expected_results = worker_results
os.environ[ENABLE_SHARE_CUDA_VISIBLE_DEVICES_ENV] = "1"
e = BackendExecutor(
config, num_workers=num_workers, resources_per_worker={"GPU": 1}
)
e.start()
_start_training(e, get_resources)
results = e.finish_training()
results.sort()
assert results == expected_results
@pytest.mark.parametrize(
"worker_results",
[
(1, [[0]]),
(
2,
[[0]] * 2,
),
(3, [[0, 1]] * 3),
(4, [[0, 1]] * 4),
(5, [[0]] + [[0, 1]] * 4),
(6, [[0]] * 2 + [[0, 1]] * 4),
(7, [[0, 1]] * 7),
(8, [[0, 1]] * 8),
],
)
def test_cuda_visible_devices_fractional(ray_2_node_2_gpu, worker_results):
config = TestConfig()
if worker_results[0] != len(worker_results[1]):
raise ValueError(
"Invalid test parameter. Length of expected result should "
"match number of workers."
)
def get_resources():
cuda_visible_devices = os.environ["CUDA_VISIBLE_DEVICES"]
# Sort the cuda visible devices to have exact match with expected result.
sorted_devices = [
int(device) for device in sorted(cuda_visible_devices.split(","))
]
return sorted_devices
num_workers, expected_results = worker_results
os.environ[ENABLE_SHARE_CUDA_VISIBLE_DEVICES_ENV] = "1"
e = BackendExecutor(
config, num_workers=num_workers, resources_per_worker={"GPU": 0.5}
)
e.start()
_start_training(e, get_resources)
results = e.finish_training()
results.sort()
assert results == expected_results
@pytest.mark.parametrize(
"worker_results",
[
(1, [[0, 1]]),
(2, [[0, 1, 2, 3]] * 2),
(3, [[0, 1]] + [[0, 1, 2, 3]] * 2),
(4, [[0, 1, 2, 3]] * 4),
],
)
def test_cuda_visible_devices_multiple(ray_2_node_4_gpu, worker_results):
config = TestConfig()
def get_resources():
cuda_visible_devices = os.environ["CUDA_VISIBLE_DEVICES"]
# Sort the cuda visible devices to have exact match with expected result.
sorted_devices = [
int(device) for device in sorted(cuda_visible_devices.split(","))
]
return sorted_devices
if worker_results[0] != len(worker_results[1]):
raise ValueError(
"Invalid test parameter. Length of expected result should "
"match number of workers."
)
num_workers, expected_results = worker_results
os.environ[ENABLE_SHARE_CUDA_VISIBLE_DEVICES_ENV] = "1"
e = BackendExecutor(
config, num_workers=num_workers, resources_per_worker={"GPU": 2}
)
e.start()
_start_training(e, get_resources)
results = e.finish_training()
results.sort()
assert results == expected_results
@pytest.mark.parametrize(
"worker_results",
[
(1, [[0]]),
(2, [[0, 1]] * 2),
(3, [[0]] + [[0, 1]] * 2),
(4, [[0, 1]] * 4),
],
)
def test_neuron_core_accelerator_ids(ray_2_node_2_neuron_cores, worker_results):
config = TestConfig()
def get_resources():
neuron_resource_ids = os.environ[NEURON_RT_VISIBLE_CORES_ENV_VAR]
# Sort the runtime ids to have exact match with expected result.
sorted_devices = [
int(device) for device in sorted(neuron_resource_ids.split(","))
]
return sorted_devices
num_workers, expected_results = worker_results
# sharing enabled by default
os.environ.pop(ENABLE_SHARE_NEURON_CORES_ACCELERATOR_ENV, None)
e = BackendExecutor(
config,
num_workers=num_workers,
resources_per_worker={"neuron_cores": 1},
)
e.start()
_start_training(e, get_resources)
results = e.finish_training()
results.sort()
assert results == expected_results
@pytest.mark.parametrize(
"worker_results",
[
(1, [[0]]),
(2, [[0]] + [[1]]),
(3, [[0]] * 2 + [[1]]),
(4, [[0]] * 2 + [[1]] * 2),
],
)
def test_neuron_core_accelerator_ids_sharing_disabled(
ray_2_node_2_neuron_cores, worker_results
):
config = TestConfig()
def get_resources():
neuron_resource_ids = os.environ[NEURON_RT_VISIBLE_CORES_ENV_VAR]
# Sort the runtime ids to have exact match with expected result.
sorted_devices = [
int(device) for device in sorted(neuron_resource_ids.split(","))
]
return sorted_devices
num_workers, expected_results = worker_results
os.environ[ENABLE_SHARE_NEURON_CORES_ACCELERATOR_ENV] = "0"
e = BackendExecutor(
config,
num_workers=num_workers,
resources_per_worker={"neuron_cores": 1},
)
e.start()
_start_training(e, get_resources)
results = e.finish_training()
results.sort()
assert results == expected_results
def get_node_id_set() -> Set[str]:
return {a.node_id for a in list_actors()}
@pytest.mark.parametrize("num_workers", [3, 4, 5])
def test_placement_group_pack(ray_4_node_4_cpu, num_workers):
"""Tests that workers are packed on nodes."""
config = TestConfig()
e = BackendExecutor(config, num_workers=num_workers)
e.start()
node_id_set = get_node_id_set()
assert len(node_id_set) == math.ceil(num_workers / 4)
@pytest.mark.parametrize("num_workers", [3, 4, 5])
def test_placement_group_spread(ray_4_node_4_cpu, num_workers):
"""Tests that workers are spread across nodes."""
os.environ[TRAIN_ENABLE_WORKER_SPREAD_ENV] = "1"
config = TestConfig()
e = BackendExecutor(config, num_workers=num_workers)
e.start()
node_id_set = get_node_id_set()
assert len(node_id_set) == min(num_workers, 4)
@pytest.mark.parametrize("placement_group_capture_child_tasks", [True, False])
def test_placement_group_parent(ray_4_node_4_cpu, placement_group_capture_child_tasks):
"""Tests that parent placement group will be used."""
num_workers = 2
bundle = {"CPU": 1}
bundles = [bundle.copy() for _ in range(num_workers + 1)]
placement_group = ray.util.placement_group(bundles)
def train_func():
return get_current_placement_group().id
@ray.remote
def test():
config = TestConfig()
e = BackendExecutor(config, num_workers=2)
e.start()
_start_training(e, train_func)
return e.finish_training()
results_future = test.options(
scheduling_strategy=PlacementGroupSchedulingStrategy(
placement_group=placement_group,
placement_group_capture_child_tasks=placement_group_capture_child_tasks,
),
).remote()
results = ray.get(results_future)
for worker_result in results:
if placement_group_capture_child_tasks:
assert worker_result == placement_group.id
else:
assert worker_result != placement_group.id
@pytest.mark.parametrize("timeout_s", [5, 0])
@pytest.mark.skipif(
sys.version_info >= (3, 12),
reason="Current jax version is not supported in python 3.12+",
)
def test_jax_distributed_shutdown_timeout(ray_start_2_cpus, monkeypatch, timeout_s):
"""Test that JAX distributed shutdown respects the timeout env var."""
monkeypatch.setenv(JAX_DISTRIBUTED_SHUTDOWN_TIMEOUT_S, str(timeout_s))
jax_config = JaxConfig(use_tpu=True)
e = BackendExecutor(jax_config, num_workers=2)
e.start()
_start_training(e, lambda: 1)
assert e.finish_training() == [1, 1]
# Verify that we do not raise an exception even if we time out
e._backend.on_shutdown(e.worker_group, e._backend_config)
if __name__ == "__main__":
import sys
import pytest
sys.exit(pytest.main(["-v", "-x", __file__]))