ProprietaryLegal's picture
PLI Labs public snapshot 2026-10-28 (history squashed)
e1acc15
Raw History Blame Contribute Delete
11.1 kB
"""NVLink peer-to-peer collectives for the TP = 2 worker (p2p.cu): build / load and the per-rank state.
The extension is compiled once with torch.utils.cpp_extension (nvcc from CUDA 12.6, sm_86 only) into
$DSPARK_P2P_BUILD (default ~/dsv41-offload/e/build/p2p); later loads reuse the build while the source is unchanged.
"""
from __future__ import annotations
import math
import os
import time
from collections.abc import Iterable
from types import ModuleType
import torch
_EXT: ModuleType | None = None
SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)), "p2p.cu")
DEFAULT_TIMEOUT_MS = 2000.0
def ext() -> ModuleType:
global _EXT
if _EXT is None and os.environ.get("DSPARK_P2P_PREBUILT", "").strip():
# production: the pinned build of exactly this p2p.cu, never a compile at start (prebuilt.py)
from .prebuilt import load_prebuilt
_EXT = load_prebuilt(os.environ["DSPARK_P2P_PREBUILT"].strip(), SRC)
if _EXT is None:
from torch.utils.cpp_extension import load
cuda = os.environ.get("DSPARK_P2P_CUDA_HOME", "/usr/local/cuda-12.6")
os.environ["CUDA_HOME"] = cuda
if os.path.join(cuda, "bin") not in os.environ.get("PATH", "").split(":"):
os.environ["PATH"] = os.path.join(cuda, "bin") + ":" + os.environ.get("PATH", "")
build = os.path.expanduser(os.environ.get("DSPARK_P2P_BUILD", "~/dsv41-offload/e/build/p2p"))
os.makedirs(build, exist_ok=True)
_EXT = load(name="dspark_p2p", sources=[SRC], build_directory=build, verbose=False,
extra_cflags=["-O2"], extra_cuda_cflags=["-O3", "-gencode=arch=compute_86,code=sm_86"])
return _EXT
def preload(dev: torch.device) -> None:
"""Launch every collective kernel once on `dev` with itself as the peer (each passes its own handshake at once).
CUDA lazy loading may synchronize the context on a kernel's first launch; a first launch while a collective
kernel spins on the same device, waiting for work the blocked host has not issued yet, would deadlock until the
timeout (CUDA programming guide, lazy loading: concurrent execution). Run before any collective is in flight."""
e = ext()
with torch.cuda.device(dev):
st = RankState(dev, 4 * 5120)
x = torch.ones(5120, dtype=torch.float32, device=dev)
e.ar_sum(x, torch.empty_like(x), st.buf, st.ptr, st.max_words, 1, int(1e9))
e.ar_sum(x, torch.empty(5120, dtype=torch.bfloat16, device=dev), st.buf, st.ptr, st.max_words, 1, int(1e9))
v = torch.zeros(1, dtype=torch.float32, device=dev)
i = torch.zeros(1, dtype=torch.int64, device=dev)
out = torch.zeros(1, 1, dtype=torch.int64, device=dev)
e.xchg_argmax(v, i, 0, 1, out, 0, True, st.buf, st.ptr, st.max_words, int(1e9))
e.xchg_margin(torch.zeros(1, 2, dtype=torch.float32, device=dev), i, 0, 1, out,
torch.zeros(1, 1, dtype=torch.float32, device=dev), 0, True, st.buf, st.ptr, st.max_words,
int(1e9))
e.xchg_markov(i, torch.zeros(1, dtype=torch.int32, device=dev), torch.zeros(1, 1, device=dev),
torch.zeros(1, 1, dtype=torch.int64, device=dev), torch.zeros(1, 1, device=dev),
torch.zeros(1, 1, dtype=torch.int64, device=dev), 0, 1, out, 0, True, st.buf, st.ptr,
st.max_words, int(1e9))
torch.cuda.synchronize(dev)
h = [int(a) for a in st.hdr[:5].cpu()]
if h[2] != 0 or h[4] != 5:
raise RuntimeError(f"p2p preload on {dev}: header {h}")
e.cancel(st.buf)
e.reset(st.buf)
torch.cuda.synchronize(dev)
class RankState:
"""One rank's state block (header, flags, double receive buffer) in its own device memory."""
def __init__(self, dev: torch.device, max_words: int):
self.dev = dev
self.max_words = max_words
self.buf = ext().allocate_state(max_words, dev.index)
self.hdr = self.buf[:64].view(torch.int32) # epoch, done, error, err_epoch, count, ...
@property
def ptr(self) -> int:
return self.buf.data_ptr()
def check_allocator() -> None:
"""Async Torch allocation remains unsupported. Peer blocks use native cudaMalloc independently of Torch's
expandable-segment configuration, including runtime changes made by exllamav3's Python import."""
for var in ("PYTORCH_CUDA_ALLOC_CONF", "PYTORCH_ALLOC_CONF"):
v = os.environ.get(var, "").replace(" ", "").lower()
if "backend:cudamallocasync" in v:
raise RuntimeError(f"{var}={os.environ[var]!r}: the p2p worker has not qualified cudaMallocAsync")
def wait_idle(devs: Iterable[torch.device], timeout_s: float,
streams: Iterable[torch.cuda.Stream] = ()) -> None:
"""Wait until every device's current stream is idle, at most timeout_s (raises TimeoutError): never an unbounded
synchronize on a path that runs after a failure, where a wedged GPU would block the worker forever."""
evs = []
all_streams = dict.fromkeys([*streams, *(torch.cuda.current_stream(d) for d in dict.fromkeys(devs))])
for stream in all_streams:
d = stream.device
with torch.cuda.device(d):
ev = torch.cuda.Event()
ev.record(stream)
evs.append((d, ev))
deadline = time.monotonic() + timeout_s
for d, ev in evs:
while not ev.query():
if time.monotonic() > deadline:
raise TimeoutError(f"device {d} did not go idle within {timeout_s:.1f} s")
time.sleep(0.0005)
class P2PPair:
"""The two ranks' states with peer access enabled both ways. Collective calls launch on the calling rank's
current stream; both ranks must issue the same sequence of collectives (TPDrafter does). vocab bounds the ids an
exchange accepts from the peer."""
def __init__(self, devs: list[torch.device], max_words: int, timeout_ms: float | None = None,
vocab: int = 1 << 62):
if len(devs) != 2 or devs[0] == devs[1] or any(d.type != "cuda" or d.index is None for d in devs):
raise ValueError(f"P2PPair needs two distinct devices, got {devs}")
if max_words <= 0 or max_words % 4 or vocab <= 0:
raise ValueError("P2PPair requires positive 4-aligned max_words and positive vocab")
if timeout_ms is None:
timeout_ms = float(os.environ.get("DSPARK_TP_TIMEOUT_MS", DEFAULT_TIMEOUT_MS))
if not math.isfinite(timeout_ms) or timeout_ms <= 0:
raise ValueError(f"P2PPair requires finite positive timeout_ms, got {timeout_ms}")
check_allocator()
self.poisoned: str | None = None
self.launch_streams: set[torch.cuda.Stream] = set()
self.vocab = int(vocab)
e = ext()
e.enable_peer(devs[0].index, devs[1].index)
e.enable_peer(devs[1].index, devs[0].index)
for d in devs:
preload(d)
self.devs = devs
self.max_words = max_words
self.timeout_ns = int(timeout_ms * 1e6)
self.idle_timeout_s = max(10.0, 20 * timeout_ms / 1000.0) # host waits on a failure path are bounded too
self.st = [RankState(d, max_words) for d in devs]
self.cancel_streams = [torch.cuda.Stream(d) for d in devs]
for d in devs:
torch.cuda.synchronize(d)
def peer(self, r: int) -> int:
return self.st[1 - r].ptr
def poison(self, reason: str) -> None:
"""Host-only latch. A CUDA exception requires process restart, never header clearing or buffer reuse."""
if self.poisoned is None:
self.poisoned = reason
def _usable(self) -> None:
if self.poisoned is not None:
raise RuntimeError(f"TP CUDA context poisoned; restart worker: {self.poisoned}")
def _launch(self, r: int, name: str, *args: object) -> None:
self._usable()
try:
# Retain every eager stream: checking only the caller's current stream can miss a parked collective.
stream = torch.cuda.current_stream(self.devs[r])
self.launch_streams.add(stream)
getattr(ext(), name)(*args)
except RuntimeError as error:
self.poison(str(error))
raise
def ar_sum(self, r: int, x: torch.Tensor, out: torch.Tensor, nblocks: int) -> torch.Tensor:
self._launch(r, "ar_sum", x, out, self.st[r].buf, self.peer(r), self.max_words, nblocks, self.timeout_ns)
return out
def xchg_argmax(self, r: int, val: torch.Tensor, idx: torch.Tensor, v_off: int, out: torch.Tensor,
col: int) -> None:
self._launch(r, "xchg_argmax", val, idx, v_off, self.vocab, out, col, r == 0, self.st[r].buf, self.peer(r),
self.max_words, self.timeout_ns)
def xchg_margin(self, r: int, top2: torch.Tensor, idx: torch.Tensor, v_off: int, out: torch.Tensor,
mg: torch.Tensor, col: int) -> None:
self._launch(r, "xchg_margin", top2, idx, v_off, self.vocab, out, mg, col, r == 0, self.st[r].buf, self.peer(r),
self.max_words, self.timeout_ns)
def xchg_markov(self, r: int, prev: torch.Tensor, slot_of_token: torch.Tensor, parts: tuple, v_off: int,
out: torch.Tensor, col: int) -> None:
vals_c, idx_c, vals_u, idx_u = parts
self._launch(r, "xchg_markov", prev, slot_of_token, vals_c, idx_c, vals_u, idx_u, v_off, self.vocab, out, col, r == 0,
self.st[r].buf, self.peer(r), self.max_words, self.timeout_ns)
def headers(self) -> list[list[int]]:
"""Host read of both headers once both devices are idle (bounded wait: TimeoutError)."""
self._usable()
try:
wait_idle(self.devs, self.idle_timeout_s, self.launch_streams)
out = []
for s in self.st:
with torch.cuda.device(s.dev):
out.append([int(v) for v in s.hdr[:5].cpu()])
return out
except (RuntimeError, TimeoutError) as error:
self.poison(str(error))
raise
def cancel(self) -> None:
"""Abort both ranks on independent, pre-created streams. Buffers remain owned until idle/reset completes."""
self._usable()
for r, stream in enumerate(self.cancel_streams):
with torch.cuda.device(self.devs[r]), torch.cuda.stream(stream):
self._launch(r, "cancel", self.st[r].buf)
def reset(self) -> None:
"""Zero both headers and flag sets. Only with both devices idle (bounded waits before and after)."""
self._usable()
try:
wait_idle(self.devs, self.idle_timeout_s, self.launch_streams)
for s in self.st:
with torch.cuda.device(s.dev):
ext().reset(s.buf)
wait_idle(self.devs, self.idle_timeout_s)
self.launch_streams.clear()
except (RuntimeError, TimeoutError) as error:
self.poison(str(error))
raise