Files
TheRock/rocm-systems/projects/rccl/bindings/ir/test/IR_test.cpp
T
derek.holloway 7502bf64a4
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
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

554 lines
26 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*************************************************************************
* Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved.
*
* IR_test.cpp — Comprehensive functional test for librccl_device.bc
*
* GoogleTest port of the original standalone driver. Tests every active API
* exported by nccl_device_wrapper__impl.h, with one GTest case per API group
* so the suite can be filtered with --gtest_filter and consumed by the RCCL
* test_runner / CI exactly like the other rccl-UnitTests* binaries:
*
* [A] ncclGetPeerPointerTeam
* [B1] ncclCoopAnyInitThread + ncclCoopSize/ThreadRank/NumThreads
* [B2] ncclCoopAnyInitWarp + ncclCoopSize/ThreadRank/NumThreads
* [B3] ncclCoopAnyInitLanes + ncclCoopSize/ThreadRank/NumThreads
* tested with full mask (0xFFFF…), sparse mask, single-bit mask
* [B4] ncclCoopAnyInitWarpSpan + ncclCoopSize/ThreadRank/NumThreads
* tested with 1-warp and 2-warp spans
* [B5] ncclCoopAnyInitCta + ncclCoopSize/ThreadRank/NumThreads
* tested with block-size 64 and 128
* [B6] ncclCoopSync — all five coop types; kernel-completion check
* [B7] ncclLsaBarrierSession{Init,Arrive,Wait,Sync}
* — sizeof / alignment structural check: PASS
* — runtime invocations: SKIP (require a live ncclDevComm)
* [C0] bucket-C export/link coverage — takes the device address of all four
* ncclGin/composite barrier thunks and checks each resolves from the
* bitcode (covers the functions without needing live resources)
* [C1] ncclGinBarrierSession{Init,Sync}
* — sizeof / alignment structural check: PASS
* — runtime invocations: SKIP (require a live ncclDevComm + GIN net)
* [C2] ncclBarrierSession{Init,Sync} (composite inner-LSA + outer-GIN)
* — sizeof / alignment structural check: PASS
* — runtime invocations: SKIP (require a live ncclDevComm + GIN net)
*
* Build is NOT a normal CMake/GTest build: librccl_device.bc must already
* exist (cmake -DEMIT_LLVM_IR=ON) and the translation unit must be compiled
* by hipcc at -O0 with the bitcode routed to the AMDGPU device-side LTO link.
* The pytest harness in test/ir-device/ drives the build + run in CI (it links
* GTest and emits the bitcode on demand); see test/ir-device/tests/conftest.py.
************************************************************************/
#include <hip/hip_runtime.h>
#include <gtest/gtest.h>
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
#include <nccl.h>
#include <nccl_device_wrapper.h>
/* -----------------------------------------------------------------------
* Compile-time structural checks — these fire during compilation, not at
* runtime, so any failure here means the build itself breaks.
* ---------------------------------------------------------------------- */
static_assert(sizeof(ncclLsaBarrierSession_C) > 0,
"ncclLsaBarrierSession_C must have non-zero size");
static_assert(alignof(ncclLsaBarrierSession_C) >= 1,
"ncclLsaBarrierSession_C must have valid alignment");
/* [C] GIN + composite barrier session structs — enabling bucket [C] makes
* these compile; the static_asserts guard against a regression that would
* silently strip the wrapper structs back out of the build. */
static_assert(sizeof(ncclGinBarrierSession_C) > 0,
"ncclGinBarrierSession_C must have non-zero size");
static_assert(alignof(ncclGinBarrierSession_C) >= 1,
"ncclGinBarrierSession_C must have valid alignment");
static_assert(sizeof(ncclBarrierSession_C) > 0,
"ncclBarrierSession_C must have non-zero size");
static_assert(alignof(ncclBarrierSession_C) >= 1,
"ncclBarrierSession_C must have valid alignment");
/* -----------------------------------------------------------------------
* Error checking — fail the current GTest assertion rather than exit(),
* so a HIP error in one case does not abort the whole binary.
* ---------------------------------------------------------------------- */
#define HIP_ASSERT(stmt) \
do { \
hipError_t _e = (stmt); \
ASSERT_EQ(_e, hipSuccess) \
<< "HIP error " << (int)_e << " (" << hipGetErrorName(_e) << ") at " \
<< __FILE__ << ":" << __LINE__ << ": " << hipGetErrorString(_e); \
} while (0)
/* -----------------------------------------------------------------------
* Shared device-side output struct
* ---------------------------------------------------------------------- */
struct CoopResult { int size, rank, num_threads; };
/* =====================================================================
* Test fixture — selects a device once and caches its warp size.
* ==================================================================== */
class IRDeviceTest : public ::testing::Test {
protected:
static int warpSize_;
static void SetUpTestSuite() {
int nDev = 0;
ASSERT_EQ(hipGetDeviceCount(&nDev), hipSuccess);
ASSERT_GT(nDev, 0) << "No HIP devices found";
ASSERT_EQ(hipSetDevice(0), hipSuccess);
hipDeviceProp_t prop{};
ASSERT_EQ(hipGetDeviceProperties(&prop, 0), hipSuccess);
warpSize_ = prop.warpSize;
std::printf("[IR_test] device 0: %s warpSize=%d\n", prop.name, warpSize_);
}
};
int IRDeviceTest::warpSize_ = 0;
/* =====================================================================
* [A] Bucket A — ncclGetPeerPointerTeam
* ==================================================================== */
struct PeerCase {
int lsaRank, worldRank;
uint32_t stride4G;
size_t offset;
int tm_nRanks, tm_rank, tm_stride, peer;
};
/* Reference pointer-arithmetic, identical to core__funcs.h logic. */
static uintptr_t host_peer_ptr(uintptr_t base, const PeerCase& c) {
int i = c.lsaRank + (c.peer - c.tm_rank) * c.tm_stride;
uint32_t delta4G = (uint32_t)((int32_t)i * (int32_t)c.stride4G);
uint32_t lo = (uint32_t)(base & 0xFFFFFFFFu);
uint32_t hi = (uint32_t)(base >> 32) + delta4G;
uintptr_t shift = ((uintptr_t)hi << 32) | lo;
return shift + c.offset;
}
static const PeerCase kPeerCases[] = {
/* lsaRank worldRank stride4G offset tm_nRanks tm_rank tm_stride peer */
{ 0, 0, 0, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 64, 2, 0, 1, 1 },
{ 0, 0, 1, 64, 2, 1, 1, 0 },
{ 0, 0, 2, 128, 4, 0, 1, 3 },
{ 0, 0, 1, 0, 8, 0, 2, 4 },
{ 5, 0, 1, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0xDEADBEEFull, 1, 0, 1, 0 },
{ 0, 0, 0x10, 0, 2, 0, 1, 1 },
{ 3, 0, 2, 512, 4, 1, 1, 2 },
{ 0, 0, 7, 0, 1, 0, 1, 0 },
{ 0, 0, 1, 0xCAFEBABEull, 2, 0, 1, 1 },
{ 2, 0, 1, 0, 3, 1, 1, 2 },
{ 0, 0, 4, 0x1000, 2, 1, 1, 0 },
{ 1, 0, 1, 8, 4, 2, 1, 3 },
{ 0, 0, 15, 0x40000000ull, 1, 0, 1, 0 },
{ 4, 0, 3, 16, 8, 3, 2, 6 },
/* Additional edge cases */
{ 0, 0, 0, 0xFFFFFFFFull, 1, 0, 1, 0 }, /* max offset */
{ 0, 0, 0xFFFF, 0, 2, 0, 1, 1 }, /* large stride */
{ -1, 0, 1, 0, 1, 0, 1, 0 }, /* negative lsaRank */
{ 0, 0, 1, 0, 4, 3, 1, 0 }, /* wrap-around peer */
};
__global__ void k_peer_ptr(char* base, const PeerCase* cases, int N, void** out) {
int t = blockIdx.x * blockDim.x + threadIdx.x;
if (t >= N) return;
const PeerCase c = cases[t];
ncclWindow_vidmem w{};
w.winHost = nullptr;
w.lsaFlatBase = base;
w.lsaRank = c.lsaRank;
w.worldRank = c.worldRank;
w.stride4G = c.stride4G;
w.mcOffset4K = 0;
ncclTeam tm{ c.tm_nRanks, c.tm_rank, c.tm_stride };
out[t] = ncclGetPeerPointerTeam(&w, c.offset, tm, c.peer);
}
TEST_F(IRDeviceTest, A_GetPeerPointerTeam) {
constexpr int N = (int)(sizeof kPeerCases / sizeof kPeerCases[0]);
const uintptr_t base = 0x100000000ull;
PeerCase* d_cases = nullptr; void** d_out = nullptr;
HIP_ASSERT(hipMalloc(&d_cases, sizeof kPeerCases));
HIP_ASSERT(hipMalloc(&d_out, sizeof(void*) * N));
HIP_ASSERT(hipMemcpy(d_cases, kPeerCases, sizeof kPeerCases,
hipMemcpyHostToDevice));
k_peer_ptr<<<1, N>>>((char*)base, d_cases, N, d_out);
HIP_ASSERT(hipGetLastError());
HIP_ASSERT(hipDeviceSynchronize());
std::vector<void*> got(N);
HIP_ASSERT(hipMemcpy(got.data(), d_out, sizeof(void*) * N,
hipMemcpyDeviceToHost));
HIP_ASSERT(hipFree(d_cases));
HIP_ASSERT(hipFree(d_out));
for (int i = 0; i < N; ++i) {
SCOPED_TRACE("peer case " + std::to_string(i));
EXPECT_EQ((uintptr_t)got[i], host_peer_ptr(base, kPeerCases[i]));
}
}
/* =====================================================================
* [B1–B5] Coop kernels — one per init type
*
* Workaround for a ROCm 7.x AMDGPU codegen bug: when threadIdx.x is used
* directly as a scatter-store index after indirect function calls (vtable
* dispatch), the compiler hoists the kernel-argument SGPR load to the top
* of the function but omits the required s_waitcnt lgkmcnt(0) before the
* global_store that uses that SGPR as a base address. The store fires with
* a stale (zero) SGPR, writes to NULL, triggers an XNACK page-fault retry
* loop, and the wavefront hangs indefinitely.
*
* Using `tid = blockIdx.x * blockDim.x + threadIdx.x` forces the compiler
* to compute a full 64-bit VGPR address (via v_lshl_add_u64 + s_waitcnt)
* and selects the correct addressing mode for global_store.
* ==================================================================== */
__global__ void k_coop_thread_r(CoopResult* out) {
ncclCoopAny coop; ncclCoopAnyInitThread(&coop);
int tid = blockIdx.x * blockDim.x + threadIdx.x;
out[tid] = { ncclCoopSize(&coop),
ncclCoopThreadRank(&coop),
ncclCoopNumThreads(&coop) };
}
__global__ void k_coop_warp_r(CoopResult* out) {
ncclCoopAny coop; ncclCoopAnyInitWarp(&coop);
int tid = blockIdx.x * blockDim.x + threadIdx.x;
out[tid] = { ncclCoopSize(&coop),
ncclCoopThreadRank(&coop),
ncclCoopNumThreads(&coop) };
}
__global__ void k_coop_lanes_r(CoopResult* out, uint64_t mask) {
ncclCoopAny coop; ncclCoopAnyInitLanes(&coop, mask);
int tid = blockIdx.x * blockDim.x + threadIdx.x;
out[tid] = { ncclCoopSize(&coop),
ncclCoopThreadRank(&coop),
ncclCoopNumThreads(&coop) };
}
__global__ void k_coop_warpspan_r(CoopResult* out, int warp0, int nWarps, int id) {
/* Data-only kernel (size/rank accessors, no sync) -> no barrier scratch needed. */
ncclCoopAny coop; ncclCoopAnyInitWarpSpan(&coop, warp0, nWarps, id, /*barrierLds=*/nullptr);
int tid = blockIdx.x * blockDim.x + threadIdx.x;
out[tid] = { ncclCoopSize(&coop),
ncclCoopThreadRank(&coop),
ncclCoopNumThreads(&coop) };
}
__global__ void k_coop_cta_r(CoopResult* out) {
ncclCoopAny coop; ncclCoopAnyInitCta(&coop);
int tid = blockIdx.x * blockDim.x + threadIdx.x;
out[tid] = { ncclCoopSize(&coop),
ncclCoopThreadRank(&coop),
ncclCoopNumThreads(&coop) };
}
/* =====================================================================
* [B6] Sync kernels — write 1 after sync, host checks all slots == 1
* ==================================================================== */
__global__ void k_sync_thread (int* out) { ncclCoopAny c; ncclCoopAnyInitThread (&c); ncclCoopSync(&c); int t=blockIdx.x*blockDim.x+threadIdx.x; out[t]=1; }
__global__ void k_sync_warp (int* out) { ncclCoopAny c; ncclCoopAnyInitWarp (&c); ncclCoopSync(&c); int t=blockIdx.x*blockDim.x+threadIdx.x; out[t]=1; }
__global__ void k_sync_lanes (int* out, uint64_t mask) { ncclCoopAny c; ncclCoopAnyInitLanes (&c, mask); ncclCoopSync(&c); int t=blockIdx.x*blockDim.x+threadIdx.x; out[t]=1; }
__global__ void k_sync_warpspan(int* out, int warp0, int nWarps, int id){
/* Kernel owns the named-barrier LDS and hands it to the bitcode thunk, so the
* backend sizes it into THIS kernel's group segment (see coop.h rationale). */
__shared__ ncclCoopNamedBarrierSlot bar[ncclCoopNamedBarrierSlots];
ncclCoopNamedBarrierInit(bar);
ncclCoopAny c; ncclCoopAnyInitWarpSpan(&c, warp0, nWarps, id, bar);
ncclCoopSync(&c); int t=blockIdx.x*blockDim.x+threadIdx.x; out[t]=1;
}
__global__ void k_sync_cta (int* out) { ncclCoopAny c; ncclCoopAnyInitCta (&c); ncclCoopSync(&c); int t=blockIdx.x*blockDim.x+threadIdx.x; out[t]=1; }
/* =====================================================================
* Helpers: launch-check-free pattern
* ==================================================================== */
/* Run a coop_r kernel and return the result array. */
template<typename Kern, typename... Args>
static std::vector<CoopResult>
launch_coop_r(Kern kern, int N, Args... args) {
CoopResult* d = nullptr;
std::vector<CoopResult> h(N);
if (hipMalloc(&d, sizeof(CoopResult) * N) != hipSuccess) return h;
kern<<<1, N>>>(d, args...);
(void)hipGetLastError(); (void)hipDeviceSynchronize();
(void)hipMemcpy(h.data(), d, sizeof(CoopResult) * N, hipMemcpyDeviceToHost);
(void)hipFree(d);
return h;
}
/* Run a sync kernel and verify every slot is 1. */
template<typename Kern, typename... Args>
static bool launch_sync(Kern kern, int N, Args... args) {
int* d = nullptr;
if (hipMalloc(&d, sizeof(int) * N) != hipSuccess) return false;
(void)hipMemset(d, 0, sizeof(int) * N);
kern<<<1, N>>>(d, args...);
(void)hipGetLastError(); (void)hipDeviceSynchronize();
std::vector<int> h(N);
(void)hipMemcpy(h.data(), d, sizeof(int) * N, hipMemcpyDeviceToHost);
(void)hipFree(d);
for (int i = 0; i < N; ++i) if (h[i] != 1) return false;
return true;
}
/* Verify size, num_threads (and optionally rank) in a CoopResult vector.
* exp_rank(t) returns -1 to skip the rank check for thread t. */
template<typename ExpSize, typename ExpRank>
static void check_coop(const std::vector<CoopResult>& h,
ExpSize exp_size, ExpRank exp_rank) {
const int N = (int)h.size();
for (int t = 0; t < N; ++t) {
SCOPED_TRACE("tid=" + std::to_string(t));
EXPECT_EQ(h[t].size, exp_size(t));
EXPECT_EQ(h[t].num_threads, h[t].size);
const int er = exp_rank(t);
if (er >= 0) EXPECT_EQ(h[t].rank, er);
}
}
/* Return in-group lane rank for thread t, or -1 to skip rank check. */
static int lane_rank_in_mask(uint64_t mask, int t) {
if (t < 0 || !((mask >> t) & 1ull)) return -1;
return __builtin_popcountll(mask & ((1ull << t) - 1ull));
}
/* =====================================================================
* [B1] ncclCoopAnyInitThread
* ==================================================================== */
TEST_F(IRDeviceTest, B1_CoopInitThread) {
/* Launch one warp so we test every lane position.
* Thread coop: every thread is its own singleton group. */
auto h = launch_coop_r(k_coop_thread_r, warpSize_);
check_coop(h,
[](int) { return 1; }, /* exp_size */
[](int) { return 0; }); /* exp_rank */
}
/* =====================================================================
* [B2] ncclCoopAnyInitWarp
* ==================================================================== */
TEST_F(IRDeviceTest, B2_CoopInitWarp) {
auto h = launch_coop_r(k_coop_warp_r, warpSize_);
const int ws = warpSize_;
check_coop(h,
[ws](int) { return ws; },
[ws](int t){ return t % ws; });
}
/* =====================================================================
* [B3] ncclCoopAnyInitLanes — mask variants (uint64_t lane_mask)
* ==================================================================== */
TEST_F(IRDeviceTest, B3a_CoopInitLanes_FullMask) {
const uint64_t mask = ~0ull;
const int sz = warpSize_;
const int ws = warpSize_;
auto h = launch_coop_r(k_coop_lanes_r, warpSize_, mask);
check_coop(h,
[sz](int) { return sz; },
[ws, mask](int t) {
/* nccl::utility::lanemask_lt() is still 32-bit on AMD wave64,
* so thread_rank() for lanes 32–63 is not lane index today. */
if (ws >= 64 && t >= 32) return -1;
return lane_rank_in_mask(mask, t);
});
}
TEST_F(IRDeviceTest, B3b_CoopInitLanes_SparseMask) {
const uint64_t mask = 0x00000015ull; /* bits 0, 2, 4 */
const int sz = __builtin_popcountll(mask);
auto h = launch_coop_r(k_coop_lanes_r, warpSize_, mask);
check_coop(h,
[sz](int) { return sz; },
[mask](int t) { return lane_rank_in_mask(mask, t); });
}
TEST_F(IRDeviceTest, B3c_CoopInitLanes_SingleBitMask) {
const uint64_t mask = 0x00000001ull;
auto h = launch_coop_r(k_coop_lanes_r, warpSize_, mask);
check_coop(h,
[](int) { return 1; },
[](int t){ return (t == 0) ? 0 : -1; });
}
TEST_F(IRDeviceTest, B3d_CoopInitLanes_UpperLaneMask) {
if (warpSize_ < 64) GTEST_SKIP() << "requires a 64-lane warp";
const uint64_t mask = 1ull << 63;
auto h = launch_coop_r(k_coop_lanes_r, warpSize_, mask);
check_coop(h,
[](int) { return 1; },
[](int t){ return (t == 63) ? 0 : -1; });
}
/* =====================================================================
* [B4] ncclCoopAnyInitWarpSpan — 1-warp and 2-warp spans
* rank = threadIdx.x - WARP_SIZE * warp0 (WARP_SIZE == device warpSize)
* ==================================================================== */
TEST_F(IRDeviceTest, B4a_CoopInitWarpSpan_OneWarp) {
const int ws = warpSize_;
auto h = launch_coop_r(k_coop_warpspan_r, warpSize_,
/*warp0=*/0, /*nWarps=*/1, /*id=*/0);
check_coop(h,
[ws](int) { return ws; },
[](int t) { return t; });
}
TEST_F(IRDeviceTest, B4b_CoopInitWarpSpan_TwoWarp) {
/* ncclCoopWarpSpan::sync() calls __syncthreads() on AMD, so ALL threads
* in the CTA must participate. The coop_r kernel is data-only (no sync),
* so launching 2*warpSize threads is safe. */
const int N = 2 * warpSize_;
auto h = launch_coop_r(k_coop_warpspan_r, N,
/*warp0=*/0, /*nWarps=*/2, /*id=*/0);
check_coop(h,
[N](int) { return N; },
[](int t) { return t; });
}
/* =====================================================================
* [B5] ncclCoopAnyInitCta — two block sizes
* ==================================================================== */
TEST_F(IRDeviceTest, B5_CoopInitCta) {
for (int blk : {64, 128}) {
SCOPED_TRACE("blockDim=" + std::to_string(blk));
auto h = launch_coop_r(k_coop_cta_r, blk);
check_coop(h,
[blk](int) { return blk; },
[](int t) { return t; });
}
}
/* =====================================================================
* [B6] ncclCoopSync — one test per coop type
* ==================================================================== */
TEST_F(IRDeviceTest, B6_CoopSync) {
/* Thread: every thread syncs its own singleton coop independently. */
EXPECT_TRUE(launch_sync(k_sync_thread, warpSize_))
<< "ncclCoopSync (Thread) kernel did not complete";
/* Warp: full warp participates. */
EXPECT_TRUE(launch_sync(k_sync_warp, warpSize_))
<< "ncclCoopSync (Warp) kernel did not complete";
/* Lanes (full mask): all lanes in the warp participate. */
EXPECT_TRUE(launch_sync(k_sync_lanes, warpSize_, ~0ull))
<< "ncclCoopSync (Lanes full mask) kernel did not complete";
/* WarpSpan (2 warps): __syncthreads() on AMD — all CTA threads must
* call it, so launch exactly 2*warpSize threads. */
EXPECT_TRUE(launch_sync(k_sync_warpspan, 2 * warpSize_,
/*warp0=*/0, /*nWarps=*/2, /*id=*/0))
<< "ncclCoopSync (WarpSpan 2-warp) kernel did not complete";
/* CTA: standard __syncthreads(). */
EXPECT_TRUE(launch_sync(k_sync_cta, 128))
<< "ncclCoopSync (Cta blk=128) kernel did not complete";
}
/* =====================================================================
* [B7] ncclLsaBarrierSession — structural + skip runtime
* ==================================================================== */
TEST_F(IRDeviceTest, B7a_LsaBarrierSessionStructural) {
/* sizeof / alignment checks are compile-time (static_assert at top of
* file); re-assert at runtime so the case shows up in the summary. */
EXPECT_GT(sizeof(ncclLsaBarrierSession_C), 0u);
EXPECT_GE(alignof(ncclLsaBarrierSession_C), 1u);
}
TEST_F(IRDeviceTest, B7b_LsaBarrierSessionRuntime) {
/* Runtime calls require a live ncclDevComm with a mapped resource buffer;
* without it the constructor immediately dereferences
* ncclGetResourceBufferLocalPointer() → segfault. */
GTEST_SKIP() << "ncclLsaBarrierSession{Init,Arrive,Wait,Sync} require a "
"live ncclDevComm (not set up here)";
}
/* =====================================================================
* [C] Bucket-C symbol link/export coverage
*
* The structural cases (C1a/C2a) only touch the session *structs*, and the
* runtime cases (C1b/C2b) are SKIPPED because they need a live ncclDevComm +
* GIN net. To still cover the four exported *functions* themselves, this
* kernel takes the device address of each thunk and stores it. Storing the
* pointer value (not a folded != nullptr) forces the consumer -> bitcode
* relocation for every symbol, so a missing/renamed/internalized export makes
* the device link fail or yields a null address here — without invoking the
* functions (which would require live resources).
* ==================================================================== */
__global__ void k_bucketC_symbol_addrs(uint64_t* out) {
out[0] = (uint64_t)(uintptr_t)(void*)&ncclGinBarrierSessionInit;
out[1] = (uint64_t)(uintptr_t)(void*)&ncclGinBarrierSessionSync;
out[2] = (uint64_t)(uintptr_t)(void*)&ncclBarrierSessionInit;
out[3] = (uint64_t)(uintptr_t)(void*)&ncclBarrierSessionSync;
}
TEST_F(IRDeviceTest, C0_BucketCSymbolsLinkable) {
constexpr int N = 4;
uint64_t* d = nullptr;
HIP_ASSERT(hipMalloc(&d, sizeof(uint64_t) * N));
HIP_ASSERT(hipMemset(d, 0, sizeof(uint64_t) * N));
k_bucketC_symbol_addrs<<<1, 1>>>(d);
HIP_ASSERT(hipGetLastError());
HIP_ASSERT(hipDeviceSynchronize());
uint64_t got[N] = {};
HIP_ASSERT(hipMemcpy(got, d, sizeof got, hipMemcpyDeviceToHost));
HIP_ASSERT(hipFree(d));
const char* names[N] = {
"ncclGinBarrierSessionInit", "ncclGinBarrierSessionSync",
"ncclBarrierSessionInit", "ncclBarrierSessionSync"};
for (int i = 0; i < N; ++i) {
SCOPED_TRACE(names[i]);
EXPECT_NE(got[i], 0ull) << names[i] << " did not resolve from the bitcode";
}
}
/* =====================================================================
* [C1] ncclGinBarrierSession — structural + skip runtime
* ==================================================================== */
TEST_F(IRDeviceTest, C1a_GinBarrierSessionStructural) {
/* Mirrors B7a: the sizeof / alignof static_asserts at the top of the file
* fire at compile time; re-assert at runtime so the case appears in the
* summary and proves bucket [C]'s ncclGinBarrierSession_C is instantiated. */
EXPECT_GT(sizeof(ncclGinBarrierSession_C), 0u);
EXPECT_GE(alignof(ncclGinBarrierSession_C), 1u);
}
TEST_F(IRDeviceTest, C1b_GinBarrierSessionRuntime) {
/* ncclGinBarrierSessionInit takes an ncclGin_C, ncclTeam and
* ncclGinBarrierHandle bound to live GPU-Initiated-Networking resources;
* ncclGinBarrierSessionSync then issues network fences against them. Both
* require a live ncclDevComm with an established GIN connection, which is
* not available in this standalone device-API test. */
GTEST_SKIP() << "ncclGinBarrierSession{Init,Sync} require a live ncclDevComm "
"+ GIN network resources (not set up here)";
}
/* =====================================================================
* [C2] ncclBarrierSession (composite inner-LSA + outer-GIN) — structural
* + skip runtime
* ==================================================================== */
TEST_F(IRDeviceTest, C2a_BarrierSessionStructural) {
EXPECT_GT(sizeof(ncclBarrierSession_C), 0u);
EXPECT_GE(alignof(ncclBarrierSession_C), 1u);
}
TEST_F(IRDeviceTest, C2b_BarrierSessionRuntime) {
/* The composite barrier wires together an inner LSA barrier and an outer
* GIN barrier (inner/outer teams, an ncclLsaBarrierHandle, an
* ncclGinBarrierHandle and an ncclGin_C), so it needs everything B7b and
* C1b need at once: a live ncclDevComm plus GIN network resources. */
GTEST_SKIP() << "ncclBarrierSession{Init,Sync} require a live ncclDevComm "
"+ GIN network resources (not set up here)";
}