[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:
@@ -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]);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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};
|
||||
}
|
||||
};
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user