import logging import os import struct import time from typing import Any, List, Optional import torch import torch.distributed as dist from torch.distributed import ProcessGroup from sglang.srt.utils import log_info_on_rank0 logger = logging.getLogger(__name__) _drv = None _FD_HEADER_BYTES = 24 _FD_SEND_TIMEOUT_S = 120.0 def _get_cuda_driver(): """Lazily import cuda.bindings.driver (cached after first call).""" global _drv if _drv is None: from cuda.bindings import driver _drv = driver return _drv def check_drv(result_tuple, label): """Check a cuda.bindings driver call result and return the value.""" if not isinstance(result_tuple, tuple): result_tuple = (result_tuple,) err = result_tuple[0] drv = _get_cuda_driver() if err != drv.CUresult.CUDA_SUCCESS: raise RuntimeError(f"{label}: {err}") return result_tuple[1] if len(result_tuple) > 1 else None def is_vmm_pointer(ptr: int) -> bool: """Check if a device pointer is VMM-backed (cuMemCreate/cuMemMap). cuMemRetainAllocationHandle succeeds only on pointers from cuMemCreate; it fails on cudaMalloc pointers. """ drv = _get_cuda_driver() err, handle = drv.cuMemRetainAllocationHandle(ptr) if err == drv.CUresult.CUDA_SUCCESS: drv.cuMemRelease(handle) return True return False def make_rw_access_desc(device_id: int): """A read-write, device-local ``CUmemAccessDesc`` for ``device_id``.""" drv = _get_cuda_driver() desc = drv.CUmemAccessDesc() desc.location.type = drv.CUmemLocationType.CU_MEM_LOCATION_TYPE_DEVICE desc.location.id = device_id desc.flags = drv.CUmemAccess_flags.CU_MEM_ACCESS_FLAGS_PROT_READWRITE return desc def all_ranks_ok(group: ProcessGroup, ok: bool) -> bool: """True iff ``ok`` holds on every rank in ``group`` (BAND all-reduce).""" flag = torch.tensor([1 if ok else 0], dtype=torch.int32) dist.all_reduce(flag, op=dist.ReduceOp.BAND, group=group) return flag.item() == 1 def release_mappings(mappings) -> None: """Unmap + address-free each ``(va, span_size, [(rel, size), ...])`` mapping. Pops from ``mappings`` so a partially-released list is safe to retry. """ drv = _get_cuda_driver() while mappings: va, span_size, mapped_chunks = mappings.pop() for rel, size in mapped_chunks: check_drv(drv.cuMemUnmap(int(va) + int(rel), int(size)), "cuMemUnmap") check_drv(drv.cuMemAddressFree(int(va), int(span_size)), "cuMemAddressFree") def _send_fd(sock, fd: int, src_rank: int, base_idx: int) -> None: import array import socket fds = array.array("i", [int(fd)]) header = struct.pack(" int: """Import a peer allocation, map it at a freshly reserved VA, return the VA.""" drv = _get_cuda_driver() imp_h = import_peer_handle( fabric_handle, fd, use_fabric=use_fabric, peer_rank=peer_rank ) prop = check_drv( drv.cuMemGetAllocationPropertiesFromHandle(imp_h), "cuMemGetAllocationPropertiesFromHandle", ) gran = check_drv( drv.cuMemGetAllocationGranularity( prop, drv.CUmemAllocationGranularity_flags.CU_MEM_ALLOC_GRANULARITY_RECOMMENDED, ), "cuMemGetAllocationGranularity", ) va = check_drv( drv.cuMemAddressReserve(alloc_size, int(gran), 0, 0), "cuMemAddressReserve" ) check_drv(drv.cuMemMap(int(va), alloc_size, 0, imp_h, 0), "cuMemMap") access = make_rw_access_desc(device_id) check_drv(drv.cuMemSetAccess(int(va), alloc_size, [access], 1), "cuMemSetAccess") check_drv(drv.cuMemRelease(imp_h), "cuMemRelease(peer)") return int(va) def map_chunk_into_span( fabric_handle, fd, span_va: int, rel: int, alloc_size: int, device_id: int, *, use_fabric: bool, peer_rank: int, ) -> None: """Import + map a peer chunk into a caller-reserved span at ``span_va + rel``.""" drv = _get_cuda_driver() imp_h = import_peer_handle( fabric_handle, fd, use_fabric=use_fabric, peer_rank=peer_rank ) check_drv( drv.cuMemMap(int(span_va) + rel, int(alloc_size), 0, imp_h, 0), "cuMemMap(span)", ) access = make_rw_access_desc(device_id) check_drv( drv.cuMemSetAccess(int(span_va) + rel, int(alloc_size), [access], 1), "cuMemSetAccess(span)", ) check_drv(drv.cuMemRelease(imp_h), "cuMemRelease(span)") class VmmGraphInputManager: def __init__( self, obj: Any, group: ProcessGroup, rank: int, world_size: int, ) -> None: self.obj = obj self.group = group self.rank = rank self.world_size = world_size self._peer_mappings = [] def register_graph_inputs(self): """Register graph capture inputs via VMM handle exchange. VMM-compatible path for expandable_segments. The C++ side deduplicates graph capture pointers into unique base allocations via cuMemGetAddressRange. Python exports handles for each unique base, imports + cuMemMaps peer allocations, then registers the peer VAs. FABRIC handles are preferred; POSIX file descriptors are used when FABRIC is unavailable. """ FABRIC_HANDLE_BYTES = 64 MAX_VMM_BASES = 4096 MAX_CHUNKS_PER_INPUT = 16 t0 = time.perf_counter() bases_info, input_chunk_indices, input_offsets = ( self.obj.get_graph_capture_bases() ) if not bases_info: return new_count = len(input_chunk_indices) num_bases = len(bases_info) device_id = torch.cuda.current_device() if num_bases > MAX_VMM_BASES: raise RuntimeError( f"Too many VMM bases to share: {num_bases} > {MAX_VMM_BASES}" ) drv = _get_cuda_driver() local_posix_fds: List[int] = [] retained_handles = [] try: for base_ptr, _ in bases_info: alloc_h = check_drv( drv.cuMemRetainAllocationHandle(base_ptr), "cuMemRetainAllocationHandle", ) retained_handles.append(alloc_h) local_fabric_handles, local_posix_fds, use_fabric = ( export_shareable_handles(retained_handles, self.group, self.rank) ) local_input_chunks = [ [int(idx) for idx in indices] for indices in input_chunk_indices ] for chunks in local_input_chunks: if len(chunks) > MAX_CHUNKS_PER_INPUT: raise RuntimeError( "Too many VMM chunks for graph input: " f"{len(chunks)} > {MAX_CHUNKS_PER_INPUT}" ) # All-gather base metadata and per-input VMM spans. A captured tensor # can cross expandable-segment allocation boundaries, so peer mappings # must preserve each input's contiguous virtual-address span. FABRIC # handles are inline metadata; POSIX fds are exchanged separately via # SCM_RIGHTS because fd integers are process-local. header_struct = struct.Struct(" local VA peer_span_va = {} # (rank, chunk_indices...) -> (local VA, peer base) new_mappings = [] try: for peer_rank in range(self.world_size): if peer_rank == self.rank: for idx, (bp, _) in enumerate(bases_info): peer_base_va[(peer_rank, idx)] = int(bp) continue peer_bases = all_base_payload[peer_rank] for idx, (_, fb, alloc_size) in enumerate(peer_bases): fd = None if use_fabric else posix_peer_fds[(peer_rank, idx)] va = import_and_map_alloc( fb, fd, alloc_size, device_id, use_fabric=use_fabric, peer_rank=peer_rank, ) peer_base_va[(peer_rank, idx)] = va new_mappings.append((va, alloc_size, [(0, alloc_size)])) # Build per-input peer VA lists and register. peer_ptrs = [] for j in range(new_count): ptrs_j = [] for rank in range(self.world_size): chunks = all_input_chunks[rank][j] off = all_input_offsets[rank][j] if len(chunks) == 1: ptrs_j.append(peer_base_va[(rank, chunks[0])] + off) continue span_key = (rank, *chunks) if span_key not in peer_span_va: peer_bases = all_base_payload[rank] first_base = peer_bases[chunks[0]][0] last_base, _, last_size = peer_bases[chunks[-1]] span_size = ( int(last_base) + int(last_size) - int(first_base) ) if rank == self.rank: span_va = int(first_base) else: span_va = check_drv( drv.cuMemAddressReserve(span_size, 0, 0, 0), "cuMemAddressReserve(span)", ) mapped_chunks = [] for chunk_idx in chunks: base_ptr, fb, alloc_size = peer_bases[chunk_idx] rel = int(base_ptr) - int(first_base) fd = ( None if use_fabric else posix_peer_fds[(rank, chunk_idx)] ) map_chunk_into_span( fb, fd, span_va, rel, int(alloc_size), device_id, use_fabric=use_fabric, peer_rank=rank, ) mapped_chunks.append((rel, int(alloc_size))) new_mappings.append( (int(span_va), span_size, mapped_chunks) ) peer_span_va[span_key] = (int(span_va), int(first_base)) span_va, _ = peer_span_va[span_key] ptrs_j.append(span_va + off) peer_ptrs.append(ptrs_j) self.obj.register_peer_mapped_inputs(peer_ptrs) self._peer_mappings.extend(new_mappings) except Exception: release_mappings(new_mappings) raise finally: for fd in posix_peer_fds.values(): os.close(fd) elapsed_ms = (time.perf_counter() - t0) * 1000 transport = "FABRIC" if use_fabric else "POSIX fd" log_info_on_rank0( logger, f"Registered {new_count} cuda graph addresses via " f"{transport} handles ({num_bases} unique allocations) " f"in {elapsed_ms:.1f} ms", ) finally: for fd in local_posix_fds: os.close(fd) for h in retained_handles: check_drv(drv.cuMemRelease(h), "cuMemRelease(retained)") def close(self): if not self._peer_mappings: return release_mappings(self._peer_mappings)