[INFRA] Import NVIDIA/CCCL upstream as optimization reference library

CCCL (CUDA C++ Core Libraries) provides:
- CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk)
- Thrust: high-level parallel algorithms (transform_reduce, sort, scan)
- libcudacxx: CUDA C++ standard library (atomics, barriers, memory)
- cudax: experimental features (memory resources, allocators)
- Tuning policies: per-SM hardware-specific algorithm parameters

Competition optimization vectors mapped to CCCL:
- Output TPS (83% weight): warp_reduce, block_reduce, device_topk
- Input TPS (14% weight): device_scan, block_load, prefetch
- Cache TPS (3% weight): prefix caching strategy patterns
- Memory (0.9 util): pooled/cached/buddy allocators

Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only)
License: Apache-2.0
This commit is contained in:
EngineX CI
2026-07-30 09:35:51 +00:00
parent b4d01f481e
commit 56fd68e7dd
8871 changed files with 1454674 additions and 0 deletions

View File

@@ -0,0 +1,359 @@
// SPDX-FileCopyrightText: Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <cub/detail/type_traits.cuh>
#include <cub/thread/thread_reduce.cuh>
#include <cub/util_macro.cuh>
#include <cuda/functional>
#include <cuda/std/functional>
#include <cuda/std/limits>
#include <cuda/std/mdspan>
#include <cuda/std/type_traits>
#include <cstring>
#include <functional>
#include <limits>
#include <numeric>
#include "c2h/catch2_test_helper.h"
#include "c2h/extended_types.h"
#include "c2h/generators.h"
#include <catch2/matchers/catch_matchers_floating_point.hpp>
/***********************************************************************************************************************
* Thread Reduce Wrapper Kernels
**********************************************************************************************************************/
template <int NUM_ITEMS, typename T, typename ReduceOperator>
__global__ void thread_reduce_kernel(const T* __restrict__ d_in, T* __restrict__ d_out, ReduceOperator reduce_operator)
{
T thread_data[NUM_ITEMS];
#pragma unroll
for (int i = 0; i < NUM_ITEMS; ++i)
{
thread_data[i] = d_in[i];
}
*d_out = cub::ThreadReduce(thread_data, reduce_operator);
}
template <int NUM_ITEMS, typename T, typename ReduceOperator>
__global__ void thread_reduce_kernel_array(const T* d_in, T* d_out, ReduceOperator reduce_operator)
{
cuda::std::array<T, NUM_ITEMS> thread_data;
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NUM_ITEMS; ++i)
{
thread_data[i] = d_in[i];
}
*d_out = cub::ThreadReduce(thread_data, reduce_operator);
}
template <int NUM_ITEMS, typename T, typename ReduceOperator>
__global__ void thread_reduce_kernel_span(const T* d_in, T* d_out, ReduceOperator reduce_operator)
{
T thread_data[NUM_ITEMS];
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NUM_ITEMS; ++i)
{
thread_data[i] = d_in[i];
}
cuda::std::span<T, NUM_ITEMS> span(thread_data);
*d_out = cub::ThreadReduce(span, reduce_operator);
}
template <int NUM_ITEMS, typename T, typename ReduceOperator>
__global__ void thread_reduce_kernel_mdspan(const T* d_in, T* d_out, ReduceOperator reduce_operator)
{
T thread_data[NUM_ITEMS];
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NUM_ITEMS; ++i)
{
thread_data[i] = d_in[i];
}
using Extent = cuda::std::extents<int, NUM_ITEMS>;
cuda::std::mdspan<T, Extent> mdspan(thread_data, cuda::std::extents<int, NUM_ITEMS>{});
*d_out = cub::ThreadReduce(mdspan, reduce_operator);
}
/***********************************************************************************************************************
* CUB operator to STD operator
**********************************************************************************************************************/
template <typename T, typename>
struct cub_operator_to_std;
template <typename T>
struct cub_operator_to_std<T, cuda::std::plus<>>
{
using type = ::std::plus<T>; // T: MSVC complains about possible loss of data
};
template <typename T>
struct cub_operator_to_std<T, cuda::std::multiplies<>>
{
using type = ::std::multiplies<T>; // T: MSVC complains about possible loss of data
};
template <typename T>
struct cub_operator_to_std<T, cuda::std::bit_and<>>
{
using type = ::std::bit_and<T>; // T: MSVC complains about possible loss of data
};
template <typename T>
struct cub_operator_to_std<T, cuda::std::bit_or<>>
{
using type = ::std::bit_or<T>; // T: MSVC complains about possible loss of data
};
template <typename T>
struct cub_operator_to_std<T, cuda::std::bit_xor<>>
{
using type = ::std::bit_xor<T>; // T: MSVC complains about possible loss of data
};
template <typename T>
struct cub_operator_to_std<T, cuda::minimum<>>
{
using type = cuda::minimum<>;
};
template <typename T>
struct cub_operator_to_std<T, cuda::maximum<>>
{
using type = cuda::maximum<>;
};
template <typename T, typename Operator>
using cub_operator_to_std_t = typename cub_operator_to_std<T, Operator>::type;
/***********************************************************************************************************************
* Type list definition
**********************************************************************************************************************/
using narrow_precision_type_list = c2h::type_list<
#if TEST_HALF_T()
__half,
#endif // TEST_HALF_T()
#if TEST_BF_T()
__nv_bfloat16
#endif // TEST_BF_T()
>;
using integral_type_list =
c2h::type_list<cuda::std::int8_t, cuda::std::int16_t, cuda::std::uint16_t, cuda::std::int32_t, cuda::std::int64_t>;
using fp_type_list = c2h::type_list<float, double>;
using cub_operator_integral_list =
c2h::type_list<cuda::std::plus<>,
cuda::std::multiplies<>,
cuda::std::bit_and<>,
cuda::std::bit_or<>,
cuda::std::bit_xor<>,
cuda::minimum<>,
cuda::maximum<>>;
using cub_operator_fp_list =
c2h::type_list<cuda::std::plus<>, cuda::std::multiplies<>, cuda::minimum<>, cuda::maximum<>>;
/***********************************************************************************************************************
* Verify results and kernel launch
**********************************************************************************************************************/
_CCCL_TEMPLATE(typename T)
_CCCL_REQUIRES((cuda::std::is_floating_point_v<T>) )
void verify_results(const T& expected_data, const T& test_results)
{
REQUIRE_THAT(expected_data, Catch::Matchers::WithinRel(test_results, T{0.05}));
}
_CCCL_TEMPLATE(typename T)
_CCCL_REQUIRES((!cuda::std::is_floating_point_v<T>) )
void verify_results(const T& expected_data, const T& test_results)
{
REQUIRE(expected_data == test_results);
}
template <typename T, typename ReduceOperator>
void run_thread_reduce_kernel(
int num_items, const c2h::device_vector<T>& in, c2h::device_vector<T>& out, ReduceOperator reduce_operator)
{
switch (num_items)
{
case 1:
thread_reduce_kernel<1>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 2:
thread_reduce_kernel<2>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 3:
thread_reduce_kernel<3>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 4:
thread_reduce_kernel<4>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 5:
thread_reduce_kernel<5>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 6:
thread_reduce_kernel<6>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 7:
thread_reduce_kernel<7>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 8:
thread_reduce_kernel<8>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 9:
thread_reduce_kernel<9>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 10:
thread_reduce_kernel<10>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 11:
thread_reduce_kernel<11>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 12:
thread_reduce_kernel<12>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 13:
thread_reduce_kernel<13>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 14:
thread_reduce_kernel<14>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 15:
thread_reduce_kernel<15>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
case 16:
thread_reduce_kernel<16>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
break;
default:
FAIL("Unsupported number of items");
}
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
}
constexpr int max_size = 16;
constexpr int num_seeds = 10;
/***********************************************************************************************************************
* Test cases
**********************************************************************************************************************/
C2H_TEST("ThreadReduce Integral Type Tests", "[reduce][thread]", integral_type_list, cub_operator_integral_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
constexpr auto reduce_op = op_t{};
constexpr auto std_reduce_op = cub_operator_to_std_t<value_t, op_t>{};
constexpr auto operator_identity = cuda::identity_element<op_t, value_t>();
CAPTURE(c2h::type_name<value_t>(), max_size, c2h::type_name<decltype(reduce_op)>());
c2h::device_vector<value_t> d_in(max_size);
c2h::device_vector<value_t> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, cuda::std::numeric_limits<value_t>::min());
c2h::host_vector<value_t> h_in = d_in;
for (int num_items = 1; num_items <= max_size; ++num_items)
{
auto reference_result = std::accumulate(h_in.begin(), h_in.begin() + num_items, operator_identity, std_reduce_op);
run_thread_reduce_kernel(num_items, d_in, d_out, reduce_op);
verify_results(reference_result, c2h::host_vector<value_t>(d_out)[0]);
}
}
C2H_TEST("ThreadReduce Floating-Point Type Tests", "[reduce][thread]", fp_type_list, cub_operator_fp_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
constexpr auto reduce_op = op_t{};
constexpr auto std_reduce_op = cub_operator_to_std_t<value_t, op_t>{};
const auto operator_identity = cuda::identity_element<op_t, value_t>();
CAPTURE(c2h::type_name<value_t>(), max_size, c2h::type_name<decltype(reduce_op)>());
c2h::device_vector<value_t> d_in(max_size);
c2h::device_vector<value_t> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, cuda::std::numeric_limits<value_t>::min());
c2h::host_vector<value_t> h_in = d_in;
for (int num_items = 1; num_items <= max_size; ++num_items)
{
auto reference_result = std::accumulate(h_in.begin(), h_in.begin() + num_items, operator_identity, std_reduce_op);
run_thread_reduce_kernel(num_items, d_in, d_out, reduce_op);
verify_results(reference_result, c2h::host_vector<value_t>(d_out)[0]);
}
}
#if TEST_HALF_T() || TEST_BF_T()
C2H_TEST("ThreadReduce Narrow PrecisionType Tests",
"[reduce][thread][narrow]",
narrow_precision_type_list,
cub_operator_fp_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
constexpr auto reduce_op = op_t{};
constexpr auto std_reduce_op = cub_operator_to_std_t<float, op_t>{};
const auto operator_identity = cuda::identity_element<op_t, float>();
c2h::device_vector<value_t> d_in(max_size);
c2h::device_vector<value_t> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, value_t{1.0f}, value_t{2.0f});
c2h::host_vector<float> h_in_float = d_in;
for (int num_items = 1; num_items <= max_size; ++num_items)
{
CAPTURE(c2h::type_name<value_t>(), num_items, c2h::type_name<decltype(reduce_op)>());
auto reference_result =
std::accumulate(h_in_float.begin(), h_in_float.begin() + num_items, operator_identity, std_reduce_op);
run_thread_reduce_kernel(num_items, d_in, d_out, reduce_op);
verify_results(reference_result, float{c2h::host_vector<value_t>(d_out)[0]});
}
}
#endif // TEST_HALF_T() || TEST_BF_T()
C2H_TEST("ThreadReduce Container Tests", "[reduce][thread]")
{
c2h::device_vector<int> d_in(max_size);
c2h::device_vector<int> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in);
c2h::host_vector<int> h_in = d_in;
auto reference_result = std::accumulate(h_in.begin(), h_in.end(), 0, std::plus<int>{});
thread_reduce_kernel_array<max_size>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), cuda::std::plus<>{});
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
verify_results(reference_result, c2h::host_vector<int>(d_out)[0]);
thread_reduce_kernel_span<max_size>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), cuda::std::plus<>{});
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
verify_results(reference_result, c2h::host_vector<int>(d_out)[0]);
thread_reduce_kernel_mdspan<max_size>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), cuda::std::plus<>{});
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
verify_results(reference_result, c2h::host_vector<int>(d_out)[0]);
}

