@@ -107,8 +107,10 @@ struct AcclRemoteRegion {
107107
108108struct 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 ®ions = 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+
429465XChannel *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