Files
project_6/cccl_upstream/cub/cub/agent/agent_scan.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

568 lines
17 KiB
Plaintext

// SPDX-FileCopyrightText: Copyright (c) 2011, Duane Merrill. All rights reserved.
// SPDX-FileCopyrightText: Copyright (c) 2011-2022, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
/**
* @file
* @brief cub::AgentScan implements a stateful abstraction of CUDA thread blocks
* for participating in device-wide prefix scan.
*/
#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/agent/single_pass_scan_operators.cuh>
#include <cub/block/block_load.cuh>
#include <cub/block/block_scan.cuh>
#include <cub/block/block_store.cuh>
#include <cub/grid/grid_queue.cuh>
#include <cub/iterator/cache_modified_input_iterator.cuh>
#include <cub/util_device.cuh>
#include <cuda/std/__type_traits/conditional.h>
#include <cuda/std/__type_traits/is_pointer.h>
#include <cuda/std/__type_traits/is_same.h>
CUB_NAMESPACE_BEGIN
namespace detail
{
// TODO(bgruber): remove when C++20 is the minimum, since then we can pass policy values as NTTPs
template <int NominalThreadsPerBlock4B,
int NominalItemsPerThread4B,
typename ComputeT,
BlockLoadAlgorithm LoadAlgorithm,
CacheLoadModifier LoadModifier,
BlockStoreAlgorithm StoreAlgorithm,
BlockScanAlgorithm ScanAlgorithm,
typename ScalingType = detail::MemBoundScaling<NominalThreadsPerBlock4B, NominalItemsPerThread4B, ComputeT>,
typename DelayConstructorT = detail::default_delay_constructor_t<ComputeT>>
struct agent_scan_policy : ScalingType
{
static constexpr BlockLoadAlgorithm LOAD_ALGORITHM = LoadAlgorithm;
static constexpr CacheLoadModifier LOAD_MODIFIER = LoadModifier;
static constexpr BlockStoreAlgorithm STORE_ALGORITHM = StoreAlgorithm;
static constexpr BlockScanAlgorithm SCAN_ALGORITHM = ScanAlgorithm;
struct detail
{
using delay_constructor_t = DelayConstructorT;
};
};
} // namespace detail
/******************************************************************************
* Tuning policy types
******************************************************************************/
/**
* @brief Parameterizable tuning policy type for AgentScan
*
* @tparam NominalThreadsPerBlock4B
* Threads per thread block
*
* @tparam NominalItemsPerThread4B
* Items per thread (per tile of input)
*
* @tparam ComputeT
* Dominant compute type
*
* @tparam LoadAlgorithm
* The BlockLoad algorithm to use
*
* @tparam LoadModifier
* Cache load modifier for reading input elements
*
* @tparam StoreAlgorithm
* The BlockStore algorithm to use
*
* @tparam ScanAlgorithm
* The BlockScan algorithm to use
*
* @tparam DelayConstructorT
* Implementation detail, do not specify directly, requirements on the
* content of this type are subject to breaking change.
*/
//! Deprecated [Since 3.5]
template <int NominalThreadsPerBlock4B,
int NominalItemsPerThread4B,
typename ComputeT,
BlockLoadAlgorithm LoadAlgorithm,
CacheLoadModifier LoadModifier,
BlockStoreAlgorithm StoreAlgorithm,
BlockScanAlgorithm ScanAlgorithm,
typename ScalingType = detail::MemBoundScaling<NominalThreadsPerBlock4B, NominalItemsPerThread4B, ComputeT>,
typename DelayConstructorT = detail::default_delay_constructor_t<ComputeT>>
using AgentScanPolicy CCCL_DEPRECATED_BECAUSE("Use the tuning API for DeviceScan") = detail::agent_scan_policy<
NominalThreadsPerBlock4B,
NominalItemsPerThread4B,
ComputeT,
LoadAlgorithm,
LoadModifier,
StoreAlgorithm,
ScanAlgorithm,
ScalingType,
DelayConstructorT>;
/******************************************************************************
* Thread block abstractions
******************************************************************************/
namespace detail::scan
{
/**
* @brief AgentScan implements a stateful abstraction of CUDA thread blocks for
* participating in device-wide prefix scan.
* @tparam AgentScanPolicyT
* Parameterized AgentScanPolicyT tuning policy type
*
* @tparam InputIteratorT
* Random-access input iterator type
*
* @tparam OutputIteratorT
* Random-access output iterator type
*
* @tparam ScanOpT
* Scan functor type
*
* @tparam InitValueT
* The init_value element for ScanOpT type (cub::NullType for inclusive scan)
*
* @tparam OffsetT
* Signed integer type for global offsets
*
* @tparam AccumT
* The type of intermediate accumulator (according to P2322R6)
*/
template <typename AgentScanPolicyT,
typename InputIteratorT,
typename OutputIteratorT,
typename ScanOpT,
typename InitValueT,
typename OffsetT,
typename AccumT,
bool ForceInclusive = false,
bool UsePDL = false,
bool StableReductionOrder = false>
struct AgentScan
{
//---------------------------------------------------------------------
// Types and constants
//---------------------------------------------------------------------
// The input value type
using InputT = cub::detail::it_value_t<InputIteratorT>;
// Tile status descriptor interface type
using ScanTileStateT = ScanTileState<AccumT>;
// Input iterator wrapper type (for applying cache modifier)
// Wrap the native input pointer with CacheModifiedInputIterator
// or directly use the supplied input iterator type
using WrappedInputIteratorT =
::cuda::std::_If<::cuda::std::is_pointer_v<InputIteratorT>,
CacheModifiedInputIterator<AgentScanPolicyT::LOAD_MODIFIER, InputT, OffsetT>,
InputIteratorT>;
// Inclusive scan if no init_value type is provided
static constexpr bool HAS_INIT = !::cuda::std::is_same_v<InitValueT, NullType>;
static constexpr bool IS_INCLUSIVE = ForceInclusive || !HAS_INIT; // We are relying on either initial value not being
// `NullType` or the ForceInclusive tag to be true
// for inclusive scan to get picked up.
static constexpr int BLOCK_THREADS = AgentScanPolicyT::BLOCK_THREADS;
static constexpr int ITEMS_PER_THREAD = AgentScanPolicyT::ITEMS_PER_THREAD;
static constexpr int TILE_ITEMS = BLOCK_THREADS * ITEMS_PER_THREAD;
// Parameterized BlockLoad type
using BlockLoadT =
BlockLoad<AccumT,
AgentScanPolicyT::BLOCK_THREADS,
AgentScanPolicyT::ITEMS_PER_THREAD,
AgentScanPolicyT::LOAD_ALGORITHM>;
// Parameterized BlockStore type
using BlockStoreT =
BlockStore<AccumT,
AgentScanPolicyT::BLOCK_THREADS,
AgentScanPolicyT::ITEMS_PER_THREAD,
AgentScanPolicyT::STORE_ALGORITHM>;
// Parameterized BlockScan type
using BlockScanT = BlockScan<AccumT, AgentScanPolicyT::BLOCK_THREADS, AgentScanPolicyT::SCAN_ALGORITHM>;
// Callback type for obtaining tile prefix during block scan
using DelayConstructorT = typename AgentScanPolicyT::detail::delay_constructor_t;
using TilePrefixCallbackOpT =
TilePrefixCallbackOp<AccumT, ScanOpT, ScanTileStateT, DelayConstructorT, StableReductionOrder>;
// Stateful BlockScan prefix callback type for managing a running total while
// scanning consecutive tiles
using RunningPrefixCallbackOp = BlockScanRunningPrefixOp<AccumT, ScanOpT>;
// Shared memory type for this thread block
union _TempStorage
{
// Smem needed for tile loading
typename BlockLoadT::TempStorage load;
// Smem needed for tile storing
typename BlockStoreT::TempStorage store;
struct ScanStorage
{
// Smem needed for cooperative prefix callback
typename TilePrefixCallbackOpT::TempStorage prefix;
// Smem needed for tile scanning
typename BlockScanT::TempStorage scan;
} scan_storage;
};
// Alias wrapper allowing storage to be unioned
struct TempStorage : Uninitialized<_TempStorage>
{};
//---------------------------------------------------------------------
// Per-thread fields
//---------------------------------------------------------------------
_TempStorage& temp_storage; ///< Reference to temp_storage
WrappedInputIteratorT d_in; ///< Input data
OutputIteratorT d_out; ///< Output data
ScanOpT scan_op; ///< Binary scan operator
InitValueT init_value; ///< The init_value element for ScanOpT
//---------------------------------------------------------------------
// Block scan utility methods
//---------------------------------------------------------------------
template <bool Inclusive = IS_INCLUSIVE>
_CCCL_DEVICE _CCCL_FORCEINLINE void
ScanFirstTile(AccumT (&items)[ITEMS_PER_THREAD], InitValueT init_value, ScanOpT scan_op, AccumT& block_aggregate)
{
BlockScanT blockScan(temp_storage.scan_storage.scan);
if constexpr (Inclusive)
{
if constexpr (HAS_INIT)
{
blockScan.InclusiveScan(items, items, init_value, scan_op, block_aggregate);
block_aggregate = scan_op(init_value, block_aggregate);
}
else
{
blockScan.InclusiveScan(items, items, scan_op, block_aggregate);
}
}
else
{
blockScan.ExclusiveScan(items, items, init_value, scan_op, block_aggregate);
block_aggregate = scan_op(init_value, block_aggregate);
}
}
template <typename PrefixCallback, bool Inclusive = IS_INCLUSIVE>
_CCCL_DEVICE _CCCL_FORCEINLINE void
ScanSubsequentTile(AccumT (&items)[ITEMS_PER_THREAD], ScanOpT scan_op, PrefixCallback& prefix_op)
{
BlockScanT blockScan(temp_storage.scan_storage.scan);
if constexpr (Inclusive)
{
blockScan.InclusiveScan(items, items, scan_op, prefix_op);
}
else
{
blockScan.ExclusiveScan(items, items, scan_op, prefix_op);
}
}
//---------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------
/**
* @param temp_storage
* Reference to temp_storage
*
* @param d_in
* Input data
*
* @param d_out
* Output data
*
* @param scan_op
* Binary scan operator
*
* @param init_value
* Initial value to seed the exclusive scan
*/
_CCCL_DEVICE _CCCL_FORCEINLINE AgentScan(
TempStorage& temp_storage, InputIteratorT d_in, OutputIteratorT d_out, ScanOpT scan_op, InitValueT init_value)
: temp_storage(temp_storage.Alias())
, d_in(d_in)
, d_out(d_out)
, scan_op(scan_op)
, init_value(init_value)
{}
//---------------------------------------------------------------------
// Cooperatively scan a device-wide sequence of tiles with other CTAs
//---------------------------------------------------------------------
/**
* Process a tile of input (dynamic chained scan)
* @tparam IS_LAST_TILE
* Whether the current tile is the last tile
*
* @param num_remaining
* Number of global input items remaining (including this tile)
*
* @param tile_idx
* Tile index
*
* @param tile_offset
* Tile offset
*
* @param tile_state
* Global tile state descriptor
*/
template <bool IS_LAST_TILE>
_CCCL_DEVICE _CCCL_FORCEINLINE void
ConsumeTile(OffsetT num_remaining, int tile_idx, OffsetT tile_offset, ScanTileStateT& tile_state)
{
// Load items
AccumT items[ITEMS_PER_THREAD];
if constexpr (IS_LAST_TILE)
{
// Fill last element with the first element because collectives are
// not suffix guarded.
BlockLoadT(temp_storage.load).Load(d_in + tile_offset, items, num_remaining, *(d_in + tile_offset));
}
else
{
BlockLoadT(temp_storage.load).Load(d_in + tile_offset, items);
}
__syncthreads();
// Perform tile scan
if (tile_idx == 0)
{
// Scan first tile
AccumT block_aggregate;
ScanFirstTile(items, init_value, scan_op, block_aggregate);
if ((!IS_LAST_TILE) && (threadIdx.x == 0))
{
tile_state.SetInclusive(0, block_aggregate);
}
}
else
{
// Scan non-first tile
TilePrefixCallbackOpT prefix_op(tile_state, temp_storage.scan_storage.prefix, scan_op, tile_idx);
ScanSubsequentTile(items, scan_op, prefix_op);
}
__syncthreads();
if constexpr (UsePDL)
{
_CCCL_PDL_TRIGGER_NEXT_LAUNCH(); // omitting makes almost no difference in cub.bench.scan.exclusive.sum.base
}
// Store items
if constexpr (IS_LAST_TILE)
{
BlockStoreT(temp_storage.store).Store(d_out + tile_offset, items, num_remaining);
}
else
{
BlockStoreT(temp_storage.store).Store(d_out + tile_offset, items);
}
}
/**
* @brief Scan tiles of items as part of a dynamic chained scan
*
* @param num_items
* Total number of input items
*
* @param tile_state
* Global tile state descriptor
*
* @param start_tile
* The starting tile for the current grid
*/
_CCCL_DEVICE _CCCL_FORCEINLINE void ConsumeRange(OffsetT num_items, ScanTileStateT& tile_state, int start_tile)
{
// Blocks are launched in increasing order, so just assign one tile per
// block
// Current tile index
int tile_idx = static_cast<int>(start_tile + blockIdx.x);
// Global offset for the current tile
OffsetT tile_offset = OffsetT(TILE_ITEMS) * tile_idx;
// Remaining items (including this tile)
OffsetT num_remaining = num_items - tile_offset;
if (num_remaining > TILE_ITEMS)
{
// Not last tile
ConsumeTile<false>(num_remaining, tile_idx, tile_offset, tile_state);
}
else if (num_remaining > 0)
{
// Last tile
ConsumeTile<true>(num_remaining, tile_idx, tile_offset, tile_state);
}
}
//---------------------------------------------------------------------------
// Scan an sequence of consecutive tiles (independent of other thread blocks)
//---------------------------------------------------------------------------
/**
* @brief Process a tile of input
*
* @param tile_offset
* Tile offset
*
* @param prefix_op
* Running prefix operator
*
* @param valid_items
* Number of valid items in the tile
*/
template <bool IS_FIRST_TILE, bool IS_LAST_TILE>
_CCCL_DEVICE _CCCL_FORCEINLINE void
ConsumeTile(OffsetT tile_offset, RunningPrefixCallbackOp& prefix_op, int valid_items = TILE_ITEMS)
{
// Load items
AccumT items[ITEMS_PER_THREAD];
if constexpr (IS_LAST_TILE)
{
// Fill last element with the first element because collectives are
// not suffix guarded.
BlockLoadT(temp_storage.load).Load(d_in + tile_offset, items, valid_items, *(d_in + tile_offset));
}
else
{
BlockLoadT(temp_storage.load).Load(d_in + tile_offset, items);
}
__syncthreads();
// Block scan
if constexpr (IS_FIRST_TILE)
{
AccumT block_aggregate;
ScanFirstTile(items, init_value, scan_op, block_aggregate);
prefix_op.running_total = block_aggregate;
}
else
{
ScanSubsequentTile(items, scan_op, prefix_op);
}
__syncthreads();
// Store items
if constexpr (IS_LAST_TILE)
{
BlockStoreT(temp_storage.store).Store(d_out + tile_offset, items, valid_items);
}
else
{
BlockStoreT(temp_storage.store).Store(d_out + tile_offset, items);
}
}
/**
* @brief Scan a consecutive share of input tiles
*
* @param[in] range_offset
* Threadblock begin offset (inclusive)
*
* @param[in] range_end
* Threadblock end offset (exclusive)
*/
_CCCL_DEVICE _CCCL_FORCEINLINE void ConsumeRange(OffsetT range_offset, OffsetT range_end)
{
BlockScanRunningPrefixOp<AccumT, ScanOpT> prefix_op(scan_op);
if (range_offset + TILE_ITEMS <= range_end)
{
// Consume first tile of input (full)
ConsumeTile<true, true>(range_offset, prefix_op);
range_offset += TILE_ITEMS;
// Consume subsequent full tiles of input
while (range_offset + TILE_ITEMS <= range_end)
{
ConsumeTile<false, true>(range_offset, prefix_op);
range_offset += TILE_ITEMS;
}
// Consume a partially-full tile
if (range_offset < range_end)
{
int valid_items = range_end - range_offset;
ConsumeTile<false, false>(range_offset, prefix_op, valid_items);
}
}
else
{
// Consume the first tile of input (partially-full)
int valid_items = range_end - range_offset;
ConsumeTile<true, false>(range_offset, prefix_op, valid_items);
}
}
/**
* @brief Scan a consecutive share of input tiles, seeded with the
* specified prefix value
* @param[in] range_offset
* Threadblock begin offset (inclusive)
*
* @param[in] range_end
* Threadblock end offset (exclusive)
*
* @param[in] prefix
* The prefix to apply to the scan segment
*/
_CCCL_DEVICE _CCCL_FORCEINLINE void ConsumeRange(OffsetT range_offset, OffsetT range_end, AccumT prefix)
{
BlockScanRunningPrefixOp<AccumT, ScanOpT> prefix_op(prefix, scan_op);
// Consume full tiles of input
while (range_offset + TILE_ITEMS <= range_end)
{
ConsumeTile<true, false>(range_offset, prefix_op);
range_offset += TILE_ITEMS;
}
// Consume a partially-full tile
if (range_offset < range_end)
{
int valid_items = range_end - range_offset;
ConsumeTile<false, false>(range_offset, prefix_op, valid_items);
}
}
};
} // namespace detail::scan
CUB_NAMESPACE_END