View File

@@ -0,0 +1,206 @@
// SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
// #define CCCL_CHECK_SASS
#if defined(CCCL_CHECK_SASS)
# include <cub/detail/type_traits.cuh>
# include <cub/thread/thread_reduce.cuh>
# include <cub/util_macro.cuh>
# include <cuda/functional>
# include <cuda/std/functional>
# include <cuda/std/limits>
# include <cuda/std/type_traits>
# include <cstring>
# include <functional>
# include <limits>
# include <numeric>
# include "c2h/catch2_test_helper.h"
# include "c2h/extended_types.h"
# include "c2h/generators.h"
# include <catch2/matchers/catch_matchers_floating_point.hpp>
/***********************************************************************************************************************
* Thread Reduce Wrapper Kernels
**********************************************************************************************************************/
template <int NUM_ITEMS, typename T, typename ReduceOperator>
__global__ void thread_reduce_kernel(const T* __restrict__ d_in, T* __restrict__ d_out, ReduceOperator reduce_operator)
{
T thread_data[NUM_ITEMS];
auto d_in_aligned = static_cast<T*>(__builtin_assume_aligned(d_in, (sizeof(T) < 4) ? 4 : sizeof(T)));
::memcpy(thread_data, d_in_aligned, sizeof(thread_data));
*d_out = cub::ThreadReduce(thread_data, reduce_operator);
}
template <int NUM_ITEMS, typename T, typename ReduceOperator>
__global__ void thread_reduce_kernel_array(const T* d_in, T* d_out, ReduceOperator reduce_operator)
{
cuda::std::array<T, NUM_ITEMS> thread_data;
# pragma unroll
for (int i = 0; i < NUM_ITEMS; ++i)
{
thread_data[i] = d_in[i];
}
*d_out = cub::ThreadReduce(thread_data, reduce_operator);
}
/***********************************************************************************************************************
* CUB operator to STD operator
**********************************************************************************************************************/
template <typename T, typename>
struct cub_operator_to_std;
template <typename T>
struct cub_operator_to_std<T, cuda::std::plus<>>
{
using type = ::std::plus<>;
};
template <typename T>
struct cub_operator_to_std<T, cuda::std::multiplies<>>
{
using type = ::std::multiplies<>;
};
template <typename T>
struct cub_operator_to_std<T, cuda::std::bit_xor<>>
{
using type = ::std::bit_xor<>;
};
template <typename T>
struct cub_operator_to_std<T, cuda::minimum<>>
{
using type = cuda::minimum<>;
};
template <typename T, typename Operator>
using cub_operator_to_std_t = typename cub_operator_to_std<T, Operator>::type;
/***********************************************************************************************************************
* Type list definition
**********************************************************************************************************************/
using narrow_precision_type_list = c2h::type_list<
# if TEST_HALF_T()
__half,
# endif // TEST_HALF_T()
# if TEST_BF_T()
__nv_bfloat16
# endif // TEST_BF_T()
>;
using fp_type_list = c2h::type_list<float>;
using integral_type_list = c2h::type_list<cuda::std::int8_t, cuda::std::int16_t, cuda::std::int32_t>;
using cub_operator_integral_list =
c2h::type_list<cuda::std::plus<>, cuda::std::multiplies<>, cuda::std::bit_xor<>, cuda::minimum<>>;
using cub_operator_fp_list = c2h::type_list<cuda::std::plus<>, cuda::minimum<>>;
/***********************************************************************************************************************
* Verify results and kernel launch
**********************************************************************************************************************/
_CCCL_TEMPLATE(typename T)
_CCCL_REQUIRES((cuda::std::is_floating_point<T>::value))
void verify_results(const T& expected_data, const T& test_results)
{
REQUIRE_THAT(expected_data, Catch::Matchers::WithinRel(test_results, T{0.05}));
}
_CCCL_TEMPLATE(typename T)
_CCCL_REQUIRES((!cuda::std::is_floating_point<T>::value))
void verify_results(const T& expected_data, const T& test_results)
{
REQUIRE(expected_data == test_results);
}
template <typename T, typename ReduceOperator>
void run_thread_reduce_kernel(
const c2h::device_vector<T>& in, c2h::device_vector<T>& out, ReduceOperator reduce_operator)
{
thread_reduce_kernel<18>
<<<1, 1>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), reduce_operator);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
}
/***********************************************************************************************************************
* Test cases
**********************************************************************************************************************/
constexpr int size = 16;
C2H_TEST("ThreadReduce Integral Type Tests", "[reduce][thread]", integral_type_list, cub_operator_integral_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
constexpr auto reduce_op = op_t{};
constexpr auto std_reduce_op = cub_operator_to_std_t<value_t, op_t>{};
constexpr auto operator_identity = cuda::identity_element<op_t, value_t>();
CAPTURE(c2h::type_name<value_t>(), size, c2h::type_name<decltype(reduce_op)>());
c2h::device_vector<value_t> d_in(size);
c2h::device_vector<value_t> d_out(1);
c2h::gen(C2H_SEED(1), d_in, std::numeric_limits<value_t>::min());
c2h::host_vector<value_t> h_in = d_in;
auto reference_result = std::accumulate(h_in.begin(), h_in.begin() + size, operator_identity, std_reduce_op);
run_thread_reduce_kernel(d_in, d_out, reduce_op);
verify_results(reference_result, c2h::host_vector<value_t>(d_out)[0]);
}
C2H_TEST("ThreadReduce Floating-Point Type Tests", "[reduce][thread]", fp_type_list, cub_operator_fp_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
constexpr auto reduce_op = op_t{};
constexpr auto std_reduce_op = cub_operator_to_std_t<value_t, op_t>{};
const auto operator_identity = cuda::identity_element<op_t, value_t>();
CAPTURE(c2h::type_name<value_t>(), size, c2h::type_name<decltype(reduce_op)>());
c2h::device_vector<value_t> d_in(size);
c2h::device_vector<value_t> d_out(1);
c2h::gen(C2H_SEED(1), d_in, std::numeric_limits<value_t>::min());
c2h::host_vector<value_t> h_in = d_in;
auto reference_result = std::accumulate(h_in.begin(), h_in.begin() + size, operator_identity, std_reduce_op);
run_thread_reduce_kernel(d_in, d_out, reduce_op);
verify_results(reference_result, c2h::host_vector<value_t>(d_out)[0]);
}
# if TEST_HALF_T() || TEST_BF_T()
C2H_TEST("ThreadReduce Narrow PrecisionType Tests",
"[reduce][thread][narrow]",
narrow_precision_type_list,
cub_operator_fp_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
constexpr auto reduce_op = op_t{};
constexpr auto std_reduce_op = cub_operator_to_std_t<float, op_t>{};
const auto operator_identity = cuda::identity_element<op_t, float>();
c2h::device_vector<value_t> d_in(size);
c2h::device_vector<value_t> d_out(1);
c2h::gen(C2H_SEED(1), d_in, value_t{1.0f}, value_t{2.0f});
c2h::host_vector<float> h_in_float = d_in;
CAPTURE(c2h::type_name<value_t>(), size, c2h::type_name<decltype(reduce_op)>());
auto reference_result =
std::accumulate(h_in_float.begin(), h_in_float.begin() + size, operator_identity, std_reduce_op);
run_thread_reduce_kernel(d_in, d_out, reduce_op);
verify_results(reference_result, float{c2h::host_vector<value_t>(d_out)[0]});
}
# endif // TEST_HALF_T() || TEST_BF_T()
#else
# include "c2h/catch2_test_helper.h"
C2H_TEST("ThreadReduce Empty Test", "[reduce][thread][empty]") {}
#endif // CCCL_CHECK_SASS

