Files
2026-07-13 13:17:40 +08:00

204 lines
6.7 KiB
Python

from dataclasses import dataclass
from typing import TYPE_CHECKING, List, Optional
import ray
from ray.experimental.rdt.tensor_transport_manager import (
CommunicatorMetadata,
TensorTransportManager,
TensorTransportMetadata,
)
if TYPE_CHECKING:
import torch
@dataclass
class CollectiveTransportMetadata(TensorTransportMetadata):
"""Metadata for tensors stored in the GPU object store for collective transport."""
@dataclass
class CollectiveCommunicatorMetadata(CommunicatorMetadata):
"""Metadata for the collective communicator (e.g. NCCL, GLOO).
Args:
src_rank: The rank of the source actor.
dst_rank: The rank of the destination actor.
"""
communicator_name: str = ""
src_rank: Optional[int] = None
dst_rank: Optional[int] = None
class CollectiveTensorTransport(TensorTransportManager):
def tensor_transport_backend(self) -> str:
raise NotImplementedError(
"NCCLTensorTransport or GLOOTensorTransport should be used instead of this base class."
)
@staticmethod
def is_one_sided() -> bool:
return False
@staticmethod
def can_abort_transport() -> bool:
return False
def actor_has_tensor_transport(self, actor: "ray.actor.ActorHandle") -> bool:
from ray.experimental.collective import get_collective_groups
communicators = get_collective_groups(
[actor], backend=self.tensor_transport_backend()
)
return len(communicators) > 0
def extract_tensor_transport_metadata(
self,
obj_id: str,
rdt_object: List["torch.Tensor"],
) -> CollectiveTransportMetadata:
tensor_meta = []
device = None
if rdt_object:
device = rdt_object[0].device
for t in rdt_object:
if t.device.type != device.type:
raise ValueError(
"All tensors in an RDT object must have the same device type."
)
tensor_meta.append((t.shape, t.dtype))
return CollectiveTransportMetadata(
tensor_meta=tensor_meta,
tensor_device=device.type if device else None,
)
def get_communicator_metadata(
self,
src_actor: "ray.actor.ActorHandle",
dst_actor: "ray.actor.ActorHandle",
backend: Optional[str] = None,
) -> CollectiveCommunicatorMetadata:
from ray.experimental.collective import get_collective_groups
communicators = get_collective_groups(
[src_actor, dst_actor],
backend=backend,
)
# TODO(kevin85421): Support multiple communicators.
if len(communicators) == 0:
raise ValueError(
f"No communicators found for actors {src_actor} and {dst_actor}. "
"Create a communicator with "
"`ray.experimental.collective.create_collective_group` "
"before calling actor tasks. with non-default tensor_transport."
)
elif len(communicators) > 1:
raise ValueError(
f"There are {len(communicators)} possible communicators that contain actors {src_actor} and {dst_actor}. "
"Currently, RDT objects only support one communicator. Please make sure only "
"one communicator exists."
)
communicator = communicators[0]
src_rank = communicator.get_rank(src_actor)
if src_rank == -1:
raise ValueError(
f"Sender actor {src_actor} not found in communicator. "
"Please make sure the sender and receiver are in the same communicator."
)
dst_rank = communicator.get_rank(dst_actor)
if dst_rank == -1:
raise ValueError(
f"Receiver actor {dst_actor} not found in communicator. "
"Please make sure the sender and receiver are in the same communicator."
)
communicator_metadata = CollectiveCommunicatorMetadata(
communicator_name=communicator.name,
src_rank=src_rank,
dst_rank=dst_rank,
)
return communicator_metadata
def recv_multiple_tensors(
self,
obj_id: str,
tensor_transport_metadata: TensorTransportMetadata,
communicator_metadata: CommunicatorMetadata,
target_buffers: Optional[List["torch.Tensor"]] = None,
):
from ray.experimental.rdt.util import (
create_empty_tensors_from_metadata,
)
from ray.util.collective.collective import recv
assert isinstance(tensor_transport_metadata, CollectiveTransportMetadata)
assert isinstance(communicator_metadata, CollectiveCommunicatorMetadata)
tensors = target_buffers or create_empty_tensors_from_metadata(
tensor_transport_metadata
)
for tensor in tensors:
recv(
tensor,
communicator_metadata.src_rank,
communicator_metadata.communicator_name,
)
return tensors
def send_multiple_tensors(
self,
tensors: List["torch.Tensor"],
tensor_transport_metadata: TensorTransportMetadata,
communicator_metadata: CommunicatorMetadata,
):
import ray.util.collective as collective
assert isinstance(
tensor_transport_metadata, CollectiveTransportMetadata
), "metadata must be a CollectiveTransportMetadata object for non-NIXL transport"
assert isinstance(
communicator_metadata, CollectiveCommunicatorMetadata
), "metadata must be a CollectiveCommunicatorMetadata object for non-NIXL transport"
device = tensors[0].device if tensors else None
for tensor in tensors:
if tensor.device.type != device.type:
raise ValueError(
f"tensor device {tensor.device} does not match device {device}"
)
collective.send(
tensor,
communicator_metadata.dst_rank,
communicator_metadata.communicator_name,
)
def garbage_collect(
self,
obj_id: str,
tensor_transport_meta: TensorTransportMetadata,
tensors: List["torch.Tensor"],
):
pass
def abort_transport(
self,
obj_id: str,
communicator_metadata: CommunicatorMetadata,
):
raise NotImplementedError(
"Collective transport does not support abort_transport for now."
)
class NCCLTensorTransport(CollectiveTensorTransport):
def tensor_transport_backend(self) -> str:
return "NCCL"
class GLOOTensorTransport(CollectiveTensorTransport):
def tensor_transport_backend(self) -> str:
return "GLOO"