Skip to content

Commit 94a424f

Browse files
ymmyysclaude
authored andcommitted
p2p/accl: chunk MR registration to fit vsolar's 64MB per-MR cap
vLLM PD registers the whole KV-cache region in one flagcxP2pRegister call (2.5GB+); vsolar's verbs reject GPU MRs above ~64MB (ENOMEM), so engine creation-time registration failed and PD could not start on 810e. Mirror Mooncake's barex transport (eic_max_block_size): split registrations into 64MB chunks (FLAGCX_ACCL_MAX_MR_MB overrides, 0 disables), publish one desc-table entry per chunk in the handshake, and split each submitted iov at local and remote chunk boundaries with per-chunk lkeys/rkeys. MakeDesc validates ranges against merged spans so cross-chunk writes stay accepted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5dfc993 commit 94a424f

1 file changed

Lines changed: 189 additions & 68 deletions

File tree

flagcx/core/flagcx_p2p_accl.cc

Lines changed: 189 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,10 @@ struct AcclRemoteRegion {
107107

108108
struct AcclMrEntry {
109109
uint64_t mrId;
110-
uintptr_t baseAddr;
110+
uintptr_t baseAddr; /* this chunk */
111111
size_t size;
112+
uintptr_t regBase; /* whole logical registration this chunk belongs to */
113+
size_t regSize;
112114
device_type dtype;
113115
int deviceId;
114116
uint32_t nKeys;
@@ -158,7 +160,10 @@ struct FlagcxAcclEngine {
158160

159161
std::mutex mrMu;
160162
uint64_t nextMrId = 1;
161-
std::map<uintptr_t, AcclMrEntry> mrByBase;
163+
std::map<uintptr_t, AcclMrEntry> mrByBase; /* keyed by chunk base */
164+
/* vsolar caps a single GPU MR at 64MB, so registrations are split into
165+
chunks of at most this many bytes (FLAGCX_ACCL_MAX_MR_MB, 0 = off). */
166+
size_t mrChunkBytes = 64ull << 20;
162167

163168
std::mutex xferMu;
164169
uint64_t nextXferId = 1;
@@ -186,8 +191,11 @@ struct FlagcxAcclConn {
186191
struct flagcxSocket notifSock;
187192
bool notifConnected = false;
188193

189-
std::vector<AcclRemoteRegion> remoteRegions;
190-
std::vector<XChannel *> channels; /* initiator side only */
194+
std::vector<AcclRemoteRegion> remoteRegions; /* one per remote chunk */
195+
/* merged contiguous extents of remoteRegions, for range validation
196+
(chunks of one logical registration are contiguous by construction) */
197+
std::vector<std::pair<uint64_t, uint64_t>> remoteSpans; /* base, size */
198+
std::vector<XChannel *> channels; /* initiator side */
191199
std::atomic<uint64_t> rr{0};
192200
};
193201

@@ -426,6 +434,34 @@ bool findMrContaining(FlagcxAcclEngine *engine, uintptr_t addr, size_t size,
426434
return false;
427435
}
428436

437+
/* Chunked registrations: a remote VA resolves to the chunk that contains
438+
it (regions are sorted by base). Returns nullptr when the conn has no
439+
region table (legacy peers exchanging single-region descs). */
440+
const AcclRemoteRegion *findRemoteRegion(const FlagcxAcclConn *conn,
441+
uint64_t va) {
442+
const auto &regions = conn->remoteRegions;
443+
if (regions.empty())
444+
return nullptr;
445+
size_t lo = 0, hi = regions.size();
446+
while (lo < hi) { /* first region with base > va, then step back */
447+
size_t mid = lo + (hi - lo) / 2;
448+
if (regions[mid].baseAddr <= va)
449+
lo = mid + 1;
450+
else
451+
hi = mid;
452+
}
453+
if (lo == 0)
454+
return nullptr;
455+
const AcclRemoteRegion &r = regions[lo - 1];
456+
return (va >= r.baseAddr && va < r.baseAddr + r.size) ? &r : nullptr;
457+
}
458+
459+
uint32_t regionKeyForNic(const AcclRemoteRegion &r, int nic) {
460+
if (nic < 0 || (uint32_t)nic >= r.nKeys || nic >= kMaxNics)
461+
return r.nKeys > 0 ? r.rkeys[0] : 0;
462+
return r.rkeys[nic];
463+
}
464+
429465
XChannel *pickChannel(FlagcxAcclConn *conn) {
430466
const size_t n = conn->channels.size();
431467
if (n == 0)
@@ -467,26 +503,50 @@ int acclSubmit(FlagcxAcclConn *conn, const std::vector<void *> &localVec,
467503
sizeVec[i]);
468504
return -1;
469505
}
470-
AcclMrEntry entry;
471-
if (!findMrContaining(engine, (uintptr_t)localVec[i], sizeVec[i], &entry)) {
472-
WARN("NET/ACCL_P2P : local buffer %p not registered", localVec[i]);
473-
return -1;
474-
}
475-
if (localNic < 0 || (uint32_t)localNic >= entry.nKeys) {
476-
WARN("NET/ACCL_P2P : lkey for nic %d missing (nKeys=%u)", localNic,
477-
entry.nKeys);
478-
return -1;
506+
/* Registrations are chunked (vsolar 64MB per-MR cap), so one iov may
507+
span several local MRs and several remote regions. Split at every
508+
chunk boundary on either side; each slice carries the lkey/rkey of
509+
the chunks it lands in. */
510+
uintptr_t lcur = (uintptr_t)localVec[i];
511+
uint64_t rcur = descs[i].addr;
512+
size_t remaining = sizeVec[i];
513+
while (remaining > 0) {
514+
AcclMrEntry entry;
515+
if (!findMrContaining(engine, lcur, 1, &entry)) {
516+
WARN("NET/ACCL_P2P : local buffer %p not registered", (void *)lcur);
517+
return -1;
518+
}
519+
if (localNic < 0 || (uint32_t)localNic >= entry.nKeys) {
520+
WARN("NET/ACCL_P2P : lkey for nic %d missing (nKeys=%u)", localNic,
521+
entry.nKeys);
522+
return -1;
523+
}
524+
size_t slice = std::min(remaining, entry.baseAddr + entry.size - lcur);
525+
uint32_t rkey;
526+
const AcclRemoteRegion *rr = findRemoteRegion(conn, rcur);
527+
if (rr != nullptr) {
528+
slice = std::min(slice, (size_t)(rr->baseAddr + rr->size - rcur));
529+
rkey = regionKeyForNic(*rr, peerNic);
530+
} else {
531+
/* no region table entry — trust the caller's desc keys wholesale */
532+
rkey = descKeyForNic(descs[i], peerNic);
533+
}
534+
535+
rw_memp_t w{};
536+
w.sg.addr = (uint64_t)lcur;
537+
w.sg.length = (uint32_t)slice;
538+
w.sg.lkey = entry.lkeys[localNic];
539+
w.data.d_type = entry.dtype;
540+
w.data.device_id = entry.deviceId;
541+
w.r_addr = rcur;
542+
w.r_key = rkey;
543+
w.r_ttl_ms = UINT64_MAX;
544+
batch->push_back(w);
545+
546+
lcur += slice;
547+
rcur += slice;
548+
remaining -= slice;
479549
}
480-
rw_memp_t w{};
481-
w.sg.addr = (uint64_t)(uintptr_t)localVec[i];
482-
w.sg.length = (uint32_t)sizeVec[i];
483-
w.sg.lkey = entry.lkeys[localNic];
484-
w.data.d_type = entry.dtype;
485-
w.data.device_id = entry.deviceId;
486-
w.r_addr = descs[i].addr;
487-
w.r_key = descKeyForNic(descs[i], peerNic);
488-
w.r_ttl_ms = UINT64_MAX;
489-
batch->push_back(w);
490550
}
491551
if (batch->empty()) {
492552
*transferId = 0; /* nothing to do; XferStatus(0) reports done */
@@ -588,6 +648,20 @@ int acclHandshake(FlagcxAcclEngine *engine, struct bootstrapState *bsConn,
588648
memcpy(r.rkeys, remoteTable[i].rkeys, sizeof(r.rkeys));
589649
conn->remoteRegions.push_back(r);
590650
}
651+
std::sort(conn->remoteRegions.begin(), conn->remoteRegions.end(),
652+
[](const AcclRemoteRegion &a, const AcclRemoteRegion &b) {
653+
return a.baseAddr < b.baseAddr;
654+
});
655+
/* merge contiguous chunks into spans for range validation */
656+
conn->remoteSpans.clear();
657+
for (const AcclRemoteRegion &r : conn->remoteRegions) {
658+
if (!conn->remoteSpans.empty() &&
659+
conn->remoteSpans.back().first + conn->remoteSpans.back().second ==
660+
r.baseAddr)
661+
conn->remoteSpans.back().second += r.size;
662+
else
663+
conn->remoteSpans.emplace_back(r.baseAddr, r.size);
664+
}
591665
return remoteHello.barexPort;
592666
}
593667

@@ -638,6 +712,14 @@ FlagcxP2pEngine *flagcxAcclEngineCreate() {
638712
engine->localGpuIdx = inferLocalGpuIdxAccl();
639713
memset(&engine->notifListenSock, 0, sizeof(engine->notifListenSock));
640714

715+
const char *mrMb = flagcxGetEnv("FLAGCX_ACCL_MAX_MR_MB");
716+
if (mrMb != nullptr) {
717+
engine->mrChunkBytes = (size_t)strtoull(mrMb, nullptr, 10) << 20;
718+
INFO(FLAGCX_INIT, "NET/ACCL_P2P : MR chunk size %zu MB%s",
719+
engine->mrChunkBytes >> 20,
720+
engine->mrChunkBytes == 0 ? " (chunking off)" : "");
721+
}
722+
641723
XDeviceManager *mgr = nullptr;
642724
if (XDeviceManager::Singleton(mgr) != BAREX_SUCCESS || mgr == nullptr) {
643725
WARN("NET/ACCL_P2P : XDeviceManager::Singleton failed");
@@ -1017,7 +1099,7 @@ int flagcxAcclEngineReg(FlagcxP2pEngine *e, uintptr_t data, size_t size,
10171099
std::lock_guard<std::mutex> lk(engine->mrMu);
10181100
auto it = engine->mrByBase.find(data);
10191101
if (it != engine->mrByBase.end()) {
1020-
if (it->second.size != size) {
1102+
if (it->second.regBase != data || it->second.regSize != size) {
10211103
WARN("NET/ACCL_P2P : re-register 0x%lx with different size",
10221104
(unsigned long)data);
10231105
return -1;
@@ -1031,52 +1113,77 @@ int flagcxAcclEngineReg(FlagcxP2pEngine *e, uintptr_t data, size_t size,
10311113
int devId;
10321114
classifyPtr(reinterpret_cast<void *>(data), &dtype, &devId);
10331115

1034-
memp_t mem;
1035-
BarexResult r = engine->mempool->RegUserMr(
1036-
mem, reinterpret_cast<void *>(data), size, dtype, devId);
1037-
if (r != BAREX_SUCCESS) {
1038-
WARN("NET/ACCL_P2P : RegUserMr(%p,%zu,%s,dev%d) failed: %s "
1039-
"(VMM memory cannot be registered — run with FLAGCX_VMM_ENABLE=0)",
1040-
reinterpret_cast<void *>(data), size, dtype == GPU ? "GPU" : "CPU",
1041-
devId, bxstr(r));
1042-
return -1;
1043-
}
1116+
/* vsolar rejects GPU MRs above ~64MB (ibv_reg_mr ENOMEM), so register in
1117+
chunks like Mooncake's barex transport does (eic_max_block_size). */
1118+
const size_t chunkBytes =
1119+
engine->mrChunkBytes > 0 ? engine->mrChunkBytes : size;
1120+
std::vector<AcclMrEntry> chunks;
1121+
for (size_t off = 0; off < size; off += chunkBytes) {
1122+
const uintptr_t cbase = data + off;
1123+
const size_t csize = std::min(chunkBytes, size - off);
1124+
memp_t mem;
1125+
BarexResult r = engine->mempool->RegUserMr(
1126+
mem, reinterpret_cast<void *>(cbase), csize, dtype, devId);
1127+
if (r != BAREX_SUCCESS) {
1128+
WARN("NET/ACCL_P2P : RegUserMr(%p,%zu,%s,dev%d) failed: %s "
1129+
"(chunk %zu/%zu of %p+%zu; VMM memory cannot be registered — "
1130+
"run with FLAGCX_VMM_ENABLE=0)",
1131+
reinterpret_cast<void *>(cbase), csize, dtype == GPU ? "GPU" : "CPU",
1132+
devId, bxstr(r), off / chunkBytes + 1,
1133+
(size + chunkBytes - 1) / chunkBytes, reinterpret_cast<void *>(data),
1134+
size);
1135+
for (const AcclMrEntry &c : chunks)
1136+
engine->mempool->DeregUserMr(reinterpret_cast<void *>(c.baseAddr),
1137+
dtype);
1138+
return -1;
1139+
}
10441140

1045-
AcclMrEntry entry;
1046-
memset(&entry, 0, sizeof(entry));
1047-
entry.baseAddr = data;
1048-
entry.size = size;
1049-
entry.dtype = dtype;
1050-
entry.deviceId = devId;
1051-
entry.nKeys = 0;
1052-
for (auto &kv : mem.mrs) {
1053-
const int nic = kv.first;
1054-
if (nic < 0 || nic >= kMaxNics || kv.second == nullptr) {
1055-
WARN("NET/ACCL_P2P : unexpected mr map entry nic=%d", nic);
1056-
continue;
1141+
AcclMrEntry entry;
1142+
memset(&entry, 0, sizeof(entry));
1143+
entry.baseAddr = cbase;
1144+
entry.size = csize;
1145+
entry.regBase = data;
1146+
entry.regSize = size;
1147+
entry.dtype = dtype;
1148+
entry.deviceId = devId;
1149+
entry.nKeys = 0;
1150+
for (auto &kv : mem.mrs) {
1151+
const int nic = kv.first;
1152+
if (nic < 0 || nic >= kMaxNics || kv.second == nullptr) {
1153+
WARN("NET/ACCL_P2P : unexpected mr map entry nic=%d", nic);
1154+
continue;
1155+
}
1156+
entry.lkeys[nic] = kv.second->lkey;
1157+
entry.rkeys[nic] = kv.second->rkey;
1158+
if ((uint32_t)(nic + 1) > entry.nKeys)
1159+
entry.nKeys = nic + 1;
10571160
}
1058-
entry.lkeys[nic] = kv.second->lkey;
1059-
entry.rkeys[nic] = kv.second->rkey;
1060-
if ((uint32_t)(nic + 1) > entry.nKeys)
1061-
entry.nKeys = nic + 1;
1062-
}
1063-
if (entry.nKeys == 0) {
1064-
engine->mempool->DeregUserMr(reinterpret_cast<void *>(data), dtype);
1065-
return -1;
1161+
if (entry.nKeys == 0) {
1162+
engine->mempool->DeregUserMr(reinterpret_cast<void *>(cbase), dtype);
1163+
for (const AcclMrEntry &c : chunks)
1164+
engine->mempool->DeregUserMr(reinterpret_cast<void *>(c.baseAddr),
1165+
dtype);
1166+
return -1;
1167+
}
1168+
chunks.push_back(entry);
10661169
}
10671170

10681171
std::lock_guard<std::mutex> lk(engine->mrMu);
10691172
auto raced = engine->mrByBase.find(data);
10701173
if (raced != engine->mrByBase.end()) {
10711174
/* concurrent Reg of the same base won the race between our dedup
1072-
check and this insert; keep theirs, drop our duplicate MR */
1073-
engine->mempool->DeregUserMr(reinterpret_cast<void *>(data), dtype);
1175+
check and this insert; keep theirs, drop our duplicate MRs */
1176+
for (const AcclMrEntry &c : chunks)
1177+
engine->mempool->DeregUserMr(reinterpret_cast<void *>(c.baseAddr), dtype);
10741178
mrId = raced->second.mrId;
10751179
return 0;
10761180
}
1077-
entry.mrId = engine->nextMrId++;
1078-
engine->mrByBase[data] = entry;
1079-
mrId = entry.mrId;
1181+
const uint64_t id = engine->nextMrId++;
1182+
for (AcclMrEntry &c : chunks) {
1183+
c.mrId = id;
1184+
engine->mrByBase[c.baseAddr] = c;
1185+
}
1186+
mrId = id;
10801187
return 0;
10811188
}
10821189

@@ -1085,12 +1192,13 @@ void flagcxAcclEngineMrDestroy(FlagcxP2pEngine *e, FlagcxP2pMr mr) {
10851192
if (engine == nullptr)
10861193
return;
10871194
std::lock_guard<std::mutex> lk(engine->mrMu);
1088-
for (auto it = engine->mrByBase.begin(); it != engine->mrByBase.end(); ++it) {
1195+
for (auto it = engine->mrByBase.begin(); it != engine->mrByBase.end();) {
10891196
if (it->second.mrId == mr) {
10901197
engine->mempool->DeregUserMr(reinterpret_cast<void *>(it->first),
10911198
it->second.dtype);
1092-
engine->mrByBase.erase(it);
1093-
return;
1199+
it = engine->mrByBase.erase(it);
1200+
} else {
1201+
++it;
10941202
}
10951203
}
10961204
}
@@ -1101,14 +1209,21 @@ int flagcxAcclEnginePrepareDesc(FlagcxP2pEngine *e, FlagcxP2pMr mr,
11011209
if (engine == nullptr || data == nullptr || descBuf == nullptr)
11021210
return -1;
11031211
std::lock_guard<std::mutex> lk(engine->mrMu);
1212+
const uintptr_t addr = (uintptr_t)data;
11041213
for (auto &kv : engine->mrByBase) {
1105-
if (kv.second.mrId != mr)
1214+
const AcclMrEntry &entry = kv.second;
1215+
if (entry.mrId != mr)
1216+
continue;
1217+
/* chunked mrId: pick the chunk containing data. The 64B desc can only
1218+
carry one chunk's rkeys; a peer writing across chunk boundaries must
1219+
resolve per-chunk keys from its handshake region table. */
1220+
if (addr < entry.baseAddr || addr >= entry.baseAddr + entry.size)
11061221
continue;
11071222
FlagcxP2pRdmaDesc desc;
11081223
memset(&desc, 0, sizeof(desc));
1109-
desc.addr = (uint64_t)(uintptr_t)data;
1224+
desc.addr = (uint64_t)addr;
11101225
desc.size = (uint32_t)size;
1111-
fillDescKeys(&desc, kv.second.rkeys, kv.second.nKeys);
1226+
fillDescKeys(&desc, entry.rkeys, entry.nKeys);
11121227
flagcxP2pSerializeRdmaDesc(desc, descBuf);
11131228
return 0;
11141229
}
@@ -1120,12 +1235,18 @@ int flagcxAcclEngineMakeDesc(FlagcxP2pConn *c, uint64_t remoteVa, uint32_t size,
11201235
FlagcxAcclConn *conn = C(c);
11211236
if (conn == nullptr || desc == nullptr)
11221237
return -1;
1123-
for (const auto &r : conn->remoteRegions) {
1124-
if (remoteVa >= r.baseAddr && remoteVa + size <= r.baseAddr + r.size) {
1238+
/* the range may cross chunk boundaries — validate against merged spans;
1239+
acclSubmit re-resolves per-chunk rkeys, the desc carries the first
1240+
chunk's keys for legacy/single-chunk consumers. */
1241+
for (const auto &span : conn->remoteSpans) {
1242+
if (remoteVa >= span.first && remoteVa + size <= span.first + span.second) {
1243+
const AcclRemoteRegion *r = findRemoteRegion(conn, remoteVa);
1244+
if (r == nullptr)
1245+
return -1;
11251246
memset(desc, 0, sizeof(*desc));
11261247
desc->addr = remoteVa;
11271248
desc->size = size;
1128-
fillDescKeys(desc, r.rkeys, r.nKeys);
1249+
fillDescKeys(desc, r->rkeys, r->nKeys);
11291250
return 0;
11301251
}
11311252
}

0 commit comments

Comments
 (0)