11/* ************************************************************************
22 * Copyright (c) 2026 BAAI. All rights reserved.
33 *
4- * FlagCX net adaptor "barex" — collective/C2C network transport over the
5- * vendor ACCL library (accl::barex) for PPU + vsolar hosts.
4+ * FlagCX net adaptor "barex": collective/C2C transport over the vendor
5+ * ACCL library (accl::barex) for PPU + vsolar hosts, where GPU memory
6+ * cannot be registered via peer-mem or DMA-BUF and must go through
7+ * ACCL's RegUserMr / XChannel. Requires FLAGCX_VMM_ENABLE=0 (VMM memory
8+ * is unpinnable) so staging buffers come from cudaMalloc.
69 *
7- * Why this exists: on PPU (810e), device memory cannot be registered
8- * through peer-mem or DMA-BUF (the driver stack supports neither), so
9- * the in-tree IBRC adaptor cannot move GPU buffers. The only supported
10- * way to pin PPU memory for RDMA is ACCL's XSimpleMempool::RegUserMr;
11- * data movement must likewise go through ACCL channels. CUDA-VMM memory
12- * cannot be pinned at all — run with FLAGCX_VMM_ENABLE=0 so staging
13- * buffers (deviceAdaptor->gdrMemAlloc) come from cudaMalloc.
10+ * Rendezvous (mirrors ibrc's CTS design): connect sends HELLO{commId}
11+ * over an XChannel; irecv posts CTS{slot,addr,size,rkeys,seq}; isend
12+ * answers WriteSingle(imm=slot) and OnImmRecvCall completes the recv
13+ * (write-with-imm orders payload before the imm). Shared state is mutex-
14+ * or atomic-guarded (callbacks run on ACCL IO threads).
1415 *
15- * Wire protocol (rendezvous, mirroring the ibrc adaptor's CTS design
16- * with ACCL primitives):
17- * - connect/accept: the 64-byte listen handle carries the engine's
18- * OOB IP, the shared barex data port, and a random commId. The
19- * connector opens an XChannel to the listener and sends HELLO
20- * {commId}; the acceptor demuxes the channel to the matching
21- * pending accept.
22- * - irecv posts CTS {slot, addr, size, per-NIC rkeys} to the sender
23- * over XChannel::Send (small host message).
24- * - isend pops a CTS and issues one WriteSingle(signal_peer=true,
25- * imm_data=slot) straight into the receiver's buffer; the
26- * receiver's OnImmRecvCall(imm) marks the recv request complete
27- * (RDMA write-with-imm orders data placement before imm delivery).
28- * - test() reads an atomic request state; iflush is a no-op (write
29- * completion on an RC channel implies remote placement; PPU-side
30- * ordering is the vendor stack's contract, validated by
31- * barex_benchmark -E data checks).
32- *
33- * Threading: connect/accept run on the proxy service thread,
34- * isend/irecv/test on the proxy progress thread, callbacks on ACCL IO
35- * threads — every shared structure below is mutex- or atomic-guarded.
36- *
37- * Selected at build time via USE_ACCL_BAREX=1 (takes the IBRC registry
38- * slot, like USE_UCX does); FLAGCX_BAREX_DISABLE=1 falls back to the
39- * socket adaptor at runtime.
16+ * Built with USE_ACCL_BAREX=1 (IBRC registry slot, like USE_UCX) or
17+ * loaded as a plugin .so (preferred; see the export note below).
18+ * FLAGCX_BAREX_DISABLE=1 opts out at runtime.
4019 ************************************************************************/
4120
4221#ifdef USE_ACCL_BAREX
@@ -152,10 +131,8 @@ struct BarexCtsMsg {
152131 uint64_t size;
153132 uint32_t nKeys;
154133 uint32_t rkeys[kMaxNics ];
155- uint32_t seq; /* receiver's post-order index; sender matches in this order
156- because CTS are delivered via a multi-threaded callback
157- pool and may otherwise reorder — see barexIsend/OnRecvCall.
158- Occupies the struct's former tail padding (size stays 64). */
134+ uint32_t seq; /* receiver's post-order index; sender consumes CTS in
135+ this order (callbacks may reorder). Former padding. */
159136};
160137static_assert (sizeof (BarexCtsMsg) == 64 , " CTS wire layout must be stable" );
161138
@@ -170,9 +147,8 @@ struct BarexNetHandle {
170147 uint32_t state; /* connect-side stage */
171148 uint32_t pad;
172149 void *connectState; /* connect-side heap state across retries */
173- /* NOTE: transport.cc writes handle->stage.comm (flagcxIbHandle offset
174- 56..64) after bootstrapRecv; our fields end at 56, so that write
175- lands in the buffer's tail padding. Keep this struct <= 56 bytes. */
150+ /* Keep <= 56 bytes: transport.cc writes stage.comm at offset 56 after
151+ bootstrapRecv, landing in this buffer's tail padding. */
176152};
177153static_assert (sizeof (BarexNetHandle) <= 56 ,
178154 " must not overlap flagcxIbHandle::stage.comm at offset 56" );
@@ -232,11 +208,9 @@ struct BarexComm {
232208 std::atomic<bool > dead{false };
233209
234210 std::mutex mu; /* guards ctsPending + slot alloc + seq counters */
235- /* Sender side: CTS from the receiver, keyed by the receiver's post-order
236- seq. The sender consumes strictly in seq order (sendExpectedSeq) so a
237- chunk's data always lands in the buffer the receiver posted for that
238- same chunk, regardless of the order the callback pool delivered the
239- CTS. Receiver side: recvSeq stamps each outgoing CTS in post order. */
211+ /* Sender: CTS keyed by the receiver's post-order seq, consumed strictly
212+ in order (sendExpectedSeq) so chunk k lands in the buffer posted for
213+ it regardless of callback delivery order. Receiver: recvSeq stamps. */
240214 std::map<uint64_t , BarexCtsMsg> ctsPending;
241215 uint64_t recvSeq = 0 ; /* receiver: next CTS seq to stamp */
242216 uint64_t sendExpectedSeq = 0 ; /* sender: next CTS seq to consume */
@@ -544,9 +518,7 @@ static flagcxResult_t barexGetProperties(int dev, void *props) {
544518 p->name = const_cast <char *>(name);
545519 p->pciPath = nullptr ;
546520 p->guid = (uint64_t )dev;
547- /* GPU staging works because RegUserMr pins cudaMalloc'd PPU memory
548- (VMM must be off). No DMABUF: the whole point of this adaptor is
549- that the PPU stack has no dmabuf support. */
521+ /* RegUserMr pins cudaMalloc'd PPU memory (VMM off). PPU has no dmabuf. */
550522 p->ptrSupport = FLAGCX_PTR_HOST | FLAGCX_PTR_CUDA ;
551523 p->regIsGlobal = 1 ; /* MRs live in the engine-wide mempool */
552524 p->speed = (int )flagcxParamBarexSpeed ();
@@ -592,8 +564,7 @@ static flagcxResult_t barexListen(int dev, void *opaqueHandle,
592564}
593565
594566/* Non-blocking, resumable: *sendComm stays NULL until the channel is up
595- and HELLO has been delivered. State survives retries inside the
596- handle (the proxy re-passes the same buffer each call). */
567+ and HELLO delivered; state lives in the handle across retries. */
597568static flagcxResult_t barexConnect (int dev, void *opaqueHandle,
598569 void **sendComm) {
599570 *sendComm = nullptr ;
@@ -876,9 +847,7 @@ static flagcxResult_t barexRegMr(void *comm, void *data, size_t size, int type,
876847 return flagcxSuccess;
877848}
878849
879- /* The recv-side proxy calls this unguarded when FLAGCX_DMABUF_ENABLE=1;
880- the PPU stack has no dmabuf, so treat the fd as irrelevant and pin
881- through RegUserMr like regMr does. */
850+ /* PPU has no dmabuf; ignore the fd and pin through RegUserMr like regMr. */
882851static flagcxResult_t barexRegMrDmaBuf (void *comm, void *data, size_t size,
883852 int type, uint64_t offset, int fd,
884853 int mrFlags, void **mhandle) {
@@ -931,13 +900,9 @@ static flagcxResult_t barexIsend(void *sendComm, void *data, size_t size,
931900 BarexRequest *req = nullptr ;
932901 {
933902 std::lock_guard<std::mutex> lk (comm->mu );
934- /* Consume CTS strictly in the receiver's post order. The proxy calls
935- isend once per chunk in increasing chunk index, so chunk k must pair
936- with the CTS the receiver posted for its k-th irecv (seq == k). If
937- that CTS has not arrived yet (the callback pool may deliver a later
938- seq first), leave sendExpectedSeq untouched and let the proxy retry —
939- do NOT pair this chunk with a different CTS or the data lands in the
940- wrong buffer. */
903+ /* Consume CTS in receiver post order (seq == chunk index). If the
904+ expected seq hasn't arrived, leave sendExpectedSeq and let the proxy
905+ retry — never pair a chunk with a different CTS. */
941906 auto it = comm->ctsPending .find (comm->sendExpectedSeq );
942907 if (it == comm->ctsPending .end ())
943908 return flagcxSuccess; /* CTS for this chunk not here yet — retry */
@@ -949,9 +914,7 @@ static flagcxResult_t barexIsend(void *sendComm, void *data, size_t size,
949914 comm->sendExpectedSeq ++;
950915 }
951916
952- /* Receiver posted cts.size; both sides run the same chunk schedule so
953- sizes agree, but clamp for safety (ibrc semantics: send truncates
954- to the posted recv size). */
917+ /* Clamp to the posted recv size (ibrc semantics: send truncates). */
955918 const size_t wsize = size < cts.size ? size : (size_t )cts.size ;
956919 req->size = wsize;
957920
@@ -1057,13 +1020,9 @@ static flagcxResult_t barexIrecv(void *recvComm, int n, void **data,
10571020 return flagcxSuccess;
10581021}
10591022
1060- /* Write completion on an RC channel implies remote placement, and
1061- write-with-imm orders payload before the imm that completes the recv
1062- request; the vendor stack owns NIC->PPU visibility (validated by
1063- barex_benchmark -E). Nothing to flush — but the IBRC-slot consumer
1064- (net.cc flagcxProxyRecv) only advances its flush stage when a request
1065- is returned, so hand back the shared (void*)0x1 sentinel the caller
1066- already special-cases as "instantly done" before calling test(). */
1023+ /* Nothing to flush: write-with-imm orders payload before the completing
1024+ imm. Return the (void*)0x1 sentinel because the IBRC-slot consumer
1025+ (net.cc flagcxProxyRecv) only advances its flush stage on a request. */
10671026static flagcxResult_t barexIflush (void *recvComm, int n, void **data,
10681027 int *sizes, void **mhandles, void **request) {
10691028 (void )recvComm;
@@ -1086,9 +1045,8 @@ static flagcxResult_t barexTest(void *request, int *done, int *sizes) {
10861045 if (st == BAREX_REQ_PENDING )
10871046 return flagcxSuccess;
10881047 if (st == BAREX_REQ_ERROR ) {
1089- /* Sticky: the proxy ignores test()'s return code and would re-test
1090- the same pointer, so the slot must not be recycled. The comm is
1091- broken; surface the error every call. */
1048+ /* Sticky: proxy re-tests the same pointer, so don't recycle the slot;
1049+ surface the error every call. */
10921050 return flagcxInternalError;
10931051 }
10941052 *done = 1 ;
@@ -1121,8 +1079,8 @@ static flagcxResult_t barexGetDevFromName(char *name, int *dev) {
11211079
11221080} // namespace barexnet
11231081
1124- /* One-sided iput/iget/iputSignal serve the P2P/one-sided engine, not
1125- the proxy collective path — left NULL like the UCX adaptor. */
1082+ /* One-sided iput/iget/iputSignal serve the P2P engine, not the proxy
1083+ collective path — left NULL like the UCX adaptor. */
11261084struct flagcxNetAdaptor flagcxNetBarex = {
11271085 // Basic functions
11281086 " BAREX" , barexnet::barexInit, barexnet::barexDevices,
@@ -1154,13 +1112,10 @@ struct flagcxNetAdaptor flagcxNetBarex = {
11541112 NULL , // igetBatch
11551113};
11561114
1157- /* Plugin export (FLAGCX_NET_ADAPTOR_PLUGIN mechanism, v1 vtable).
1158- IMPORTANT deployment note: prefer loading barex as a plugin .so rather
1159- than linking libaccl_barex into libflagcx. The plugin loader dlopens
1160- with RTLD_LOCAL, which keeps libaccl_barex's dependency libu2mm.so out
1161- of the global symbol table — libpccl's internal u2mm implementation
1162- resolves against a globally visible libu2mm and crashes in
1163- wrap_u2mm_symbols during pcclCommInitRank otherwise. */
1115+ /* Plugin export (FLAGCX_NET_ADAPTOR_PLUGIN, v1 vtable). Prefer this over
1116+ linking libaccl_barex into libflagcx: the loader uses RTLD_LOCAL, keeping
1117+ libu2mm.so out of the global symbol table — otherwise libpccl's own u2mm
1118+ crashes in wrap_u2mm_symbols during pcclCommInitRank. */
11641119extern " C" __attribute__((visibility(
11651120 " default" ))) struct flagcxNetAdaptor_v1 flagcxNetAdaptorPlugin_v1 = {
11661121 " BAREX" ,
0 commit comments