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

104 lines
3.3 KiB
Python

import sys
import pytest
import ray
import ray.cloudpickle as ray_pickle
from ray.train.v2._internal.execution.callback import (
ControllerCallback,
WorkerGroupCallback,
)
from ray.train.v2._internal.execution.context import TrainRunContext
from ray.train.v2.api.data_parallel_trainer import DataParallelTrainer
from ray.train.v2.api.exceptions import ControllerError, WorkerGroupError
def block_import(import_name):
import sys
class BlockTorchImport:
def find_spec(self, fullname, path, target=None):
if fullname == import_name or fullname.startswith(import_name + "."):
raise ImportError(
f"Test error: {import_name} not installed on this node"
)
sys.meta_path.insert(0, BlockTorchImport())
def test_captured_imports(ray_start_4_cpus):
import torch
def capture_torch_import_fn():
# torch is captured in the closure of the train_fn
# and should be re-imported on each worker.
torch.ones(1)
class AssertImportsCallback(ControllerCallback):
def after_controller_start(self, train_run_context: TrainRunContext):
# Check that torch is not imported in the controller process.
# The train_fn should be deserialized directly on the workers.
assert "torch" not in sys.modules
trainer = DataParallelTrainer(
capture_torch_import_fn,
run_config=ray.train.RunConfig(callbacks=[AssertImportsCallback()]),
scaling_config=ray.train.ScalingConfig(num_workers=2),
)
trainer.fit()
def test_deserialization_error(ray_start_4_cpus):
"""Test that train_fn deserialization errors are propagated properly.
This test showcases a common deserialization error example, where
the driver script successfully imports torch, but torch is not
installed on the worker nodes.
"""
import torch
def capture_torch_import_fn():
torch.ones(1)
class BlockTorchImportCallback(WorkerGroupCallback):
def after_worker_group_start(self, worker_group):
# Make it so that the torch import that happens on
# train_fn deserialization will fail on workers.
worker_group.execute(block_import, "torch")
trainer = DataParallelTrainer(
capture_torch_import_fn,
run_config=ray.train.RunConfig(callbacks=[BlockTorchImportCallback()]),
scaling_config=ray.train.ScalingConfig(num_workers=2),
)
with pytest.raises(ControllerError, match="torch not installed on this node"):
trainer.fit()
@pytest.mark.parametrize(
"error",
[
WorkerGroupError(
"Training failed on multiple workers",
{0: ValueError("worker 0 failed"), 1: RuntimeError("worker 1 failed")},
),
ControllerError(Exception("Controller crashed")),
],
)
def test_exceptions_are_picklable(error):
"""Test that WorkerGroupError and ControllerError are picklable."""
# Test pickle/unpickle for WorkerGroupError
pickled_error = ray_pickle.dumps(error)
unpickled_error = ray_pickle.loads(pickled_error)
# Verify attributes are preserved
assert str(unpickled_error) == str(error)
assert type(unpickled_error) is type(error)
if __name__ == "__main__":
import sys
sys.exit(pytest.main(["-v", "-x", __file__]))