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
+345
View File
@@ -0,0 +1,345 @@
"""Unit tests for RDTManager."""
import re
import sys
from dataclasses import dataclass
from typing import Any, List
import pytest
from ray.exceptions import GetTimeoutError
from ray.experimental import (
CommunicatorMetadata,
TensorTransportManager,
TensorTransportMetadata,
register_tensor_transport,
)
from ray.experimental.rdt.rdt_manager import RDTManager, RDTMeta
from ray.experimental.rdt.tensor_transport_manager import FetchRequest
_BACKEND_NAME = "TEST_PIPELINE"
_TWO_SIDED_BACKEND_NAME = "TEST_TWO_SIDED"
@dataclass
class _TestCommMeta(CommunicatorMetadata):
pass
@dataclass
class _TrackedFetchRequest(FetchRequest):
"""FetchRequest subclass that records when it is deleted."""
def __del__(self):
_PipelineCheckingTransport.deleted_requests.add(self.obj_id)
class _PipelineCheckingTransport(TensorTransportManager):
"""Fake one-sided transport that records the order of fetch/wait calls.
Each fetch_multiple_tensors call appends ("fetch", obj_id) to call_log,
and each wait_fetch_complete call appends ("wait", obj_id). The test
asserts that all fetch entries appear before any wait entry.
call_log is a class-level list so the singleton instance created by
get_tensor_transport_manager records to the same list across all tests.
"""
call_log: List = []
fail_on_wait: set = set()
wait_delay: float = 0
deleted_requests: set = set()
def tensor_transport_backend(self) -> str:
return _BACKEND_NAME
@staticmethod
def is_one_sided() -> bool:
return True
@staticmethod
def can_abort_transport() -> bool:
return False
def actor_has_tensor_transport(self, actor) -> bool:
return True
def extract_tensor_transport_metadata(self, obj_id, rdt_object):
return TensorTransportMetadata(tensor_meta=[], tensor_device="cpu")
def get_communicator_metadata(self, src_actor, dst_actor, backend=None):
return _TestCommMeta()
def fetch_multiple_tensors(
self,
obj_id: str,
tensor_transport_metadata,
communicator_metadata,
target_buffers=None,
) -> FetchRequest:
self.__class__.call_log.append(("fetch", obj_id))
return _TrackedFetchRequest(obj_id=obj_id, tensors=[f"val:{obj_id}"])
def wait_fetch_complete(
self, fetch_request: FetchRequest, timeout: float = -1
) -> List[Any]:
if self.__class__.wait_delay > 0:
import time
time.sleep(self.__class__.wait_delay)
self.__class__.call_log.append(("wait", fetch_request.obj_id))
if fetch_request.obj_id in self.__class__.fail_on_wait:
raise RuntimeError(f"wait failed for {fetch_request.obj_id}")
return fetch_request.tensors
def recv_multiple_tensors(self, obj_id, meta, comm_meta, target_buffers=None):
return []
def send_multiple_tensors(self, tensors, meta, comm_meta):
pass
def garbage_collect(self, obj_id, meta, tensors):
pass
def abort_transport(self, obj_id, comm_meta):
pass
class _TwoSidedTransport(TensorTransportManager):
"""Fake two-sided transport (e.g. NCCL/GLOO style)."""
def tensor_transport_backend(self) -> str:
return _TWO_SIDED_BACKEND_NAME
@staticmethod
def is_one_sided() -> bool:
return False
@staticmethod
def can_abort_transport() -> bool:
return False
def actor_has_tensor_transport(self, actor) -> bool:
return True
def extract_tensor_transport_metadata(self, obj_id, rdt_object):
return TensorTransportMetadata(tensor_meta=[], tensor_device="cpu")
def get_communicator_metadata(self, src_actor, dst_actor, backend=None):
return _TestCommMeta()
def fetch_multiple_tensors(self, obj_id, meta, comm_meta, target_buffers=None):
raise NotImplementedError
def recv_multiple_tensors(self, obj_id, meta, comm_meta, target_buffers=None):
raise NotImplementedError
def send_multiple_tensors(self, tensors, meta, comm_meta):
raise NotImplementedError
def garbage_collect(self, obj_id, meta, tensors):
pass
def abort_transport(self, obj_id, comm_meta):
pass
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture(scope="module", autouse=True)
def register_test_transports():
"""Register both test transports once for the lifetime of the module."""
try:
register_tensor_transport(
_BACKEND_NAME, ["cpu"], _PipelineCheckingTransport, list
)
except ValueError:
pass # already registered (e.g. test module loaded more than once)
try:
register_tensor_transport(
_TWO_SIDED_BACKEND_NAME, ["cpu"], _TwoSidedTransport, list
)
except ValueError:
pass
@pytest.fixture(autouse=True)
def clear_call_log():
"""Reset the pipeline transport's call log and error config before each test."""
_PipelineCheckingTransport.call_log.clear()
_PipelineCheckingTransport.fail_on_wait.clear()
_PipelineCheckingTransport.wait_delay = 0
_PipelineCheckingTransport.deleted_requests.clear()
def _build_manager(object_ids: List[str], backend: str = _BACKEND_NAME) -> RDTManager:
"""Return an RDTManager pre-populated with fake RDT metadata.
Uses a real RDTStore so no Ray cluster is required.
All objects are non-primary copies (pop_object=True in fetch_and_get_rdt_objects).
"""
manager = RDTManager()
meta = TensorTransportMetadata(tensor_meta=[], tensor_device="cpu")
for obj_id in object_ids:
manager.set_rdt_metadata(
obj_id,
RDTMeta(
src_actor=None,
tensor_transport_backend=backend,
tensor_transport_meta=meta,
sent_dest_actors=set(),
sent_to_src_actor_and_others_warned=False,
target_buffers=None,
),
)
return manager
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_fetch_and_get():
object_ids = ["obj1", "obj2", "obj3"]
manager = _build_manager(object_ids)
result = manager.fetch_and_get_rdt_objects(object_ids)
call_log = _PipelineCheckingTransport.call_log
fetch_indices = [i for i, (kind, _) in enumerate(call_log) if kind == "fetch"]
wait_indices = [i for i, (kind, _) in enumerate(call_log) if kind == "wait"]
# All fetch_multiple_tensors calls must precede all wait_fetch_complete
# calls.
assert len(fetch_indices) == len(object_ids), f"call_log={call_log}"
assert len(wait_indices) == len(object_ids), f"call_log={call_log}"
assert max(fetch_indices) < min(
wait_indices
), f"Expected all fetches before all waits, got call_log={call_log}"
# One entry per requested object ID.
assert set(result.keys()) == set(object_ids)
call_log = _PipelineCheckingTransport.call_log
# Each object ID triggers exactly one fetch and one wait.
fetched = [oid for kind, oid in call_log if kind == "fetch"]
waited = [oid for kind, oid in call_log if kind == "wait"]
assert sorted(fetched) == sorted(object_ids)
assert sorted(waited) == sorted(object_ids)
def test_primary_copy_objects_skip_fetch():
"""Objects already in the store must not trigger a fetch."""
secondary_ids = ["secondary1", "secondary2"]
primary_id = "primary1"
manager = _build_manager(secondary_ids + [primary_id])
# Add the primary-copy and one secondary-copy object to the store directly.
# Phase 1 of fetch_and_get_rdt_objects skips objects in store.
manager.rdt_store.add_object(primary_id, ["primary_value"], is_primary=True)
manager.rdt_store.add_object(secondary_ids[0], ["secondary"], is_primary=False)
result = manager.fetch_and_get_rdt_objects(secondary_ids + [primary_id])
call_log = _PipelineCheckingTransport.call_log
fetched = [oid for kind, oid in call_log if kind == "fetch"]
assert set(fetched) == set(
secondary_ids[1:]
), f"objects in store should not be fetched; got fetched={fetched}"
# One fetch + one wait for each secondary object; zero for the primary one.
assert len(call_log) == 2, f"call_log={call_log}"
# All objects should be returned in the results.
assert set(result.keys()) == set(secondary_ids + [primary_id])
assert result[primary_id] == ["primary_value"]
assert result[secondary_ids[0]] == ["secondary"]
def test_empty_object_list_returns_empty_dict():
"""Calling fetch_and_get_rdt_objects with an empty list returns an empty dict."""
manager = _build_manager([])
result = manager.fetch_and_get_rdt_objects([])
assert result == {}
assert _PipelineCheckingTransport.call_log == []
def test_two_sided_transport_raises_on_fetch_and_get_rdt_objects():
"""ray.get (use_object_store=False) must raise ValueError for two-sided transports."""
obj_id = "two_sided_obj"
manager = _build_manager([obj_id], backend=_TWO_SIDED_BACKEND_NAME)
with pytest.raises(
ValueError,
match=re.escape(
f"ray.get is not allowed on RDT objects using the two-sided transport {_TWO_SIDED_BACKEND_NAME}. "
"Either use a one-sided RDT transport or pass _use_object_store=True to ray.get to fetch the object through the object store instead."
),
):
manager.fetch_and_get_rdt_objects([obj_id], use_object_store=False)
def test_fetch_requests_deleted_on_exception():
"""If _wait_fetch raises, all FetchRequests are deleted (cleaning up resources via __del__)."""
import gc
object_ids = ["obj1", "obj2", "obj3"]
manager = _build_manager(object_ids)
_PipelineCheckingTransport.fail_on_wait.add("obj1")
with pytest.raises(RuntimeError, match="wait failed for obj1"):
manager.fetch_and_get_rdt_objects(object_ids)
gc.collect()
assert _PipelineCheckingTransport.deleted_requests == set(object_ids), (
f"All FetchRequests must be GCed even if one fails; "
f"deleted={_PipelineCheckingTransport.deleted_requests}"
)
def test_object_fetch_timed_out_error():
"""fetch_and_get_rdt_objects raises ObjectFetchTimedOutError when RDT timeout is hit."""
from ray.exceptions import ObjectFetchTimedOutError
object_ids = ["obj1", "obj2"]
manager = _build_manager(object_ids)
# Make wait_fetch_complete slow enough to exceed a very short timeout.
_PipelineCheckingTransport.wait_delay = 0.2
with pytest.raises(ObjectFetchTimedOutError):
# timeout=None means no user timeout, so only RDT timeout applies.
# We monkeypatch the constant to a very small value.
import ray._private.ray_constants as rc
original = rc.RDT_FETCH_FAIL_TIMEOUT_SECONDS
rc.RDT_FETCH_FAIL_TIMEOUT_SECONDS = 0.1
try:
manager.fetch_and_get_rdt_objects(object_ids)
finally:
rc.RDT_FETCH_FAIL_TIMEOUT_SECONDS = original
def test_get_timed_out_error():
"""fetch_and_get_rdt_objects raises GetTimeoutError when user timeout is hit."""
object_ids = ["obj1", "obj2"]
manager = _build_manager(object_ids)
# Make wait_fetch_complete slow enough to exceed a very short timeout.
_PipelineCheckingTransport.wait_delay = 0.2
# Check that user timeout triggers before fetch fail timeout.
with pytest.raises(GetTimeoutError):
import ray._private.ray_constants as rc
original = rc.RDT_FETCH_FAIL_TIMEOUT_SECONDS
rc.RDT_FETCH_FAIL_TIMEOUT_SECONDS = 1
try:
manager.fetch_and_get_rdt_objects(object_ids, timeout=0.1)
finally:
rc.RDT_FETCH_FAIL_TIMEOUT_SECONDS = original
if __name__ == "__main__":
sys.exit(pytest.main(["-sv", __file__]))