View File

@@ -0,0 +1,225 @@
// SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include <cuda/cmath>
#include <cuda/functional>
#include <cuda/std/cmath>
#include <cuda/std/cstdint>
#include <cuda/std/functional>
#include <cuda/std/limits>
#include <cuda/std/numeric>
#include <cuda/std/type_traits>
#include <ostream>
/***********************************************************************************************************************
* Sensible Distribution Intervals for Test Data
**********************************************************************************************************************/
namespace detail
{
// Convert a bound computed in a wider or floating-point domain down to `To`, avoiding the pitfalls of a plain
// static_cast:
// * integer -> narrower integer: static_cast wraps around when the value is outside `To`'s range (e.g.
// static_cast<int16_t>(INT_MAX) == -1), which can invert the resulting interval (min > max). Saturating clamps to
// `To`'s range instead, preserving the intersection.
// * floating-point -> integer: static_cast is UB when the (truncated) value is outside `To`'s range (e.g.
// static_cast<int64_t>(exp2(log2(INT64_MAX))), where log2/exp2 round-trip up to 2^63). Clamp to `To`'s range first.
template <typename To, typename From>
constexpr To clamp_to(From value)
{
if constexpr (cuda::std::__cccl_is_integer_v<To> && cuda::std::__cccl_is_integer_v<From>)
{
return cuda::std::saturating_cast<To>(value);
}
else if constexpr (cuda::std::__cccl_is_integer_v<To> && cuda::std::is_floating_point_v<From>)
{
if (value <= static_cast<From>(cuda::std::numeric_limits<To>::lowest()))
{
return cuda::std::numeric_limits<To>::lowest();
}
if (value >= static_cast<From>(cuda::std::numeric_limits<To>::max()))
{
return cuda::std::numeric_limits<To>::max();
}
return static_cast<To>(value);
}
else
{
return static_cast<To>(value);
}
}
template <typename T, typename Operator, cuda::std::ptrdiff_t MaxReductionLength, typename = void>
struct dist_interval
{
static constexpr T min()
{
return cuda::std::numeric_limits<T>::lowest();
}
static constexpr T max()
{
return cuda::std::numeric_limits<T>::max();
}
};
template <typename T, cuda::std::ptrdiff_t MaxReductionLength>
struct dist_interval<
T,
cuda::std::plus<>,
MaxReductionLength,
cuda::std::enable_if_t<cuda::std::__cccl_is_signed_integer_v<T> || cuda::std::is_floating_point_v<T>>>
{
// signed_integer: Avoid possibility of over-/underflow causing UB
// floating_point: Avoid possibility of over-/underflow causing inf destroying pseudo-associativity
static constexpr T min()
{
return static_cast<T>(cuda::std::numeric_limits<T>::lowest() / MaxReductionLength);
}
static constexpr T max()
{
return static_cast<T>(cuda::std::numeric_limits<T>::max() / MaxReductionLength);
}
};
template <typename T, cuda::std::ptrdiff_t MaxReductionLength>
struct dist_interval<
T,
cuda::std::multiplies<>,
MaxReductionLength,
cuda::std::enable_if_t<cuda::std::__cccl_is_signed_integer_v<T> || cuda::std::is_floating_point_v<T>>>
{
// signed_integer: Avoid possibility of over-/underflow causing UB
// floating_point: Avoid possibility of over-/underflow causing inf destroying pseudo-associativity
// Use floating point arithmetic to avoid unnecessarily small interval.
// clamp_to guards the floating -> T conversion: the log2/exp2 round-trip can round up above T's range (e.g. for
// int64_t at MaxReductionLength == 1, exp2(log2(INT64_MAX)) == 2^63), and static_cast of that is float-cast-overflow
// UB.
static constexpr T min()
{
const double log2_abs_min = cuda::std::log2(cuda::std::fabs(cuda::std::numeric_limits<T>::lowest()));
return clamp_to<T>(-cuda::std::exp2(log2_abs_min / MaxReductionLength));
}
static constexpr T max()
{
const double log2_max = cuda::std::log2(cuda::std::numeric_limits<T>::max());
return clamp_to<T>(cuda::std::exp2(log2_max / MaxReductionLength));
}
};
} // namespace detail
template <typename Input,
typename Operator,
cuda::std::ptrdiff_t MaxRedductionLength,
typename Accum = cuda::std::__accumulator_t<Operator, Input>,
typename Output = Accum>
struct dist_interval
{
// Values in the interval need to be representable in Input and if either Output or Accum are signed integers we want
// to avoid UB.
// If Accum is FP, we also want to avoid overflow b/c it breaks down pseudo-associativity.
static constexpr Input min()
{
auto res = cuda::std::numeric_limits<Input>::lowest();
if constexpr (cuda::std::__cccl_is_signed_integer_v<Output>)
{
res = cuda::std::max(
res, detail::clamp_to<Input>(detail::dist_interval<Output, Operator, MaxRedductionLength>::min()));
}
if constexpr (cuda::std::__cccl_is_signed_integer_v<Accum> || cuda::std::is_floating_point_v<Accum>)
{
res = cuda::std::max(res,
detail::clamp_to<Input>(detail::dist_interval<Accum, Operator, MaxRedductionLength>::min()));
}
return res;
}
static constexpr Input max()
{
auto res = cuda::std::numeric_limits<Input>::max();
if constexpr (cuda::std::__cccl_is_signed_integer_v<Output>)
{
res = cuda::std::min(
res, detail::clamp_to<Input>(detail::dist_interval<Output, Operator, MaxRedductionLength>::max()));
}
if constexpr (cuda::std::__cccl_is_signed_integer_v<Accum> || cuda::std::is_floating_point_v<Accum>)
{
res = cuda::std::min(res,
detail::clamp_to<Input>(detail::dist_interval<Accum, Operator, MaxRedductionLength>::max()));
}
return res;
}
};
// Regression guard: these (Input, Op, num_items, Output) combinations used to compute inverted intervals (min > max)
// because a wider Accum/Output bound was narrowed to Input with a wrapping static_cast (e.g.
// static_cast<int16_t>(INT_MAX) == -1). An inverted interval violates the precondition of Catch2's random(min, max).
// These cover the integer-narrowing cases; they avoid log2/exp2 so they are always constant-evaluable across host
// compilers.
template <typename Input, typename Op, cuda::std::ptrdiff_t MaxReductionLength, typename Output>
using test_dist_interval = dist_interval<Input, Op, MaxReductionLength, cuda::std::__accumulator_t<Op, Input>, Output>;
// Primary reported case (int16_t input, wider int accumulator): assert the exact overflow-safe bounds, not just
// ordering, so a semantic regression (not only an inversion) is caught too.
static_assert(test_dist_interval<cuda::std::int16_t, cuda::std::plus<>, 16, cuda::std::int16_t>::min() == -2048);
static_assert(test_dist_interval<cuda::std::int16_t, cuda::std::plus<>, 16, cuda::std::int16_t>::max() == 2047);
// Further combinations that previously inverted (bit_and reaches the primary full-range specialization; the signed
// int8_t/int32_t pair is exercised by catch2_test_thread_scan_inclusive_partial.cu).
static_assert(test_dist_interval<cuda::std::int16_t, cuda::std::bit_and<>, 16, cuda::std::int16_t>::min()
<= test_dist_interval<cuda::std::int16_t, cuda::std::bit_and<>, 16, cuda::std::int16_t>::max());
static_assert(test_dist_interval<cuda::std::int8_t, cuda::std::plus<>, 3, cuda::std::int32_t>::min()
<= test_dist_interval<cuda::std::int8_t, cuda::std::plus<>, 3, cuda::std::int32_t>::max());
/***********************************************************************************************************************
* For testing for invalid values being passed to the binary operator
**********************************************************************************************************************/
struct segment
{
using offset_t = int32_t;
// Make sure that default constructed segments can not be merged
offset_t begin = cuda::std::numeric_limits<offset_t>::min();
offset_t end = cuda::std::numeric_limits<offset_t>::max();
__host__ __device__ friend bool operator==(segment left, segment right)
{
return left.begin == right.begin && left.end == right.end;
}
// Needed for final comparison with reference
friend std::ostream& operator<<(std::ostream& os, const segment& seg)
{
return os << "[ " << seg.begin << ", " << seg.end << " )";
}
};
// Needed for data input using fancy iterators
struct tuple_to_segment_op
{
__host__ __device__ segment operator()(cuda::std::tuple<segment::offset_t, segment::offset_t> interval)
{
const auto [begin, end] = interval;
return {begin, end};
}
};
// Actual scan operator doing the core test when run on device
struct merge_segments_op
{
bool* error_flag_ptr;
__device__ void check_inputs(segment left, segment right)
{
if (left.end != right.begin || left == right)
{
*error_flag_ptr = true;
}
}
__host__ __device__ segment operator()(segment left, segment right)
{
NV_IF_TARGET(NV_IS_DEVICE, check_inputs(left, right););
return {left.begin, right.end};
}
};

