# SPDX-License-Identifier: Apache-2.0 """Adapter to lane O1's transport ``ofl`` (offload/transport/API.md, commit 41cf2f7+). Endpoint forms (``bind`` = this side's direct-link IPv4; it also selects the RDMA device and GID, API.md "Selecting device / port / GID"): o1rdma://10.202.28.1:18620?bind=10.202.28.2 RDMA RC (the fast path) o1tcp://10.202.28.1:18620?bind=10.202.28.2 ofl's TCP backend Mapping onto the contract (CONTRACT 5): one contract message = one ofl request. ofl op = AppHeader.op (1..5; ofl's op 0 is its built-in echo) ofl generation = AppHeader.seq (echoed by ofl; the contract's own echo is checked too) ofl payload = the whole contract message (AppHeader + body) ofl response = the whole contract reply frame, RDMA-written into the registered reply buffer (one range) The ofl library and its ctypes binding are loaded from VLLM_DSPARK_REMOTE_OFL_DIR (a directory holding libofl.so and ofl.py, bind-mounted into the container), else from OFL_LIB's directory. Timeouts break the ofl connection by design (the QP goes to error before the call returns, so late data never lands); the proxy then reconnects. """ from __future__ import annotations import ctypes import importlib.util import os import struct import sys from .transport import Endpoint, RpcClient, TransportError, TransportTimeout SERVICE = "dspark-v1" DEFAULT_MAX_REQ = 16 << 20 # >= 32 + 192 + 512 rows x 30,720 B = 15.73 MB (CONTRACT 8.1) DEFAULT_MAX_RESP = 64 << 10 # STEP replies are < 1 KiB; STATS JSON fits _OP_SEQ = struct.Struct(" tuple[int, int]: """Address and length of a contiguous writable buffer (bytearray, numpy, memoryview).""" if hasattr(buf, "ctypes") and hasattr(buf, "nbytes"): if not buf.flags.c_contiguous: raise TransportError("buffer must be contiguous") return int(buf.ctypes.data), int(buf.nbytes) mv = memoryview(buf) if mv.readonly: raise TransportError("buffer must be writable (ctypes needs its address)") if not mv.contiguous: raise TransportError("buffer must be contiguous") c = (ctypes.c_char * mv.nbytes).from_buffer(mv.cast("B")) return ctypes.addressof(c), mv.nbytes class O1Client(RpcClient): def __init__(self, ep: Endpoint): if ep.scheme not in ("o1tcp", "o1rdma"): raise TransportError(f"O1Client cannot serve {ep.scheme}://") self.ofl = load_ofl() self.ep = ep self.backend = self.ofl.BACKEND_RDMA if ep.scheme == "o1rdma" else self.ofl.BACKEND_TCP self.local_ip = ep.options.get("bind") if not self.local_ip: raise TransportError( f"endpoint {ep}: ?bind= is required " "(it selects the RDMA device and GID)") self.max_req = int(ep.options.get("max_req", DEFAULT_MAX_REQ)) self.max_resp = int(ep.options.get("max_resp", DEFAULT_MAX_RESP)) self.gid_index = int(ep.options.get("gid", -1)) self.rdma_device = ep.options.get("dev") self._c = None self._regions: dict[int, tuple[int, int, int]] = {} # id(buf) -> (addr, len, region) self._keep: list[object] = [] self._res = None self._seg = None @property def connected(self) -> bool: return self._c is not None def connect(self, timeout_s: float) -> None: self.close() try: self._c = self.ofl.Client( self.backend, self.ep.host, self.ep.port, self.local_ip, service=SERVICE, max_req=self.max_req, max_resp=self.max_resp, inflight=1, rdma_device=self.rdma_device, gid_index=self.gid_index, connect_timeout_ms=max(1, int(timeout_s * 1000))) except self.ofl.OflError as e: self._c = None if e.code == -3: raise TransportTimeout(f"ofl connect {self.ep}: {e}") from e raise TransportError(f"ofl connect {self.ep}: {e}") from e self._regions.clear() self._keep.clear() self._res = self.ofl.Result() self._seg = (self.ofl._Seg * 1)() def _region_for(self, buf) -> tuple[int, int]: addr, n = _addr(buf) hit = self._regions.get(id(buf)) if hit is not None and hit[0] == addr and hit[1] == n: return hit[2], n if n < self.max_resp: raise TransportError(f"reply buffer of {n} B < max_resp {self.max_resp}") try: region = self._c.reg(buf) except self.ofl.OflError as e: raise TransportError(f"ofl reg: {e}") from e self._regions[id(buf)] = (addr, n, region) self._keep.append(buf) return region, n def request(self, msg, resp_buf, timeout_s: float) -> int: if self._c is None: raise TransportError("not connected") addr, n = _addr(msg) if n > self.max_req: raise TransportError(f"message of {n} B > ofl max_req {self.max_req}") op, seq = _OP_SEQ.unpack_from(memoryview(msg).cast("B"), 6) return self._call(addr, n, op, seq, resp_buf, timeout_s) def request_parts(self, parts, resp_buf, timeout_s: float) -> int: """The parts are copied straight into ofl's send slot (ofl_client_payload_buf), and the call then skips its own payload copy: the same single copy request() costs, with no joined buffer in between.""" c = self._c if c is None: raise TransportError("not connected") spans = [_addr(p) for p in parts] n = sum(k for _, k in spans) if n > self.max_req: raise TransportError(f"message of {n} B > ofl max_req {self.max_req}") if not spans or spans[0][1] < _OP_SEQ.size + 6: raise TransportError("the first part must hold the AppHeader") op, seq = _OP_SEQ.unpack_from(memoryview(parts[0]).cast("B"), 6) pl, cap = c.payload_buf() if not pl: self.close() raise TransportError("ofl connection is broken (no payload slot)") if n > cap: raise TransportError(f"message of {n} B > ofl payload slot {cap} B") off = 0 for a, k in spans: ctypes.memmove(pl + off, a, k) off += k return self._call(pl, n, op, seq, resp_buf, timeout_s) def _call(self, addr: int, n: int, op: int, seq: int, resp_buf, timeout_s: float) -> int: c = self._c region, cap = self._region_for(resp_buf) seg = self._seg seg[0].region, seg[0].offset, seg[0].len = region, 0, min(cap, self.max_resp) L = self.ofl.lib() rc = L.ofl_client_call(c.h, op, seq, addr, n, seg, 1, ctypes.byref(self._res), int(timeout_s * 1e9)) if rc != 0: msg_txt = (L.ofl_client_error(c.h) or b"").decode(errors="replace") name = self.ofl.ERRORS.get(rc, str(rc)) if rc == -3: # ETIMEDOUT: ofl already broke the connection self.close() raise TransportTimeout(f"ofl call: {name}: {msg_txt}") self.close() raise TransportError(f"ofl call: {name}: {msg_txt}") if self._res.generation != seq: self.close() raise TransportError(f"ofl generation echo {self._res.generation} != {seq}") return int(self._res.resp_len) def close(self) -> None: if self._c is not None: try: self._c.close() finally: self._c = None self._regions.clear() self._keep.clear()