ibverbs 2026.7.17__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. ibverbs-2026.7.17/CLAUDE.md +131 -0
  2. ibverbs-2026.7.17/LICENSE +28 -0
  3. ibverbs-2026.7.17/MANIFEST.in +3 -0
  4. ibverbs-2026.7.17/PKG-INFO +226 -0
  5. ibverbs-2026.7.17/README.md +201 -0
  6. ibverbs-2026.7.17/pyproject.toml +67 -0
  7. ibverbs-2026.7.17/setup.cfg +4 -0
  8. ibverbs-2026.7.17/setup.py +67 -0
  9. ibverbs-2026.7.17/src/ibverbs/__init__.py +86 -0
  10. ibverbs-2026.7.17/src/ibverbs/_ibverbs.c +60521 -0
  11. ibverbs-2026.7.17/src/ibverbs/_ibverbs.pyx +1735 -0
  12. ibverbs-2026.7.17/src/ibverbs/_libverbs.pxd +251 -0
  13. ibverbs-2026.7.17/src/ibverbs/_version.py +1 -0
  14. ibverbs-2026.7.17/src/ibverbs/cuda.py +244 -0
  15. ibverbs-2026.7.17/src/ibverbs/enums.py +214 -0
  16. ibverbs-2026.7.17/src/ibverbs/helpers.py +156 -0
  17. ibverbs-2026.7.17/src/ibverbs.egg-info/PKG-INFO +226 -0
  18. ibverbs-2026.7.17/src/ibverbs.egg-info/SOURCES.txt +33 -0
  19. ibverbs-2026.7.17/src/ibverbs.egg-info/dependency_links.txt +1 -0
  20. ibverbs-2026.7.17/src/ibverbs.egg-info/requires.txt +7 -0
  21. ibverbs-2026.7.17/src/ibverbs.egg-info/top_level.txt +1 -0
  22. ibverbs-2026.7.17/tests/_rc.py +105 -0
  23. ibverbs-2026.7.17/tests/conftest.py +108 -0
  24. ibverbs-2026.7.17/tests/test_comp_channel.py +68 -0
  25. ibverbs-2026.7.17/tests/test_cq.py +55 -0
  26. ibverbs-2026.7.17/tests/test_cuda_helpers.py +103 -0
  27. ibverbs-2026.7.17/tests/test_device.py +82 -0
  28. ibverbs-2026.7.17/tests/test_enums.py +61 -0
  29. ibverbs-2026.7.17/tests/test_gpudirect.py +146 -0
  30. ibverbs-2026.7.17/tests/test_helpers.py +104 -0
  31. ibverbs-2026.7.17/tests/test_import.py +13 -0
  32. ibverbs-2026.7.17/tests/test_loopback.py +176 -0
  33. ibverbs-2026.7.17/tests/test_pd_mr.py +104 -0
  34. ibverbs-2026.7.17/tests/test_qp.py +114 -0
  35. ibverbs-2026.7.17/tests/test_two_device.py +109 -0
