Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
fe35c54
rccl-tests: extract shared gin_sdma_harness.py (AICOMRCCL-2221)
dlamd1dai Sep 13, 2026
a7fabe1
rccl-tests: GIN device-API CI and harness cleanup (AICOMRCCL-2221)
dlamd1dai Sep 13, 2026
264938b
rccl: do not include Anvil-SDMA headers in every DeviceLinker TU.
dlamd1dai Sep 13, 2026
6c1e471
rccl: address GIN harness and device-api review
dlamd1dai Sep 13, 2026
f822f02
rccl: RDC-link device-api matrix binaries; harness dedupe
dlamd1dai Sep 14, 2026
97377b5
rccl: isolate Anvil headers from atomics poison
dlamd1dai Sep 14, 2026
097f4da
rccl: add external Anvil poison-boundary test
dlamd1dai Sep 14, 2026
c0b537c
rccl: add post-Anvil atomics poison test
dlamd1dai Sep 14, 2026
53b1b3c
rccl-tests/gin-sdma: port GIN Anvil-SDMA ReduceScatter (-D 3) from st…
dlamd1dai Aug 29, 2026
07e84b8
rccl-tests: align ReduceScatter testEngine with develop all_gather co…
dlamd1dai Sep 3, 2026
ae6cfb3
ci(rccl/gin): run ReduceScatter GIN pytest from the GIN CI job
dlamd1dai Sep 12, 2026
36c3964
fix(rccl-tests/gin-sdma): address review findings on the RS device path
dlamd1dai Sep 13, 2026
b696ac7
fix(rccl-tests): clamp RS grid to the barrier pool, harden CTA env parse
dlamd1dai Sep 13, 2026
dbc66d2
refactor(rccl-tests): move RS CTA env parsing into the policy header
dlamd1dai Sep 22, 2026
6031206
fix(rccl-tests): -b is the total send buffer, not the per-rank slice
dlamd1dai Sep 22, 2026
4319984
fix(rccl-tests): fail the devtime datacheck when no verdict was printed
dlamd1dai Sep 22, 2026
740d740
docs(rccl-tests): correct the in-place aliasing and macro-location co…
dlamd1dai Sep 22, 2026
61b1613
fix(rccl-tests): do not hide the RS env parsers from the HIP device pass
dlamd1dai Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions projects/rccl-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,11 @@ if (ENABLE_ROCSHMEM_GIN)
if (NOT ROCSHMEM_SOURCE_DIR AND EXISTS "${_mono_root}/projects/rocshmem/CMakeLists.txt")
set(ROCSHMEM_SOURCE_DIR "${_mono_root}/projects/rocshmem")
endif()
# Installed rocshmem headers (rocshmem_config.h) are needed globally:
# RCCL's public nccl_device.h → gin_anvil_sdma.h → anvil_device.hpp
# → log.hpp → rocshmem/rocshmem_config.h. Must come before source
# includes so the generated config is found first.
# Installed rocshmem headers (rocshmem_config.h) for GIN-SDMA perf binaries
# that set NCCL_GIN_ANVIL_SDMA_ENABLE=1. Must come before source includes
# so the generated config is found first.
include_directories(BEFORE ${ROCSHMEM_INSTALL_DIR}/include)
# Internal rocshmem headers (sdma/anvil_device.hpp) needed globally.
# Internal rocshmem headers (sdma/anvil_device.hpp) for those same TUs.
if (ROCSHMEM_SOURCE_DIR)
include_directories(${ROCSHMEM_SOURCE_DIR}/src)
endif()
Expand Down
69 changes: 38 additions & 31 deletions projects/rccl-tests/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ set(COMMON_FILES
common.cu
gin_sdma_allgather_policy.h
gin_sdma_broadcast_policy.h
gin_sdma_reducescatter_policy.h
gin_sdma_reduce.h
gin_sdma_devtime.h
gin_sdma_devtime_host.h
nccl1_compat.h
Expand Down Expand Up @@ -145,27 +147,6 @@ endif()
add_rccl_test(all_gather)
add_rccl_test(all_reduce)
add_rccl_test(alltoall)
if (ENABLE_ROCSHMEM_GIN AND TARGET roc::rocshmem)
target_compile_definitions(alltoall_perf PRIVATE ENABLE_ROCSHMEM_GIN)
target_compile_options(alltoall_perf PRIVATE -fgpu-rdc $<$<CONFIG:Debug>:-ggdb -O0>)
# --whole-archive: librccl.so (ENABLE_ROCSHMEM_GIN) leaves rocshmem host
# symbols unresolved (--allow-shlib-undefined). The executable must pull
# in ALL rocshmem symbols (not just those it references directly) so that
# -rdynamic exports them for librccl.so to resolve at runtime.
target_link_libraries(alltoall_perf PRIVATE
-Wl,--whole-archive roc::rocshmem -Wl,--no-whole-archive)
target_link_options(alltoall_perf PRIVATE -fgpu-rdc --hip-link -rdynamic)

# GIN-SDMA hybrid AllGather (all_gather_perf -D 3): same device-RDC link as
# alltoall_perf so the GinHybridAllGatherKernel resolves the GIN device backend.
if (ENABLE_DEVICE_API)
target_compile_definitions(all_gather_perf PRIVATE ENABLE_DEVICE_API)
endif()
target_compile_options(all_gather_perf PRIVATE -fgpu-rdc $<$<CONFIG:Debug>:-ggdb -O0>)
target_link_libraries(all_gather_perf PRIVATE
-Wl,--whole-archive roc::rocshmem -Wl,--no-whole-archive)
target_link_options(all_gather_perf PRIVATE -fgpu-rdc --hip-link -rdynamic)
endif()
if (ENABLE_ROCSHMEM_GIN AND TARGET roc::rocshmem AND RCCL_SOURCE_DIR)
add_executable(gin_rocshmem_gda_factory_test ${CMAKE_CURRENT_SOURCE_DIR}/gin_rocshmem_gda_factory_test.cu)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/gin_rocshmem_gda_factory_test.cu PROPERTIES LANGUAGE CXX)
Expand All @@ -185,20 +166,46 @@ if (ENABLE_ROCSHMEM_GIN AND TARGET roc::rocshmem AND RCCL_SOURCE_DIR)
endif()
add_rccl_test(alltoallv)
add_rccl_test(broadcast)
if (ENABLE_ROCSHMEM_GIN AND TARGET roc::rocshmem)
# GIN-SDMA hybrid Broadcast (broadcast_perf -D 3): same device-RDC link as
# alltoall_perf/all_gather_perf so the Gin*BroadcastKernel variants resolve
# the GIN device backend (SDMA puts + LSA/LL peer stores).
target_compile_definitions(broadcast_perf PRIVATE ENABLE_ROCSHMEM_GIN)
target_compile_options(broadcast_perf PRIVATE -fgpu-rdc $<$<CONFIG:Debug>:-ggdb -O0>)
target_link_libraries(broadcast_perf PRIVATE
-Wl,--whole-archive roc::rocshmem -Wl,--no-whole-archive)
target_link_options(broadcast_perf PRIVATE -fgpu-rdc --hip-link -rdynamic)
endif()
add_rccl_test(gather)
add_rccl_test(hypercube)
add_rccl_test(reduce_scatter)
add_rccl_test(reduce)
add_rccl_test(scatter)
add_rccl_test(sendrecv)
add_rccl_test(all_reduce_bias)
if (ENABLE_ROCSHMEM_GIN AND TARGET roc::rocshmem)
function(_rccl_tests_link_rocshmem_gin_rdc target)
target_compile_definitions(${target} PRIVATE ENABLE_ROCSHMEM_GIN NCCL_GIN_ANVIL_SDMA_ENABLE=1)
target_compile_options(${target} PRIVATE -fgpu-rdc $<$<CONFIG:Debug>:-ggdb -O0>)
# --whole-archive: librccl.so (ENABLE_ROCSHMEM_GIN) leaves rocshmem host
# symbols unresolved (--allow-shlib-undefined). The executable must pull
# in ALL rocshmem symbols (not just those it references directly) so that
# -rdynamic exports them for librccl.so to resolve at runtime.
target_link_libraries(${target} PRIVATE
-Wl,--whole-archive roc::rocshmem -Wl,--no-whole-archive)
target_link_options(${target} PRIVATE -fgpu-rdc --hip-link -rdynamic)
endfunction()

