from __future__ import annotations
import logging
import time
from typing import Any
import torch
from .types import ManifestMismatchError, TensorDescriptor
logger = logging.getLogger("modelexpress.nixl_transfer")
NIXL_AVAILABLE = False
NixlAgent = None
nixl_agent_config = None
try:
from nixl._api import nixl_agent as NixlAgent
from nixl._api import nixl_agent_config
NIXL_AVAILABLE = True
except ImportError:
pass
def is_nixl_available() -> bool:
return NIXL_AVAILABLE
class NixlTransferManager:
def __init__(self, agent_name: str, device_id: int, listen_port: int | None = None):
self._agent_name = agent_name
self._device_id = device_id
self._listen_port = listen_port
self._agent: Any = None
self._metadata: bytes = b""
self._tensor_descriptors: list[TensorDescriptor] = []
self._tensors: dict[str, torch.Tensor] = {}
self._registered_regions: list[tuple[int, int]] | None = None
@property
def agent_name(self) -> str:
return self._agent_name
@property
def nixl_metadata(self) -> bytes:
return self._metadata
@property
def tensor_descriptors(self) -> list[TensorDescriptor]:
return self._tensor_descriptors
def initialize(self) -> None:
if not NIXL_AVAILABLE:
raise RuntimeError("NIXL is not available")
if self._agent is not None:
return
torch.cuda.set_device(self._device_id)
if self._listen_port is not None and nixl_agent_config:
config = nixl_agent_config(
backends=["UCX"],
enable_listen_thread=True,
listen_port=self._listen_port,
)
logger.info(
f"NIXL listen thread enabled on port {self._listen_port}"
)
elif nixl_agent_config:
config = nixl_agent_config(backends=["UCX"])
else:
config = None
self._agent = NixlAgent(self._agent_name, config)
logger.info(f"NIXL agent '{self._agent_name}' created on device {self._device_id}")
def register_tensors(self, tensors: dict[str, torch.Tensor]) -> bytes:
import os
if self._agent is None:
raise RuntimeError("NIXL agent not initialized")
self._tensors = tensors
tensor_descriptors = []
for name, tensor in tensors.items():
if not tensor.is_contiguous():
raise ManifestMismatchError(
f"Tensor '{name}' is not contiguous. "
"Non-contiguous tensors cannot be used for RDMA transfers."
)
tensor_descriptors.append(TensorDescriptor(
name=name,
addr=tensor.data_ptr(),
size=tensor.numel() * tensor.element_size(),
device_id=self._device_id,
dtype=str(tensor.dtype),
))
self._tensor_descriptors = tensor_descriptors
use_contiguous = os.environ.get("MX_CONTIGUOUS_REG", "0") == "1"
if use_contiguous:
regions = self._find_contiguous_regions(tensor_descriptors)
logger.info(
f"[Contiguous Registration] Found {len(regions)} contiguous regions "
f"from {len(tensor_descriptors)} tensors "
f"({(1 - len(regions)/len(tensor_descriptors))*100:.1f}% reduction)"
)
region_tuples = [(r[0], r[1], self._device_id, "cuda") for r in regions]
self._agent.register_memory(region_tuples, mem_type="cuda", backends=["UCX"])
self._registered_regions = regions
logger.info(f"Registered {len(regions)} contiguous regions with NIXL")
if len(regions) > 0:
logger.info(f"[Contiguous Registration] DEBUG - First 3 regions: {[(hex(r[0]), r[1]) for r in regions[:3]]}")
else:
tensor_list = list(tensors.values())
self._agent.register_memory(tensor_list, backends=["UCX"])
self._registered_regions = None
logger.info(f"Registered {len(tensor_list)} individual tensors with NIXL")
self._metadata = self._agent.get_agent_metadata()
return self._metadata
def get_registered_descriptors(self) -> list[TensorDescriptor]:
if self._registered_regions is not None:
return [
TensorDescriptor(
name=f"__region_{i}__",
addr=addr,
size=size,
device_id=self._device_id,
dtype="contiguous_region",
)
for i, (addr, size) in enumerate(self._registered_regions)
]
else:
return self._tensor_descriptors
def _find_contiguous_regions(
self, descriptors: list[TensorDescriptor]
) -> list[tuple[int, int]]:
if not descriptors:
return []
sorted_descs = sorted(descriptors, key=lambda d: d.addr)
regions = []
current_start = sorted_descs[0].addr
current_end = current_start + sorted_descs[0].size
for desc in sorted_descs[1:]:
if desc.addr == current_end:
current_end = desc.addr + desc.size
else:
regions.append((current_start, current_end - current_start))
current_start = desc.addr
current_end = desc.addr + desc.size
regions.append((current_start, current_end - current_start))
return regions
def fetch_remote_and_wait(
self,
remote_agent_name: str,
ip: str,
port: int,
timeout_seconds: float = 120.0,
) -> None:
if self._agent is None:
raise RuntimeError("NIXL agent not initialized")
logger.info(
f"Fetching remote metadata from {remote_agent_name} at {ip}:{port}"
)
self._agent.fetch_remote_metadata(remote_agent_name, ip, port)
start = time.perf_counter()
while True:
if time.perf_counter() - start >= timeout_seconds:
raise TimeoutError(
f"Timed out waiting for remote metadata from "
f"{remote_agent_name} at {ip}:{port}"
)
if self._agent.check_remote_metadata(remote_agent_name):
logger.info(
f"Remote metadata loaded for {remote_agent_name} "
f"({time.perf_counter() - start:.2f}s)"
)
return
time.sleep(0.01)
def receive_from_source(
self,
source_metadata: bytes,
source_tensors: list[TensorDescriptor],
timeout_seconds: float | None = None,
coalesce_transfers: bool = True,
remote_agent_name: str | None = None,
) -> tuple[int, int, float]:
if self._agent is None:
raise RuntimeError("NIXL agent not initialized")
start_time = time.perf_counter()
torch.cuda.set_device(self._device_id)
if remote_agent_name is None:
add_start = time.perf_counter()
remote_agent_name = self._agent.add_remote_agent(source_metadata)
add_time = time.perf_counter() - add_start
logger.info(
f"[TIMING] add_remote_agent: {add_time:.3f}s "
f"(agent={remote_agent_name}, blob={len(source_metadata)} bytes)"
)
else:
logger.info(f"Using pre-loaded remote agent {remote_agent_name}")
is_region_transfer = (
len(source_tensors) > 0 and
source_tensors[0].name.startswith("__region_")
)
if is_region_transfer:
if self._registered_regions is None:
logger.error("Source sent region descriptors but we didn't register regions!")
logger.error("Set MX_CONTIGUOUS_REG=1 on target to enable region transfer")
raise ManifestMismatchError("Region transfer mismatch: target must also use MX_CONTIGUOUS_REG=1")
logger.info(f"Region-based transfer: {len(source_tensors)} source regions -> {len(self._registered_regions)} local regions")
if len(source_tensors) != len(self._registered_regions):
logger.warning(
f"Region count mismatch: source has {len(source_tensors)}, "
f"local has {len(self._registered_regions)}. Proceeding with min."
)
remote_descs = []
local_descs = [] total_bytes = 0
matched_count = min(len(source_tensors), len(self._registered_regions))
for i in range(matched_count):
src_region = source_tensors[i]
local_addr, local_size = self._registered_regions[i]
if src_region.size != local_size:
logger.warning(f"Region {i} size mismatch: source={src_region.size}, local={local_size}")
remote_descs.append((src_region.addr, src_region.size, src_region.device_id))
local_descs.append((local_addr, local_size, self._device_id))
total_bytes += src_region.size
matched_tensors = matched_count
use_raw_descriptors = True
coalesced_count = matched_count
logger.info(f"[Region Transfer] Matched {matched_count} regions, {total_bytes / 1e9:.2f} GB")
if matched_count > 0:
logger.info(f"[Region Transfer] DEBUG - First 3 source regions: {[(hex(r[0]), r[1]) for r in remote_descs[:3]]}")
logger.info(f"[Region Transfer] DEBUG - First 3 local regions: {[(hex(r[0]), r[1]) for r in local_descs[:3]]}")
else:
remote_descs = []
local_tensor_list = []
total_bytes = 0
matched_tensors = 0
skipped_source = []
skipped_target = []
source_names = {t.name for t in source_tensors}
local_names = set(self._tensors.keys())
source_only = source_names - local_names
target_only = local_names - source_names
if source_only:
skipped_source = sorted(source_only)
logger.warning(
f"[Manifest Mismatch] {len(source_only)} source tensors have no "
f"local match (will NOT be transferred): "
f"{skipped_source[:5]}{'...' if len(skipped_source) > 5 else ''}"
)
if target_only:
skipped_target = sorted(target_only)
logger.warning(
f"[Manifest Mismatch] {len(target_only)} local tensors have no "
f"source match (will keep dummy values): "
f"{skipped_target[:5]}{'...' if len(skipped_target) > 5 else ''}"
)
if source_only or target_only:
raise ManifestMismatchError(
f"[Manifest Mismatch] Source has {len(source_names)} tensors, "
f"target has {len(local_names)} tensors, "
f"{len(source_names & local_names)} matched. "
f"Source-only: {skipped_source[:5]}, "
f"Target-only: {skipped_target[:5]}. "
f"Aborting transfer to prevent incorrect inference."
)
for src_tensor in source_tensors:
if src_tensor.name not in self._tensors:
continue
local_tensor = self._tensors[src_tensor.name]
local_size = local_tensor.numel() * local_tensor.element_size()
if src_tensor.size != local_size:
raise ManifestMismatchError(
f"[Manifest Mismatch] Tensor '{src_tensor.name}' size mismatch: "
f"source={src_tensor.size}, target={local_size}. "
f"Aborting transfer to prevent incorrect inference."
)
remote_descs.append((src_tensor.addr, src_tensor.size, src_tensor.device_id))
local_tensor_list.append(local_tensor)
total_bytes += src_tensor.size
matched_tensors += 1
if not remote_descs:
logger.warning("No matching tensors found for transfer")
return 0, 0, 0.0
local_descs = local_tensor_list
use_raw_descriptors = False
coalesced_count = matched_tensors
if is_region_transfer:
logger.info(f"[Region Transfer] Skipping coalesce - already optimized with {coalesced_count} regions")
elif coalesce_transfers:
logger.info(f"[Coalesce] Starting coalescing of {len(remote_descs)} descriptors...")
remote_descs, local_descs, coalesced_count = self._coalesce_transfers(
remote_descs, local_tensor_list
)
reduction_pct = (1 - coalesced_count / matched_tensors) * 100 if matched_tensors > 0 else 0
logger.info(
f"[Coalesce] Reduced {matched_tensors} descriptors -> {coalesced_count} regions "
f"({reduction_pct:.1f}% reduction)"
)
use_raw_descriptors = True
else:
logger.info(f"[Coalesce] DISABLED - transferring {matched_tensors} individual tensors")
local_descs = local_tensor_list
use_raw_descriptors = False
coalesced_count = matched_tensors
src_prepped = self._agent.prep_xfer_dlist(
agent_name=remote_agent_name,
xfer_list=remote_descs,
mem_type="cuda",
backends=["UCX"],
)
if use_raw_descriptors:
dst_prepped = self._agent.prep_xfer_dlist(
agent_name="",
xfer_list=local_descs,
mem_type="cuda",
backends=["UCX"],
)
else:
dst_prepped = self._agent.prep_xfer_dlist(
agent_name="",
xfer_list=local_descs,
mem_type="cuda",
backends=["UCX"],
)
indices = list(range(len(remote_descs)))
handle = self._agent.make_prepped_xfer(
operation="READ",
local_xfer_side=dst_prepped,
local_indices=indices,
remote_xfer_side=src_prepped,
remote_indices=indices,
backends=["UCX"],
)
self._agent.transfer(handle)
start_wait = time.perf_counter()
while True:
if timeout_seconds is not None and time.perf_counter() - start_wait >= timeout_seconds:
self._agent.release_xfer_handle(handle)
raise TimeoutError("Transfer timed out")
status = self._agent.check_xfer_state(handle)
if status in ("DONE", "SUCCESS"):
self._agent.release_xfer_handle(handle)
break
if status in ("ERR", "ERROR", "FAIL"):
self._agent.release_xfer_handle(handle)
raise RuntimeError(f"NIXL transfer failed with status {status}")
time.sleep(0.001)
torch.cuda.synchronize(self._device_id)
duration = time.perf_counter() - start_time
bandwidth_gbps = (total_bytes * 8) / (duration * 1e9) if duration > 0 else 0.0
if coalesce_transfers and coalesced_count < matched_tensors:
logger.info(
f"Transfer complete: {matched_tensors} tensors ({coalesced_count} regions), "
f"{total_bytes / 1e9:.2f} GB in {duration:.2f}s "
f"({bandwidth_gbps:.1f} Gbps)"
)
else:
logger.info(
f"Transfer complete: {matched_tensors} tensors, "
f"{total_bytes / 1e9:.2f} GB in {duration:.2f}s "
f"({bandwidth_gbps:.1f} Gbps)"
)
return total_bytes, matched_tensors, duration
def _coalesce_transfers(
self,
remote_descs: list[tuple[int, int, int]],
local_tensors: list[torch.Tensor],
) -> tuple[list[tuple[int, int, int]], list[tuple[int, int, int]], int]:
if len(remote_descs) <= 1:
if local_tensors:
t = local_tensors[0]
local_descs = [(t.data_ptr(), t.numel() * t.element_size(), self._device_id)]
else:
local_descs = []
return remote_descs, local_descs, len(remote_descs)
indexed = []
for remote, local in zip(remote_descs, local_tensors, strict=True):
local_addr = local.data_ptr()
local_size = local.numel() * local.element_size()
indexed.append((remote, local_addr, local_size))
indexed.sort(key=lambda x: x[0][0])
coalesced_remote = []
coalesced_local = []
i = 0
while i < len(indexed):
start_remote_addr = indexed[i][0][0]
start_local_addr = indexed[i][1]
current_remote_end = start_remote_addr + indexed[i][0][1]
current_local_end = start_local_addr + indexed[i][2]
device_id = indexed[i][0][2]
j = i + 1
while j < len(indexed):
next_remote_addr = indexed[j][0][0]
next_remote_size = indexed[j][0][1]
next_local_addr = indexed[j][1]
next_local_size = indexed[j][2]
next_device = indexed[j][0][2]
remote_contiguous = (next_remote_addr == current_remote_end)
local_contiguous = (next_local_addr == current_local_end)
same_device = (next_device == device_id)
if remote_contiguous and local_contiguous and same_device:
current_remote_end = next_remote_addr + next_remote_size
current_local_end = next_local_addr + next_local_size
j += 1
else:
break
total_remote_size = current_remote_end - start_remote_addr
total_local_size = current_local_end - start_local_addr
coalesced_remote.append((start_remote_addr, total_remote_size, device_id))
coalesced_local.append((start_local_addr, total_local_size, self._device_id))
i = j
original_count = len(remote_descs)
coalesced_count = len(coalesced_remote)
if coalesced_count < original_count:
reduction_pct = 100 * (1 - coalesced_count / original_count)
logger.info(
f"Coalesced {original_count} tensors into {coalesced_count} regions "
f"({reduction_pct:.1f}% reduction in descriptors)"
)
return coalesced_remote, coalesced_local, coalesced_count
def is_healthy(self) -> bool:
return self._agent is not None and len(self._metadata) > 0
def shutdown(self) -> None:
self._agent = None
self._metadata = b""
self._tensor_descriptors.clear()
self._tensors.clear()
logger.info("NixlTransferManager shutdown complete")