View File

@@ -0,0 +1,291 @@
// SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause
#include <cub/detail/type_traits.cuh>
#include <cub/thread/thread_reduce.cuh>
#include <cub/util_macro.cuh>
#include <cuda/functional>
#include <cuda/std/__algorithm/clamp.h>
#include <cuda/std/functional>
#include <cuda/std/limits>
#include <cuda/std/mdspan>
#include <cuda/std/type_traits>
#include "catch2_test_device_reduce.cuh"
#include "thread_reduce/catch2_test_thread_reduce_helper.cuh"
#include <c2h/catch2_test_helper.h>
#include <c2h/extended_types.h>
#include <c2h/generators.h>
#include <c2h/operator.cuh>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
inline constexpr int max_size = 16;
inline constexpr int num_seeds = 3;
/***********************************************************************************************************************
* Thread Reduce Wrapper Kernels
**********************************************************************************************************************/
template <int NumItems, typename In, typename Out, typename ReduceOperator>
__global__ void thread_reduce_partial_kernel(In d_in, Out d_out, ReduceOperator reduce_operator, int valid_items)
{
using value_t = cuda::std::iter_value_t<In>;
value_t thread_data[NumItems];
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NumItems; ++i)
{
thread_data[i] = d_in[i];
}
*d_out = cub::detail::ThreadReducePartial(thread_data, reduce_operator, valid_items);
}
template <int NumItems, typename T, typename ReduceOperator>
__global__ void
thread_reduce_partial_kernel_array(const T* d_in, T* d_out, ReduceOperator reduce_operator, int valid_items)
{
cuda::std::array<T, NumItems> thread_data;
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NumItems; ++i)
{
thread_data[i] = d_in[i];
}
*d_out = cub::detail::ThreadReducePartial(thread_data, reduce_operator, valid_items);
}
template <int NumItems, typename T, typename ReduceOperator>
__global__ void
thread_reduce_partial_kernel_span(const T* d_in, T* d_out, ReduceOperator reduce_operator, int valid_items)
{
T thread_data[NumItems];
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NumItems; ++i)
{
thread_data[i] = d_in[i];
}
cuda::std::span<T, NumItems> span(thread_data);
*d_out = cub::detail::ThreadReducePartial(span, reduce_operator, valid_items);
}
template <int NumItems, typename T, typename ReduceOperator>
__global__ void
thread_reduce_partial_kernel_mdspan(const T* d_in, T* d_out, ReduceOperator reduce_operator, int valid_items)
{
T thread_data[NumItems];
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < NumItems; ++i)
{
thread_data[i] = d_in[i];
}
using Extent = cuda::std::extents<int, NumItems>;
cuda::std::mdspan<T, Extent> mdspan(thread_data, cuda::std::extents<int, NumItems>{});
*d_out = cub::detail::ThreadReducePartial(mdspan, reduce_operator, valid_items);
}
/***********************************************************************************************************************
* Type list definition
**********************************************************************************************************************/
using narrow_precision_type_list = c2h::type_list<
#if TEST_HALF_T()
half_t,
#endif // TEST_HALF_T()
#if TEST_BF_T()
bfloat16_t
#endif // TEST_BF_T()
>;
using integral_type_list =
c2h::type_list<cuda::std::int8_t, cuda::std::uint16_t, cuda::std::int32_t, cuda::std::uint64_t>;
using fp_type_list = c2h::type_list<float, double>;
using cub_operator_integral_list =
c2h::type_list<cuda::std::plus<>, cuda::std::multiplies<>, cuda::std::bit_or<>, cuda::maximum<>>;
using cub_operator_fp_list = c2h::type_list<cuda::std::plus<>, cuda::std::multiplies<>, cuda::minimum<>>;
static_assert(max_size > 4);
using items_per_thread_list = c2h::enum_type_list<int, 1, 3, max_size - 1, max_size>;
/***********************************************************************************************************************
* Test cases
**********************************************************************************************************************/
C2H_TEST("ThreadReduce Integral Type Tests",
"[reduce][thread]",
integral_type_list,
cub_operator_integral_list,
items_per_thread_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
using accum_t = cuda::std::__accumulator_t<op_t, value_t>;
constexpr int num_items = c2h::get<2, TestType>::value;
using dist_param = dist_interval<value_t, op_t, num_items>;
constexpr auto reduce_op = op_t{};
constexpr auto operator_identity = cuda::identity_element<op_t, accum_t>();
const int valid_items = GENERATE_COPY(
take(1, random(2, cuda::std::max(2, num_items - 1))),
take(1, random(num_items + 2, cuda::std::numeric_limits<int>::max())),
values({1, num_items, num_items + 1}));
CAPTURE(c2h::type_name<value_t>(), num_items, c2h::type_name<decltype(reduce_op)>(), valid_items);
c2h::device_vector<value_t> d_in(num_items);
c2h::device_vector<accum_t> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, dist_param::min(), dist_param::max());
c2h::host_vector<value_t> h_in = d_in;
const int bounded_valid_items = cuda::std::min(valid_items, num_items);
auto reference_result =
compute_single_problem_reference(h_in.cbegin(), h_in.cbegin() + bounded_valid_items, reduce_op, operator_identity);
thread_reduce_partial_kernel<num_items>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), reduce_op, valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(reference_result == c2h::host_vector<accum_t>(d_out)[0]);
}
C2H_TEST("ThreadReduce Floating-Point Type Tests",
"[reduce][thread]",
fp_type_list,
cub_operator_fp_list,
items_per_thread_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
using accum_t = cuda::std::__accumulator_t<op_t, value_t>;
constexpr int num_items = c2h::get<2, TestType>::value;
using dist_param = dist_interval<value_t, op_t, num_items>;
constexpr auto reduce_op = op_t{};
const auto operator_identity = cuda::identity_element<op_t, accum_t>();
const int valid_items = GENERATE_COPY(
take(1, random(2, cuda::std::max(2, num_items - 1))),
take(1, random(num_items + 2, cuda::std::numeric_limits<int>::max())),
values({1, num_items, num_items + 1}));
CAPTURE(c2h::type_name<value_t>(), num_items, c2h::type_name<decltype(reduce_op)>(), valid_items);
c2h::device_vector<value_t> d_in(num_items);
c2h::device_vector<accum_t> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, dist_param::min(), dist_param::max());
c2h::host_vector<value_t> h_in = d_in;
const int bounded_valid_items = cuda::std::min(valid_items, num_items);
auto reference_result =
compute_single_problem_reference(h_in.cbegin(), h_in.cbegin() + bounded_valid_items, reduce_op, operator_identity);
thread_reduce_partial_kernel<num_items>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), reduce_op, valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(reference_result == c2h::host_vector<accum_t>(d_out)[0]);
}
#if TEST_HALF_T() || TEST_BF_T()
C2H_TEST("ThreadReduce Narrow PrecisionType Tests",
"[reduce][thread][narrow]",
narrow_precision_type_list,
cub_operator_fp_list,
items_per_thread_list)
{
using value_t = c2h::get<0, TestType>;
using op_t = c2h::get<1, TestType>;
using accum_t = cuda::std::__accumulator_t<op_t, value_t>;
constexpr int num_items = c2h::get<2, TestType>::value;
using dist_param = dist_interval<value_t, op_t, num_items>;
constexpr auto reduce_op = unwrap_op(std::true_type{}, op_t{});
const auto operator_identity = identity_v<op_t, accum_t>;
const int valid_items = GENERATE_COPY(
take(1, random(2, cuda::std::max(2, num_items - 1))),
take(1, random(num_items + 2, cuda::std::numeric_limits<int>::max())),
values({1, num_items, num_items + 1}));
c2h::device_vector<value_t> d_in(num_items);
c2h::device_vector<accum_t> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, dist_param::min(), dist_param::max());
c2h::host_vector<value_t> h_in = d_in;
CAPTURE(h_in, dist_param::min(), dist_param::max());
CAPTURE(c2h::type_name<value_t>(), c2h::type_name<decltype(reduce_op)>(), valid_items, num_items, operator_identity);
const int bounded_valid_items = cuda::std::min(valid_items, num_items);
const value_t reference_result =
compute_single_problem_reference(h_in.cbegin(), h_in.cbegin() + bounded_valid_items, reduce_op, operator_identity);
thread_reduce_partial_kernel<num_items><<<1, 1>>>(
unwrap_it(thrust::raw_pointer_cast(d_in.data())),
unwrap_it(thrust::raw_pointer_cast(d_out.data())),
reduce_op,
valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(reference_result == c2h::host_vector<accum_t>(d_out)[0]);
}
#endif // TEST_HALF_T() || TEST_BF_T()
C2H_TEST("ThreadReduce Container Tests", "[reduce][thread]")
{
using op_t = cuda::std::plus<>;
using dist_param = dist_interval<int, op_t, max_size>;
c2h::device_vector<int> d_in(max_size);
c2h::device_vector<int> d_out(1);
c2h::gen(C2H_SEED(num_seeds), d_in, dist_param::min(), dist_param::max());
c2h::host_vector<int> h_in = d_in;
const int valid_items = GENERATE_COPY(
take(1, random(2, max_size - 2)),
take(1, random(max_size + 2, cuda::std::numeric_limits<int>::max())),
values({1, max_size - 1, max_size, max_size + 1}));
const int bounded_valid_items = cuda::std::clamp(valid_items, 0, max_size);
auto reference_result =
compute_single_problem_reference(h_in.cbegin(), h_in.cbegin() + bounded_valid_items, op_t{}, 0);
thread_reduce_partial_kernel_array<max_size>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), op_t{}, valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(reference_result == c2h::host_vector<int>(d_out)[0]);
thread_reduce_partial_kernel_span<max_size>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), op_t{}, valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(reference_result == c2h::host_vector<int>(d_out)[0]);
thread_reduce_partial_kernel_mdspan<max_size>
<<<1, 1>>>(thrust::raw_pointer_cast(d_in.data()), thrust::raw_pointer_cast(d_out.data()), op_t{}, valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(reference_result == c2h::host_vector<int>(d_out)[0]);
}
C2H_TEST("ThreadReducePartial does not invoke the reduction operator on invalid elements", "[reduce][thread]")
{
const auto in_it = cuda::make_transform_iterator(
thrust::make_zip_iterator(cuda::counting_iterator<segment::offset_t>{1},
cuda::counting_iterator<segment::offset_t>{2}),
tuple_to_segment_op{});
const int valid_items = GENERATE_COPY(
take(3, random(2, max_size - 1)),
take(1, random(max_size + 1, cuda::std::numeric_limits<int>::max())),
values({-1, 0, 1}));
const int bounded_valid_items = cuda::std::clamp(valid_items, 0, max_size);
CAPTURE(valid_items);
// First initialize with invalid segments than overwrite the first valid_items
c2h::host_vector<segment> h_in(max_size);
thrust::copy(in_it, in_it + bounded_valid_items, h_in.begin());
c2h::device_vector<segment> d_in = h_in;
auto reference_result = compute_single_problem_reference(
h_in.cbegin(), h_in.cbegin() + bounded_valid_items, merge_segments_op{nullptr}, segment{1, 1});
c2h::device_vector<segment> d_out(max_size);
c2h::device_vector<bool> error_flag(1, false);
thread_reduce_partial_kernel<max_size><<<1, 1>>>(
thrust::raw_pointer_cast(d_in.data()),
thrust::raw_pointer_cast(d_out.data()),
merge_segments_op{thrust::raw_pointer_cast(error_flag.data())},
valid_items);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
REQUIRE(error_flag.front() == false);
if (valid_items > 0)
{
REQUIRE(reference_result == c2h::host_vector<segment>(d_out)[0]);
}
}