CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
281 lines
10 KiB
C++
281 lines
10 KiB
C++
/*************************************************************************
|
|
* Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
*
|
|
* See LICENSE.txt for license information
|
|
************************************************************************/
|
|
|
|
/*
|
|
This test evaluates the capability of the WarpSpeed feature to correctly
|
|
handle back-to-back enqueues of different message sizes, both below and
|
|
above the threshold, without synchronization.
|
|
This test is requires warpSpeed enablement and will skip if RCCL_WARP_SPEED_AUTO=0
|
|
or if architecture doesn't support warpSpeed.
|
|
*/
|
|
|
|
#include "DeviceBufferHelpers.hpp"
|
|
#include "MPITestBase.hpp"
|
|
#include "ResourceGuards.hpp"
|
|
#include "TestChecks.hpp"
|
|
#include "comm.h"
|
|
#include "rccl_common.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
|
|
#ifdef ENABLE_WARP_SPEED
|
|
|
|
using namespace MPITestConstants;
|
|
using namespace RCCLTestGuards;
|
|
using namespace RCCLTestHelpers;
|
|
|
|
namespace WarpSpeedConstants
|
|
{
|
|
constexpr int64_t kSmallBufferThresholdDivisor = 64;
|
|
constexpr int64_t kLargeBufferThresholdMultiplier = 2;
|
|
|
|
inline size_t smallBufferElementCount(int64_t threshold_bytes)
|
|
{
|
|
const int64_t bytes = threshold_bytes / kSmallBufferThresholdDivisor;
|
|
return static_cast<size_t>(bytes / static_cast<int64_t>(sizeof(hip_bfloat16)));
|
|
}
|
|
|
|
inline size_t largeBufferElementCount(int64_t threshold_bytes)
|
|
{
|
|
const int64_t bytes = threshold_bytes * kLargeBufferThresholdMultiplier;
|
|
return static_cast<size_t>(bytes / static_cast<int64_t>(sizeof(hip_bfloat16)));
|
|
}
|
|
|
|
constexpr int kSmallOpsBefore = 10;
|
|
constexpr int kSmallOpsAfter = 10;
|
|
|
|
constexpr int kTrainIterations = 30;
|
|
}
|
|
|
|
using namespace WarpSpeedConstants;
|
|
|
|
class WarpSpeedMPITest : public MPITestBase
|
|
{
|
|
protected:
|
|
hipStream_t stream_{};
|
|
void* d_small_{};
|
|
void* d_large_{};
|
|
int64_t ar_threshold_bytes_{};
|
|
size_t small_count_{};
|
|
size_t large_count_{};
|
|
|
|
void skipUnlessWarpSpeedAutoEnabled()
|
|
{
|
|
if(rcclParamWarpSpeedAutoMode() == 0)
|
|
{
|
|
GTEST_SKIP() << "RCCL_WARP_SPEED_AUTO is not 1 (WarpSpeed auto not enabled)";
|
|
}
|
|
struct ncclComm* comm = reinterpret_cast<struct ncclComm*>(getActiveCommunicator());
|
|
if(!rcclCanUseWarpSpeedAuto(comm, comm->nNodes))
|
|
{
|
|
GTEST_SKIP() << "RCCL_WARP_SPEED_AUTO=1 but auto mode unavailable (gfx950, "
|
|
"single-node required)";
|
|
}
|
|
}
|
|
|
|
void initBufferCounts()
|
|
{
|
|
ar_threshold_bytes_ = rcclParamWarpSpeedARThreshold();
|
|
ASSERT_GE(ar_threshold_bytes_,
|
|
kSmallBufferThresholdDivisor * static_cast<int64_t>(sizeof(hip_bfloat16)))
|
|
<< "RCCL_WARP_SPEED_AR_THRESHOLD too small for small buffer (threshold/64)";
|
|
small_count_ = smallBufferElementCount(ar_threshold_bytes_);
|
|
large_count_ = largeBufferElementCount(ar_threshold_bytes_);
|
|
TEST_INFO("RCCL_WARP_SPEED_AR_THRESHOLD=%ld bytes, small=%zu elems (%ld bytes), "
|
|
"large=%zu elems (%ld bytes)",
|
|
static_cast<long>(ar_threshold_bytes_),
|
|
small_count_,
|
|
static_cast<long>(small_count_ * sizeof(hip_bfloat16)),
|
|
large_count_,
|
|
static_cast<long>(large_count_ * sizeof(hip_bfloat16)));
|
|
}
|
|
|
|
void TearDown() override
|
|
{
|
|
if(d_small_) { hipFree(d_small_); d_small_ = nullptr; }
|
|
if(d_large_) { hipFree(d_large_); d_large_ = nullptr; }
|
|
if(stream_)
|
|
{
|
|
hipStreamSynchronize(stream_);
|
|
hipStreamDestroy(stream_);
|
|
stream_ = {};
|
|
}
|
|
MPITestBase::TearDown();
|
|
}
|
|
|
|
ncclResult_t allocateBuffers()
|
|
{
|
|
HIPCHECK(hipMalloc(&d_small_, small_count_ * sizeof(hip_bfloat16)));
|
|
HIPCHECK(hipMalloc(&d_large_, large_count_ * sizeof(hip_bfloat16)));
|
|
return ncclSuccess;
|
|
}
|
|
|
|
/** Expected value after numInPlaceSumOps consecutive in-place AllReduce sums. */
|
|
static double expectedInPlaceAllReduceSum(int nranks, int numInPlaceSumOps)
|
|
{
|
|
const double base = static_cast<double>(nranks) * (nranks - 1) / 2.0;
|
|
if(numInPlaceSumOps <= 1)
|
|
return base;
|
|
return base * std::pow(static_cast<double>(nranks), numInPlaceSumOps - 1);
|
|
}
|
|
|
|
ncclResult_t prepareIteration()
|
|
{
|
|
const int rank = MPIEnvironment::world_rank;
|
|
const auto pattern = [rank](size_t) {
|
|
return hip_bfloat16(static_cast<float>(rank));
|
|
};
|
|
HIPCHECK(initializeBufferWithPattern<hip_bfloat16>(d_small_, small_count_, pattern));
|
|
HIPCHECK(initializeBufferWithPattern<hip_bfloat16>(d_large_, large_count_, pattern));
|
|
return ncclSuccess;
|
|
}
|
|
|
|
bool verifyBuffers(double expected_small,
|
|
double expected_large) const
|
|
{
|
|
const auto expect_small = [expected_small](size_t) {
|
|
return hip_bfloat16(static_cast<float>(expected_small));
|
|
};
|
|
const auto expect_large = [expected_large](size_t) {
|
|
return hip_bfloat16(static_cast<float>(expected_large));
|
|
};
|
|
const bool small_ok = verifyBufferData<hip_bfloat16>(
|
|
d_small_, small_count_, expect_small);
|
|
const bool large_ok = verifyBufferData<hip_bfloat16>(
|
|
d_large_, large_count_, expect_large);
|
|
return small_ok && large_ok;
|
|
}
|
|
|
|
void runValidatedTrainingLoop()
|
|
{
|
|
const int nranks = MPIEnvironment::world_size;
|
|
const int small_ops = kSmallOpsBefore + kSmallOpsAfter;
|
|
const double expected_small = expectedInPlaceAllReduceSum(nranks, small_ops);
|
|
const double expected_large = expectedInPlaceAllReduceSum(nranks, 1);
|
|
|
|
int correct_count = 0;
|
|
int wrong_count = 0;
|
|
|
|
for(int iter = 0; iter < kTrainIterations; iter++)
|
|
{
|
|
ASSERT_MPI_EQ(ncclSuccess, prepareIteration());
|
|
ASSERT_MPI_EQ(ncclSuccess, submitIteration());
|
|
|
|
if(verifyBuffers(expected_small, expected_large))
|
|
correct_count++;
|
|
else
|
|
wrong_count++;
|
|
}
|
|
|
|
TEST_INFO("Expected small: %.6g (%d ops), large: %.6g (1 op), Correct: %d/%d, Wrong: %d",
|
|
expected_small,
|
|
small_ops,
|
|
expected_large,
|
|
correct_count,
|
|
kTrainIterations,
|
|
wrong_count);
|
|
|
|
EXPECT_EQ(correct_count, kTrainIterations)
|
|
<< "AllReduce result mismatch on small and/or large buffer (possible silent "
|
|
"corruption)";
|
|
}
|
|
|
|
ncclResult_t submitIteration()
|
|
{
|
|
ncclComm_t comm = getActiveCommunicator();
|
|
|
|
for(int i = 0; i < kSmallOpsBefore; i++)
|
|
RCCL_TEST_CHECK(ncclAllReduce(d_small_, d_small_, small_count_,
|
|
ncclBfloat16, ncclSum, comm, stream_));
|
|
|
|
RCCL_TEST_CHECK(ncclAllReduce(d_large_, d_large_, large_count_,
|
|
ncclBfloat16, ncclSum, comm, stream_));
|
|
|
|
for(int i = 0; i < kSmallOpsAfter; i++)
|
|
RCCL_TEST_CHECK(ncclAllReduce(d_small_, d_small_, small_count_,
|
|
ncclBfloat16, ncclSum, comm, stream_));
|
|
|
|
HIPCHECK(hipStreamSynchronize(stream_));
|
|
return ncclSuccess;
|
|
}
|
|
};
|
|
|
|
TEST_F(WarpSpeedMPITest, MixedThresholdRace)
|
|
{
|
|
int min_processes = 8;
|
|
int max_processes = 8;
|
|
int min_nodes = 1;
|
|
int max_nodes = 1;
|
|
ASSERT_TRUE(validateTestPrerequisites(min_processes, max_processes, kNoPowerOfTwoRequired, min_nodes, max_nodes))
|
|
<< "Requires exactly 8 ranks on a single node";
|
|
|
|
ASSERT_EQ(ncclSuccess, createTestCommunicator());
|
|
skipUnlessWarpSpeedAutoEnabled();
|
|
HIP_TEST_CHECK_GTEST_FAIL(hipStreamCreate(&stream_));
|
|
initBufferCounts();
|
|
ASSERT_MPI_EQ(ncclSuccess, allocateBuffers());
|
|
|
|
TEST_INFO("%d non-WS + 1 WS + %d non-WS AllReduce per iter, no intermediate sync",
|
|
kSmallOpsBefore, kSmallOpsAfter);
|
|
|
|
runValidatedTrainingLoop();
|
|
|
|
TEST_INFO("All %d iterations passed", kTrainIterations);
|
|
}
|
|
|
|
// High-rank single-node comm init + AllGather; exercises the bootstrap topology
|
|
// AllGather and single-node WarpSpeed channel setup (a regression aborts init).
|
|
TEST_F(WarpSpeedMPITest, HighRankCommInitAndAllGather)
|
|
{
|
|
// Single node, all available ranks (repro uses 64 on CPX+NPS2).
|
|
ASSERT_TRUE(validateTestPrerequisites(kMinProcessesForMPI,
|
|
kNoProcessLimit,
|
|
kNoPowerOfTwoRequired,
|
|
kRequireSingleNode,
|
|
kRequireSingleNode))
|
|
<< "Requires a single-node run";
|
|
|
|
// Init exercises the bootstrap topology AllGather and WarpSpeed channel setup.
|
|
ASSERT_EQ(ncclSuccess, createTestCommunicator());
|
|
skipUnlessWarpSpeedAutoEnabled();
|
|
HIP_TEST_CHECK_GTEST_FAIL(hipStreamCreate(&stream_));
|
|
|
|
const int nranks = MPIEnvironment::world_size;
|
|
const int rank = MPIEnvironment::world_rank;
|
|
const size_t sendCount = 1u << 20; // 1M floats (4 MB) per rank
|
|
const size_t recvCount = sendCount * static_cast<size_t>(nranks);
|
|
|
|
void* d_send = nullptr;
|
|
void* d_recv = nullptr;
|
|
HIP_TEST_CHECK_GTEST_FAIL(hipMalloc(&d_send, sendCount * sizeof(float)));
|
|
HIP_TEST_CHECK_GTEST_FAIL(hipMalloc(&d_recv, recvCount * sizeof(float)));
|
|
// Free on any exit path (including early ASSERT/FAIL returns) to avoid leaks.
|
|
SCOPE_EXIT(if(d_send) { (void)hipFree(d_send); });
|
|
SCOPE_EXIT(if(d_recv) { (void)hipFree(d_recv); });
|
|
|
|
// Each rank fills its send buffer with its own rank value.
|
|
HIP_TEST_CHECK_GTEST_FAIL(initializeBufferWithPattern<float>(
|
|
d_send, sendCount, [rank](size_t) { return static_cast<float>(rank); }));
|
|
|
|
ncclComm_t comm = getActiveCommunicator();
|
|
ASSERT_MPI_EQ(ncclSuccess,
|
|
ncclAllGather(d_send, d_recv, sendCount, ncclFloat, comm, stream_));
|
|
HIP_TEST_CHECK_GTEST_FAIL(hipStreamSynchronize(stream_));
|
|
|
|
// After the AllGather, rank i's chunk (indices [i*sendCount, (i+1)*sendCount))
|
|
// must contain the value i.
|
|
const bool ok = verifyBufferData<float>(
|
|
d_recv, recvCount,
|
|
[sendCount](size_t idx) { return static_cast<float>(idx / sendCount); });
|
|
EXPECT_TRUE(ok) << "AllGather result mismatch across " << nranks << " ranks";
|
|
|
|
TEST_INFO("HighRankCommInitAndAllGather passed on %d ranks (single node)", nranks);
|
|
}
|
|
|
|
#endif // ENABLE_WARP_SPEED
|