Skip to content

Commit 7657a51

Browse files
authored
[ CRL] Kernel proxy bypass RMA Proxy with direct netAdaptor posting (flagos-ai#514)
1 parent 42d5dd5 commit 7657a51

19 files changed

Lines changed: 900 additions & 137 deletions

flagcx/adaptor/device/cuda_adaptor.cc

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
#include "adaptor.h"
66
#include "alloc.h"
77
#include "param.h"
8+
#include <mutex>
89
#include <unistd.h>
10+
#include <unordered_map>
11+
12+
static std::mutex gVmmHandleMapMtx;
13+
static std::unordered_map<void *, CUmemGenericAllocationHandle> gVmmHandleMap;
914

1015
std::map<flagcxMemcpyType_t, cudaMemcpyKind> memcpy_type_map = {
1116
{flagcxMemcpyHostToDevice, cudaMemcpyHostToDevice},
@@ -145,22 +150,31 @@ flagcxResult_t cudaAdaptorGdrMemAlloc(void **ptr, size_t size,
145150
DEVCHECK(cuDeviceGetAttribute(
146151
&flag, CU_DEVICE_ATTRIBUTE_GPU_DIRECT_RDMA_WITH_CUDA_VMM_SUPPORTED,
147152
currentDev));
153+
INFO(FLAGCX_INIT,
154+
"[gdrMemAlloc] dev=%d GPU_DIRECT_RDMA_WITH_CUDA_VMM_SUPPORTED=%d "
155+
"size=%zu",
156+
cudaDev, flag, size);
148157
if (flag)
149158
memprop.allocFlags.gpuDirectRDMACapable = 1;
150159
DEVCHECK(cuMemGetAllocationGranularity(&memGran, &memprop,
151160
CU_MEM_ALLOC_GRANULARITY_RECOMMENDED));
152161
ALIGN_SIZE(handleSize, memGran);
162+
INFO(FLAGCX_INIT,
163+
"[gdrMemAlloc] memGran=%zu handleSize=%zu gpuDirectRDMACapable=%d",
164+
memGran, handleSize, (int)memprop.allocFlags.gpuDirectRDMACapable);
153165
/* Allocate the physical memory on the device */
154166
DEVCHECK(cuMemCreate(&handle, handleSize, &memprop, 0));
155167
/* Reserve a virtual address range */
156168
cuRes = cuMemAddressReserve((CUdeviceptr *)ptr, handleSize, memGran, 0, 0);
157169
if (cuRes != CUDA_SUCCESS) {
170+
WARN("[gdrMemAlloc] cuMemAddressReserve FAILED: %d", (int)cuRes);
158171
cuMemRelease(handle);
159172
return flagcxUnhandledDeviceError;
160173
}
161174
/* Map the virtual address range to the physical allocation */
162175
cuRes = cuMemMap((CUdeviceptr)*ptr, handleSize, 0, handle, 0);
163176
if (cuRes != CUDA_SUCCESS) {
177+
WARN("[gdrMemAlloc] cuMemMap FAILED: %d", (int)cuRes);
164178
cuMemAddressFree((CUdeviceptr)*ptr, handleSize);
165179
cuMemRelease(handle);
166180
*ptr = NULL;
@@ -173,14 +187,21 @@ flagcxResult_t cudaAdaptorGdrMemAlloc(void **ptr, size_t size,
173187
accessDesc.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE;
174188
cuRes = cuMemSetAccess((CUdeviceptr)*ptr, handleSize, &accessDesc, 1);
175189
if (cuRes != CUDA_SUCCESS) {
190+
WARN("[gdrMemAlloc] cuMemSetAccess FAILED: %d", (int)cuRes);
176191
cuMemUnmap((CUdeviceptr)*ptr, handleSize);
177192
cuMemAddressFree((CUdeviceptr)*ptr, handleSize);
178193
cuMemRelease(handle);
179194
*ptr = NULL;
180195
return flagcxUnhandledDeviceError;
181196
}
182-
/* Release the create-time handle reference; the mapping holds its own. */
183-
cuMemRelease(handle);
197+
INFO(FLAGCX_INIT, "[gdrMemAlloc] VMM alloc OK: ptr=%p size=%zu", *ptr,
198+
handleSize);
199+
/* Retain the handle so cuMemGetHandleForAddressRange can export DMA-BUF fds.
200+
Released in cudaAdaptorGdrMemFree. */
201+
{
202+
std::lock_guard<std::mutex> lk(gVmmHandleMapMtx);
203+
gVmmHandleMap[*ptr] = handle;
204+
}
184205
#else
185206
DEVCHECK(cudaMalloc(ptr, size));
186207
cudaPointerAttributes attrs;
@@ -206,6 +227,16 @@ flagcxResult_t cudaAdaptorGdrMemFree(void *ptr, void *memHandle) {
206227
DEVCHECK(cuMemGetAddressRange(NULL, &size, (CUdeviceptr)ptr));
207228
DEVCHECK(cuMemUnmap((CUdeviceptr)ptr, size));
208229
DEVCHECK(cuMemAddressFree((CUdeviceptr)ptr, size));
230+
231+
// Release the VMM handle we retained at alloc time
232+
{
233+
std::lock_guard<std::mutex> lk(gVmmHandleMapMtx);
234+
auto it = gVmmHandleMap.find(ptr);
235+
if (it != gVmmHandleMap.end()) {
236+
cuMemRelease(it->second);
237+
gVmmHandleMap.erase(it);
238+
}
239+
}
209240
#else
210241
DEVCHECK(cudaFree(ptr));
211242
#endif
@@ -509,8 +540,11 @@ flagcxResult_t
509540
cudaAdaptorMemGetHandleForAddressRange(void *handleOut, void *buffer,
510541
size_t size, unsigned long long flags) {
511542
CUdeviceptr dptr = (CUdeviceptr)buffer;
512-
DEVCHECK(cuMemGetHandleForAddressRange(
513-
handleOut, dptr, size, CU_MEM_RANGE_HANDLE_TYPE_DMA_BUF_FD, flags));
543+
CUresult err = cuMemGetHandleForAddressRange(
544+
handleOut, dptr, size, CU_MEM_RANGE_HANDLE_TYPE_DMA_BUF_FD, flags);
545+
if (err != CUDA_SUCCESS) {
546+
return flagcxUnhandledDeviceError;
547+
}
514548
return flagcxSuccess;
515549
}
516550

@@ -544,7 +578,13 @@ flagcxResult_t cudaAdaptorSymPhysAlloc(void *ptr, size_t size,
544578
return flagcxSystemError;
545579

546580
// Retain the physical allocation handle from the VMM-backed pointer
547-
DEVCHECK(cuMemRetainAllocationHandle(cuHandle, ptr));
581+
CUresult retainRes = cuMemRetainAllocationHandle(cuHandle, ptr);
582+
if (retainRes != CUDA_SUCCESS) {
583+
WARN("[symPhysAlloc] cuMemRetainAllocationHandle FAILED: %d ptr=%p",
584+
(int)retainRes, ptr);
585+
free(cuHandle);
586+
return flagcxUnhandledDeviceError;
587+
}
548588

549589
// Discover actual physical allocation size (already granularity-aligned)
550590
size_t actualAllocSize = 0;
@@ -556,8 +596,16 @@ flagcxResult_t cudaAdaptorSymPhysAlloc(void *ptr, size_t size,
556596
free(cuHandle);
557597
return flagcxInvalidArgument;
558598
}
559-
DEVCHECK(cuMemExportToShareableHandle(
560-
shareableHandle, *cuHandle, CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR, 0));
599+
CUresult exportRes = cuMemExportToShareableHandle(
600+
shareableHandle, *cuHandle, CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR, 0);
601+
if (exportRes != CUDA_SUCCESS) {
602+
WARN("[symPhysAlloc] cuMemExportToShareableHandle FAILED: %d",
603+
(int)exportRes);
604+
free(cuHandle);
605+
return flagcxUnhandledDeviceError;
606+
}
607+
INFO(FLAGCX_INIT, "[symPhysAlloc] ptr=%p allocSize=%zu fd=%d", ptr,
608+
actualAllocSize, *(int *)shareableHandle);
561609
*handleSize = sizeof(int); // POSIX fd is an int
562610
*physHandle = cuHandle;
563611
return flagcxSuccess;

flagcx/adaptor/flagcx_device.cc

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ extern "C" flagcxResult_t flagcxDevCommDestroy(flagcxComm_t comm,
10081008
if (comm != nullptr && comm->heteroComm != nullptr &&
10091009
comm->heteroComm->devCommHandle == devComm) {
10101010
if (devComm->signalBuffer) {
1011-
flagcxOneSideSignalDeregister(comm->heteroComm);
1011+
flagcxOneSideSignalDeregister(comm);
10121012
}
10131013
comm->heteroComm->devCommHandle = nullptr;
10141014
}
@@ -1244,11 +1244,10 @@ extern "C" flagcxResult_t flagcxDevMemDestroy(flagcxComm_t comm,
12441244
return flagcxSuccess;
12451245
}
12461246

1247-
// Mark IPC table entry as no longer in use (actual cleanup deferred to
1248-
// flagcxCommDestroy.
1249-
if (comm != nullptr && devMem->ipcIndex >= 0 &&
1250-
devMem->ipcIndex < FLAGCX_MAX_IPC_ENTRIES) {
1251-
comm->ipcTable[devMem->ipcIndex].inUse = false;
1247+
// Release IPC table slot (resources moved to deferred queue for cleanup at
1248+
// comm destroy).
1249+
if (devMem->ipcIndex >= 0) {
1250+
releaseIpcTableSlot(comm, devMem->ipcIndex);
12521251
}
12531252

12541253
// Free window allocation if present
@@ -1464,6 +1463,65 @@ flagcxResult_t flagcxCommCleanupIpcTable(flagcxComm_t comm) {
14641463
return flagcxSuccess;
14651464
}
14661465

1466+
// ==========================================================================
1467+
// Deferred IPC table slot release.
1468+
// ==========================================================================
1469+
void releaseIpcTableSlot(flagcxComm_t comm, int slot) {
1470+
if (comm == nullptr || slot < 0 || slot >= FLAGCX_MAX_IPC_ENTRIES)
1471+
return;
1472+
struct flagcxIpcTableEntry *e = &comm->ipcTable[slot];
1473+
if (e->hostPeerPtrs == nullptr && e->devPeerPtrs == nullptr) {
1474+
e->inUse = false;
1475+
return;
1476+
}
1477+
1478+
// Move resources to deferred linked list for cleanup at comm destroy
1479+
struct flagcxDeferredIpcEntry *d =
1480+
(struct flagcxDeferredIpcEntry *)malloc(sizeof(*d));
1481+
if (d == nullptr) {
1482+
// OOM: leave slot occupied so flagcxCommCleanupIpcTable handles it at
1483+
// destroy. The slot won't be reusable, but resources are still safe.
1484+
WARN(
1485+
"releaseIpcTableSlot: OOM, keeping slot %d occupied until comm destroy",
1486+
slot);
1487+
e->inUse = false;
1488+
return;
1489+
}
1490+
d->hostPeerPtrs = e->hostPeerPtrs;
1491+
d->devPeerPtrs = e->devPeerPtrs;
1492+
d->nPeers = e->nPeers;
1493+
d->basePtr = e->basePtr;
1494+
d->next = nullptr;
1495+
flagcxIntruQueueEnqueue(&comm->deferredIpcQueue, d);
1496+
1497+
// Clear slot — now reusable by buildIpcPeerPointers
1498+
e->hostPeerPtrs = nullptr;
1499+
e->devPeerPtrs = nullptr;
1500+
e->nPeers = 0;
1501+
e->basePtr = nullptr;
1502+
e->inUse = false;
1503+
}
1504+
1505+
flagcxResult_t flagcxCommDrainDeferredIpc(flagcxComm_t comm) {
1506+
if (comm == nullptr)
1507+
return flagcxSuccess;
1508+
while (!flagcxIntruQueueEmpty(&comm->deferredIpcQueue)) {
1509+
struct flagcxDeferredIpcEntry *d =
1510+
flagcxIntruQueueDequeue(&comm->deferredIpcQueue);
1511+
if (d->hostPeerPtrs) {
1512+
for (int j = 0; j < d->nPeers; j++) {
1513+
if (d->hostPeerPtrs[j] && d->hostPeerPtrs[j] != d->basePtr)
1514+
deviceAdaptor->ipcMemHandleClose(d->hostPeerPtrs[j]);
1515+
}
1516+
free(d->hostPeerPtrs);
1517+
}
1518+
if (d->devPeerPtrs)
1519+
deviceAdaptor->deviceFree(d->devPeerPtrs, flagcxMemDevice, NULL);
1520+
free(d);
1521+
}
1522+
return flagcxSuccess;
1523+
}
1524+
14671525
// ==========================================================================
14681526
// Deferred device/host-pinned memory free.
14691527
// ==========================================================================

flagcx/adaptor/include/device_api/default_comm_traits.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,10 @@ struct CommTraits<Default<PlatformTag>> {
494494
// ---- MR offset helper ----
495495
FLAGCX_DEVICE_INLINE_DECORATOR
496496
static size_t toDataOffset(const Window &win, size_t off) {
497-
void *ptr = win.getLocalPointer(off);
498-
return (uintptr_t)ptr - win.mrBase;
497+
// Use rawPtr (the original buffer VA used for MR registration) rather
498+
// than getLocalPointer() which may return a VMM flat-mapped VA that
499+
// differs from the MR-registered VA.
500+
return (uintptr_t)win.getRawPtr() + off - win.mrBase;
499501
}
500502

501503
// ---- Action decomposition helpers ----

flagcx/adaptor/net/ibrc_adaptor.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2681,7 +2681,6 @@ flagcxResult_t flagcxIbIputSignal(void *sendComm, uint64_t srcOff,
26812681
void *dstPtr = (void *)(dstInfo->baseVas[dstRank] + dstOff);
26822682
uint32_t lkey = srcInfo->lkeys[srcRank];
26832683
uint32_t rkey = dstInfo->rkeys[dstRank];
2684-
26852684
wr[0].opcode = IBV_WR_RDMA_WRITE;
26862685
wr[0].send_flags = 0; // No CQE — only signal gets CQE
26872686
wr[0].wr_id = req - comm->base.reqs;

flagcx/core/flagcx_hetero.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,6 +789,10 @@ flagcxResult_t flagcxHeteroFlushRma(flagcxHeteroComm_t comm, int peer,
789789
return flagcxRemoteError;
790790
usleep(100);
791791
}
792+
// Final rmaError check: kernel proxy or network failures set rmaError;
793+
// catch errors that occurred after doneSeqs reached the target.
794+
if (__atomic_load_n(&proxy->rmaError, __ATOMIC_ACQUIRE))
795+
return flagcxRemoteError;
792796
return flagcxSuccess;
793797
}
794798

@@ -827,6 +831,10 @@ flagcxResult_t flagcxHeteroFlushAllRma(flagcxHeteroComm_t comm) {
827831
usleep(100);
828832
}
829833
}
834+
// Final rmaError check: kernel proxy or network failures set rmaError;
835+
// catch errors that occurred after doneSeqs reached the target.
836+
if (__atomic_load_n(&proxy->rmaError, __ATOMIC_ACQUIRE))
837+
return flagcxRemoteError;
830838
return flagcxSuccess;
831839
}
832840

flagcx/core/include/global_comm.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ struct flagcxDeferredFree {
3535
int memType; // flagcxMemDevice, flagcxMemHost, etc.
3636
};
3737

38+
// Deferred IPC entry — moved from ipcTable when a slot is released at runtime.
39+
// Actual ipcMemHandleClose + deviceFree happens at comm destroy.
40+
struct flagcxDeferredIpcEntry {
41+
void **hostPeerPtrs;
42+
void **devPeerPtrs;
43+
int nPeers;
44+
void *basePtr;
45+
struct flagcxDeferredIpcEntry *next;
46+
};
47+
3848
// Deferred DevComm buffer handle — buffers that cannot be freed immediately
3949
// in flagcxDevCommDestroy because peers may still hold IPC mappings to them.
4050
// Drained at flagcxCommDestroy time.
@@ -110,6 +120,11 @@ struct flagcxComm {
110120
// IPC peer pointer table — deferred cleanup
111121
struct flagcxIpcTableEntry ipcTable[FLAGCX_MAX_IPC_ENTRIES];
112122

123+
// Deferred IPC entry queue — IPC resources moved here when slots are released
124+
// at runtime; actual ipcMemHandleClose + deviceFree deferred to comm destroy.
125+
flagcxIntruQueue<struct flagcxDeferredIpcEntry, &flagcxDeferredIpcEntry::next>
126+
deferredIpcQueue;
127+
113128
// Deferred DevComm buffer queue — buffers stashed here during
114129
// flagcxDevCommDestroy, drained at flagcxCommDestroy.
115130
flagcxIntruQueue<struct flagcxDevCommBufferHandle,

flagcx/core/include/onesided.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,27 @@ struct flagcxOneSideHandleInfo {
2323
void *localMrHandle; // local rank's MR handle for deregMr
2424
void *localRecvComm; // recvComm used for MR registration (PD match)
2525
// Full-mesh IB connections (including self loopback, aligned with NCCL GIN)
26-
void **fullSendComms; // [nRanks] per-peer sendComm (NULL if not owner)
27-
void **fullRecvComms; // [nRanks] per-peer recvComm (NULL if not owner)
26+
void **fullSendComms; // [nRanks] per-peer sendComm — alias for
27+
// contextSendComms[0]
28+
void **fullRecvComms; // [nRanks] per-peer recvComm — alias for
29+
// contextRecvComms[0]
2830
int nRanks; // number of ranks (for cleanup iteration)
2931

32+
// Per-context QP arrays for thread isolation (NCCL GIN pattern).
33+
// Context 0 = RMA proxy; contexts 1..N = kernel proxy threads.
34+
// Each context has its own full-mesh of RC QPs so no QP is shared
35+
// across threads. All contexts share the same MR handles/rkeys (same PD).
36+
void ***contextSendComms; // [nContexts][nRanks]
37+
void ***contextRecvComms; // [nContexts][nRanks]
38+
int nContexts; // 1 + nKernelProxies
39+
3040
// Symmetric memory window for intra-node D2D bypass (CE path).
3141
// NULL if VMM not available or window not registered with
3242
// FLAGCX_WIN_COLL_SYMMETRIC.
3343
struct flagcxSymWindow *symWin;
44+
45+
// IPC table slot index for signal buffer D2D bypass (-1 if none).
46+
int signalIpcSlot;
3447
};
3548

3649
// Internal implementation used by sym_heap and flagcxCommRegister

flagcx/core/include/proxy.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,19 @@ enum flagcxProxyOpState {
3030
struct flagcxProxyKernelState {
3131
pthread_t threads[FLAGCX_DEVICE_CTA_COUNT];
3232
flagcxFifo_t fifos[FLAGCX_DEVICE_CTA_COUNT];
33-
int contextCount = 1;
33+
int contextCount = 0;
3434
flagcxStream_t stream;
3535
int stop = 0;
3636
// Synchronization for initialization
3737
pthread_mutex_t initMutex;
3838
pthread_cond_t initCond;
3939
int ready = 0;
40+
int initFailed = 0;
41+
// Shared per-peer spinlocks for PUT_VALUE staging slot protection.
42+
// All kernel proxy threads lock pvLocks[peer] before writing the staging
43+
// buffer and posting iput, preventing cross-thread corruption.
44+
pthread_spinlock_t *pvLocks = nullptr;
45+
int pvLocksCount = 0;
4046
};
4147

4248
struct flagcxProxyArgs;

0 commit comments

Comments
 (0)