Skip to content

Commit 1090508

Browse files
Santosh Narayankhedkarfacebook-github-bot
authored andcommitted
Add registered-source send to IBGDA transport (#3411)
Summary: Add resumable and blocking registered-source send APIs that let IBGDA read directly from a registered caller buffer. Track posting separately from local NIC completion so callers can safely reuse the source only after an explicit drain. Preserve the existing staged protocol cursor, receiver staging, signaling, credits, and default behavior. Differential Revision: D114552007
1 parent 2b8618e commit 1090508

7 files changed

Lines changed: 934 additions & 0 deletions

comms/prims/tests/MultipeerIbgdaTransportTest.cc

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
#include <chrono>
1010
#include <memory>
1111
#include <string>
12+
#include <thread>
13+
#include <tuple>
1214
#include <vector>
1315

1416
#ifdef __HIP_PLATFORM_AMD__
@@ -1666,6 +1668,179 @@ TEST_P(
16661668
}
16671669
}
16681670

1671+
TEST_F(
1672+
MultipeerIbgdaTransportTestFixture,
1673+
RegisteredSendTailsDrainAndStagingIsolation) {
1674+
if (numRanks != 2) {
1675+
GTEST_SKIP() << "Skipping test: requires exactly 2 ranks, got " << numRanks;
1676+
}
1677+
if (!test::supportsProgressSendRecv()) {
1678+
GTEST_SKIP() << "registered-source send is not supported for this build";
1679+
}
1680+
1681+
constexpr std::size_t perChannelSize = 64 * 1024;
1682+
constexpr int pipelineDepth = 2;
1683+
constexpr std::size_t pipelineChunk = perChannelSize / pipelineDepth;
1684+
constexpr std::size_t maxSignalBytes = 4 * 1024;
1685+
constexpr int numBlocks = 1;
1686+
constexpr int blockSize = 128;
1687+
constexpr uint8_t stagingPoison = 0xA5;
1688+
const int peerRank = globalRank == 0 ? 1 : 0;
1689+
const std::array<std::size_t, 8> sizes{
1690+
0,
1691+
1,
1692+
7,
1693+
15,
1694+
16,
1695+
17,
1696+
pipelineChunk - 1,
1697+
pipelineChunk + 1,
1698+
};
1699+
1700+
std::unique_ptr<MultipeerIbgdaTransport> transport;
1701+
try {
1702+
MultipeerIbgdaTransportConfig config{
1703+
.cudaDevice = localRank,
1704+
.perChannelSize = perChannelSize,
1705+
.max_num_channels = numBlocks,
1706+
.pipelineDepth = pipelineDepth,
1707+
};
1708+
auto bootstrap = std::make_shared<meta::comms::MpiBootstrap>();
1709+
transport = std::make_unique<MultipeerIbgdaTransport>(
1710+
globalRank, numRanks, bootstrap, config);
1711+
transport->exchange();
1712+
} catch (const std::exception& e) {
1713+
GTEST_SKIP() << "IBGDA transport not available: " << e.what();
1714+
}
1715+
1716+
auto* peerTransport = transport->getP2pTransportDevice(peerRank);
1717+
DeviceBuffer errorCountBuffer(sizeof(int));
1718+
DeviceBuffer observationBuffer(sizeof(test::RegisteredSendObservation));
1719+
auto* errorCount = static_cast<int*>(errorCountBuffer.get());
1720+
auto* observation =
1721+
static_cast<test::RegisteredSendObservation*>(observationBuffer.get());
1722+
1723+
test::testFillTransportStaging(
1724+
peerTransport,
1725+
globalRank == 0,
1726+
0,
1727+
perChannelSize,
1728+
stagingPoison,
1729+
numBlocks,
1730+
blockSize);
1731+
CUDACHECK_TEST(cudaDeviceSynchronize());
1732+
1733+
for (std::size_t index = 0; index < sizes.size(); ++index) {
1734+
const std::size_t nbytes = sizes[index];
1735+
const std::size_t allocationBytes = nbytes == 0 ? 1 : nbytes;
1736+
const uint8_t pattern = static_cast<uint8_t>(0x20 + index * 13);
1737+
DeviceBuffer sendBuffer(allocationBytes);
1738+
DeviceBuffer recvBuffer(allocationBytes);
1739+
IbgdaLocalBuffer registeredSource{};
1740+
if (globalRank == 0) {
1741+
if (nbytes > 0) {
1742+
registeredSource = transport->registerBuffer(sendBuffer.get(), nbytes);
1743+
}
1744+
test::fillBufferWithPattern(
1745+
sendBuffer.get(), nbytes, pattern, numBlocks, blockSize);
1746+
CUDACHECK_TEST(
1747+
cudaMemset(observation, 0, sizeof(test::RegisteredSendObservation)));
1748+
} else {
1749+
CUDACHECK_TEST(cudaMemset(recvBuffer.get(), 0, allocationBytes));
1750+
}
1751+
CUDACHECK_TEST(cudaDeviceSynchronize());
1752+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1753+
1754+
// Keep the receiver idle for the first non-empty transfer so the sender
1755+
// mutates its source immediately after Drained and before recv consumes it.
1756+
if (index == 1 && globalRank == 1) {
1757+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
1758+
}
1759+
test::testRegisteredSendRecv(
1760+
peerTransport,
1761+
registeredSource,
1762+
recvBuffer.get(),
1763+
nbytes,
1764+
maxSignalBytes,
1765+
globalRank == 0,
1766+
numBlocks,
1767+
blockSize,
1768+
globalRank == 0 ? observation : nullptr,
1769+
index == 4,
1770+
globalRank == 0 && nbytes > 0,
1771+
static_cast<uint8_t>(pattern ^ 0xFF));
1772+
CUDACHECK_TEST(cudaDeviceSynchronize());
1773+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1774+
1775+
if (globalRank == 0) {
1776+
test::RegisteredSendObservation hostObservation{};
1777+
CUDACHECK_TEST(cudaMemcpy(
1778+
&hostObservation,
1779+
observation,
1780+
sizeof(hostObservation),
1781+
cudaMemcpyDeviceToHost));
1782+
if (nbytes == 0 || index == 4) {
1783+
EXPECT_EQ(hostObservation.postedCount, 0);
1784+
} else {
1785+
EXPECT_EQ(hostObservation.postedCount, 1);
1786+
}
1787+
EXPECT_EQ(hostObservation.drainedCount, 1);
1788+
} else {
1789+
CUDACHECK_TEST(cudaMemset(errorCount, 0, sizeof(int)));
1790+
test::verifyBufferPattern(
1791+
recvBuffer.get(), nbytes, pattern, errorCount, numBlocks, blockSize);
1792+
CUDACHECK_TEST(cudaDeviceSynchronize());
1793+
int hostErrors = 0;
1794+
CUDACHECK_TEST(cudaMemcpy(
1795+
&hostErrors, errorCount, sizeof(hostErrors), cudaMemcpyDeviceToHost));
1796+
EXPECT_EQ(hostErrors, 0) << "registered send corrupted size " << nbytes;
1797+
}
1798+
1799+
if (index == 1 && globalRank == 1) {
1800+
CUDACHECK_TEST(cudaMemset(errorCount, 0, sizeof(int)));
1801+
test::testVerifyTransportStaging(
1802+
peerTransport,
1803+
false,
1804+
1,
1805+
protocol::Simple::kData - 1,
1806+
stagingPoison,
1807+
errorCount,
1808+
numBlocks,
1809+
blockSize);
1810+
CUDACHECK_TEST(cudaDeviceSynchronize());
1811+
int hostErrors = 0;
1812+
CUDACHECK_TEST(cudaMemcpy(
1813+
&hostErrors, errorCount, sizeof(hostErrors), cudaMemcpyDeviceToHost));
1814+
EXPECT_EQ(hostErrors, 0)
1815+
<< "registered send wrote rounded tail bytes into recv staging";
1816+
}
1817+
if (globalRank == 0 && nbytes > 0) {
1818+
transport->deregisterBuffer(sendBuffer.get());
1819+
}
1820+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1821+
}
1822+
1823+
if (globalRank == 0) {
1824+
CUDACHECK_TEST(cudaMemset(errorCount, 0, sizeof(int)));
1825+
test::testVerifyTransportStaging(
1826+
peerTransport,
1827+
true,
1828+
0,
1829+
perChannelSize,
1830+
stagingPoison,
1831+
errorCount,
1832+
numBlocks,
1833+
blockSize);
1834+
CUDACHECK_TEST(cudaDeviceSynchronize());
1835+
int hostErrors = 0;
1836+
CUDACHECK_TEST(cudaMemcpy(
1837+
&hostErrors, errorCount, sizeof(hostErrors), cudaMemcpyDeviceToHost));
1838+
EXPECT_EQ(hostErrors, 0)
1839+
<< "registered send modified transport send staging";
1840+
}
1841+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1842+
}
1843+
16691844
// =============================================================================
16701845
// Sustained chunked send/recv - repro for the GB200 per-channel DATA_READY
16711846
// deadlock (two NICs atomic-FA the same flag at maxGroups>=8). Streams a large

0 commit comments

Comments
 (0)