UCX tag-matching (MPI-style tags) c10d backend for torch.distributed, on CPU and CUDA
Project description
commux
A custom PyTorch c10d backend over UCX that gives real MPI-style
(sender, tag) point-to-point matching — the thing NCCL cannot do — plus
allreduce / reduce / broadcast / barrier, on CPU host tensors and CUDA
device tensors (UCX cuda_ipc / cuda_copy / gdr_copy).
Motivation: codes that drive stencil/halo exchange through tagged c10d
send/recv (pg.send(bufs, dst, tag)) can't use NCCL for it — NCCL ignores tags
and matches only by stream/communicator ordering. commux honors tags via UCX's
ucp_tag_send_nbx / ucp_tag_recv_nbx.
Install
commux is Linux-only (it builds on UCX, which does not compile on macOS) and
needs UCX — but you do not install UCX yourself: every wheel vendors
UCX (+ the gdrcopy userspace lib), so a pip install is self-contained.
# Released wheel (self-contained: bundled UCX, no system UCX needed):
pip install commux
pip install "commux @ git+https://github.com/zoeyzyhu/commux"
# From a source checkout -- builds against your active torch, so no isolation:
pip install . --no-build-isolation
# Use a preinstalled / module UCX instead of building one from source:
UCX_ROOT=/path/to/ucx pip install . --no-build-isolation \
-C cmake.define.COMMUX_UCX_PROVIDER=system
# Force building UCX(+gdrcopy) from source even if a system one exists:
pip install . --no-build-isolation -C cmake.define.COMMUX_UCX_PROVIDER=bundled
The installed package under site-packages/commux/ is fully self-contained:
_C.so (the extension), lib/libcommux.so plus the vendored lib/libuc*.so /
libgdrapi.so (relocatable — repointed to $ORIGIN), and include/ with the
commux and UCX C++ headers for downstream C++ consumers (see below).
gdr_copyonly engages at runtime when thegdrdrvkernel module is loaded (/dev/gdrdrv) and an RDMA NIC is present; otherwise UCX usescuda_copy/cuda_ipc. The kernel module is a host/driver prerequisite that a wheel cannot provide.
Use with torch.distributed
import torch, torch.distributed as dist
import commux; commux.register() # registers backends "ucx" and "commux"
dist.init_process_group(backend="ucx", init_method="env://")
dist.all_reduce(t) # CPU or CUDA
dist.isend(x, dst=1, tag=100); dist.irecv(y, src=0, tag=100)
Launch with torchrun --nproc-per-node=N ... as usual.
Out-of-order tag matching must use non-blocking
isend/irecv(post all, then wait). Blocking out-of-order send/recv deadlocks under the CUDA rendezvous protocol, exactly as with real MPI.
Picking a backend portably (replace Gloo where UCX exists)
commux works on CPU as well as CUDA, so on Linux you can use it instead of
Gloo for CPU collectives/p2p. Since commux installs only where UCX is supported
(Linux), the import itself is the availability check — prefer it, and fall back
to Gloo elsewhere (e.g. macOS):
try:
from commux import preferred_backend
backend = preferred_backend() # registers commux, returns "ucx"
except ImportError:
backend = "gloo" # macOS / no UCX
dist.init_process_group(backend=backend, init_method="env://")
The fallback lives in the consumer because commux is absent on the
unsupported platform; commux itself is the UCX provider on both CPU and CUDA.
Use from C++
Because the wheel ships the C++ library + headers (and bundles UCX), a C++
project can just pip install commux and link the installed package — no
source build, no UCX of its own. Locate it from CMake by probing the Python
package:
execute_process(
COMMAND ${Python3_EXECUTABLE} -c "import commux,os;print(os.path.dirname(commux.__file__))"
OUTPUT_VARIABLE COMMUX_DIR OUTPUT_STRIP_TRAILING_WHITESPACE)
find_library(COMMUX_LIBRARY commux HINTS ${COMMUX_DIR}/lib)
include_directories(${COMMUX_DIR}/include) # commux/*.hpp + bundled ucp/*.h
link_directories(${COMMUX_DIR}/lib) # lets the linker resolve bundled UCX
target_link_libraries(your_lib PUBLIC ${COMMUX_LIBRARY})
#include <commux/process_group_ucx.hpp>
auto backend = c10::make_intrusive<commux::ProcessGroupUCX>(store, rank, size);
pg->setBackend(c10::DeviceType::CPU, c10d::ProcessGroup::BackendType::CUSTOM, backend);
pg->setBackend(c10::DeviceType::CUDA, c10d::ProcessGroup::BackendType::CUSTOM, backend);
Prefer to build from source? commux also exports the CMake target
commux::commux, so FetchContent works too:
include(FetchContent)
FetchContent_Declare(commux GIT_REPOSITORY https://github.com/zoeyzyhu/commux GIT_TAG main)
FetchContent_MakeAvailable(commux)
target_link_libraries(your_lib PUBLIC commux::commux)
Build the C++ tests
cmake -S . -B build -DCOMMUX_BUILD_TESTS=ON # add -DUCX_ROOT=~/ucx-install to use a prebuilt UCX
cmake --build build -j
for r in 0 1; do ./build/test_commux $r 2 127.0.0.1 29581 & done; wait # CPU
UCXPG_DEVICE=cuda ./build/test_commux 0 2 127.0.0.1 29592 & \
UCXPG_DEVICE=cuda ./build/test_commux 1 2 127.0.0.1 29592 & wait # CUDA
test_commuxalso covers the coalescing features: it exercises the IOV path underCOMMUX_COALESCE=1and the coalescing window underCOMMUX_GROUP=1. SetCOMMUX_BENCH=1 COMMUX_BENCH_V=<N>for aV-tensor ping-pong latency benchmark (compareCOMMUX_COALESCE=0vs1).
CMake options
| option | default | meaning |
|---|---|---|
COMMUX_UCX_PROVIDER |
auto |
auto (system, else build) / system / bundled |
COMMUX_WITH_GDRCOPY |
auto |
build gdrcopy into bundled UCX (auto/on/off) |
COMMUX_UCX_VERSION |
1.18.0 |
UCX release to bundle |
COMMUX_CUDA |
ON |
enable CUDA path if c10_cuda is present |
COMMUX_BUILD_TESTS |
OFF |
build test_commux |
COMMUX_BUILD_PYTHON |
OFF |
build the commux._C extension (set by the wheel) |
scripts/build_ucx.sh [PREFIX] [UCX_VER] [GDR_VER] builds UCX+gdrcopy once with
the same flags, for pointing UCX_ROOT at.
Runtime tuning (environment variables)
Read once at process start; opt-in and off by default, so default behavior is unchanged.
| variable | default | meaning |
|---|---|---|
COMMUX_COALESCE |
off | Merge a multi-tensor send()/recv() vector (tensors.size() > 1, all on one memory type) into one UCX message via the IOV datatype — one rendezvous / tag-match / stream-sync instead of V. Changes the wire format, so every rank must set it the same way. 1/on to enable. |
COMMUX_GROUP |
off | Enable the coalescing window — the c10d startCoalescing/endCoalescing hooks, i.e. the analog of ncclGroupStart/ncclGroupEnd. Between the markers, send/recv defer their posts and are flushed together with a single stream-sync at endCoalescing. Wire-compatible with a non-grouping peer. 1/on to enable. |
Always on (no flag): wait paths are event-driven — a Work::wait()
sleeps on the worker's wakeup fd (UCP_FEATURE_WAKEUP + ucp_worker_wait)
instead of busy-spinning ucp_worker_progress, so a rank blocked on a transfer
no longer pins a CPU core. Falls back to a yielding poll if the active transports
expose no wakeup fd.
When do these help?
COMMUX_COALESCE(IOV packing) cuts per-message overhead, so it helps most when onesend/recvcarries many buffers over a latency-bound / high-latency link (InfiniBand). Caveat: UCX's IOV path is not zero-copy for CUDA (it gathers into a staging buffer), so for large vectors on intra-nodecuda_ipcit can be slower than separate messages. Measured intra-node: faster atV≈2, slower atV≈16— benchmark before enabling.COMMUX_GROUP(op-batching) collapses an exchange's many per-tensor posts into one stream-sync + one aggregateWork. Because the posts are already issued concurrently, the intra-node win is small; it is most useful on multi-node InfiniBand, where it reduces handshakes. Exposed through the standard c10d coalescing API, so a C++ consumer just callspg->startCoalescing(dev)/pg->endCoalescing(dev)->wait()around its posts. The two flags compose: group the window, and IOV-pack any multi-tensor op in it.
Design
64-bit ucp_tag = [63:48] senderRank | [47:33] sub-index | [32] collective-bit | [31:0] userTag, so receivers match exactly on (sender, tag); recvAnysource
wildcards the rank field. Endpoints bootstrap by exchanging worker addresses
through the c10d::Store. send/recv are non-blocking and return a Work
whose wait() is event-driven — it sleeps on the worker's wakeup fd rather than
busy-spinning ucp_worker_progress; collectives run over tagged p2p with
at::add/minimum/maximum so the same code reduces CPU and CUDA tensors. CUDA
buffers are stream-synchronized before UCX touches them. Optional
message/operation coalescing is available at runtime — see Runtime tuning.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file commux-0.1.2-cp313-cp313-manylinux_2_27_x86_64.whl.
File metadata
- Download URL: commux-0.1.2-cp313-cp313-manylinux_2_27_x86_64.whl
- Upload date:
- Size: 18.8 MB
- Tags: CPython 3.13, manylinux: glibc 2.27+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9a7afaa5774e58becb36e022e92b7554860519d369049abc070ace471472afc3
|
|
| MD5 |
7ce7a24cf194840eff37c0fcf59bd139
|
|
| BLAKE2b-256 |
f70c41fa7b55f8de2bf0e245892166bbb522ebdd1ad50cce7c0dde6053c95d5a
|
File details
Details for the file commux-0.1.2-cp312-cp312-manylinux_2_27_x86_64.whl.
File metadata
- Download URL: commux-0.1.2-cp312-cp312-manylinux_2_27_x86_64.whl
- Upload date:
- Size: 18.8 MB
- Tags: CPython 3.12, manylinux: glibc 2.27+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54e175a30c79f07373c645d0a34cdd1671a55bda17308d12c583d9cd311dd5dc
|
|
| MD5 |
fbbf276723216a06ef604523faf774b4
|
|
| BLAKE2b-256 |
aadc257d8f0109ea973db6e7fe6dadd98cbcbd41aed95b04a0613b2f20784d00
|
File details
Details for the file commux-0.1.2-cp311-cp311-manylinux_2_27_x86_64.whl.
File metadata
- Download URL: commux-0.1.2-cp311-cp311-manylinux_2_27_x86_64.whl
- Upload date:
- Size: 18.8 MB
- Tags: CPython 3.11, manylinux: glibc 2.27+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
207aeab3e6c14497eda74fcc559d540aef71cc43ada3c8cf8b24c5ed4dba86a6
|
|
| MD5 |
4cee772c003bb13db2806d4856592716
|
|
| BLAKE2b-256 |
c1966a1a8fc9ea5dcfeea7b0444a9aca497a7cdc68aa1c1bdb5ed5549a8f1f23
|
File details
Details for the file commux-0.1.2-cp310-cp310-manylinux_2_27_x86_64.whl.
File metadata
- Download URL: commux-0.1.2-cp310-cp310-manylinux_2_27_x86_64.whl
- Upload date:
- Size: 18.8 MB
- Tags: CPython 3.10, manylinux: glibc 2.27+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
422fd72cba53c9c9382bc44f3b59baa88eee37fa55b42783e69ed946917d5f95
|
|
| MD5 |
5a929c91885ac2d5ce17ec142586b73d
|
|
| BLAKE2b-256 |
be4e175f12f4f9da80cbadca1f9ce25b4a2bacf2bc58d94f627736fbc5f4aa6e
|