Files
project_6_89d52222/cccl_upstream/cub/cub/thread/thread_reduce.cuh
EngineX CI 56fd68e7dd [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
2026-07-30 09:35:51 +00:00

526 lines
21 KiB
Plaintext

// SPDX-FileCopyrightText: Copyright (c) 2011, Duane Merrill. All rights reserved.
// SPDX-FileCopyrightText: Copyright (c) 2011-2025, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
//! @file
//! Thread reduction over statically-sized array-like types
#pragma once
#include <cub/config.cuh>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <cub/detail/array_utils.cuh> // to_array()
#include <cub/detail/type_traits.cuh> // are_same()
#include <cub/detail/unsafe_bitcast.cuh>
#include <cub/thread/thread_load.cuh> // UnrolledCopy
#include <cub/thread/thread_operators.cuh>
#include <cub/thread/thread_simd.cuh>
#include <cub/util_namespace.cuh>
#include <cuda/__cmath/round_down.h>
#include <cuda/__functional/maximum.h>
#include <cuda/std/__functional/invoke.h>
#include <cuda/std/__functional/operations.h>
#include <cuda/std/__iterator/iterator_traits.h>
#include <cuda/std/__type_traits/conditional.h>
#include <cuda/std/__type_traits/is_integral.h>
#include <cuda/std/__type_traits/is_same.h>
#include <cuda/std/array>
#include <cuda/std/cassert>
#include <cuda/std/cstdint>
CUB_NAMESPACE_BEGIN
//! @rst
//! The ``ThreadReduce`` function computes a reduction of items assigned to a single CUDA thread.
//!
//! Overview
//! ++++++++++++++++++++++++++
//!
//! - A `reduction <http://en.wikipedia.org/wiki/Reduce_(higher-order_function)>`__ (or *fold*)
//! uses a binary combining operator to compute a single aggregate from a list of input elements.
//! - Supports array-like types that are statically-sized and can be indexed with the ``[] operator``:
//! raw arrays, ``std::array``, ``std::span``, ``std::mdspan`` (C++23)
//!
//! Main Function and Overloading
//! +++++++++++++++++++++++++++++
//!
//! Reduction over statically-sized array-like types, seeded with the specified prefix
//!
//! .. code-block:: c++
//!
//! template <typename Input,
//! typename ReductionOp,
//! typename ValueT = ..., // type of a single input element
//! typename AccumT = ...> // accumulator type
//! [[nodiscard]] __device__ __forceinline__ AccumT
//! ThreadReduce(const Input& input, ReductionOp reduction_op)
//!
//! .. code-block:: c++
//!
//! template <typename Input,
//! typename ReductionOp,
//! typename PrefixT,
//! typename ValueT = ..., // type of a single input element
//! typename AccumT = ...> // accumulator type
//! [[nodiscard]] __device__ __forceinline__ AccumT
//! ThreadReduce(const Input& input, ReductionOp reduction_op, PrefixT prefix)
//!
//! Performance Considerations
//! ++++++++++++++++++++++++++
//!
//! The function provides the following optimizations:
//!
//! - *Vectorization/SIMD* for:
//!
//! - Minimum (``cuda::minimum<>``) and Maximum (``cuda::maximum<>``) on SM90+ for ``int16_t/uint16_t``
//! data types (Hopper DPX instructions)
//! - Sum (``cuda::std::plus<>``) and Multiplication (``cuda::std::multiplies<>``) on SM80+ for ``__nv_bfloat16``
//! data type
//! - Minimum (``cuda::minimum<>``) and Maximum (``cuda::maximum<>``) on SM80+ for ``__half/__nv_bfloat16``
//! data types
//! - Sum (``cuda::std::plus<>``) and Multiplication (``cuda::std::multiplies<>``) on SM70+ for ``__half`` data type
//!
//! - *Instruction-Level Parallelism (ILP)* by exploiting a *ternary tree reduction* for:
//!
//! - Minimum (``cuda::minimum<>``) and Maximum (``cuda::maximum<>``) on SM90+ for ``int32_t/uint32_t`` data types
//! (Hopper DPX instructions)
//! - Minimum (``cuda::minimum<>``) and Maximum (``cuda::maximum<>``) on SM80+ for integer data types (Hopper DPX
//! instructions), ``__half2``, ``__nv_bfloat162``, ``__half`` (after vectorization), and ``__nv_bfloat16``
//! (after vectorization) data types
//! - Sum (``cuda::std::plus<>``), Bitwise AND (``cuda::std::bit_and<>``), OR (``cuda::std::bit_or<>``), XOR
//! (``cuda::std::bit_xor<>``) on SM50+ for integer data types
//!
//! - *Instruction-Level Parallelism (ILP)* by exploiting a *binary tree reduction* for
//!
//! - All other cases that maps to predefined operators
//!
//! Simple Example
//! ++++++++++++++++++++++++++
//!
//! The code snippet below illustrates a simple sum reductions over 4 integer values.
//!
//! .. code-block:: c++
//!
//! #include <cub/cub.cuh>
//!
//! __global__ void ExampleKernel(...)
//! {
//! int array[4] = {1, 2, 3, 4};
//! int sum = cub::ThreadReduce(array, ::cuda::std::plus<>{}); // sum = 10
//! }
//!
//! @endrst
//!
//! @brief Reduction over statically-sized array-like types.
//!
//! @tparam Input
//! <b>[inferred]</b> The data type to be reduced having member
//! <tt>operator[](int i)</tt> and must be statically-sized (size() method or static array)
//!
//! @tparam ReductionOp
//! <b>[inferred]</b> Binary reduction operator type having member
//! <tt>T operator()(const T &a, const T &b)</tt>
//!
//! @param[in] input
//! Array-like input
//!
//! @param[in] reduction_op
//! Binary reduction operator
//!
//! @return Accumulation of type (simplified) ``decltype(reduction_op(a, b))`` see
//! <a
//! href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2322r6.html#return-the-result-of-the-initial-invocation">P2322</a>
//!
template <typename Input,
typename ReductionOp,
typename ValueT = ::cuda::std::iter_value_t<Input>,
typename AccumT = ::cuda::std::__accumulator_t<ReductionOp, ValueT>>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT ThreadReduce(const Input& input, ReductionOp reduction_op);
// forward declaration
/***********************************************************************************************************************
* Internal Reduction Implementations
**********************************************************************************************************************/
#ifndef _CCCL_DOXYGEN_INVOKED // Do not document
/// Internal namespace (to prevent ADL mishaps between static functions when mixing different CUB installations)
namespace detail
{
/***********************************************************************************************************************
* Enable SIMD/Tree reduction heuristics (Trait)
**********************************************************************************************************************/
/// DPX instructions compute min, max, and sum for up to three 16 and 32-bit signed or unsigned integer parameters
/// see DPX documentation https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#dpx
/// NOTE: The compiler is able to automatically vectorize all cases with 3 operands
/// However, all other cases with per-halfword comparison need to be explicitly vectorized
///
/// DPX reduction is enabled if the following conditions are met:
/// - Hopper+ architectures. DPX instructions are emulated before Hopper
/// - The number of elements must be large enough for performance reasons (see below)
/// - All types must be the same
/// - Only works with integral types of 2 bytes
/// - DPX instructions provide Min, Max SIMD operations
/// If the number of instructions is the same, we favor the compiler
///
/// length | Standard | DPX
/// 2 | 1 | NA
/// 3 | 1 | NA
/// 4 | 2 | 3
/// 5 | 2 | 3
/// 6 | 3 | 3
/// 7 | 3 | 3
/// 8 | 4 | 4
/// 9 | 4 | 4
/// 10 | 5 | 4 // ***
/// 11 | 5 | 4 // ***
/// 12 | 6 | 5 // ***
/// 13 | 6 | 5 // ***
/// 14 | 7 | 5 // ***
/// 15 | 7 | 5 // ***
/// 16 | 8 | 6 // ***
// TODO: add Blackwell support
//----------------------------------------------------------------------------------------------------------------------
// SM90 SIMD
template <typename T, typename ReductionOp, int Length>
inline constexpr bool enable_sm90_simd_reduction_v =
is_one_of_v<T, int16_t, uint16_t> && is_cuda_minimum_maximum_v<ReductionOp, T> && Length >= 10;
//----------------------------------------------------------------------------------------------------------------------
// SM80 SIMD
template <typename T, typename ReductionOp, int Length>
inline constexpr bool enable_sm80_simd_reduction_v = false;
# if _CCCL_HAS_NVFP16()
template <typename ReductionOp, int Length>
inline constexpr bool enable_sm80_simd_reduction_v<__half, ReductionOp, Length> =
(is_cuda_minimum_maximum_v<ReductionOp, __half> || is_cuda_std_plus_mul_v<ReductionOp, __half>) && Length >= 4;
# endif // defined(_CCCL_HAS_NVFP16)
# if _CCCL_HAS_NVBF16()
template <typename ReductionOp, int Length>
inline constexpr bool enable_sm80_simd_reduction_v<__nv_bfloat16, ReductionOp, Length> =
(is_cuda_minimum_maximum_v<ReductionOp, __nv_bfloat16> || is_cuda_std_plus_mul_v<ReductionOp, __nv_bfloat16>)
&& Length >= 4;
# endif // _CCCL_HAS_NVBF16()
//----------------------------------------------------------------------------------------------------------------------
// SM70 SIMD
# if _CCCL_HAS_NVFP16()
template <typename T, typename ReductionOp, int Length>
inline constexpr bool enable_sm70_simd_reduction_v =
::cuda::std::is_same_v<T, __half> && is_cuda_std_plus_mul_v<ReductionOp, T> && Length >= 4;
# else // _CCCL_HAS_NVFP16() ^^^^ / !_CCCL_HAS_NVFP16() vvvv
template <typename T, typename ReductionOp, int Length>
inline constexpr bool enable_sm70_simd_reduction_v = false;
# endif // !_CCCL_HAS_NVFP16() ^^^^
/***********************************************************************************************************************
* Enable Ternary Reduction (Trait)
**********************************************************************************************************************/
template <typename T, typename ReductionOp>
inline constexpr bool enable_ternary_reduction_sm90_v =
is_one_of_v<T, int32_t, uint32_t> && is_cuda_minimum_maximum_v<ReductionOp, T>;
# if _CCCL_HAS_NVFP16()
template <typename ReductionOp>
inline constexpr bool enable_ternary_reduction_sm90_v<__half2, ReductionOp> =
is_cuda_minimum_maximum_v<ReductionOp, __half2> || is_one_of_v<ReductionOp, SimdMin<__half>, SimdMax<__half>>;
# endif // _CCCL_HAS_NVFP16()
# if _CCCL_HAS_NVBF16()
template <typename ReductionOp>
inline constexpr bool enable_ternary_reduction_sm90_v<__nv_bfloat162, ReductionOp> =
is_cuda_minimum_maximum_v<ReductionOp, __nv_bfloat162>
|| is_one_of_v<ReductionOp, SimdMin<__nv_bfloat16>, SimdMax<__nv_bfloat16>>;
# endif // _CCCL_HAS_NVBF16()
template <typename T, typename ReductionOp>
inline constexpr bool enable_ternary_reduction_sm50_v =
::cuda::std::is_integral_v<T> && sizeof(T) <= 4
&& (is_cuda_std_plus_v<ReductionOp, T> || is_cuda_std_bitwise_v<ReductionOp, T>);
/***********************************************************************************************************************
* Internal Reduction Algorithms: Sequential, Binary, Ternary
**********************************************************************************************************************/
template <typename AccumT, typename Input, typename ReductionOp>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT ThreadReduceSequential(const Input& input, ReductionOp reduction_op)
{
auto retval = static_cast<AccumT>(input[0]);
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 1; i < static_size_v<Input>; ++i)
{
retval = reduction_op(retval, input[i]);
}
return retval;
}
template <typename AccumT, typename Input, typename ReductionOp>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT ThreadReduceBinaryTree(const Input& input, ReductionOp reduction_op)
{
constexpr auto length = static_size_v<Input>;
auto array = cub::detail::to_array<AccumT>(input);
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 1; i < length; i *= 2)
{
_CCCL_PRAGMA_UNROLL_FULL()
for (int j = 0; j + i < length; j += i * 2)
{
array[j] = reduction_op(array[j], array[j + i]); // NOLINT(bugprone-misplaced-widening-cast)
}
}
return array[0];
}
template <typename AccumT, typename Input, typename ReductionOp>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT ThreadReduceTernaryTree(const Input& input, ReductionOp reduction_op)
{
constexpr auto length = static_size_v<Input>;
auto array = cub::detail::to_array<AccumT>(input);
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 1; i < length; i *= 3)
{
_CCCL_PRAGMA_UNROLL_FULL()
for (int j = 0; j + i < length; j += i * 3)
{
auto value = reduction_op(array[j], array[j + i]);
array[j] = (j + i * 2 < length) ? reduction_op(value, array[j + i * 2]) : value;
}
}
return array[0];
}
template <typename AccumT, typename Input, typename ReductionOp>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT
ThreadReduceSequentialPartial(const Input& input, ReductionOp reduction_op, int valid_items)
{
auto retval = static_cast<AccumT>(input[0]);
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 1; i < static_size_v<Input>; ++i)
{
if (i < valid_items)
{
retval = reduction_op(retval, input[i]);
}
}
return retval;
}
/***********************************************************************************************************************
* SIMD Reduction
**********************************************************************************************************************/
template <typename Input, typename ReductionOp>
_CCCL_DEVICE _CCCL_FORCEINLINE auto ThreadReduceSimd(const Input& input, ReductionOp)
{
using cub::detail::unsafe_bitcast;
using T = ::cuda::std::iter_value_t<Input>;
using SimdReduceOp = cuda_operator_to_simd_x2_t<ReductionOp, T>;
using SimdType = vector_type_x2_t<T>;
constexpr auto length = static_size_v<Input>;
constexpr auto simd_ratio = sizeof(SimdType) / sizeof(T);
constexpr auto length_rounded = ::cuda::round_down(length, simd_ratio);
using UnpackedType = ::cuda::std::array<T, simd_ratio>;
using SimdArray = ::cuda::std::array<SimdType, length / simd_ratio>;
static_assert(simd_ratio == 2, "Only SIMD size == 2 is supported");
T local_array[length_rounded];
UnrolledCopy<length_rounded>(input, local_array);
auto simd_input = unsafe_bitcast<SimdArray>(local_array);
auto simd_reduction = cub::ThreadReduce(simd_input, SimdReduceOp{});
auto unpacked_values = unsafe_bitcast<UnpackedType>(simd_reduction);
// Create a reversed copy of the SIMD reduction result and apply the SIMD operator.
// This avoids redundant instructions for converting to and from 32-bit registers
T unpacked_values_rev[] = {unpacked_values[1], unpacked_values[0]};
auto simd_reduction_rev = unsafe_bitcast<SimdType>(unpacked_values_rev);
SimdType result = SimdReduceOp{}(simd_reduction, simd_reduction_rev);
// repeat the same optimization for the last element
if constexpr (length % simd_ratio == 1)
{
T tail[] = {input[length - 1], T{}};
auto tail_simd = unsafe_bitcast<SimdType>(tail);
result = SimdReduceOp{}(result, tail_simd);
}
return unsafe_bitcast<UnpackedType>(result)[0];
}
template <typename ReductionOp, typename T>
inline constexpr bool enable_min_max_promotion_v =
is_cuda_minimum_maximum_v<ReductionOp, T> && ::cuda::std::is_integral_v<T> && sizeof(T) <= 2;
/***********************************************************************************************************************
* Partial Reduction
**********************************************************************************************************************/
template <typename Input,
typename ReductionOp,
typename ValueT = ::cuda::std::iter_value_t<Input>,
typename AccumT = ::cuda::std::__accumulator_t<ReductionOp, ValueT>>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT
ThreadReducePartial(const Input& input, ReductionOp reduction_op, int valid_items)
{
static_assert(is_fixed_size_random_access_range_v<Input>,
"Input must support the subscript operator[] and have a compile-time size");
static_assert(has_binary_call_operator<ReductionOp, ValueT>::value,
"ReductionOp must have the binary call operator: operator(ValueT, ValueT)");
if constexpr (static_size_v<Input> == 1)
{
return static_cast<AccumT>(input[0]);
}
else
{
using PromT = ::cuda::std::_If<enable_min_max_promotion_v<ReductionOp, ValueT>, int, AccumT>;
return cub::detail::ThreadReduceSequentialPartial<PromT>(input, reduction_op, valid_items);
}
}
} // namespace detail
/***********************************************************************************************************************
* Reduction Interface/Dispatch (public)
**********************************************************************************************************************/
template <typename Input, typename ReductionOp, typename ValueT, typename AccumT>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT ThreadReduce(const Input& input, ReductionOp reduction_op)
{
using namespace cub::detail;
static_assert(is_fixed_size_random_access_range_v<Input>,
"Input must support the subscript operator[] and have a compile-time size");
static_assert(has_binary_call_operator<ReductionOp, ValueT>::value,
"ReductionOp must have the binary call operator: operator(ValueT, ValueT)");
static constexpr auto length = static_size_v<Input>;
if constexpr (length == 1)
{
return static_cast<AccumT>(input[0]);
}
using PromT = ::cuda::std::_If<enable_min_max_promotion_v<ReductionOp, ValueT>, int, AccumT>;
// TODO: should be part of the tuning policy
if constexpr ((!is_simd_enabled_cuda_operator<ReductionOp, ValueT> && !is_simd_operator_v<ReductionOp>)
|| sizeof(ValueT) >= 8)
{
return ThreadReduceSequential<AccumT>(input, reduction_op);
}
if constexpr (::cuda::std::is_same_v<ValueT, AccumT> && enable_sm90_simd_reduction_v<ValueT, ReductionOp, length>)
{
NV_IF_TARGET(NV_PROVIDES_SM_90, (return ThreadReduceSimd(input, reduction_op);))
}
if constexpr (::cuda::std::is_same_v<ValueT, AccumT> && enable_sm80_simd_reduction_v<ValueT, ReductionOp, length>)
{
NV_IF_TARGET(NV_PROVIDES_SM_80, (return ThreadReduceSimd(input, reduction_op);))
}
if constexpr (::cuda::std::is_same_v<ValueT, AccumT> && enable_sm70_simd_reduction_v<ValueT, ReductionOp, length>)
{
NV_IF_TARGET(NV_PROVIDES_SM_70, (return ThreadReduceSimd(input, reduction_op);))
}
if constexpr (length >= 6)
{
// apply SM90 min/max ternary reduction only if the input is natively int32/uint32
if constexpr (enable_ternary_reduction_sm90_v<ValueT, ReductionOp>)
{
// with the current tuning policies, SM90/int32/+ uses too many registers (TODO: fix tuning policy)
if constexpr ((is_one_of_v<ReductionOp, ::cuda::std::plus<>, ::cuda::std::plus<PromT>>
&& is_one_of_v<PromT, int32_t, uint32_t>)
// the compiler generates bad code for int8/uint8 and min/max for SM90
|| (is_cuda_minimum_maximum_v<ReductionOp, ValueT> && is_one_of_v<PromT, int8_t, uint8_t>) )
{
NV_IF_TARGET(NV_PROVIDES_SM_90, (return ThreadReduceSequential<PromT>(input, reduction_op);));
}
NV_IF_TARGET(NV_PROVIDES_SM_90, (return ThreadReduceTernaryTree<PromT>(input, reduction_op);));
}
if constexpr (enable_ternary_reduction_sm50_v<ValueT, ReductionOp>)
{
NV_IF_TARGET(NV_PROVIDES_SM_50, (return ThreadReduceSequential<PromT>(input, reduction_op);));
}
}
return ThreadReduceBinaryTree<PromT>(input, reduction_op);
}
//! @brief Reduction over statically-sized array-like types, seeded with the specified @p prefix.
//!
//! @tparam Input
//! <b>[inferred]</b> The data type to be reduced having member
//! <tt>operator[](int i)</tt> and must be statically-sized (size() method or static array)
//!
//! @tparam ReductionOp
//! <b>[inferred]</b> Binary reduction operator type having member
//! <tt>T operator()(const T &a, const T &b)</tt>
//!
//! @tparam PrefixT
//! <b>[inferred]</b> The prefix type
//!
//! @param[in] input
//! Input array
//!
//! @param[in] reduction_op
//! Binary reduction operator
//!
//! @param[in] prefix
//! Prefix to seed reduction with
//!
//! @return Aggregate of type <tt>cuda::std::__accumulator_t<ReductionOp, ValueT, PrefixT></tt>
//!
template <typename Input,
typename ReductionOp,
typename PrefixT,
typename ValueT = ::cuda::std::iter_value_t<Input>,
typename AccumT = ::cuda::std::__accumulator_t<ReductionOp, ValueT, PrefixT>>
[[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE AccumT
ThreadReduce(const Input& input, ReductionOp reduction_op, PrefixT prefix)
{
using namespace cub::detail;
static_assert(is_fixed_size_random_access_range_v<Input>,
"Input must support the subscript operator[] and have a compile-time size");
static_assert(has_binary_call_operator<ReductionOp, ValueT>::value,
"ReductionOp must have the binary call operator: operator(ValueT, ValueT)");
constexpr int length = static_size_v<Input>;
// copy to a temporary array of type AccumT
AccumT array[length + 1];
array[0] = prefix;
_CCCL_PRAGMA_UNROLL_FULL()
for (int i = 0; i < length; ++i)
{
array[i + 1] = input[i];
}
return cub::ThreadReduce<decltype(array), ReductionOp, AccumT, AccumT>(array, reduction_op);
}
#endif // !_CCCL_DOXYGEN_INVOKED
CUB_NAMESPACE_END