Skip to content

Commit d28941e

Browse files
Santosh Narayankhedkarfacebook-github-bot
authored andcommitted
Add bulk-buffer registration leases for IBGDA (#3414)
Summary: Add move-only bulk-buffer registration leases with stable generations and non-owning contained-range views. Bulk registrations request PCIe Relaxed Ordering, share the existing per-allocation MR cache, and reject invalid, overflowing, stale, or released ranges. Expose the lease operations through `MultiPeerTransport` for the IBGDA zero-copy AllReduce path. This change does not alter collective behavior or enable zero-copy Ring or Tree. Add focused lifetime, containment, overlap, ordering, and re-registration coverage. IBRC exercises the shared transport-base implementation as regression coverage; IBGDA remains the product scope. Reviewed By: rmahidhar Differential Revision: D114554503
1 parent 35b46e5 commit d28941e

5 files changed

Lines changed: 466 additions & 5 deletions

File tree

comms/prims/tests/MultiSegmentRegistrationTest.cc

Lines changed: 189 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <folly/init/Init.h>
77
#include <cstddef>
8+
#include <limits>
89
#include <memory>
910
#include <vector>
1011

@@ -151,12 +152,39 @@ struct TransportHandle {
151152
ibrc->deregisterBuffer(ptr);
152153
}
153154
}
155+
156+
IbBufferRegistrationLease registerIbBulkBuffer(void* ptr, std::size_t size) {
157+
return ibgda ? ibgda->registerIbBulkBuffer(ptr, size)
158+
: ibrc->registerIbBulkBuffer(ptr, size);
159+
}
160+
161+
std::optional<IbBufferRegistrationView> lookupIbBulkBuffer(
162+
const IbBufferRegistrationLease& lease,
163+
void* ptr,
164+
std::size_t size) const {
165+
return ibgda ? ibgda->lookupIbBulkBuffer(lease, ptr, size)
166+
: ibrc->lookupIbBulkBuffer(lease, ptr, size);
167+
}
168+
169+
void deregisterIbBulkBuffer(IbBufferRegistrationLease& lease) {
170+
if (ibgda) {
171+
ibgda->deregisterIbBulkBuffer(lease);
172+
} else {
173+
ibrc->deregisterIbBulkBuffer(lease);
174+
}
175+
}
176+
177+
bool isIbBulkBufferViewActive(const IbBufferRegistrationView& view) const {
178+
return ibgda ? ibgda->isIbBulkBufferViewActive(view)
179+
: ibrc->isIbBulkBufferViewActive(view);
180+
}
154181
};
155182