# GIN-SDMA hybrid AllGather (all_gather_perf -D 3) needs ENABLE_DEVICE_API
# so the GinHybridAllGatherKernel is compiled into this binary.
if (ENABLE_DEVICE_API)
target_compile_definitions(all_gather_perf PRIVATE ENABLE_DEVICE_API)
endif()

# Device-api CI builds one --rocshmem-gin librccl.so and runs the matrix
# against it. Every launched binary must RDC-link rocSHMEM so GIN host
# symbols resolve from the executable. all_reduce_perf is omitted: it
# still does not build under ENABLE_ROCSHMEM_GIN (gin.sbatch uses
# `make -k` for the same reason).
foreach(_gin_rdc_bin IN ITEMS
all_gather_perf
alltoall_perf
broadcast_perf
gather_perf
reduce_perf
reduce_scatter_perf
scatter_perf
sendrecv_perf)
_rccl_tests_link_rocshmem_gin_rdc(${_gin_rdc_bin})
endforeach()
endif()
2 changes: 1 addition & 1 deletion projects/rccl-tests/src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ struct testColl {
testResult_t (*getCollImplInfo)(ncclComm_t comm, size_t count, ncclDataType_t type, ncclRedOp_t op,
const void* sendbuff, void* recvbuff, int graphCapturing, int* algo, int* proto, int* nchannels);
// Optional device-side (in-kernel wall_clock64) timing hook. Non-null only for
// collectives that implement it (AllToAll, AllGather). Driven by BenchTime via
// collectives that implement it (AllToAll, AllGather, Broadcast, ReduceScatter). Driven by BenchTime via
// --device_timing (0=off, 1=augment, 2=device-time-only):
// - outDeltaSec == nullptr (mode 1): prints an extra device-only
// latency/busbw line alongside the normal graph/hipEvent numbers (report,
Expand Down
166 changes: 166 additions & 0 deletions projects/rccl-tests/src/gin_sdma_reduce.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*************************************************************************
* Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
*
* See LICENSE.txt for license information
************************************************************************/

// Device-side reduction ops for the GIN-SDMA ReduceScatter (deviceImpl == 3).
//
// These MUST bit-match rccl-tests' correctness oracle (verifiable.cu). The
// verifier's compare tolerance is 0 for sum/prod/min/max on EVERY type (2 for
// floating avg, 1 for fp8_e5m2 sum with >1 rank -- see ncclVerifiableVerify), so
// each op here mirrors the corresponding Reduce* struct in verifiable.cu exactly:
// * low-precision types (half / bf16 / fp8) convert to float, apply the op,
// then narrow back to T on EVERY pairwise step (the verifier folds the same
// way, so accumulating in float and narrowing once would NOT bit-match);
// * the caller folds contributions in ascending source-rank order -- the same
// order ncclVerifiablePrepareExpected uses;
// * avg premultiplies each element by 1/rankN for floating T (postOp identity)
// but sums-then-divides for integral T (preOp identity), exactly as
// ReduceAvg_Base<T, integral> does.
//
// mulsum / PreMulSum is intentionally NOT supported (deferred); the dispatch
// macro (SPECIALIZE_REDUCE_KERNEL in reduce_scatter.cu) returns nullptr for it,
// and fp8 prod is excluded there too.
//
// This header is device-only: include it AFTER common.h (which pulls in rccl.h
// for __half / hip_bfloat16 and rccl_float8.h for the fp8 types) so all element
// types and the HAVE_BF16 / HAVE_FP8 guards are in scope.

#ifndef GIN_SDMA_REDUCE_H_
#define GIN_SDMA_REDUCE_H_

#if defined(ENABLE_DEVICE_API) && NCCL_VERSION_CODE >= NCCL_VERSION(2,28,0)

#include <type_traits>
#if HAVE_BF16
#include <hip/hip_bfloat16.h> // hip_bfloat16 (same type verifiable.cu folds with)
#endif

namespace gin_sdma_reduce {

// ------------------------------- sum (a + b) -------------------------------
// Template covers all integer types (int8/uint8/int32/uint32/int64/uint64) plus
// float/double; the explicit overloads below are more specialized so they win
// for the low-precision types (matching verifiable.cu::ReduceSum).
template <class T> __device__ __forceinline__ T rsum(T a, T b) { return a + b; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rsum, rprod, rmin and rmax repeat the same widen-to-float-then-narrow body 16 times over the four low-precision types, and rprod has already drifted: bf16 uses hip_bfloat16(...) construction at line 71 while fp8 uses static_cast<rccl_float8>(...) at 76, where rsum uses construction for both. Same meaning, but one per-type macro emitting all four would take this from about 60 lines to 14 and make the drift impossible.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed the drift you describe: rprod constructs for bf16 (line 71) but
static_casts for fp8 (line 76), where rsum constructs for both.

Both forms are correct today — same widen-to-float, same result — so this is a readability and
future-drift concern rather than a live bug, which is why I'm not changing numeric code late in
review. Folding it into the single pass over this header together with the
GIN_SDMA_HOST_ONLY escape you asked for at line 139, so the macro rewrite lands with tests
that can actually verify it didn't change results.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, including the drift: rprod constructs hip_bfloat16(...) for bf16 but takes a different route for fp8, which is exactly the kind of divergence a shared widen/narrow helper would prevent. Leaving open under AICOMRCCL-1803, and it pairs with the missing bit-match tests in the gin_sdma_reduce.h thread.

__device__ __forceinline__ __half rsum(__half a, __half b) {
return __float2half(__half2float(a) + __half2float(b));
}
#if HAVE_BF16
__device__ __forceinline__ hip_bfloat16 rsum(hip_bfloat16 a, hip_bfloat16 b) {
return hip_bfloat16(static_cast<float>(a) + static_cast<float>(b));
}
#endif
#if HAVE_FP8
__device__ __forceinline__ rccl_float8 rsum(rccl_float8 a, rccl_float8 b) {
return rccl_float8(static_cast<float>(a) + static_cast<float>(b));
}
__device__ __forceinline__ rccl_bfloat8 rsum(rccl_bfloat8 a, rccl_bfloat8 b) {
return rccl_bfloat8(static_cast<float>(a) + static_cast<float>(b));
}
#endif

// ------------------------------- prod (a * b) ------------------------------
template <class T> __device__ __forceinline__ T rprod(T a, T b) { return a * b; }
__device__ __forceinline__ __half rprod(__half a, __half b) {
return __float2half(__half2float(a) * __half2float(b));
}
#if HAVE_BF16
__device__ __forceinline__ hip_bfloat16 rprod(hip_bfloat16 a, hip_bfloat16 b) {
return hip_bfloat16(static_cast<float>(a) * static_cast<float>(b));
}
#endif
#if HAVE_FP8
__device__ __forceinline__ rccl_float8 rprod(rccl_float8 a, rccl_float8 b) {
return static_cast<rccl_float8>(static_cast<float>(a) * static_cast<float>(b));
}
__device__ __forceinline__ rccl_bfloat8 rprod(rccl_bfloat8 a, rccl_bfloat8 b) {
return static_cast<rccl_bfloat8>(static_cast<float>(a) * static_cast<float>(b));
}
#endif

// ------------------------------- min / max ---------------------------------
template <class T> __device__ __forceinline__ T rmin(T a, T b) { return a < b ? a : b; }
template <class T> __device__ __forceinline__ T rmax(T a, T b) { return a > b ? a : b; }
__device__ __forceinline__ __half rmin(__half a, __half b) {
return __half2float(a) < __half2float(b) ? a : b;
}
__device__ __forceinline__ __half rmax(__half a, __half b) {
return __half2float(a) > __half2float(b) ? a : b;
}
#if HAVE_BF16
__device__ __forceinline__ hip_bfloat16 rmin(hip_bfloat16 a, hip_bfloat16 b) {
return static_cast<float>(a) < static_cast<float>(b) ? a : b;
}
__device__ __forceinline__ hip_bfloat16 rmax(hip_bfloat16 a, hip_bfloat16 b) {
return static_cast<float>(a) > static_cast<float>(b) ? a : b;
}
#endif
#if HAVE_FP8
__device__ __forceinline__ rccl_float8 rmin(rccl_float8 a, rccl_float8 b) {
return static_cast<float>(a) < static_cast<float>(b) ? a : b;
}
__device__ __forceinline__ rccl_float8 rmax(rccl_float8 a, rccl_float8 b) {
return static_cast<float>(a) > static_cast<float>(b) ? a : b;
}
__device__ __forceinline__ rccl_bfloat8 rmin(rccl_bfloat8 a, rccl_bfloat8 b) {
return static_cast<float>(a) < static_cast<float>(b) ? a : b;
}
__device__ __forceinline__ rccl_bfloat8 rmax(rccl_bfloat8 a, rccl_bfloat8 b) {
return static_cast<float>(a) > static_cast<float>(b) ? a : b;
}
#endif

// ---------------------------- avg premul factor ----------------------------
// The 1/rankN factor, built in the same intermediate precision the verifier
// uses (ReduceAvg_Base<T,false>: T1 = float when sizeof(T) < sizeof(double),
// else double). Only used on the floating branch; integral avg divides in postOp.
template <class T> __device__ __forceinline__ T avgFactor(int rankN) {
return (T)(1.0f / (float)rankN);
}
template <> __device__ __forceinline__ double avgFactor<double>(int rankN) {
return 1.0 / (double)rankN;
}

// ------------------------ op-tagged pre / combine / post --------------------
// `op` is an ncclRedOp_t value (ncclSum=0, ncclProd=1, ncclMax=2, ncclMin=3,
// ncclAvg=4). PreMulSum is never dispatched here.

template <class T> __device__ __forceinline__ T preOp(int op, T x, int rankN) {
// Floating avg premultiplies each element by 1/rankN before summing; integral
// avg (and every other op) leaves the element untouched (divides in postOp).
if (op == ncclAvg && !std::is_integral<T>::value) {
return rprod(avgFactor<T>(rankN), x);
}
return x;
}

template <class T> __device__ __forceinline__ T combine(int op, T a, T b) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing unit-tests combine, preOp or postOp against the Reduce* structs in verifiable/verifiable.cu that they must bit-match, and neither new gtest includes this header. It also hardcodes __device__ with no GIN_SDMA_HOST_ONLY escape like the three policy headers have, so it cannot be host-tested as written. The only coverage is the non-gating rccl-gin-rocshmem-sdma-rs-d3 sweep. Could the same HD macro go here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, and this is the one I'd pick first out of the style items. Bit-matching
combine/preOp/postOp against the Reduce* structs in verifiable.cu is the highest-value
missing check in the PR, precisely because the only thing covering it today is a non-gating
sweep — if they ever diverge, datacheck failures would be the first symptom and the cause would
be unobvious.

Adding the GIN_SDMA_HOST_ONLY escape so the header can be host-tested is the enabling step,
as you say. Deferring it here to keep this diff from growing further, but flagging it as
follow-up work rather than declining it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and this is the coverage gap I would most want closed. combine/preOp/postOp must bit-match the Reduce* structs in verifiable/verifiable.cu, and nothing asserts that directly — the datacheck only catches it end to end. The GIN_SDMA_HOST_ONLY seam that would let a gtest include this header is also missing. Tracked under AICOMRCCL-1803; leaving open.

switch (op) {
case ncclProd: return rprod(a, b);
case ncclMax: return rmax(a, b);
case ncclMin: return rmin(a, b);
default: return rsum(a, b); // ncclSum and ncclAvg both accumulate by +
}
}

// Integral avg finishes with an integer divide of the (wrapped) sum; floating T
// already divided in preOp. Split via SFINAE so `x / rankN` is only instantiated
// for integral T (fp8/bf16/half have no unambiguous `operator/(T,int)`).
template <class T> __device__ __forceinline__
typename std::enable_if<std::is_integral<T>::value, T>::type
postDivAvg(T x, int rankN) { return (T)(x / rankN); }
template <class T> __device__ __forceinline__
typename std::enable_if<!std::is_integral<T>::value, T>::type
postDivAvg(T x, int /*rankN*/) { return x; }

template <class T> __device__ __forceinline__ T postOp(int op, T x, int rankN) {
if (op == ncclAvg) return postDivAvg<T>(x, rankN);
return x;
}

} // namespace gin_sdma_reduce

#endif // ENABLE_DEVICE_API && >= 2.28
#endif // GIN_SDMA_REDUCE_H_
Loading
Loading