Skip to content

Commit 31a56e7

Browse files
Santosh Narayankhedkarfacebook-github-bot
authored andcommitted
Add bulk-buffer registration leases for IBGDA (meta-pytorch#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 a085ece commit 31a56e7

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(), 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(
@@ -1081,6 +1102,10 @@ IbgdaLocalBuffer MultiPeerIbTransportBase::registerBuffer(
10811102
throw std::invalid_argument("Invalid buffer pointer or size");
10821103
}
10831104

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

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

0 commit comments

Comments
 (0)