@@ -0,0 +1,131 @@
1
+ # CLAUDE.md — ibverbs
2
+
3
+ Guidance for working on the `ibverbs` package (low-level libibverbs bindings).
4
+
5
+ ## What this is
6
+
7
+ A Cython wrapper over `libibverbs`. The goal is a **faithful, low-level** verbs
8
+ API in Python that a higher-level RDMA library can be built on — not a
9
+ transport itself. Keep it thin: one Python object per verbs object, minimal
10
+ policy, no hidden threads, no runtime dependencies, and **no torch/CUDA
11
+ linkage** (GPUDirect works via raw addresses / dma-buf fds).
12
+
13
+ ## Layout
14
+
15
+ ```
16
+ ibverbs/
17
+ pyproject.toml # build (setuptools+Cython), metadata, pytest markers
18
+ setup.py # Extension built via `pkg-config libibverbs`
19
+ src/ibverbs/
20
+ __init__.py # public re-exports + __version__
21
+ _libverbs.pxd # `cdef extern` declarations of <infiniband/verbs.h>
22
+ _ibverbs.pyx # all cdef classes + posting logic (the real binding)
23
+ enums.py # IntEnum/IntFlag mirrors of the rdma-core ABI
24
+ helpers.py # QPInfo, RC connect helpers, reg_tensor (pure Python)
25
+ cuda.py # optional GPUDirect: register_tensor/GpuMR (lazy libcuda)
26
+ tests/
27
+ conftest.py # device/port fixtures, RoCE GID selection, markers
28
+ _rc.py # HostBuffer + Endpoint + connected-pair helpers
29
+ test_*.py # unit + integration (see README feature table)
30
+ ```
31
+
32
+ ## Build & test
33
+
34
+ ```bash
35
+ # Dev venv lives at repo-root .venv (Python 3.14, has torch+cython+pytest+numpy).
36
+ uv pip install --python ../.venv -e . --no-build-isolation # rebuild after .pyx/.pxd edits
37
+ ../.venv/bin/python -m pytest -rs -q # run the suite
38
+ ```
39
+
40
+ - Editing `.py` (enums/helpers/tests): no rebuild needed.
41
+ - Editing `.pyx` / `.pxd`: **must rebuild** (`uv pip install -e . --no-build-isolation`).
42
+ - The Cython-generated `_ibverbs.c` and the `.so` are git-ignored.
43
+
44
+ ## Linkage model: dlopen, not direct link (portability)
45
+
46
+ The extension is compiled against the rdma-core headers but is **not** linked
47
+ against `libibverbs`. `setup.py` uses `pkg-config --cflags-only-I` for the
48
+ header path and links only `libdl`. At import, `_load_libibverbs()` in
49
+ `_ibverbs.pyx` `dlopen`s `libibverbs.so.1` (RTLD_NOW|RTLD_GLOBAL) and `dlsym`s
50
+ each exported verb into a typed function pointer (`_v_*`). Consequences:
51
+
52
+ - The `.so`'s only `NEEDED` is `libc` — no external dep for `auditwheel`, so a
53
+ clean manylinux wheel; missing libibverbs → clean `ImportError`.
54
+ - `ibv_reg_dmabuf_mr` is loaded **optionally** (may be NULL on rdma-core < 34);
55
+ `PD.reg_dmabuf_mr` raises a clear error if it's unavailable.
56
+ - Built as **abi3** (Limited API, floor 3.9) → one wheel for CPython 3.9+.
57
+ - Only the five `static inline` data-path verbs
58
+ (`ibv_post_send/recv`, `ibv_poll_cq`, `ibv_req_notify_cq`,
59
+ `ibv_post_srq_recv`) are still declared extern-from-header: they dispatch
60
+ through the provider op table and reference no exported symbol, so they
61
+ compile inline with zero symbol resolution (the fast path stays fast). This
62
+ is why a pure-ctypes binding is a poor fit and we use a compiled extension.
63
+
64
+ When adding a verb: declare its function-pointer type (`fp_*`), a `_v_*`
65
+ global, and a `_req_sym`/`dlsym` line in `_load_libibverbs`, then call `_v_*`.
66
+
67
+ ## How the C API is declared (`_libverbs.pxd`)
68
+
69
+ - Structs are declared **partially** — only the fields we touch. Cython trusts
70
+ the real header for layout, so this is safe even for stack-allocated structs.
71
+ - Enum-typed fields/params are declared as plain `int` (C converts implicitly);
72
+ this keeps the file small and robust across rdma-core versions.
73
+ - `ibv_reg_mr` and `ibv_query_port` are **macros** in the header, so we never
74
+ reference them by name: we `dlsym` `ibv_reg_mr_iova2` (called with
75
+ `iova == addr`) and the raw `ibv_query_port` symbol (called with a zeroed
76
+ attr struct, which is what the header's `___ibv_query_port` compat shim does).
77
+ - `ibv_send_wr.wr` is a C **union** (`rdma` / `atomic` / `ud`); declared with
78
+ named nested `cdef struct`s that are never emitted (extern), only used for
79
+ field-access type-checking.
80
+
81
+ ## Conventions in `_ibverbs.pyx`
82
+
83
+ - **RAII:** each `cdef class` frees its handle in `__dealloc__` and in an
84
+ idempotent `close()`; all are context managers.
85
+ - **Lifetime:** children hold a Python reference to their parent (MR→PD,
86
+ QP→PD+CQs+SRQ, CQ→Context/CompChannel) so the GC can't free a parent first.
87
+ - **GIL:** build C structs while holding the GIL, then wrap only the actual
88
+ `ibv_*` call in `with nogil:`. Coercing a Python object inside `nogil` is a
89
+ compile error — convert to a C local first.
90
+ - **Errors:** verbs failures raise `VerbsError(OSError)` carrying `errno`; a bad
91
+ *work completion* status does **not** raise (it's returned in the `WC`);
92
+ `WC.raise_for_status()` is opt-in.
93
+ - **Byte order:** `imm_data` crosses the wire in network order — `htonl` on
94
+ send, `ntohl` on completion, so Python always sees host order.
95
+ - **CQ ↔ event mapping:** `create_cq` passes `<void*>cq` as the CQ context, so
96
+ `CompChannel.get_cq_event()` can hand back the exact Python `CQ`.
97
+
98
+ ## Adding a wrapped verb
99
+
100
+ 1. Declare the C signature/struct fields in `_libverbs.pxd` (partial is fine).
101
+ 2. Add the method/class in `_ibverbs.pyx`; follow the RAII + GIL + error rules
102
+ above; add any new enum values to `enums.py`.
103
+ 3. Re-export new public names in `__init__.py` (and its `__all__`).
104
+ 4. Write a test in `tests/` — a unit test if it needs no wire, otherwise add to
105
+ the loopback/integration set. Rebuild, then `pytest`.
106
+
107
+ ## RDMA gotchas learned here (don't regress these)
108
+
109
+ - **RoCE GID selection matters.** Link-local (`fe80::`) GIDs do **not** loop
110
+ back or route to themselves on this fabric; use a global **RoCE v2** GID.
111
+ `tests/conftest.find_roce_gid` scores candidates accordingly.
112
+ - **Union aliasing.** In `post_send`, only populate the `wr` union member for
113
+ the actual opcode — writing `atomic.*` over `rdma.*` clobbers the rkey.
114
+ - **`char*` comparison.** Comparing device names must use `strcmp`, not `==`
115
+ (which compares pointers in Cython).
116
+ - **GPUDirect here.** `nvidia_peermem` may fail to load (kernel/driver skew);
117
+ the dma-buf path (`reg_dmabuf_mr` + `cuMemGetHandleForAddressRange`) is the
118
+ reliable route and needs no kernel module. `ibverbs.cuda.register_tensor`
119
+ wraps it — it stays torch-free (duck-typed on `data_ptr()`) and imports no
120
+ CUDA at build. Requirements learned the hard way: torch memory must be
121
+ VMM-backed (`PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True`); the dma-buf
122
+ export needs the **base page-aligned down and length page-aligned up**
123
+ (`register_tensor` handles this via `offset`/`iova`); and `ibv_mr.addr` is
124
+ **not** meaningful for dma-buf MRs, so `GpuMR` carries the real device VA.
125
+ - Loopback (two QPs on one port) is a real NIC round-trip and is the primary
126
+ integration signal; two-NIC tests skip when the NICs aren't mutually routable.
127
+
128
+ ## Hardware this was validated on
129
+
130
+ 12× Mellanox mlx5 NICs (RoCEv2, Ethernet link layer), 8× NVIDIA H100, CUDA
131
+ driver 580.x, rdma-core 61, kernel 6.16.
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Tristan Rice
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,3 @@
1
+ include README.md CLAUDE.md LICENSE MANIFEST.in pyproject.toml setup.py
2
+ include src/ibverbs/*.pyx src/ibverbs/*.pxd
3
+ recursive-include tests *.py
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: ibverbs
3
+ Version: 2026.7.17
4
+ Summary: Low-level Pythonic bindings for libibverbs (RDMA), with GPUDirect support
5
+ Author: Tristan Rice
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/tristanr/rdma4py
8
+ Project-URL: Repository, https://github.com/tristanr/rdma4py
9
+ Keywords: rdma,ibverbs,infiniband,roce,gpudirect,networking
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Cython
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: System :: Networking
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=7; extra == "test"
21
+ Requires-Dist: numpy; extra == "test"
22
+ Provides-Extra: gpu
23
+ Requires-Dist: torch; extra == "gpu"
24
+ Dynamic: license-file
25
+
26
+ # ibverbs
27
+
28
+ Low-level, Pythonic bindings for **libibverbs** (RDMA), designed as a
29
+ foundation for building high-performance RDMA libraries in Python — including
30
+ **GPUDirect** transfers to/from GPU memory.
31
+
32
+ The bindings are a thin, faithful wrapper over the verbs API (device, PD, MR,
33
+ CQ, QP, SRQ, AH, work requests, completions, async events) plus a small,
34
+ optional set of RC connection helpers. They are written in Cython so the data
35
+ path (`post_send` / `post_recv` / `poll`) compiles to direct C calls and
36
+ releases the GIL, and so the `static inline` verbs fast-path functions are
37
+ called correctly (they can't be reached through `dlsym`).
38
+
39
+ - **No runtime dependencies.** Only libibverbs, which is `dlopen`ed at import.
40
+ - **No torch / CUDA linkage.** GPUDirect works by registering an integer
41
+ device address or an exported dma-buf fd; CUDA stays entirely in your code.
42
+ - **One `abi3` wheel for all of CPython 3.9+** on Linux.
43
+
44
+ ## Portability
45
+
46
+ The extension does **not** link `libibverbs`. It is compiled against the
47
+ rdma-core headers (for struct layouts and the `static inline` data-path verbs)
48
+ but resolves the exported verbs at import time with `dlopen`/`dlsym`. As a
49
+ result:
50
+
51
+ - The compiled module's only `NEEDED` library is `libc` — no external
52
+ dependency for `auditwheel`, so a single **manylinux** wheel is portable
53
+ across distros.
54
+ - It is built against the **CPython Limited API (abi3)**, so one wheel works on
55
+ CPython **3.9 through 3.14+** — no per-version builds.
56
+ - A missing `libibverbs` yields a clean `ImportError`, not a loader crash.
57
+ - Newer verbs are optional: `ibv_reg_dmabuf_mr` (rdma-core ≥ 34) is loaded if
58
+ present and only errors if you actually call `reg_dmabuf_mr`, so the wheel
59
+ still imports on older systems.
60
+
61
+ At runtime you only need `libibverbs.so.1` (any `rdma-core` from the last
62
+ several years). The data path (`post_send`/`poll`/…) stays compiled inline and
63
+ dispatches through the provider op table, so `dlopen` costs nothing on the hot
64
+ path.
65
+
66
+ ## Requirements
67
+
68
+ - Linux with an RDMA-capable NIC (tested on Mellanox/NVIDIA **mlx5**, RoCEv2).
69
+ - **Runtime:** `libibverbs.so.1` (`rdma-core` — `libibverbs1` on Debian/Ubuntu,
70
+ `libibverbs` on RHEL/Fedora). No compiler or headers needed to *use* a wheel.
71
+ - **Build from source only:** a C compiler, Cython, and the `rdma-core`
72
+ development headers (`libibverbs-dev` / `rdma-core-devel`).
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ pip install ibverbs # prebuilt abi3 manylinux wheel (once published)
78
+ ```
79
+
80
+ Building from source (needs the rdma-core dev headers + a compiler):
81
+
82
+ ```bash
83
+ pip install "Cython>=3.0" "setuptools>=77" wheel
84
+ pip install ./ibverbs # or: pip install -e ./ibverbs
85
+ ```
86
+
87
+ ## Quickstart
88
+
89
+ ```python
90
+ import ibverbs as ib
91
+
92
+ # 1. Open a device and set up resources.
93
+ dev = ib.get_device_list()[0]
94
+ ctx = dev.open()
95
+ pd = ctx.alloc_pd()
96
+ cq = ctx.create_cq(64)
97
+
98
+ # 2. Register memory (host or GPU address — any integer VA works).
99
+ import numpy as np
100
+ buf = np.zeros(4096, dtype=np.uint8)
101
+ access = ib.AccessFlags.LOCAL_WRITE | ib.AccessFlags.REMOTE_WRITE | ib.AccessFlags.REMOTE_READ
102
+ mr = pd.reg_mr(buf.ctypes.data, buf.nbytes, access)
103
+
104
+ # 3. Create a reliable-connected QP.
105
+ qp = pd.create_qp(ib.QPInitAttr(send_cq=cq, recv_cq=cq, qp_type=ib.QPType.RC))
106
+
107
+ # 4. Exchange connection info with the peer out-of-band, then connect.
108
+ port = 1
109
+ port_attr = ctx.query_port(port)
110
+ gid = ctx.query_gid(port, gid_index) # pick a routable RoCEv2 GID
111
+ local = ib.local_qp_info(qp, port_attr, gid, port=port, psn=0)
112
+ # ... send local.to_bytes() to peer, receive remote_bytes ...
113
+ remote = ib.QPInfo.from_bytes(remote_bytes)
114
+ ib.connect_rc(qp, remote, port=port, sgid_index=gid_index, access=access)
115
+
116
+ # 5. Post an RDMA write and reap the completion.
117
+ qp.post_send(ib.SendWR(
118
+ wr_id=1, sg_list=[ib.SGE(mr, 4096)], opcode=ib.WROpcode.RDMA_WRITE,
119
+ send_flags=ib.SendFlags.SIGNALED, remote_addr=peer_addr, rkey=peer_rkey))
120
+ for wc in cq.poll(16):
121
+ wc.raise_for_status()
122
+ ```
123
+
124
+ Every resource is a context manager and frees its handle on `close()` /
125
+ garbage collection; children hold references to their parents, so destruction
126
+ order is always safe.
127
+
128
+ ## GPUDirect with torch tensors
129
+
130
+ The library never imports torch or links CUDA. The optional `ibverbs.cuda`
131
+ helper (which only lazily `dlopen`s `libcuda`) registers a CUDA tensor for RDMA
132
+ in one call — handling the dma-buf export and page alignment for you:
133
+
134
+ ```python
135
+ import os
136
+ # torch's CUDA memory must be VMM-backed to be dma-buf exportable. Set this
137
+ # BEFORE torch initializes CUDA:
138
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
139
+
140
+ import torch
141
+ import ibverbs as ib
142
+ import ibverbs.cuda
143
+
144
+ src = torch.arange(4096, dtype=torch.float32, device="cuda:0")
145
+ dst = torch.zeros(4096, dtype=torch.float32, device="cuda:0")
146
+
147
+ access = ib.AccessFlags.LOCAL_WRITE | ib.AccessFlags.REMOTE_WRITE
148
+ src_mr = ib.cuda.register_tensor(pd, src, access) # retains src until close()
149
+ dst_mr = ib.cuda.register_tensor(pd, dst, access)
150
+
151
+ # RDMA-write one GPU buffer into another, with no host staging on the data path.
152
+ torch.cuda.synchronize(src.device) # source-producing CUDA work must be done
153
+ qp.post_send(ib.SendWR(
154
+ wr_id=1, sg_list=[src_mr.sge()], opcode=ib.WROpcode.RDMA_WRITE,
155
+ send_flags=ib.SendFlags.SIGNALED,
156
+ remote_addr=dst_mr.addr, rkey=dst_mr.rkey))
157
+ for wc in qp.send_cq.poll(16):
158
+ wc.raise_for_status()
159
+
160
+ # On the receiver, after the peer has signaled that its write is complete,
161
+ # order the inbound NIC writes before launching CUDA work that consumes dst.
162
+ with torch.cuda.device(dst.device):
163
+ ib.cuda.flush_gpudirect_writes()
164
+ ```
165
+
166
+ `GpuMR` wraps the `MR` with the correct device address (`ibv_mr.addr` is not
167
+ meaningful for dma-buf MRs), retains the tensor allocation until `close()`, and
168
+ exposes `.sge()`, `.addr`, `.lkey`, `.rkey`.
169
+
170
+ CUDA work and NIC work are separate ordering domains. Synchronize the stream
171
+ that produced an outbound tensor before posting it. For inbound `SEND`, RDMA
172
+ read, or RDMA write, wait for the corresponding completion or protocol-level
173
+ notification, then call `flush_gpudirect_writes()` in the destination CUDA
174
+ context before consuming the tensor. A one-sided RDMA write does not create a
175
+ remote CQ entry by itself; use write-with-immediate or an out-of-band message
176
+ to notify the receiver.
177
+
178
+ Under the hood there are two registration paths, chosen automatically:
179
+
180
+ ```python
181
+ # dma-buf fd (default; no kernel module needed):
182
+ mr = pd.reg_dmabuf_mr(offset, length, iova=device_va, fd=dmabuf_fd, access=access)
183
+ # raw device pointer (requires the nvidia_peermem kernel module):
184
+ mr = pd.reg_mr(tensor.data_ptr(), nbytes, access)
185
+ ```
186
+
187
+ For a **host** (CPU) torch tensor or numpy array, `ib.reg_tensor(pd, tensor,
188
+ access)` registers it directly and retains the allocation. Both tensor helpers
189
+ require contiguous, non-empty tensors; split any single SGE larger than
190
+ `2**32 - 1` bytes into multiple entries. `tests/test_gpudirect.py` performs real
191
+ GPU-to-GPU RDMA writes, reads, and sends verified with `torch.equal`.
192
+
193
+ ## Feature coverage
194
+
195
+ | Area | Supported |
196
+ |------|-----------|
197
+ | Device / port / GID query | ✅ `get_device_list`, `Context.query_device/query_port/query_gid` |
198
+ | Protection domains | ✅ `alloc_pd` |
199
+ | Memory regions | ✅ `reg_mr`, `reg_dmabuf_mr` (GPUDirect) |
200
+ | Completion queues | ✅ `create_cq`, `poll`, comp channels + `req_notify`/`ack_events` |
201
+ | Queue pairs | ✅ RC / UC / UD; `modify`, `query`, `to_init`/`to_rtr`/`to_rts` |
202
+ | Work requests | ✅ SEND(/_IMM), RDMA_WRITE(/_IMM), RDMA_READ, ATOMIC_CMP_AND_SWP, ATOMIC_FETCH_AND_ADD, scatter/gather, inline/signaled/fenced/solicited flags |
203
+ | Shared receive queues | ✅ `create_srq`, `post_recv`, `modify`, `query` |
204
+ | Address handles | ✅ `create_ah` (UD) |
205
+ | Async events | ✅ `get_async_event` / `ack_async_event`, `async_fd` |
206
+ | Connection helpers | ✅ `QPInfo`, `local_qp_info`, `connect_rc` |
207
+
208
+ Out of scope for v1 (candidates for later): the extended `ibv_wr_*` / `qp_ex`
209
+ post API, device memory (`ibv_alloc_dm`), memory windows, and flow steering.
210
+
211
+ ## Testing
212
+
213
+ The suite exercises real hardware and skips features the host lacks:
214
+
215
+ ```bash
216
+ pip install -e "./ibverbs[test,gpu]" # pytest, numpy, torch
217
+ cd ibverbs && pytest -rs # -rs shows skip reasons
218
+ pytest -m "not gpu" # skip GPUDirect tests
219
+ pytest -m integration # only real-hardware tests
220
+ ```
221
+
222
+ Markers: `integration` (needs an RDMA NIC), `gpu` (needs CUDA + torch).
223
+
224
+ ## License
225
+
226
+ BSD-3-Clause. See the repository `LICENSE`.
@@ -0,0 +1,201 @@
1
+ # ibverbs
2
+
3
+ Low-level, Pythonic bindings for **libibverbs** (RDMA), designed as a
4
+ foundation for building high-performance RDMA libraries in Python — including
5
+ **GPUDirect** transfers to/from GPU memory.
6
+
7
+ The bindings are a thin, faithful wrapper over the verbs API (device, PD, MR,
8
+ CQ, QP, SRQ, AH, work requests, completions, async events) plus a small,
9
+ optional set of RC connection helpers. They are written in Cython so the data
10
+ path (`post_send` / `post_recv` / `poll`) compiles to direct C calls and
11
+ releases the GIL, and so the `static inline` verbs fast-path functions are
12
+ called correctly (they can't be reached through `dlsym`).
13
+
14
+ - **No runtime dependencies.** Only libibverbs, which is `dlopen`ed at import.
15
+ - **No torch / CUDA linkage.** GPUDirect works by registering an integer
16
+ device address or an exported dma-buf fd; CUDA stays entirely in your code.
17
+ - **One `abi3` wheel for all of CPython 3.9+** on Linux.
18
+
19
+ ## Portability
20
+
21
+ The extension does **not** link `libibverbs`. It is compiled against the
22
+ rdma-core headers (for struct layouts and the `static inline` data-path verbs)
23
+ but resolves the exported verbs at import time with `dlopen`/`dlsym`. As a
24
+ result:
25
+
26
+ - The compiled module's only `NEEDED` library is `libc` — no external
27
+ dependency for `auditwheel`, so a single **manylinux** wheel is portable
28
+ across distros.
29
+ - It is built against the **CPython Limited API (abi3)**, so one wheel works on
30
+ CPython **3.9 through 3.14+** — no per-version builds.
31
+ - A missing `libibverbs` yields a clean `ImportError`, not a loader crash.
32
+ - Newer verbs are optional: `ibv_reg_dmabuf_mr` (rdma-core ≥ 34) is loaded if
33
+ present and only errors if you actually call `reg_dmabuf_mr`, so the wheel
34
+ still imports on older systems.
35
+
36
+ At runtime you only need `libibverbs.so.1` (any `rdma-core` from the last
37
+ several years). The data path (`post_send`/`poll`/…) stays compiled inline and
38
+ dispatches through the provider op table, so `dlopen` costs nothing on the hot
39
+ path.
40
+
41
+ ## Requirements
42
+
43
+ - Linux with an RDMA-capable NIC (tested on Mellanox/NVIDIA **mlx5**, RoCEv2).
44
+ - **Runtime:** `libibverbs.so.1` (`rdma-core` — `libibverbs1` on Debian/Ubuntu,
45
+ `libibverbs` on RHEL/Fedora). No compiler or headers needed to *use* a wheel.
46
+ - **Build from source only:** a C compiler, Cython, and the `rdma-core`
47
+ development headers (`libibverbs-dev` / `rdma-core-devel`).
48
+
49
+ ## Install
50
+
51
+ ```bash
52
+ pip install ibverbs # prebuilt abi3 manylinux wheel (once published)
53
+ ```
54
+
55
+ Building from source (needs the rdma-core dev headers + a compiler):
56
+
57
+ ```bash
58
+ pip install "Cython>=3.0" "setuptools>=77" wheel
59
+ pip install ./ibverbs # or: pip install -e ./ibverbs
60
+ ```
61
+
62
+ ## Quickstart
63
+
64
+ ```python
65
+ import ibverbs as ib
66
+
67
+ # 1. Open a device and set up resources.
68
+ dev = ib.get_device_list()[0]
69
+ ctx = dev.open()
70
+ pd = ctx.alloc_pd()
71
+ cq = ctx.create_cq(64)
72
+
73
+ # 2. Register memory (host or GPU address — any integer VA works).
74
+ import numpy as np
75
+ buf = np.zeros(4096, dtype=np.uint8)
76
+ access = ib.AccessFlags.LOCAL_WRITE | ib.AccessFlags.REMOTE_WRITE | ib.AccessFlags.REMOTE_READ
77
+ mr = pd.reg_mr(buf.ctypes.data, buf.nbytes, access)
78
+
79
+ # 3. Create a reliable-connected QP.
80
+ qp = pd.create_qp(ib.QPInitAttr(send_cq=cq, recv_cq=cq, qp_type=ib.QPType.RC))
81
+
82
+ # 4. Exchange connection info with the peer out-of-band, then connect.
83
+ port = 1
84
+ port_attr = ctx.query_port(port)
85
+ gid = ctx.query_gid(port, gid_index) # pick a routable RoCEv2 GID
86
+ local = ib.local_qp_info(qp, port_attr, gid, port=port, psn=0)
87
+ # ... send local.to_bytes() to peer, receive remote_bytes ...
88
+ remote = ib.QPInfo.from_bytes(remote_bytes)
89
+ ib.connect_rc(qp, remote, port=port, sgid_index=gid_index, access=access)
90
+
91
+ # 5. Post an RDMA write and reap the completion.
92
+ qp.post_send(ib.SendWR(
93
+ wr_id=1, sg_list=[ib.SGE(mr, 4096)], opcode=ib.WROpcode.RDMA_WRITE,
94
+ send_flags=ib.SendFlags.SIGNALED, remote_addr=peer_addr, rkey=peer_rkey))
95
+ for wc in cq.poll(16):
96
+ wc.raise_for_status()
97
+ ```
98
+
99
+ Every resource is a context manager and frees its handle on `close()` /
100
+ garbage collection; children hold references to their parents, so destruction
101
+ order is always safe.
102
+
103
+ ## GPUDirect with torch tensors
104
+
105
+ The library never imports torch or links CUDA. The optional `ibverbs.cuda`
106
+ helper (which only lazily `dlopen`s `libcuda`) registers a CUDA tensor for RDMA
107
+ in one call — handling the dma-buf export and page alignment for you:
108
+
109
+ ```python
110
+ import os
111
+ # torch's CUDA memory must be VMM-backed to be dma-buf exportable. Set this
112
+ # BEFORE torch initializes CUDA:
113
+ os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True"
114
+
115
+ import torch
116
+ import ibverbs as ib
117
+ import ibverbs.cuda
118
+
119
+ src = torch.arange(4096, dtype=torch.float32, device="cuda:0")
120
+ dst = torch.zeros(4096, dtype=torch.float32, device="cuda:0")
121
+
122
+ access = ib.AccessFlags.LOCAL_WRITE | ib.AccessFlags.REMOTE_WRITE
123
+ src_mr = ib.cuda.register_tensor(pd, src, access) # retains src until close()
124
+ dst_mr = ib.cuda.register_tensor(pd, dst, access)
125
+
126
+ # RDMA-write one GPU buffer into another, with no host staging on the data path.
127
+ torch.cuda.synchronize(src.device) # source-producing CUDA work must be done
128
+ qp.post_send(ib.SendWR(
129
+ wr_id=1, sg_list=[src_mr.sge()], opcode=ib.WROpcode.RDMA_WRITE,
130
+ send_flags=ib.SendFlags.SIGNALED,
131
+ remote_addr=dst_mr.addr, rkey=dst_mr.rkey))
132
+ for wc in qp.send_cq.poll(16):
133
+ wc.raise_for_status()
134
+
135
+ # On the receiver, after the peer has signaled that its write is complete,
136
+ # order the inbound NIC writes before launching CUDA work that consumes dst.
137
+ with torch.cuda.device(dst.device):
138
+ ib.cuda.flush_gpudirect_writes()
139
+ ```
140
+
141
+ `GpuMR` wraps the `MR` with the correct device address (`ibv_mr.addr` is not
142
+ meaningful for dma-buf MRs), retains the tensor allocation until `close()`, and
143
+ exposes `.sge()`, `.addr`, `.lkey`, `.rkey`.
144
+
145
+ CUDA work and NIC work are separate ordering domains. Synchronize the stream
146
+ that produced an outbound tensor before posting it. For inbound `SEND`, RDMA
147
+ read, or RDMA write, wait for the corresponding completion or protocol-level
148
+ notification, then call `flush_gpudirect_writes()` in the destination CUDA
149
+ context before consuming the tensor. A one-sided RDMA write does not create a
150
+ remote CQ entry by itself; use write-with-immediate or an out-of-band message
151
+ to notify the receiver.
152
+
153
+ Under the hood there are two registration paths, chosen automatically:
154
+
155
+ ```python
156
+ # dma-buf fd (default; no kernel module needed):
157
+ mr = pd.reg_dmabuf_mr(offset, length, iova=device_va, fd=dmabuf_fd, access=access)
158
+ # raw device pointer (requires the nvidia_peermem kernel module):
159
+ mr = pd.reg_mr(tensor.data_ptr(), nbytes, access)
160
+ ```
161
+
162
+ For a **host** (CPU) torch tensor or numpy array, `ib.reg_tensor(pd, tensor,
163
+ access)` registers it directly and retains the allocation. Both tensor helpers
164
+ require contiguous, non-empty tensors; split any single SGE larger than
165
+ `2**32 - 1` bytes into multiple entries. `tests/test_gpudirect.py` performs real
166
+ GPU-to-GPU RDMA writes, reads, and sends verified with `torch.equal`.
167
+
168
+ ## Feature coverage
169
+
170
+ | Area | Supported |
171
+ |------|-----------|
172
+ | Device / port / GID query | ✅ `get_device_list`, `Context.query_device/query_port/query_gid` |
173
+ | Protection domains | ✅ `alloc_pd` |
174
+ | Memory regions | ✅ `reg_mr`, `reg_dmabuf_mr` (GPUDirect) |
175
+ | Completion queues | ✅ `create_cq`, `poll`, comp channels + `req_notify`/`ack_events` |
176
+ | Queue pairs | ✅ RC / UC / UD; `modify`, `query`, `to_init`/`to_rtr`/`to_rts` |
177
+ | Work requests | ✅ SEND(/_IMM), RDMA_WRITE(/_IMM), RDMA_READ, ATOMIC_CMP_AND_SWP, ATOMIC_FETCH_AND_ADD, scatter/gather, inline/signaled/fenced/solicited flags |
178
+ | Shared receive queues | ✅ `create_srq`, `post_recv`, `modify`, `query` |
179
+ | Address handles | ✅ `create_ah` (UD) |
180
+ | Async events | ✅ `get_async_event` / `ack_async_event`, `async_fd` |
181
+ | Connection helpers | ✅ `QPInfo`, `local_qp_info`, `connect_rc` |
182
+
183
+ Out of scope for v1 (candidates for later): the extended `ibv_wr_*` / `qp_ex`
184
+ post API, device memory (`ibv_alloc_dm`), memory windows, and flow steering.
185
+
186
+ ## Testing
187
+
188
+ The suite exercises real hardware and skips features the host lacks:
189
+
190
+ ```bash
191
+ pip install -e "./ibverbs[test,gpu]" # pytest, numpy, torch
192
+ cd ibverbs && pytest -rs # -rs shows skip reasons
193
+ pytest -m "not gpu" # skip GPUDirect tests
194
+ pytest -m integration # only real-hardware tests
195
+ ```
196
+
197
+ Markers: `integration` (needs an RDMA NIC), `gpu` (needs CUDA + torch).
198
+
199
+ ## License
200
+
201
+ BSD-3-Clause. See the repository `LICENSE`.
@@ -0,0 +1,67 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "Cython>=3.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "ibverbs"
7
+ dynamic = ["version"]
8
+ description = "Low-level Pythonic bindings for libibverbs (RDMA), with GPUDirect support"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "BSD-3-Clause"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Tristan Rice" }]
14
+ keywords = ["rdma", "ibverbs", "infiniband", "roce", "gpudirect", "networking"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Programming Language :: Cython",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: System :: Networking",
22
+ ]
23
+ # No runtime dependencies: the library only needs libibverbs at load time.
24
+ dependencies = []
25
+
26
+ [tool.setuptools.dynamic]
27
+ version = { attr = "ibverbs._version.__version__" }
28
+
29
+ [project.optional-dependencies]
30
+ test = ["pytest>=7", "numpy"]
31
+ gpu = ["torch"]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/tristanr/rdma4py"
35
+ Repository = "https://github.com/tristanr/rdma4py"
36
+
37
+ [tool.setuptools]
38
+ package-dir = { "" = "src" }
39
+ include-package-data = false
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.pytest.ini_options]
45
+ markers = [
46
+ "integration: exercises real RDMA hardware (loopback / two-NIC)",
47
+ "gpu: requires a CUDA GPU and torch (GPUDirect)",
48
+ ]
49
+ testpaths = ["tests"]
50
+
51
+ # Single abi3 wheel per platform (works on CPython 3.9+). The extension is
52
+ # built against rdma-core headers but dlopens libibverbs at runtime, so the
53
+ # build image only needs the -devel headers; the wheel has no external NEEDED
54
+ # library and is trivially manylinux-compliant.
55
+ [tool.cibuildwheel]
56
+ # cibuildwheel selects a CPython build environment; setup.py retags the
57
+ # resulting extension as cp39-abi3 via Py_LIMITED_API.
58
+ build = "cp39-*"
59
+ build-frontend = "build"
60
+ skip = ["*-musllinux*"]
61
+ test-requires = ["pytest", "numpy"]
62
+ # Hardware/GPU tests self-skip when no NIC/CUDA is present in CI.
63
+ test-command = "pytest -rs -m 'not gpu' {package}/tests"
64
+
65
+ [tool.cibuildwheel.linux]
66
+ # manylinux images are AlmaLinux-based (dnf/yum).
67
+ before-all = "dnf install -y rdma-core-devel || yum install -y rdma-core-devel"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+