@@ -1963,6 +1963,205 @@ TEST_F(
19631963 MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
19641964}
19651965
1966+ TEST_F (
1967+ MultipeerIbgdaTransportTestFixture,
1968+ RegisteredAndStagedSendShareProtocolCursor) {
1969+ if (numRanks != 2 ) {
1970+ GTEST_SKIP () << " Skipping test: requires exactly 2 ranks, got " << numRanks;
1971+ }
1972+ if (!test::supportsProgressSendRecv ()) {
1973+ GTEST_SKIP () << " registered-source send is not supported for this build" ;
1974+ }
1975+
1976+ constexpr std::size_t perChannelSize = 64 * 1024 ;
1977+ constexpr int pipelineDepth = 2 ;
1978+ constexpr std::size_t firstBytes = 17 * 1024 + 1 ;
1979+ constexpr std::size_t secondBytes = 9 * 1024 + 7 ;
1980+ constexpr std::size_t thirdBytes = 40 * 1024 + 17 ;
1981+ constexpr std::size_t totalBytes = firstBytes + secondBytes + thirdBytes;
1982+ constexpr std::size_t maxSignalBytes = 4 * 1024 ;
1983+ constexpr int numBlocks = 1 ;
1984+ constexpr int blockSize = 128 ;
1985+ const int peerRank = globalRank == 0 ? 1 : 0 ;
1986+
1987+ std::unique_ptr<MultipeerIbgdaTransport> transport;
1988+ try {
1989+ MultipeerIbgdaTransportConfig config{
1990+ .cudaDevice = localRank,
1991+ .perChannelSize = perChannelSize,
1992+ .max_num_channels = numBlocks,
1993+ .pipelineDepth = pipelineDepth,
1994+ };
1995+ auto bootstrap = std::make_shared<meta::comms::MpiBootstrap>();
1996+ transport = std::make_unique<MultipeerIbgdaTransport>(
1997+ globalRank, numRanks, bootstrap, config);
1998+ transport->exchange ();
1999+ } catch (const std::exception& e) {
2000+ GTEST_SKIP () << " IBGDA transport not available: " << e.what ();
2001+ }
2002+
2003+ auto * peerTransport = transport->getP2pTransportDevice (peerRank);
2004+ DeviceBuffer sendBuffer (totalBytes);
2005+ DeviceBuffer recvBuffer (totalBytes);
2006+ DeviceBuffer errorCountBuffer (sizeof (int ));
2007+ auto * errorCount = static_cast <int *>(errorCountBuffer.get ());
2008+ IbgdaLocalBuffer registeredSource{};
2009+ if (globalRank == 0 ) {
2010+ registeredSource = transport->registerBuffer (sendBuffer.get (), totalBytes);
2011+ test::fillBufferWithPattern (
2012+ sendBuffer.get (), firstBytes, 0x31 , numBlocks, blockSize);
2013+ test::fillBufferWithPattern (
2014+ static_cast <char *>(sendBuffer.get ()) + firstBytes,
2015+ secondBytes,
2016+ 0x72 ,
2017+ numBlocks,
2018+ blockSize);
2019+ test::fillBufferWithPattern (
2020+ static_cast <char *>(sendBuffer.get ()) + firstBytes + secondBytes,
2021+ thirdBytes,
2022+ 0xB4 ,
2023+ numBlocks,
2024+ blockSize);
2025+ } else {
2026+ CUDACHECK_TEST (cudaMemset (recvBuffer.get (), 0 , totalBytes));
2027+ }
2028+ CUDACHECK_TEST (cudaDeviceSynchronize ());
2029+ MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
2030+
2031+ test::testMixedRegisteredAndStagedSendRecv (
2032+ peerTransport,
2033+ registeredSource,
2034+ recvBuffer.get (),
2035+ firstBytes,
2036+ secondBytes,
2037+ thirdBytes,
2038+ maxSignalBytes,
2039+ globalRank == 0 ,
2040+ numBlocks,
2041+ blockSize);
2042+ CUDACHECK_TEST (cudaDeviceSynchronize ());
2043+ MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
2044+
2045+ if (globalRank == 1 ) {
2046+ const std::array<std::tuple<std::size_t , std::size_t , uint8_t >, 3 > ranges{{
2047+ {0 , firstBytes, 0x31 },
2048+ {firstBytes, secondBytes, 0x72 },
2049+ {firstBytes + secondBytes, thirdBytes, 0xB4 },
2050+ }};
2051+ for (const auto & [offset, nbytes, pattern] : ranges) {
2052+ CUDACHECK_TEST (cudaMemset (errorCount, 0 , sizeof (int )));
2053+ test::verifyBufferPattern (
2054+ static_cast <char *>(recvBuffer.get ()) + offset,
2055+ nbytes,
2056+ pattern,
2057+ errorCount,
2058+ numBlocks,
2059+ blockSize);
2060+ CUDACHECK_TEST (cudaDeviceSynchronize ());
2061+ int hostErrors = 0 ;
2062+ CUDACHECK_TEST (cudaMemcpy (
2063+ &hostErrors, errorCount, sizeof (hostErrors), cudaMemcpyDeviceToHost));
2064+ EXPECT_EQ (hostErrors, 0 )
2065+ << " mixed registered/staged send corrupted range at " << offset;
2066+ }
2067+ } else {
2068+ transport->deregisterBuffer (sendBuffer.get ());
2069+ }
2070+ MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
2071+ }
2072+
2073+ TEST_F (
2074+ MultipeerIbgdaTransportTestFixture,
2075+ RegisteredSendBackpressureAcrossStagingWrap) {
2076+ if (numRanks != 2 ) {
2077+ GTEST_SKIP () << " Skipping test: requires exactly 2 ranks, got " << numRanks;
2078+ }
2079+ if (!test::supportsProgressSendRecv ()) {
2080+ GTEST_SKIP () << " registered-source send is not supported for this build" ;
2081+ }
2082+
2083+ constexpr std::size_t perChannelSize = 64 * 1024 ;
2084+ constexpr int pipelineDepth = 2 ;
2085+ constexpr std::size_t nbytes = 8 * 1024 * 1024 + 17 ;
2086+ constexpr std::size_t maxSignalBytes = 4 * 1024 ;
2087+ constexpr int numBlocks = 1 ;
2088+ constexpr int blockSize = 128 ;
2089+ const int peerRank = globalRank == 0 ? 1 : 0 ;
2090+ std::vector<uint8_t > expected (nbytes);
2091+ for (std::size_t i = 0 ; i < nbytes; ++i) {
2092+ const std::size_t generation = i / perChannelSize;
2093+ expected[i] = static_cast <uint8_t >(generation * 37 + (i % 251 ));
2094+ }
2095+
2096+ std::unique_ptr<MultipeerIbgdaTransport> transport;
2097+ try {
2098+ MultipeerIbgdaTransportConfig config{
2099+ .cudaDevice = localRank,
2100+ .perChannelSize = perChannelSize,
2101+ .max_num_channels = numBlocks,
2102+ .pipelineDepth = pipelineDepth,
2103+ };
2104+ auto bootstrap = std::make_shared<meta::comms::MpiBootstrap>();
2105+ transport = std::make_unique<MultipeerIbgdaTransport>(
2106+ globalRank, numRanks, bootstrap, config);
2107+ transport->exchange ();
2108+ } catch (const std::exception& e) {
2109+ GTEST_SKIP () << " IBGDA transport not available: " << e.what ();
2110+ }
2111+
2112+ auto * peerTransport = transport->getP2pTransportDevice (peerRank);
2113+ DeviceBuffer sendBuffer (nbytes);
2114+ DeviceBuffer recvBuffer (nbytes);
2115+ DeviceBuffer observationBuffer (sizeof (test::RegisteredSendObservation));
2116+ auto * observation =
2117+ static_cast <test::RegisteredSendObservation*>(observationBuffer.get ());
2118+ IbgdaLocalBuffer registeredSource{};
2119+ if (globalRank == 0 ) {
2120+ registeredSource = transport->registerBuffer (sendBuffer.get (), nbytes);
2121+ CUDACHECK_TEST (cudaMemcpy (
2122+ sendBuffer.get (), expected.data (), nbytes, cudaMemcpyHostToDevice));
2123+ CUDACHECK_TEST (
2124+ cudaMemset (observation, 0 , sizeof (test::RegisteredSendObservation)));
2125+ } else {
2126+ CUDACHECK_TEST (cudaMemset (recvBuffer.get (), 0 , nbytes));
2127+ }
2128+ CUDACHECK_TEST (cudaDeviceSynchronize ());
2129+ MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
2130+
2131+ test::testRegisteredSendRecv (
2132+ peerTransport,
2133+ registeredSource,
2134+ recvBuffer.get (),
2135+ nbytes,
2136+ maxSignalBytes,
2137+ globalRank == 0 ,
2138+ numBlocks,
2139+ blockSize,
2140+ globalRank == 0 ? observation : nullptr );
2141+ CUDACHECK_TEST (cudaDeviceSynchronize ());
2142+ MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
2143+
2144+ if (globalRank == 0 ) {
2145+ test::RegisteredSendObservation hostObservation{};
2146+ CUDACHECK_TEST (cudaMemcpy (
2147+ &hostObservation,
2148+ observation,
2149+ sizeof (hostObservation),
2150+ cudaMemcpyDeviceToHost));
2151+ EXPECT_GT (hostObservation.waitingCount , 0 );
2152+ EXPECT_EQ (hostObservation.postedCount , 1 );
2153+ EXPECT_EQ (hostObservation.drainedCount , 1 );
2154+ transport->deregisterBuffer (sendBuffer.get ());
2155+ } else {
2156+ std::vector<uint8_t > received (nbytes);
2157+ CUDACHECK_TEST (cudaMemcpy (
2158+ received.data (), recvBuffer.get (), nbytes, cudaMemcpyDeviceToHost));
2159+ EXPECT_EQ (received, expected)
2160+ << " registered send corrupted data across slot wrap" ;
2161+ }
2162+ MPI_CHECK (MPI_Barrier (MPI_COMM_WORLD ));
2163+ }
2164+
19662165// =============================================================================
19672166// Sustained chunked send/recv - repro for the GB200 per-channel DATA_READY
19682167// deadlock (two NICs atomic-FA the same flag at maxGroups>=8). Streams a large
0 commit comments