156-
TransportHandle createTransport(IbTestBackend backend) {
183+
TransportHandle createTransport(
184+
IbTestBackend backend,
185+
const MultipeerIbTransportConfig& config) {
157186
auto bootstrap = std::make_shared<
158187
testing::NiceMock<meta::comms::testing::MockBootstrap>>();
159-
auto config = makeConfig();
160188
TransportHandle handle;
161189
if (backend == IbTestBackend::Ibgda) {
162190
handle.ibgda = std::make_unique<MultipeerIbgdaTransport>(
@@ -168,6 +196,10 @@ TransportHandle createTransport(IbTestBackend backend) {
168196
return handle;
169197
}
170198

199+
TransportHandle createTransport(IbTestBackend backend) {
200+
return createTransport(backend, makeConfig());
201+
}
202+
171203
class MultiSegmentRegistrationTest
172204
: public ::testing::TestWithParam<IbTestBackend> {};
173205

@@ -251,6 +283,161 @@ TEST_P(MultiSegmentRegistrationTest, ContiguousBufferRegistration) {
251283
CUDACHECK_TEST(cudaFree(devPtr));
252284
}
253285

286+
TEST_P(MultiSegmentRegistrationTest, BulkLeaseBoundsContainedViews) {
287+
CUDACHECK_TEST(cudaSetDevice(0));
288+
289+
TransportHandle transport;
290+
try {
291+
transport = createTransport(GetParam());
292+
} catch (const std::exception& e) {
293+
GTEST_SKIP() << backendName(GetParam())
294+
<< " transport not available: " << e.what();
295+
}
296+
297+
constexpr std::size_t kAllocationSize = 4 * 1024 * 1024;
298+
constexpr std::size_t kLeaseOffset = 512 * 1024;
299+
constexpr std::size_t kLeaseSize = 2 * 1024 * 1024;
300+
constexpr std::size_t kViewOffset = 128 * 1024;
301+
constexpr std::size_t kViewSize = 256 * 1024;
302+
void* allocation = nullptr;
303+
CUDACHECK_TEST(cudaMalloc(&allocation, kAllocationSize));
304+
auto* const leasePtr = static_cast<char*>(allocation) + kLeaseOffset;
305+
306+
EXPECT_THROW(
307+
transport.registerIbBulkBuffer(leasePtr, 0), std::invalid_argument);
308+
auto lease = transport.registerIbBulkBuffer(leasePtr, kLeaseSize);
309+
EXPECT_THROW(
310+
transport.lookupIbBulkBuffer(lease, leasePtr, 0), std::invalid_argument);
311+
EXPECT_THROW(
312+
transport.lookupIbBulkBuffer(
313+
lease, leasePtr, std::numeric_limits<std::size_t>::max()),
314+
std::invalid_argument);
315+
auto exact = transport.lookupIbBulkBuffer(lease, leasePtr, kLeaseSize);
316+
auto contained =
317+
transport.lookupIbBulkBuffer(lease, leasePtr + kViewOffset, kViewSize);
318+
auto tail = transport.lookupIbBulkBuffer(lease, leasePtr + kLeaseSize - 1, 1);
319+
auto before = transport.lookupIbBulkBuffer(lease, leasePtr - 1, kViewSize);
320+
auto after = transport.lookupIbBulkBuffer(
321+
lease, leasePtr + kLeaseSize - kViewSize + 1, kViewSize);
322+
323+
ASSERT_TRUE(exact.has_value());
324+
ASSERT_TRUE(contained.has_value());
325+
ASSERT_TRUE(tail.has_value());
326+
EXPECT_EQ(exact->localBuffer.ptr, leasePtr);
327+
EXPECT_EQ(contained->localBuffer.ptr, leasePtr + kViewOffset);
328+
EXPECT_EQ(contained->size, kViewSize);
329+
EXPECT_EQ(contained->leaseGeneration, lease.generation());
330+
EXPECT_FALSE(before.has_value());
331+
EXPECT_FALSE(after.has_value());
332+
EXPECT_TRUE(transport.isIbBulkBufferViewActive(*contained));
333+
334+
transport.deregisterIbBulkBuffer(lease);
335+
EXPECT_FALSE(lease.valid());
336+
EXPECT_FALSE(
337+
transport.lookupIbBulkBuffer(lease, leasePtr, kViewSize).has_value());
338+
EXPECT_FALSE(transport.isIbBulkBufferViewActive(*contained));
339+
EXPECT_THROW(transport.deregisterIbBulkBuffer(lease), std::invalid_argument);
340+
CUDACHECK_TEST(cudaFree(allocation));
341+
}
342+
343+
TEST_P(MultiSegmentRegistrationTest, BulkLeaseReportsEffectiveStrictOrdering) {
344+
CUDACHECK_TEST(cudaSetDevice(0));
345+
346+
auto config = makeConfig();
347+
config.enablePciRelaxedOrdering =
348+
MultipeerIbTransportConfig::PciRelaxedOrderingMode::Disabled;
349+
TransportHandle transport;
350+
try {
351+
transport = createTransport(GetParam(), std::move(config));
352+
} catch (const std::exception& e) {
353+
GTEST_SKIP() << backendName(GetParam())
354+
<< " transport not available: " << e.what();
355+
}
356+
357+
constexpr std::size_t kSize = 2 * 1024 * 1024;
358+
void* allocation = nullptr;
359+
CUDACHECK_TEST(cudaMalloc(&allocation, kSize));
360+
361+
auto lease = transport.registerIbBulkBuffer(allocation, kSize);
362+
auto view = transport.lookupIbBulkBuffer(lease, allocation, kSize);
363+
ASSERT_TRUE(view.has_value());
364+
EXPECT_FALSE(view->relaxedOrdering);
365+
366+
transport.deregisterIbBulkBuffer(lease);
367+
CUDACHECK_TEST(cudaFree(allocation));
368+
}
369+
370+
TEST_P(MultiSegmentRegistrationTest, OverlappingBulkLeasesRemainDistinct) {
371+
CUDACHECK_TEST(cudaSetDevice(0));
372+
373+
TransportHandle transport;
374+
try {
375+
transport = createTransport(GetParam());
376+
} catch (const std::exception& e) {
377+
GTEST_SKIP() << backendName(GetParam())
378+
<< " transport not available: " << e.what();
379+
}
380+
381+
constexpr std::size_t kAllocationSize = 4 * 1024 * 1024;
382+
constexpr std::size_t kOuterSize = 3 * 1024 * 1024;
383+
constexpr std::size_t kInnerOffset = 1024 * 1024;
384+
constexpr std::size_t kInnerSize = 1024 * 1024;
385+
void* allocation = nullptr;
386+
CUDACHECK_TEST(cudaMalloc(&allocation, kAllocationSize));
387+
auto* const base = static_cast<char*>(allocation);
388+
389+
auto outer = transport.registerIbBulkBuffer(base, kOuterSize);
390+
auto inner = transport.registerIbBulkBuffer(base + kInnerOffset, kInnerSize);
391+
auto outerView =
392+
transport.lookupIbBulkBuffer(outer, base + kInnerOffset, kInnerSize);
393+
auto innerView =
394+
transport.lookupIbBulkBuffer(inner, base + kInnerOffset, kInnerSize);
395+
396+
ASSERT_TRUE(outerView.has_value());
397+
ASSERT_TRUE(innerView.has_value());
398+
EXPECT_NE(outer.generation(), inner.generation());
399+
EXPECT_EQ(outerView->leaseGeneration, outer.generation());
400+
EXPECT_EQ(innerView->leaseGeneration, inner.generation());
401+
402+
transport.deregisterIbBulkBuffer(inner);
403+
EXPECT_FALSE(transport.isIbBulkBufferViewActive(*innerView));
404+
EXPECT_TRUE(transport.isIbBulkBufferViewActive(*outerView));
405+
transport.deregisterIbBulkBuffer(outer);
406+
CUDACHECK_TEST(cudaFree(allocation));
407+
}
408+
409+
TEST_P(MultiSegmentRegistrationTest, ReregistrationChangesLeaseGeneration) {
410+
CUDACHECK_TEST(cudaSetDevice(0));
411+
412+
TransportHandle transport;
413+
try {
414+
transport = createTransport(GetParam());
415+
} catch (const std::exception& e) {
416+
GTEST_SKIP() << backendName(GetParam())
417+
<< " transport not available: " << e.what();
418+
}
419+
420+
constexpr std::size_t kSize = 2 * 1024 * 1024;
421+
void* allocation = nullptr;
422+
CUDACHECK_TEST(cudaMalloc(&allocation, kSize));
423+
424+
auto first = transport.registerIbBulkBuffer(allocation, kSize);
425+
auto firstView = transport.lookupIbBulkBuffer(first, allocation, kSize);
426+
ASSERT_TRUE(firstView.has_value());
427+
const uint64_t firstGeneration = first.generation();
428+
transport.deregisterIbBulkBuffer(first);
429+
430+
auto second = transport.registerIbBulkBuffer(allocation, kSize);
431+
auto secondView = transport.lookupIbBulkBuffer(second, allocation, kSize);
432+
ASSERT_TRUE(secondView.has_value());
433+
EXPECT_NE(firstGeneration, second.generation());
434+
EXPECT_FALSE(transport.isIbBulkBufferViewActive(*firstView));
435+
EXPECT_TRUE(transport.isIbBulkBufferViewActive(*secondView));
436+
437+
transport.deregisterIbBulkBuffer(second);
438+
CUDACHECK_TEST(cudaFree(allocation));
439+
}
440+
254441
} // namespace comms::prims::tests
255442

256443
int main(int argc, char** argv) {

comms/prims/transport/MultiPeerIbTransport.cc

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,27 @@ void checkSendRecvSignalAlignment(const void* ptr, const char* label) {
248248
fmt::format("{} must be {}-byte aligned", label, alignof(SignalState)));
249249
}
250250
}
251+
252+
bool rangeContains(
253+
uintptr_t outerBegin,
254+
std::size_t outerSize,
255+
uintptr_t innerBegin,
256+
std::size_t innerSize) {
257+
return innerSize != 0 && innerBegin >= outerBegin && innerSize <= outerSize &&
258+
innerBegin - outerBegin <= outerSize - innerSize;
259+
}
260+
261+
uintptr_t
262+
checkedRangeEnd(const void* ptr, std::size_t size, const char* operation) {
263+
const auto begin = reinterpret_cast<uintptr_t>(ptr);
264+
if (ptr == nullptr || size == 0 ||
265+
size > std::numeric_limits<uintptr_t>::max() - begin) {
266+
throw std::invalid_argument(
267+
fmt::format(
268+
"{}: invalid buffer range ptr={} size={}", operation, ptr, size));
269+
}
270+
return begin + size;
271+
}
251272
} // namespace
252273

253274
MultiPeerIbTransportBase::MultiPeerIbTransportBase(
@@ -1080,6 +1101,10 @@ IbgdaLocalBuffer MultiPeerIbTransportBase::registerBuffer(
10801101
throw std::invalid_argument("Invalid buffer pointer or size");
10811102
}
10821103

1104+
const auto addr = reinterpret_cast<uintptr_t>(ptr);
1105+
[[maybe_unused]] const auto requestedEnd =
1106+
checkedRangeEnd(ptr, size, "registerBuffer");
1107+
10831108
// Resolve the effective Relaxed Ordering once, up front: the caller's request
10841109
// gated by config (NCCL_IB_PCI_RELAXED_ORDERING) AND by NIC capability probed
10851110
// during openNics. Gating on capability means a NIC whose driver rejects
@@ -1093,11 +1118,10 @@ IbgdaLocalBuffer MultiPeerIbTransportBase::registerBuffer(
10931118
// Fast path: containment lookup — if [ptr, ptr+size) falls entirely within an
10941119
// existing registration with the same effective ordering, return the cached
10951120
// per-NIC lkeys with no driver call.
1096-
const auto addr = reinterpret_cast<uintptr_t>(ptr);
10971121
auto it = registeredBuffers_.upper_bound(addr);
10981122
if (it != registeredBuffers_.begin()) {
10991123
--it;
1100-
if (addr + size <= it->first + it->second.allocSize) {
1124+
if (rangeContains(it->first, it->second.allocSize, addr, size)) {
11011125
// The cache holds one MR set per allocation; its access flags (including
11021126
// Relaxed Ordering) are fixed at registration, so the effective ordering
11031127
// is part of the cache key. A containment hit resolving to different
@@ -1154,7 +1178,6 @@ IbgdaLocalBuffer MultiPeerIbTransportBase::registerBuffer(
11541178
// covers the full contiguous VA — ibv_reg_dmabuf_mr handles the underlying
11551179
// physical discontinuity transparently.
11561180
{
1157-
const auto requestedEnd = reinterpret_cast<uintptr_t>(ptr) + size;
11581181
const auto allocEnd = static_cast<uintptr_t>(allocBase) + allocSize;
11591182
if (requestedEnd > allocEnd) {
11601183
allocBase = reinterpret_cast<CUdeviceptr>(ptr);
@@ -1318,6 +1341,113 @@ IbgdaLocalBuffer MultiPeerIbTransportBase::registerBuffer(
13181341
return IbgdaLocalBuffer(ptr, keys);
13191342
}
13201343

1344+
IbBufferRegistrationLease MultiPeerIbTransportBase::registerIbBulkBuffer(
1345+
void* ptr,
1346+
std::size_t size) {
1347+
checkedRangeEnd(ptr, size, "registerIbBulkBuffer");
1348+
const auto localBuffer = registerBuffer(ptr, size, /*relaxedOrdering=*/true);
1349+
1350+
try {
1351+
const auto addr = reinterpret_cast<uintptr_t>(ptr);
1352+
auto mrIt = registeredBuffers_.upper_bound(addr);
1353+
if (mrIt == registeredBuffers_.begin()) {
1354+
throw std::logic_error(
1355+
"registerIbBulkBuffer: MR missing after registration");
1356+
}
1357+
--mrIt;
1358+
if (!rangeContains(mrIt->first, mrIt->second.allocSize, addr, size)) {
1359+
throw std::logic_error(
1360+
"registerIbBulkBuffer: MR does not contain registered range");
1361+
}
1362+
if (nextBulkBufferGeneration_ == std::numeric_limits<uint64_t>::max()) {
1363+
throw std::overflow_error(
1364+
"registerIbBulkBuffer: lease generation exhausted");
1365+
}
1366+
1367+
const uint64_t generation = nextBulkBufferGeneration_++;
1368+
bulkBufferRegistrations_.emplace(
1369+
generation,
1370+
BulkBufferRegistration{
1371+
.ptr = ptr,
1372+
.size = size,
1373+
.localBuffer = localBuffer,
1374+
.relaxedOrdering = mrIt->second.relaxedOrdering,
1375+
});
1376+
return IbBufferRegistrationLease(generation);
1377+
} catch (...) {
1378+
deregisterBuffer(ptr);
1379+
throw;
1380+
}
1381+
}
1382+
1383+
std::optional<IbBufferRegistrationView>
1384+
MultiPeerIbTransportBase::lookupIbBulkBuffer(
1385+
const IbBufferRegistrationLease& lease,
1386+
void* ptr,
1387+
std::size_t size) const {
1388+
checkedRangeEnd(ptr, size, "lookupIbBulkBuffer");
1389+
if (!lease.valid()) {
1390+
return std::nullopt;
1391+
}
1392+
1393+
const auto registration = bulkBufferRegistrations_.find(lease.generation());
1394+
if (registration == bulkBufferRegistrations_.end()) {
1395+
return std::nullopt;
1396+
}
1397+
1398+
const auto& active = registration->second;
1399+
const auto requestedBegin = reinterpret_cast<uintptr_t>(ptr);
1400+
const auto registeredBegin = reinterpret_cast<uintptr_t>(active.ptr);
1401+
if (!rangeContains(registeredBegin, active.size, requestedBegin, size)) {
1402+
return std::nullopt;
1403+
}
1404+
1405+
return IbBufferRegistrationView{
1406+
.leaseGeneration = lease.generation(),
1407+
.localBuffer =
1408+
active.localBuffer.subBuffer(requestedBegin - registeredBegin),
1409+
.size = size,
1410+
.relaxedOrdering = active.relaxedOrdering,
1411+
};
1412+
}
1413+
1414+
void MultiPeerIbTransportBase::deregisterIbBulkBuffer(
1415+
IbBufferRegistrationLease& lease) {
1416+
if (!lease.valid()) {
1417+
throw std::invalid_argument(
1418+
"deregisterIbBulkBuffer: invalid registration lease");
1419+
}
1420+
1421+
const auto registration = bulkBufferRegistrations_.find(lease.generation());
1422+
if (registration == bulkBufferRegistrations_.end()) {
1423+
throw std::runtime_error(
1424+
"deregisterIbBulkBuffer: stale registration lease");
1425+
}
1426+
1427+
void* const ptr = registration->second.ptr;
1428+
bulkBufferRegistrations_.erase(registration);
1429+
deregisterBuffer(ptr);
1430+
lease.reset();
1431+
}
1432+
1433+
bool MultiPeerIbTransportBase::isIbBulkBufferViewActive(
1434+
const IbBufferRegistrationView& view) const {
1435+
if (!view.valid()) {
1436+
return false;
1437+
}
1438+
const auto registration = bulkBufferRegistrations_.find(view.leaseGeneration);
1439+
if (registration == bulkBufferRegistrations_.end()) {
1440+
return false;
1441+
}
1442+
1443+
const auto& active = registration->second;
1444+
return rangeContains(
1445+
reinterpret_cast<uintptr_t>(active.ptr),
1446+
active.size,
1447+
reinterpret_cast<uintptr_t>(view.localBuffer.ptr),
1448+
view.size);
1449+
}
1450+
13211451
void MultiPeerIbTransportBase::deregisterBuffer(void* ptr) {
13221452
// Containment lookup on the ordered map avoids resolving the allocation range
13231453
// again (which fails once the underlying memory is freed).

0 commit comments

Comments
 (0)