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
@@ -0,0 +1,21 @@
from ray.experimental.collective.collective import (
create_collective_group,
destroy_all_collective_groups,
destroy_collective_group,
get_collective_groups,
)
from ray.experimental.collective.operations import (
allgather,
allreduce,
reducescatter,
)
__all__ = [
"allgather",
"allreduce",
"reducescatter",
"get_collective_groups",
"create_collective_group",
"destroy_collective_group",
"destroy_all_collective_groups",
]
@@ -0,0 +1,209 @@
import threading
import uuid
from typing import Dict, List, Optional, Union
import ray
import ray.experimental.internal_kv as internal_kv
from ray.experimental.collective.communicator import CommunicatorHandle
from ray.util.annotations import PublicAPI
from ray.util.collective.collective_group.torch_gloo_collective_group import (
get_master_address_metadata_key,
)
from ray.util.collective.types import Backend
_remote_communicator_manager: "Optional[RemoteCommunicatorManager]" = None
_remote_communicator_manager_lock = threading.Lock()
class RemoteCommunicatorManager:
"""Singleton class to store the mapping between actors and communicators
that the actors are a part of.
"""
def __init__(self):
# Handles to communicators that we created. Key is a user-provided
# name or UUID.
self._remote_communicators: Dict[str, CommunicatorHandle] = {}
@staticmethod
def get() -> "RemoteCommunicatorManager":
global _remote_communicator_manager
with _remote_communicator_manager_lock:
if _remote_communicator_manager is None:
_remote_communicator_manager = RemoteCommunicatorManager()
return _remote_communicator_manager
def add_remote_communicator(self, comm_handle: CommunicatorHandle):
self._remote_communicators[comm_handle.name] = comm_handle
def remove_remote_communicator(self, name: str):
return self._remote_communicators.pop(name, None)
def get_collective_groups(
self,
actors: Optional[List[ray.actor.ActorHandle]] = None,
backend: Optional[Backend] = None,
):
"""
Get the collective groups that the given actors are a subset of. Filter by
backend if provided.
"""
actors = actors or []
actors = set(actors)
collectives = []
# Find all collective groups that the given actors are a subset
# of, with the matching backend if provided.
for collective in self._remote_communicators.values():
if actors.issubset(set(collective.actors)):
if backend is None or collective.backend == backend:
collectives.append(collective)
return collectives
@PublicAPI(stability="alpha")
def get_collective_groups(
actors: List[ray.actor.ActorHandle], backend: Optional[str] = None
) -> List[CommunicatorHandle]:
"""
Get the collective groups that the given actors are a subset of. Filter by
backend if provided.
Args:
actors: List of actors. Return handles to all collective groups that
these actors are a subset of.
backend: An optional backend to filter by. See
ray.util.collective.types.Backend for valid backends.
Returns:
A list of communicator handles that the actors are a subset of.
"""
manager = RemoteCommunicatorManager.get()
backend = Backend(backend) if backend is not None else None
return manager.get_collective_groups(actors, backend)
@PublicAPI(stability="alpha")
def create_collective_group(
actors: List[ray.actor.ActorHandle],
backend: str,
name: Optional[str] = None,
) -> CommunicatorHandle:
"""Create a collective group on the given list of actors. If this function
returns successfully, then the collective group has been initialized on all
actors, using the given order of actors as the ranks.
Currently, an actor can only participate in one collective group per
backend at a time. To reuse an actor, destroy its collective group and
create a new one.
Args:
actors: The actors to participate in the collective group.
backend: The backend to use. See ray.util.collective.types.Backend for
valid backends.
name: A name to use for the collective group. If None is provided, a
random name will be generated.
Returns:
Handle to the communicator.
"""
manager = RemoteCommunicatorManager.get()
if name is None:
name = str(uuid.uuid4())
# Validate the backend.
backend = Backend(backend)
world_size = len(actors)
for actor in actors:
if manager.get_collective_groups([actor], backend):
raise RuntimeError(
f"Actor {actor} already in group for backend {backend}. Actors can currently only participate in at most one group per backend."
)
actor_ids = [actor._ray_actor_id for actor in actors]
if len(set(actor_ids)) != len(actor_ids):
raise ValueError(f"All actors must be unique, got: {actors}")
metadata_key = None
if backend == Backend.GLOO:
metadata_key = get_master_address_metadata_key(name)
def _do_init_collective_group(self, rank: int):
ray.util.collective.init_collective_group(
world_size, rank, backend, group_name=name
)
try:
init_tasks = [
actor.__ray_call__.remote(
_do_init_collective_group,
rank,
)
for rank, actor in enumerate(actors)
]
ray.get(init_tasks)
finally:
# Clean up the metadata once collective group is initialized
# (or failed to initialize).
if metadata_key is not None:
internal_kv._internal_kv_del(metadata_key)
# Group was successfully created.
comm = CommunicatorHandle(actors, name, backend)
manager.add_remote_communicator(comm)
return comm
@PublicAPI(stability="alpha")
def destroy_collective_group(group_or_name: Union[CommunicatorHandle, str]):
"""
Destroy a collective group. If this functions returns successfully, then
the actors that were in the collective can be reused to create a new
collective group.
Args:
group_or_name: Either a communicator handle or the name of the group to
destroy.
"""
if isinstance(group_or_name, CommunicatorHandle):
name = group_or_name.name
elif isinstance(group_or_name, str):
name = group_or_name
else:
raise ValueError("Expected CommunicatorHandle or str (group name).")
manager = RemoteCommunicatorManager.get()
group = manager.remove_remote_communicator(name)
if group is not None:
def _do_destroy_collective_group(self):
ray.util.collective.destroy_collective_group(name)
destroy_tasks = [
actor.__ray_call__.options(concurrency_group="_ray_system").remote(
_do_destroy_collective_group
)
for actor in group.actors
]
try:
ray.get(destroy_tasks)
except ray.exceptions.ActorDiedError:
pass
else:
raise ValueError(f"No group with name {name} found.")
@PublicAPI(stability="alpha")
def destroy_all_collective_groups():
"""
Destroy all collective groups. This will destroy all collective groups that
were previously created by this process. After this function returns, the
actors participating in those collective groups can be reused to create a
new collective group.
"""
manager = RemoteCommunicatorManager.get()
for collective in manager.get_collective_groups():
destroy_collective_group(collective.name)
@@ -0,0 +1,63 @@
from dataclasses import dataclass
from typing import List
import ray
from ray.util.collective.types import Backend
@dataclass
class Communicator:
"""
A handle to a communicator that we are a member of.
"""
# The name of the communicator.
name: str
# Our rank in the collective group.
rank: int
# A valid backend, as defined by
# ray.util.collective.types.Backend.
backend: str
class CommunicatorHandle:
"""
A communicator handle used by the driver to store handles to the
actors in the communicator.
"""
def __init__(self, actors: List[ray.actor.ActorHandle], name: str, backend: str):
"""
Initializes the CommunicatorHandle with the given actor handles.
Assumes that the communicator has already been initialized on all actors.
Args:
actors: A list of actor handles to be stored.
name: Name of the communicator.
backend: Communicator backend. See
ray.util.collective.types for valid values.
"""
self._actors = actors
self._name = name
self._backend = Backend(backend)
def get_rank(self, actor: ray.actor.ActorHandle):
for i, a in enumerate(self._actors):
if a == actor:
return i
return -1
@property
def actors(self) -> List[ray.actor.ActorHandle]:
"""
Return all actor handles in this communicator.
"""
return self._actors[:]
@property
def name(self) -> str:
return self._name
@property
def backend(self) -> str:
return self._backend
@@ -0,0 +1,243 @@
import uuid
from typing import Dict, FrozenSet, List, Optional, Set, Tuple, Type
import torch
import ray
from ray.experimental.channel.common import ChannelContext
from ray.experimental.channel.communicator import (
Communicator,
ReduceOp,
TorchTensorAllocator,
)
class AbstractNcclGroup(Communicator):
"""
A dummy NCCL group for testing.
"""
def __init__(self, actor_handles: List[ray.actor.ActorHandle]):
self._actor_handles = actor_handles
self._rank = None
def initialize(self, rank: int) -> None:
self._rank = rank
def get_rank(self, actor: ray.actor.ActorHandle) -> int:
return self._actor_handles.index(actor)
def get_world_size(self) -> int:
return len(self._actor_handles)
def get_self_rank(self) -> Optional[int]:
return self._rank
def get_actor_handles(self) -> List["ray.actor.ActorHandle"]:
return self._actor_handles
def send(self, value: "torch.Tensor", peer_rank: int) -> None:
raise NotImplementedError
def recv(
self,
shape: Tuple[int],
dtype: "torch.dtype",
peer_rank: int,
allocator: Optional[TorchTensorAllocator] = None,
) -> "torch.Tensor":
raise NotImplementedError
def allgather(
self,
send_buf: "torch.Tensor",
recv_buf: "torch.Tensor",
) -> None:
raise NotImplementedError
def allreduce(
self,
send_buf: "torch.Tensor",
recv_buf: "torch.Tensor",
op: ReduceOp = ReduceOp.SUM,
) -> None:
raise NotImplementedError
def reducescatter(
self,
send_buf: "torch.Tensor",
recv_buf: "torch.Tensor",
op: ReduceOp = ReduceOp.SUM,
) -> None:
raise NotImplementedError
@property
def recv_stream(self):
return None
@property
def send_stream(self):
return None
def destroy(self) -> None:
pass
def get_transport_name(self) -> str:
return "accelerator"
@classmethod
def generate_communicator_id(cls) -> str:
pass
class MockNcclGroupSet:
def __init__(self):
# Represents a mapping from a NCCL group ID to a set of actors and a custom
# NCCL group.
self.ids_to_actors_and_custom_comms: Dict[
str, Tuple[FrozenSet["ray.actor.ActorHandle"], Optional[Communicator]]
] = {}
def __call__(
self,
actors: List["ray.actor.ActorHandle"],
custom_nccl_group: Optional[Communicator] = None,
use_communication_streams: bool = False,
accelerator_module_name: Optional[str] = None,
accelerator_communicator_cls: Optional[Type[Communicator]] = None,
) -> str:
group_id = str(uuid.uuid4())
self.ids_to_actors_and_custom_comms[group_id] = (
frozenset(actors),
custom_nccl_group,
)
if custom_nccl_group is None:
ranks = list(range(len(actors)))
else:
ranks = [custom_nccl_group.get_rank(actor) for actor in actors]
init_tasks = [
actor.__ray_call__.remote(
mock_do_init_nccl_group,
group_id,
rank,
actors,
custom_nccl_group,
)
for rank, actor in zip(ranks, actors)
]
ray.get(init_tasks, timeout=30)
ctx = ChannelContext.get_current()
if custom_nccl_group is not None:
ctx.communicators[group_id] = custom_nccl_group
else:
ctx.communicators[group_id] = AbstractNcclGroup(actors)
return group_id
def mock_destroy_nccl_group(self, group_id: str) -> None:
ctx = ChannelContext.get_current()
if group_id not in ctx.communicators:
return
actors, _ = self.ids_to_actors_and_custom_comms[group_id]
destroy_tasks = [
actor.__ray_call__.remote(
mock_do_destroy_nccl_group,
group_id,
)
for actor in actors
]
ray.wait(destroy_tasks, timeout=30)
if group_id in self.ids_to_actors_and_custom_comms:
del self.ids_to_actors_and_custom_comms[group_id]
ctx.communicators[group_id].destroy()
del ctx.communicators[group_id]
def check_teardown(self, nccl_group_ids: List[str]) -> None:
ctx = ChannelContext.get_current()
for nccl_group_id in nccl_group_ids:
assert nccl_group_id not in self.ids_to_actors_and_custom_comms
assert nccl_group_id not in ctx.communicators
@ray.remote
class CPUTorchTensorWorker:
def __init__(self):
self.device = "cpu"
def return_tensor(
self, size: int, dtype: Optional[torch.dtype] = None
) -> torch.Tensor:
return torch.ones(size, dtype=dtype, device=self.device)
def recv(self, tensor: torch.Tensor) -> Tuple[int, int]:
assert tensor.device == self.device
return tensor.shape, tensor[0]
def recv_tensors(self, *tensors) -> Tuple[torch.Tensor, ...]:
return tuple(tensors)
def mock_do_init_nccl_group(
self,
group_id: str,
rank: int,
actors: List[ray.actor.ActorHandle],
custom_nccl_group: Optional[Communicator],
) -> None:
ctx = ChannelContext.get_current()
if custom_nccl_group is None:
nccl_group = AbstractNcclGroup(actors)
nccl_group.initialize(rank)
ctx.communicators[group_id] = nccl_group
else:
custom_nccl_group.initialize(rank)
ctx.communicators[group_id] = custom_nccl_group
def mock_do_destroy_nccl_group(self, group_id: str) -> None:
ctx = ChannelContext.get_current()
if group_id not in ctx.communicators:
return
ctx.communicators[group_id].destroy()
del ctx.communicators[group_id]
def check_nccl_group_init(
monkeypatch,
dag: "ray.dag.DAGNode",
actors_and_custom_comms: Set[
Tuple[FrozenSet["ray.actor.ActorHandle"], Optional[Communicator]]
],
) -> "ray.dag.CompiledDAG":
mock_nccl_group_set = MockNcclGroupSet()
monkeypatch.setattr(
"ray.dag.compiled_dag_node._init_communicator",
mock_nccl_group_set,
)
compiled_dag = dag.experimental_compile()
assert (
set(mock_nccl_group_set.ids_to_actors_and_custom_comms.values())
== actors_and_custom_comms
)
return compiled_dag, mock_nccl_group_set
def check_nccl_group_teardown(
monkeypatch,
compiled_dag: "ray.dag.CompiledDAG",
mock_nccl_group_set: MockNcclGroupSet,
):
monkeypatch.setattr(
"ray.dag.compiled_dag_node._destroy_communicator",
mock_nccl_group_set.mock_destroy_nccl_group,
)
created_communicator_ids = compiled_dag._actors_to_created_communicator_id.values()
compiled_dag.teardown()
mock_nccl_group_set.check_teardown(created_communicator_ids)
@@ -0,0 +1,203 @@
import logging
from typing import List, Optional, Union
import ray
from ray.dag.collective_node import CollectiveOutputNode, _CollectiveOperation
from ray.dag.constants import (
BIND_INDEX_KEY,
COLLECTIVE_OPERATION_KEY,
IS_CLASS_METHOD_OUTPUT_KEY,
PARENT_CLASS_NODE_KEY,
)
from ray.experimental.channel.torch_tensor_type import Communicator, TorchTensorType
from ray.experimental.util.types import (
AllGatherOp,
AllReduceOp,
ReduceOp,
ReduceScatterOp,
_CollectiveOp,
)
from ray.util.collective.types import ReduceOp as RayReduceOp
logger = logging.getLogger(__name__)
def _bind(
inputs: Union[List["ray.dag.DAGNode"], List[List["ray.dag.DAGNode"]]],
op: _CollectiveOp,
transport: Optional[Union[str, Communicator]] = None,
):
"""
Bind inputs (input nodes or lists of input nodes) with a collective operation.
The collective operation is applied to each list of input nodes. The output nodes
will have the same shape as the input nodes.
Example of binding a list of input node:
with InputNode() as inp:
res_comp1 = [actor.comp1.bind(inp) for actor in actors]
res_comp2 = [actor.comp2.bind(inp) for actor in actors]
res_ar = allreduce.bind([res_comp1, res_comp2])
Requirements:
1. Each input node returns a torch tensor.
2. Each input node within a list is from a different actor.
3. If lists of input nodes are provided, the order of actors should
be the same for each nested list.
4. If a custom transport is specified, its actor set matches the actor
set of the input nodes.
5. If input nodes are provided, then all tensors have the same shape.
If lists of input nodes are provided, then all tensors in each
list have the same shape.
Requirements 1-3 are checked in the `CollectiveGroup` constructor.
Requirement 4 is not checked yet.
Args:
inputs: A list of DAG nodes or a list of lists of DAG nodes. Each leaf list
should contain one object per actor.
op: The collective operation.
transport: GPU communicator for the collective operation. If not
specified, the default ACCELERATOR is used.
Returns:
A list of collective output nodes or a list of lists of collective output nodes,
with the same shape as the input nodes. Each output node has the same order and
belongs to the same actor as the corresponding input node.
"""
if isinstance(inputs[0], list) and not isinstance(op, AllReduceOp):
raise ValueError(
"Currently binding a nested list of dag nodes is only supported for allreduce"
)
# Convert list of DAGNode into nested list for type checking
if not isinstance(inputs[0], list):
inputs = [inputs]
if transport is None:
transport = TorchTensorType.ACCELERATOR
collective_op = _CollectiveOperation(inputs, op, transport)
collective_output_nodes: List[CollectiveOutputNode] = []
if isinstance(op, AllGatherOp):
method_name = "allgather"
elif isinstance(op, AllReduceOp):
method_name = f"allreduce.{op.reduceOp}"
elif isinstance(op, ReduceScatterOp):
method_name = f"reducescatter.{op.reduceOp}"
else:
raise ValueError(f"Expected a collective operation, but got {op}")
for i in range(len(inputs[0])):
input_node_list = [l[i] for l in inputs if l]
actor_handle: Optional["ray.actor.ActorHandle"] = input_node_list[
0
]._get_actor_handle()
assert actor_handle is not None
collective_output_node = CollectiveOutputNode(
method_name=method_name,
method_args=tuple(input_node_list),
method_kwargs=dict(),
method_options=dict(),
other_args_to_resolve={
PARENT_CLASS_NODE_KEY: actor_handle,
BIND_INDEX_KEY: actor_handle._ray_dag_bind_index,
COLLECTIVE_OPERATION_KEY: collective_op,
},
)
actor_handle._ray_dag_bind_index += 1
if len(input_node_list) > 1:
output_nodes: List[CollectiveOutputNode] = []
for i in range(len(input_node_list)):
output_node = CollectiveOutputNode(
f"return_idx_{i}",
(collective_output_node, i),
dict(),
dict(),
{
BIND_INDEX_KEY: collective_output_node._get_bind_index(),
IS_CLASS_METHOD_OUTPUT_KEY: True,
PARENT_CLASS_NODE_KEY: actor_handle,
},
)
output_nodes.append(output_node)
collective_output_nodes.append(output_nodes)
else:
collective_output_nodes.append(collective_output_node)
return collective_output_nodes
class AllGatherWrapper:
"""Wrapper for NCCL all-gather."""
def bind(
self,
input_nodes: List["ray.dag.DAGNode"],
transport: Optional[Union[str, Communicator]] = None,
) -> List[CollectiveOutputNode]:
return _bind(input_nodes, AllGatherOp(), transport)
def __call__(
self,
tensor_list,
tensor,
group_name: str = "default",
):
from ray.util.collective.collective import allgather
return allgather(tensor_list, tensor, group_name)
class AllReduceWrapper:
"""Wrapper for NCCL all-reduce."""
def bind(
self,
input_nodes: List["ray.dag.DAGNode"],
op: ReduceOp = ReduceOp.SUM,
transport: Optional[Union[str, Communicator]] = None,
) -> List[CollectiveOutputNode]:
if not isinstance(op, ReduceOp):
raise ValueError(f"Unexpected operation: {op}")
return _bind(input_nodes, AllReduceOp(reduceOp=op), transport)
def __call__(
self,
tensor,
group_name: str = "default",
op: RayReduceOp = RayReduceOp.SUM,
):
from ray.util.collective.collective import allreduce
return allreduce(tensor, group_name, op)
class ReduceScatterWrapper:
"""Wrapper for NCCL reduce-scatter."""
def bind(
self,
input_nodes: List["ray.dag.DAGNode"],
op: ReduceOp = ReduceOp.SUM,
transport: Optional[Union[str, Communicator]] = None,
) -> List[CollectiveOutputNode]:
if not isinstance(op, ReduceOp):
raise ValueError(f"Unexpected operation: {op}")
return _bind(input_nodes, ReduceScatterOp(reduceOp=op), transport)
def __call__(
self,
tensor,
group_name: str = "default",
op: RayReduceOp = RayReduceOp.SUM,
):
from ray.util.collective.collective import reducescatter
return reducescatter(tensor, group_name, op)
allgather = AllGatherWrapper()
allreduce = AllReduceWrapper()
reducescatter = ReduceScatterWrapper()