[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples

变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
  保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
  async_reduce, custom_temporary_allocation, explicit_cuda_stream,
  global_device_vector, range_view, unwrap_pointer, wrap_pointer, device

结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
  27/27 tuning headers, 78 benchmarks, 243 tests,
  60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
muh-bot
2026-08-03 12:39:26 +00:00
parent a2a5dd8f00
commit 24ef6a91b5
5439 changed files with 0 additions and 719516 deletions

View File

@@ -1,11 +0,0 @@
#===----------------------------------------------------------------------===##
#
# Part of CUDA Experimental in CUDA C++ Core Libraries,
# under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
#
#===----------------------------------------------------------------------===##
add_subdirectory(nccl)

View File

@@ -1,20 +0,0 @@
#===----------------------------------------------------------------------===##
#
# Part of CUDA Experimental in CUDA C++ Core Libraries,
# under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
#
#===----------------------------------------------------------------------===##
if (NOT cudax_ENABLE_NCCL)
return()
endif()
file(GLOB test_srcs LIST_DIRECTORIES FALSE CONFIGURE_DEPENDS *.cu *.cpp)
foreach (src IN LISTS test_srcs)
cudax_add_multi_gpu_test("communicators.nccl" test_target "${src}")
target_link_libraries(${test_target} PRIVATE NCCL::nccl)
endforeach()

View File

@@ -1,223 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/devices>
#include <cuda/std/type_traits>
#include <cuda/std/utility>
#include <cuda/experimental/__multi_gpu/nccl_communicator.h>
#include <cuda/experimental/__multi_gpu/nccl_communicator_ref.h>
#include <nccl.h>
#include <nccl_test_common.h>
namespace
{
[[nodiscard]] ncclComm_t make_nccl_communicator_handle()
{
if (cuda::devices.size() == 0)
{
SKIP("No CUDA devices visible");
}
const int device = cuda::devices[0].get();
ncclComm_t handle{};
const ncclResult_t result = ncclCommInitAll(&handle, 1, &device);
INFO("NCCL: " << ncclGetErrorString(result));
REQUIRE(result == ncclSuccess);
return handle;
}
} // namespace
C2H_TEST("nccl_communicator_ref typedefs", "[multi_gpu]")
{
STATIC_REQUIRE(::cuda::std::is_same_v<cudax::nccl_communicator_ref::native_handle_type, ncclComm_t>);
STATIC_REQUIRE(
::cuda::std::is_same_v<cudax::nccl_communicator_ref::group_guard_type,
decltype(::cuda::std::declval<const cudax::nccl_communicator_ref&>().group_guard())>);
}
C2H_TEST("nccl_communicator(s) not constructible from NCCL_COMM_NULL", "[multi_gpu]")
{
SECTION("ref")
{
STATIC_REQUIRE(!::cuda::std::is_constructible_v<cudax::nccl_communicator_ref, decltype(NCCL_COMM_NULL)>);
STATIC_REQUIRE(!::cuda::std::is_constructible_v<cudax::nccl_communicator_ref, cuda::std::nullptr_t>);
}
SECTION("owning")
{
STATIC_REQUIRE(!::cuda::std::is_constructible_v<cudax::nccl_communicator, decltype(NCCL_COMM_NULL)>);
STATIC_REQUIRE(!::cuda::std::is_constructible_v<cudax::nccl_communicator, cuda::std::nullptr_t>);
STATIC_REQUIRE(!::cuda::std::is_constructible_v<cudax::nccl_communicator, ncclComm_t>);
}
}
C2H_TEST("nccl_communicator basic", "[multi_gpu][nccl]")
{
SECTION("is move-only")
{
STATIC_REQUIRE(!cuda::std::is_copy_constructible_v<cudax::nccl_communicator>);
STATIC_REQUIRE(!cuda::std::is_copy_assignable_v<cudax::nccl_communicator>);
STATIC_REQUIRE(cuda::std::is_move_constructible_v<cudax::nccl_communicator>);
STATIC_REQUIRE(cuda::std::is_nothrow_move_constructible_v<cudax::nccl_communicator>);
STATIC_REQUIRE(cuda::std::is_move_assignable_v<cudax::nccl_communicator>);
STATIC_REQUIRE(cuda::std::is_nothrow_move_assignable_v<cudax::nccl_communicator>);
}
SECTION("factory construction")
{
STATIC_REQUIRE(
cuda::std::is_same_v<decltype(cudax::nccl_communicator::from_native_handle(cuda::std::declval<ncclComm_t>())),
cudax::nccl_communicator>);
//! [nccl_communicator_construction]
const ncclComm_t handle = make_nccl_communicator_handle();
auto comm = cuda::experimental::nccl_communicator::from_native_handle(handle);
// comm owns the handle now
REQUIRE(comm.native_handle() == handle);
//! [nccl_communicator_construction]
}
SECTION("factory construction with logical device")
{
STATIC_REQUIRE(
cuda::std::is_same_v<decltype(cudax::nccl_communicator::from_native_handle(
cuda::std::declval<ncclComm_t>(), cuda::std::declval<cudax::logical_device>())),
cudax::nccl_communicator>);
//! [nccl_communicator_construction_with_logical_device]
const ncclComm_t handle = make_nccl_communicator_handle();
const auto device = cudax::logical_device{cuda::devices[0]};
auto comm = cudax::nccl_communicator::from_native_handle(handle, device);
REQUIRE(comm.native_handle() == handle);
REQUIRE(comm.logical_device() == device);
//! [nccl_communicator_construction_with_logical_device]
}
SECTION("no_init construction")
{
STATIC_REQUIRE(cuda::std::is_nothrow_constructible_v<cudax::nccl_communicator, cuda::no_init_t>);
//! [nccl_communicator_no_init_construction]
const auto comm = cudax::nccl_communicator{cuda::no_init};
REQUIRE(comm.native_handle() == ncclComm_t{NCCL_COMM_NULL});
//! [nccl_communicator_no_init_construction]
REQUIRE(comm.rank() == 0);
REQUIRE(comm.size() == 0);
}
SECTION("release")
{
//! [nccl_communicator_release]
const ncclComm_t handle = make_nccl_communicator_handle();
auto comm = cuda::experimental::nccl_communicator::from_native_handle(handle);
const auto released_handle = comm.release();
// comm contains the null handle after release
REQUIRE(comm.native_handle() == ncclComm_t{NCCL_COMM_NULL});
REQUIRE(released_handle == handle);
//! [nccl_communicator_release]
// so that we clean up properly
[[maybe_unused]] const auto _ = cudax::nccl_communicator::from_native_handle(handle);
}
SECTION("move construction")
{
//! [nccl_communicator_move_construction]
const ncclComm_t handle = make_nccl_communicator_handle();
auto source = cudax::nccl_communicator::from_native_handle(handle);
auto destination = cudax::nccl_communicator{cuda::std::move(source)};
// moved-from communicator is now invalid
REQUIRE(source.native_handle() == ncclComm_t{NCCL_COMM_NULL});
REQUIRE(destination.native_handle() == handle);
//! [nccl_communicator_move_construction]
}
SECTION("move assignment")
{
//! [nccl_communicator_move_assignment]
auto source = cuda::experimental::nccl_communicator::from_native_handle(make_nccl_communicator_handle());
auto destination = cuda::experimental::nccl_communicator::from_native_handle(make_nccl_communicator_handle());
// Save the native handle to verify that ownership is transferred.
const auto handle = source.native_handle();
destination = cuda::std::move(source);
REQUIRE(source.native_handle() == ncclComm_t{NCCL_COMM_NULL});
REQUIRE(destination.native_handle() == handle);
//! [nccl_communicator_move_assignment]
}
}
MULTI_GPU_TEST("nccl_communicator_ref basic", )
{
SECTION("rank and size")
{
int i = 0;
for (auto& comm : this->communicators())
{
REQUIRE(comm.rank() == i);
REQUIRE(comm.size() == static_cast<int>(cuda::devices.size()));
++i;
}
}
SECTION("native handle")
{
for (auto& comm : this->communicators())
{
REQUIRE(comm.native_handle() != NCCL_COMM_NULL);
}
}
SECTION("logical device")
{
int i = 0;
for (auto& comm : this->communicators())
{
REQUIRE(comm.logical_device().underlying_device() == cuda::devices[i]);
++i;
}
}
SECTION("group_guard round trip")
{
// Opening and closing a guard with no enqueued ops must not throw.
[[maybe_unused]] auto g = this->communicators().front().group_guard();
}
SECTION("device mismatch throws")
{
if (cuda::devices.size() > 1)
{
REQUIRE_THROWS_WITH(
cudax::nccl_communicator_ref(this->communicators()[0].native_handle(), cudax::logical_device{cuda::devices[1]}),
"Inconsistent devices, NCCL communicator device and provided logical device do not match");
}
}
}

View File

@@ -1,487 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/buffer>
#include <cuda/devices>
#include <cuda/functional>
#include <cuda/memory_pool>
#include <cuda/memory_resource>
#include <cuda/std/cstddef>
#include <cuda/std/cstdint>
#include <cuda/std/functional>
#include <vector>
#include <nccl_test_common.h>
namespace
{
constexpr cuda::std::int32_t ROOT_RANK = 0;
} // namespace
MULTI_GPU_TEST("nccl_communicator_ref all_reduce sum", )
{
auto streams = nccl_test_util::make_streams();
// Rank r contributes {r+1, r+1, r+1, r+1}; the element-wise sum is the n-th triangular number.
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
const auto values = {i + 1, i + 1, i + 1, i + 1};
auto& s = send.emplace_back(streams[i], pool, values);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].all_reduce(
g, send[i].data(), recv[i].data(), send[i].size(), ::cuda::std::plus<>{}, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
const auto sum = static_cast<cuda::std::int32_t>(cuda::devices.size() * (cuda::devices.size() + 1) / 2);
for (auto& buf : recv)
{
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected = cuda::make_buffer(buf.stream(), pool, buf.size(), sum);
const auto actual = cuda::make_buffer(buf.stream(), pool, buf);
REQUIRE_THAT(actual, Equals(expected));
}
}
MULTI_GPU_TEST("nccl_communicator_ref all_reduce maximum", )
{
auto streams = nccl_test_util::make_streams();
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
const auto values = {i, 100 - i, 2 * i};
auto& s = send.emplace_back(streams[i], pool, values);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].all_reduce(
g, send[i].data(), recv[i].data(), send[i].size(), ::cuda::maximum<>{}, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
const auto n = static_cast<cuda::std::int32_t>(cuda::devices.size());
for (auto& buf : recv)
{
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected_values = {n - 1, cuda::std::int32_t{100}, 2 * (n - 1)};
const auto expected = cuda::make_buffer(buf.stream(), pool, expected_values);
const auto actual = cuda::make_buffer(buf.stream(), pool, buf);
REQUIRE_THAT(actual, Equals(expected));
}
}
MULTI_GPU_TEST("nccl_communicator_ref reduce sum to root 0", )
{
auto streams = nccl_test_util::make_streams();
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
const auto values = {i + 1, i + 1, i + 1, i + 1};
auto& s = send.emplace_back(streams[i], pool, values);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].reduce(
g, send[i].data(), recv[i].data(), send[i].size(), ::cuda::std::plus<>{}, ROOT_RANK, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
const auto sum = static_cast<cuda::std::int32_t>(cuda::devices.size() * (cuda::devices.size() + 1) / 2);
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto actual = cuda::make_buffer(recv[ROOT_RANK].stream(), pool, recv[ROOT_RANK]);
const auto expected = cuda::make_buffer(actual.stream(), pool, actual.size(), sum);
REQUIRE_THAT(actual, Equals(expected));
}
MULTI_GPU_TEST("nccl_communicator_ref broadcast from root 0", )
{
auto streams = nccl_test_util::make_streams();
// Only root's send buffer is read; give every rank the same literal so the source is obvious.
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
const auto values = {10, 20, 30, 40};
auto& s = send.emplace_back(streams[i], pool, values);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].broadcast(g, send[i].data(), recv[i].data(), send[i].size(), ROOT_RANK, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
for (auto& buf : recv)
{
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected_values = {
cuda::std::int32_t{10}, cuda::std::int32_t{20}, cuda::std::int32_t{30}, cuda::std::int32_t{40}};
const auto expected = cuda::make_buffer(buf.stream(), pool, expected_values);
const auto actual = cuda::make_buffer(buf.stream(), pool, buf);
REQUIRE_THAT(actual, Equals(expected));
}
}
MULTI_GPU_TEST("nccl_communicator_ref all_gather", )
{
auto streams = nccl_test_util::make_streams();
// Rank r contributes {10*r, 10*r+1}; every rank ends up with the concatenation in rank order.
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
const auto values = {10 * i, 10 * i + 1};
auto& s = send.emplace_back(streams[i], pool, values);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size() * cuda::devices.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].all_gather(g, send[i].data(), recv[i].data(), send[i].size(), streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
const cuda::std::size_t per_rank = send.front().size();
std::vector<cuda::std::int32_t> expected_values(per_rank * cuda::devices.size());
for (cuda::std::size_t r = 0; r < cuda::devices.size(); ++r)
{
expected_values[r * per_rank] = static_cast<cuda::std::int32_t>(10 * r);
expected_values[(r * per_rank) + 1] = static_cast<cuda::std::int32_t>((10 * r) + 1);
}
for (auto& buf : recv)
{
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected = cuda::make_buffer<cuda::std::int32_t>(buf.stream(), pool, expected_values);
const auto actual = cuda::make_buffer(buf.stream(), pool, buf);
REQUIRE_THAT(actual, Equals(expected));
}
}
MULTI_GPU_TEST("nccl_communicator_ref gather_v to root 0", )
{
auto streams = nccl_test_util::make_streams();
// Rank r contributes (2 + r) elements: {10*r, 10*r+1, ...}. Root concatenates them in rank order.
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
std::vector<cuda::std::int32_t> h(2 + static_cast<cuda::std::size_t>(i));
for (cuda::std::size_t k = 0; k < h.size(); ++k)
{
h[k] = (10 * i) + static_cast<int>(k);
}
send.emplace_back(streams[i], pool, h);
}
std::vector<cuda::std::size_t> recv_counts(cuda::devices.size());
std::vector<cuda::std::size_t> displs(cuda::devices.size());
cuda::std::size_t total = 0;
for (cuda::std::size_t r = 0; r < cuda::devices.size(); ++r)
{
recv_counts[r] = send[r].size();
displs[r] = total;
total += recv_counts[r];
}
auto root_pool = cuda::device_default_memory_pool(cuda::devices[ROOT_RANK]);
auto recv = cuda::make_buffer(streams[ROOT_RANK], root_pool, total, cuda::std::int32_t{-1});
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].gather_v(
g, send[i].data(), send[i].size(), recv.data(), recv_counts.data(), displs.data(), ROOT_RANK, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
std::vector<cuda::std::int32_t> expected_values(total);
for (cuda::std::size_t r = 0; r < cuda::devices.size(); ++r)
{
for (cuda::std::size_t k = 0; k < recv_counts[r]; ++k)
{
expected_values[displs[r] + k] = static_cast<cuda::std::int32_t>((10 * r) + k);
}
}
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected = cuda::make_buffer<cuda::std::int32_t>(recv.stream(), pool, expected_values);
const auto actual = cuda::make_buffer(recv.stream(), pool, recv);
REQUIRE_THAT(actual, Equals(expected));
}
MULTI_GPU_TEST("nccl_communicator_ref all_to_all_v", )
{
auto streams = nccl_test_util::make_streams();
// Two elements exchanged with each peer; the block for peer j sits at offset block*j.
constexpr cuda::std::size_t block = 2;
std::vector<cuda::std::size_t> counts(cuda::devices.size(), block);
std::vector<cuda::std::size_t> displs(cuda::devices.size());
for (cuda::std::size_t j = 0; j < cuda::devices.size(); ++j)
{
displs[j] = block * j;
}
// Rank r block destined for peer j encodes 100*r + 10*j + k.
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
std::vector<cuda::std::int32_t> h(block * cuda::devices.size());
for (cuda::std::size_t j = 0; j < cuda::devices.size(); ++j)
{
h[block * j] = static_cast<cuda::std::int32_t>((100 * i) + (10 * j));
h[(block * j) + 1] = static_cast<cuda::std::int32_t>((100 * i) + (10 * j) + 1);
}
auto& s = send.emplace_back(streams[i], pool, h);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].all_to_all_v(
g, send[i].data(), counts.data(), displs.data(), recv[i].data(), counts.data(), displs.data(), streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
// Rank r receives from peer i the block i sent to r, placed at block*i: 100*i + 10*r + k.
for (cuda::std::size_t r = 0; r < cuda::devices.size(); ++r)
{
std::vector<cuda::std::int32_t> expected_values(block * cuda::devices.size());
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
expected_values[block * i] = static_cast<cuda::std::int32_t>((100 * i) + (10 * r));
expected_values[(block * i) + 1] = static_cast<cuda::std::int32_t>((100 * i) + (10 * r) + 1);
}
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected = cuda::make_buffer<cuda::std::int32_t>(recv[r].stream(), pool, expected_values);
const auto actual = cuda::make_buffer(recv[r].stream(), pool, recv[r]);
REQUIRE_THAT(actual, Equals(expected));
}
}
#if NCCL_VERSION_CODE >= NCCL_VERSION(2, 28, 0)
MULTI_GPU_TEST("nccl_communicator_ref gather to root", )
{
auto streams = nccl_test_util::make_streams();
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
const auto values = {10 * i, 10 * i + 1};
auto& s = send.emplace_back(streams[i], pool, values);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size() * cuda::devices.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].gather(g, send[i].data(), recv[i].data(), send[i].size(), ROOT_RANK, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
const cuda::std::size_t per_rank = send.front().size();
std::vector<cuda::std::int32_t> expected_values(per_rank * cuda::devices.size());
for (cuda::std::size_t r = 0; r < cuda::devices.size(); ++r)
{
expected_values[r * per_rank] = static_cast<cuda::std::int32_t>(10 * r);
expected_values[r * per_rank + 1] = static_cast<cuda::std::int32_t>(10 * r + 1);
}
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected = cuda::make_buffer<cuda::std::int32_t>(recv[ROOT_RANK].stream(), pool, expected_values);
const auto actual = cuda::make_buffer(recv[ROOT_RANK].stream(), pool, recv[ROOT_RANK]);
REQUIRE_THAT(actual, Equals(expected));
}
MULTI_GPU_TEST("nccl_communicator_ref all_to_all", )
{
auto streams = nccl_test_util::make_streams();
// Two elements exchanged with each peer; the block for peer j sits at offset block*j.
constexpr cuda::std::size_t block = 2;
// Rank r block destined for peer j encodes 100*r + 10*j + k.
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < static_cast<int>(cuda::devices.size()); ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
std::vector<cuda::std::int32_t> h(block * cuda::devices.size());
for (cuda::std::size_t j = 0; j < cuda::devices.size(); ++j)
{
h[block * j] = static_cast<cuda::std::int32_t>(100 * i + 10 * j);
h[block * j + 1] = static_cast<cuda::std::int32_t>(100 * i + 10 * j + 1);
}
auto& s = send.emplace_back(streams[i], pool, h);
recv.emplace_back(cuda::make_buffer(streams[i], pool, s.size(), cuda::std::int32_t{-1}));
}
{
auto g = this->communicators().front().group_guard();
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
this->communicators()[i].all_to_all(g, send[i].data(), recv[i].data(), block, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
// Rank r receives from peer i the block i sent to r, placed at block*i: 100*i + 10*r + k.
for (cuda::std::size_t r = 0; r < cuda::devices.size(); ++r)
{
std::vector<cuda::std::int32_t> expected_values(block * cuda::devices.size());
for (cuda::std::size_t i = 0; i < cuda::devices.size(); ++i)
{
expected_values[block * i] = static_cast<cuda::std::int32_t>(100 * i + 10 * r);
expected_values[block * i + 1] = static_cast<cuda::std::int32_t>(100 * i + 10 * r + 1);
}
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const auto expected = cuda::make_buffer<cuda::std::int32_t>(recv[r].stream(), pool, expected_values);
const auto actual = cuda::make_buffer(recv[r].stream(), pool, recv[r]);
REQUIRE_THAT(actual, Equals(expected));
}
}
#endif // NCCL_VERSION_CODE >= NCCL_VERSION(2, 28, 0)

View File

@@ -1,92 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/functional>
#include <cuda/std/functional>
#include <cuda/experimental/__multi_gpu/concepts.h>
#include <cuda/experimental/__multi_gpu/nccl_communicator_ref.h>
#include <functional>
#include <testing.cuh>
namespace
{
struct payload
{
int from;
int value;
};
struct non_trivial
{
non_trivial(const non_trivial&) {} // NOLINT(modernize-use-equals-default)
int value;
};
struct unsupported_op
{};
} // namespace
C2H_TEST("nccl_communicator_ref concept conformance", "[multi_gpu][nccl]")
{
STATIC_REQUIRE(cudax::__communicator<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_send<cudax::nccl_communicator_ref, int*>);
STATIC_REQUIRE(cudax::__has_send<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_send<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_recv<cudax::nccl_communicator_ref, int*>);
STATIC_REQUIRE(cudax::__has_recv<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_recv<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_reduce<cudax::nccl_communicator_ref, int*>);
STATIC_REQUIRE(!cudax::__has_reduce<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(cudax::__has_all_reduce<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(!cudax::__has_all_reduce<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(cudax::__has_gather<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_gather<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_gather<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_gather_v<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_gather_v<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_gather_v<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_all_gather<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_all_gather<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_all_gather<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_broadcast<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_broadcast<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_broadcast<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_all_to_all<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_all_to_all<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_all_to_all<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::__has_all_to_all_v<cudax::nccl_communicator_ref>);
STATIC_REQUIRE(cudax::__has_all_to_all_v<cudax::nccl_communicator_ref, payload*>);
STATIC_REQUIRE(!cudax::__has_all_to_all_v<cudax::nccl_communicator_ref, non_trivial*>);
STATIC_REQUIRE(cudax::nccl_transportable<int>);
STATIC_REQUIRE(cudax::nccl_transportable<int*>);
STATIC_REQUIRE(cudax::nccl_transportable<const int*>);
STATIC_REQUIRE(cudax::nccl_transportable<const volatile int* const>);
STATIC_REQUIRE(cudax::nccl_transportable<float>);
STATIC_REQUIRE(cudax::nccl_transportable<::cuda::std::int32_t>);
STATIC_REQUIRE(cudax::nccl_transportable<void>);
STATIC_REQUIRE(cudax::nccl_transportable<payload>);
STATIC_REQUIRE(!cudax::nccl_transportable<non_trivial>);
STATIC_REQUIRE(cudax::nccl_reducible<int, cuda::std::plus<>>);
STATIC_REQUIRE(cudax::nccl_reducible<int, cuda::std::multiplies<>>);
STATIC_REQUIRE(cudax::nccl_reducible<int, cuda::maximum<>>);
STATIC_REQUIRE(cudax::nccl_reducible<int, cuda::minimum<>>);
STATIC_REQUIRE(!cudax::nccl_reducible<payload, cuda::std::plus<>>);
STATIC_REQUIRE(!cudax::nccl_reducible<int, unsupported_op>);
STATIC_REQUIRE(cudax::nccl_reducible<int, std::plus<>>);
STATIC_REQUIRE(cudax::nccl_reducible<int, std::multiplies<>>);
STATIC_REQUIRE(!cudax::nccl_reducible<payload, std::plus<>>);
}

View File

@@ -1,150 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/buffer>
#include <cuda/devices>
#include <cuda/memory_pool>
#include <cuda/memory_resource>
#include <cuda/std/cstdint>
#include <vector>
#include <nccl_test_common.h>
namespace
{
struct payload
{
cuda::std::int32_t from;
cuda::std::int32_t index;
};
} // namespace
// Ring exchange via send/recv. Rank r contributes {r, r, r}.
MULTI_GPU_TEST("nccl_communicator_ref send/recv ring", )
{
if (cuda::devices.size() == 1)
{
// NCCL disallows self send/recv on a 1-rank comm.
REQUIRE(this->communicators().front().rank() == 0);
REQUIRE(this->communicators().front().size() == 1);
return;
}
const int n = static_cast<int>(cuda::devices.size());
auto streams = nccl_test_util::make_streams();
std::vector<cuda::device_buffer<cuda::std::int32_t>> send;
std::vector<cuda::device_buffer<cuda::std::int32_t>> recv;
for (int i = 0; i < n; ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
auto& s = send.emplace_back(cuda::make_buffer(streams[i], pool, 3, i));
recv.emplace_back(cuda::make_buffer<cuda::std::int32_t>(streams[i], pool, s.size(), -1));
}
{
auto g = this->communicators().front().group_guard();
for (int i = 0; i < n; ++i)
{
const int prev = (i + n - 1) % n;
const int next = (i + 1) % n;
this->communicators()[i].recv(g, recv[i].data(), recv[i].size(), prev, streams[i]);
this->communicators()[i].send(g, send[i].data(), send[i].size(), next, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
// Rank r received from its predecessor (r-1): {r-1, r-1, r-1}.
for (int r = 0; r < n; ++r)
{
const cuda::std::int32_t prev = (r + n - 1) % n;
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const cuda::host_buffer<cuda::std::int32_t> expected =
cuda::make_buffer(recv[r].stream(), pool, recv[r].size(), prev);
const cuda::host_buffer<cuda::std::int32_t> actual = cuda::make_buffer(recv[r].stream(), pool, recv[r]);
REQUIRE_THAT(actual, Equals(expected));
}
}
MULTI_GPU_TEST("nccl_communicator_ref send/recv transports trivially copyable payload", )
{
if (cuda::devices.size() == 1)
{
// NCCL disallows self send/recv on a 1-rank comm.
REQUIRE(this->communicators().front().rank() == 0);
REQUIRE(this->communicators().front().size() == 1);
return;
}
const int n = static_cast<int>(cuda::devices.size());
auto streams = nccl_test_util::make_streams();
std::vector<cuda::device_buffer<payload>> send;
std::vector<cuda::device_buffer<payload>> recv;
for (int i = 0; i < n; ++i)
{
auto pool = cuda::device_default_memory_pool(cuda::devices[i]);
std::vector<payload> h(3);
for (cuda::std::size_t k = 0; k < h.size(); ++k)
{
h[k] = payload{static_cast<cuda::std::int32_t>(i), static_cast<cuda::std::int32_t>(k)};
}
auto& s = send.emplace_back(streams[i], pool, h);
recv.emplace_back(cuda::make_buffer<payload>(streams[i], pool, s.size(), cuda::no_init));
}
{
auto g = this->communicators().front().group_guard();
for (int i = 0; i < n; ++i)
{
const int prev = (i + n - 1) % n;
const int next = (i + 1) % n;
this->communicators()[i].recv(g, recv[i].data(), recv[i].size(), prev, streams[i]);
this->communicators()[i].send(g, send[i].data(), send[i].size(), next, streams[i]);
}
}
for (auto& stream : streams)
{
stream.sync();
}
for (int r = 0; r < n; ++r)
{
const int prev = (r + n - 1) % n;
auto pool = cuda::mr::legacy_pinned_memory_resource{};
const cuda::host_buffer<payload> actual = cuda::make_buffer(recv[r].stream(), pool, recv[r]);
actual.stream().sync();
for (cuda::std::size_t k = 0; k < actual.size(); ++k)
{
REQUIRE(actual[k].from == static_cast<cuda::std::int32_t>(prev));
REQUIRE(actual[k].index == static_cast<cuda::std::int32_t>(k));
}
}
}