Skip to content

Commit 16c54d8

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. Reviewed By: rmahidhar Differential Revision: D114552007
1 parent ff6554d commit 16c54d8

9 files changed

Lines changed: 959 additions & 1 deletion

comms/prims/P2pIbTransportBuildContractTest.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@
77

88
_PROGRESS_ONLY_FUNCTIONS = (
99
"init_send_progress",
10+
"init_registered_send_progress",
1011
"init_recv_progress",
1112
"progress_send_once",
13+
"progress_registered_send_once",
14+
"progress_registered_send_drain_once",
1215
"poll_recv_data_ready",
1316
"progress_recv_once",
17+
"send_registered",
1418
"store_progress_state",
1519
"make_progress_geometry",
1620
"active_payload_offset",

comms/prims/tests/MultipeerIbgdaTransportTest.cc

Lines changed: 174 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
#include <folly/logging/xlog.h>
77
#include <array>
88

9-
#include <chrono>
109
#include <memory>
1110
#include <string>
11+
#include <tuple>
1212
#include <vector>
1313

1414
#ifdef __HIP_PLATFORM_AMD__
@@ -1791,6 +1791,179 @@ TEST_P(
17911791
}
17921792
}
17931793

1794+
TEST_F(
1795+
MultipeerIbgdaTransportTestFixture,
1796+
RegisteredSendTailsDrainAndStagingIsolation) {
1797+
if (numRanks != 2) {
1798+
GTEST_SKIP() << "Skipping test: requires exactly 2 ranks, got " << numRanks;
1799+
}
1800+
if (!test::supportsProgressSendRecv()) {
1801+
GTEST_SKIP() << "registered-source send is not supported for this build";
1802+
}
1803+
1804+
constexpr std::size_t perChannelSize = 64 * 1024;
1805+
constexpr int pipelineDepth = 2;
1806+
constexpr std::size_t pipelineChunk = perChannelSize / pipelineDepth;
1807+
constexpr std::size_t maxSignalBytes = 4 * 1024;
1808+
constexpr std::size_t simpleProtocolDataBytes = 16;
1809+
constexpr int numBlocks = 1;
1810+
constexpr int blockSize = 128;
1811+
constexpr uint8_t stagingPoison = 0xA5;
1812+
constexpr std::size_t zeroByteAfterPostedIndex = 2;
1813+
const int peerRank = globalRank == 0 ? 1 : 0;
1814+
const std::array<std::size_t, 8> sizes{
1815+
0,
1816+
1,
1817+
7,
1818+
simpleProtocolDataBytes - 1,
1819+
simpleProtocolDataBytes,
1820+
simpleProtocolDataBytes + 1,
1821+
pipelineChunk - 1,
1822+
pipelineChunk + 1,
1823+
};
1824+
1825+
std::unique_ptr<MultipeerIbgdaTransport> transport;
1826+
try {
1827+
MultipeerIbgdaTransportConfig config{
1828+
.cudaDevice = localRank,
1829+
.perChannelSize = perChannelSize,
1830+
.max_num_channels = numBlocks,
1831+
.pipelineDepth = pipelineDepth,
1832+
};
1833+
auto bootstrap = std::make_shared<meta::comms::MpiBootstrap>();
1834+
transport = std::make_unique<MultipeerIbgdaTransport>(
1835+
globalRank, numRanks, bootstrap, config);
1836+
transport->exchange();
1837+
} catch (const std::exception& e) {
1838+
GTEST_SKIP() << "IBGDA transport not available: " << e.what();
1839+
}
1840+
1841+
auto* peerTransport = transport->getP2pTransportDevice(peerRank);
1842+
DeviceBuffer errorCountBuffer(sizeof(int));
1843+
DeviceBuffer observationBuffer(sizeof(test::RegisteredSendObservation));
1844+
auto* errorCount = static_cast<int*>(errorCountBuffer.get());
1845+
auto* observation =
1846+
static_cast<test::RegisteredSendObservation*>(observationBuffer.get());
1847+
1848+
test::testFillTransportStaging(
1849+
peerTransport,
1850+
globalRank == 0,
1851+
0,
1852+
perChannelSize,
1853+
stagingPoison,
1854+
numBlocks,
1855+
blockSize);
1856+
CUDACHECK_TEST(cudaDeviceSynchronize());
1857+
1858+
for (std::size_t index = 0; index < sizes.size(); ++index) {
1859+
const std::size_t nbytes = sizes[index];
1860+
const std::size_t allocationBytes = nbytes == 0 ? 1 : nbytes;
1861+
const uint8_t pattern = static_cast<uint8_t>(0x20 + index * 13);
1862+
DeviceBuffer sendBuffer(allocationBytes);
1863+
DeviceBuffer recvBuffer(allocationBytes);
1864+
IbgdaLocalBuffer registeredSource{};
1865+
if (globalRank == 0) {
1866+
if (nbytes > 0) {
1867+
registeredSource = transport->registerBuffer(sendBuffer.get(), nbytes);
1868+
}
1869+
test::fillBufferWithPattern(
1870+
sendBuffer.get(), nbytes, pattern, numBlocks, blockSize);
1871+
CUDACHECK_TEST(
1872+
cudaMemset(observation, 0, sizeof(test::RegisteredSendObservation)));
1873+
} else {
1874+
CUDACHECK_TEST(cudaMemset(recvBuffer.get(), 0, allocationBytes));
1875+
}
1876+
CUDACHECK_TEST(cudaDeviceSynchronize());
1877+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1878+
1879+
test::testRegisteredSendRecv(
1880+
peerTransport,
1881+
registeredSource,
1882+
recvBuffer.get(),
1883+
nbytes,
1884+
maxSignalBytes,
1885+
globalRank == 0,
1886+
numBlocks,
1887+
blockSize,
1888+
globalRank == 0 ? observation : nullptr,
1889+
index == 4,
1890+
globalRank == 0 && nbytes > 0,
1891+
static_cast<uint8_t>(pattern ^ 0xFF),
1892+
index == zeroByteAfterPostedIndex);
1893+
CUDACHECK_TEST(cudaDeviceSynchronize());
1894+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1895+
1896+
if (globalRank == 0) {
1897+
test::RegisteredSendObservation hostObservation{};
1898+
CUDACHECK_TEST(cudaMemcpy(
1899+
&hostObservation,
1900+
observation,
1901+
sizeof(hostObservation),
1902+
cudaMemcpyDeviceToHost));
1903+
if (index == 4) {
1904+
EXPECT_EQ(hostObservation.postedCount, 0);
1905+
} else if (index == zeroByteAfterPostedIndex) {
1906+
EXPECT_EQ(hostObservation.postedCount, 2);
1907+
} else {
1908+
EXPECT_EQ(hostObservation.postedCount, 1);
1909+
}
1910+
EXPECT_EQ(hostObservation.drainedCount, 1);
1911+
} else {
1912+
CUDACHECK_TEST(cudaMemset(errorCount, 0, sizeof(int)));
1913+
test::verifyBufferPattern(
1914+
recvBuffer.get(), nbytes, pattern, errorCount, numBlocks, blockSize);
1915+
CUDACHECK_TEST(cudaDeviceSynchronize());
1916+
int hostErrors = 0;
1917+
CUDACHECK_TEST(cudaMemcpy(
1918+
&hostErrors, errorCount, sizeof(hostErrors), cudaMemcpyDeviceToHost));
1919+
EXPECT_EQ(hostErrors, 0) << "registered send corrupted size " << nbytes;
1920+
}
1921+
1922+
if (index == 1 && globalRank == 1) {
1923+
CUDACHECK_TEST(cudaMemset(errorCount, 0, sizeof(int)));
1924+
test::testVerifyTransportStaging(
1925+
peerTransport,
1926+
false,
1927+
1,
1928+
simpleProtocolDataBytes - 1,
1929+
stagingPoison,
1930+
errorCount,
1931+
numBlocks,
1932+
blockSize);
1933+
CUDACHECK_TEST(cudaDeviceSynchronize());
1934+
int hostErrors = 0;
1935+
CUDACHECK_TEST(cudaMemcpy(
1936+
&hostErrors, errorCount, sizeof(hostErrors), cudaMemcpyDeviceToHost));
1937+
EXPECT_EQ(hostErrors, 0)
1938+
<< "registered send wrote rounded tail bytes into recv staging";
1939+
}
1940+
if (globalRank == 0 && nbytes > 0) {
1941+
transport->deregisterBuffer(sendBuffer.get());
1942+
}
1943+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1944+
}
1945+
1946+
if (globalRank == 0) {
1947+
CUDACHECK_TEST(cudaMemset(errorCount, 0, sizeof(int)));
1948+
test::testVerifyTransportStaging(
1949+
peerTransport,
1950+
true,
1951+
0,
1952+
perChannelSize,
1953+
stagingPoison,
1954+
errorCount,
1955+
numBlocks,
1956+
blockSize);
1957+
CUDACHECK_TEST(cudaDeviceSynchronize());
1958+
int hostErrors = 0;
1959+
CUDACHECK_TEST(cudaMemcpy(
1960+
&hostErrors, errorCount, sizeof(hostErrors), cudaMemcpyDeviceToHost));
1961+
EXPECT_EQ(hostErrors, 0)
1962+
<< "registered send modified transport send staging";
1963+
}
1964+
MPI_CHECK(MPI_Barrier(MPI_COMM_WORLD));
1965+
}
1966+
17941967
// =============================================================================
17951968
// Sustained chunked send/recv - repro for the GB200 per-channel DATA_READY
17961969
// deadlock (two NICs atomic-FA the same flag at maxGroups>=8). Streams a large

0 commit comments

Comments
 (0)