165 lines
5.5 KiB
Python
165 lines
5.5 KiB
Python
# Adapted from https://github.com/vllm-project/vllm/blob/bf214ca22625e311a2c4c0dfbf7af19128f4919c/vllm/distributed/device_communicators/symm_mem.py
|
|
import logging
|
|
from typing import Optional, Union
|
|
|
|
import torch
|
|
import torch.distributed as dist
|
|
from torch.distributed import ProcessGroup
|
|
|
|
from sglang.srt.distributed.device_communicators.all_reduce_utils import (
|
|
SYMM_MEM_ALL_REDUCE_MAX_SIZES,
|
|
)
|
|
from sglang.srt.utils import is_cuda, is_hip
|
|
|
|
try:
|
|
import torch.distributed._symmetric_memory as torch_symm_mem
|
|
|
|
symm_mem_available = True
|
|
except ImportError:
|
|
symm_mem_available = False
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_is_cuda = is_cuda()
|
|
_is_hip = is_hip()
|
|
|
|
symm_mem_is_available = False
|
|
if _is_hip:
|
|
symm_mem_is_available = False
|
|
if _is_cuda:
|
|
symm_mem_is_available = True
|
|
|
|
|
|
class SymmMemCommunicator:
|
|
"""
|
|
Thin wrapper around symmetric-memory collectives.
|
|
|
|
This communicator:
|
|
- Validates device capability and world size.
|
|
- Allocates a shared symmetric buffer.
|
|
- Chooses between 'multimem' and 'two-shot' all-reduce kernels.
|
|
- Exposes a fast-path all_reduce() compatible with bfloat16 inputs.
|
|
|
|
If any prerequisite is not met, the instance remains disabled and will
|
|
decline to perform symmetric-memory all-reduce.
|
|
"""
|
|
|
|
# Mapping: compute capability major -> supported world sizes for multimem
|
|
# If the current (cc_major, world_size) is not listed, we fall back
|
|
# to the two-shot path.
|
|
_WORLD_SIZES_MULTIMEM = {
|
|
9: [4, 6, 8],
|
|
10: [6, 8],
|
|
}
|
|
|
|
def __init__(self, group: ProcessGroup, device: Union[int, str, torch.device]):
|
|
"""
|
|
Args:
|
|
group: Torch process group used for rendezvous and naming.
|
|
device: Target CUDA device (index, 'cuda:X', or torch.device).
|
|
"""
|
|
|
|
self.disabled = True
|
|
|
|
if not symm_mem_available:
|
|
return
|
|
|
|
if isinstance(device, int):
|
|
device = torch.device(f"cuda:{device}")
|
|
elif isinstance(device, str):
|
|
device = torch.device(device)
|
|
torch.cuda.set_device(device)
|
|
self.dtype = torch.bfloat16
|
|
self.device = device
|
|
self.group = group
|
|
self.world_size = dist.get_world_size(self.group)
|
|
self.device_capability = torch.cuda.get_device_capability(device)[0]
|
|
if self.device_capability < 9:
|
|
logger.warning(
|
|
"SymmMemCommunicator: Device capability %s not supported, "
|
|
"communicator is not available.",
|
|
self.device_capability,
|
|
)
|
|
return
|
|
if self.world_size not in SYMM_MEM_ALL_REDUCE_MAX_SIZES[self.device_capability]:
|
|
logger.warning(
|
|
"SymmMemCommunicator: World size %d not supported, "
|
|
"communicator is not available.",
|
|
self.world_size,
|
|
)
|
|
return
|
|
self.max_size = SYMM_MEM_ALL_REDUCE_MAX_SIZES[self.device_capability][
|
|
self.world_size
|
|
]
|
|
self.buffer = torch_symm_mem.empty(
|
|
self.max_size // self.dtype.itemsize,
|
|
device=self.device,
|
|
dtype=self.dtype,
|
|
)
|
|
handle = torch_symm_mem.rendezvous(self.buffer, self.group.group_name)
|
|
if handle.multicast_ptr == 0:
|
|
logger.warning(
|
|
"SymmMemCommunicator: symmetric memory "
|
|
"multicast operations are not supported."
|
|
)
|
|
self.buffer = None
|
|
self.disabled = True
|
|
return
|
|
self.disabled = False
|
|
|
|
def should_symm_mem_allreduce(self, inp: torch.Tensor):
|
|
"""
|
|
Fast-path eligibility check for a given tensor.
|
|
|
|
Conditions:
|
|
- Communicator must be enabled.
|
|
- dtype must be bfloat16 (matches kernel + buffer dtype).
|
|
- Total byte size must be 4-byte aligned (hardware requirement).
|
|
- Payload must be smaller than the symmetric-memory max size.
|
|
|
|
Returns:
|
|
True if the symmetric-memory path can handle this tensor.
|
|
"""
|
|
if self.disabled:
|
|
return False
|
|
if inp.dtype != self.dtype:
|
|
return False
|
|
inp_size = inp.numel() * inp.element_size()
|
|
# enforce 4-byte alignment
|
|
if inp_size % 4 != 0:
|
|
return False
|
|
return inp_size < self.max_size
|
|
|
|
def all_reduce(
|
|
self, inp: torch.Tensor, *, out: Optional[torch.Tensor] = None
|
|
) -> Optional[torch.Tensor]:
|
|
"""
|
|
Perform an in-place sum all-reduce via symmetric memory.
|
|
|
|
Args:
|
|
inp: Input tensor on the target CUDA device (bfloat16).
|
|
out: Optional output tensor; if omitted, a new tensor is allocated.
|
|
|
|
Returns:
|
|
The reduced tensor (same shape as inp), or None if disabled.
|
|
|
|
Implementation details:
|
|
- Stages 'inp' into the symmetric buffer.
|
|
- Selects 'multimem' or 'two_shot' kernel based on topology.
|
|
- Writes the result into 'out' and returns it.
|
|
"""
|
|
if out is None:
|
|
out = torch.empty_like(inp)
|
|
self.buffer[: inp.numel()].copy_(inp.view(-1))
|
|
if self.world_size in self._WORLD_SIZES_MULTIMEM[self.device_capability]:
|
|
torch.ops.symm_mem.multimem_all_reduce_(
|
|
self.buffer[: inp.numel()], "sum", self.group.group_name
|
|
)
|
|
else:
|
|
torch.ops.symm_mem.two_shot_all_reduce_(
|
|
self.buffer[: inp.numel()], "sum", self.group.group_name
|
|
)
|
|
out.copy_(self.buffer[: inp.numel()].view(out.shape))
|
|
return out
|