Files
project_6/cccl_upstream/cub/test/catch2_test_block_scan.cu
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

698 lines
22 KiB
Plaintext

// SPDX-FileCopyrightText: Copyright (c) 2011-2022, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: BSD-3
#include <cub/block/block_scan.cuh>
#include <climits>
#include <c2h/catch2_test_helper.h>
template <cub::BlockScanAlgorithm Algorithm,
int ItemsPerThread,
int BlockDimX,
int BlockDimY,
int BlockDimZ,
class T,
class ActionT>
__global__ void block_scan_kernel(T* in, T* out, ActionT action)
{
using block_scan_t = cub::BlockScan<T, BlockDimX, Algorithm, BlockDimY, BlockDimZ>;
using storage_t = typename block_scan_t::TempStorage;
__shared__ storage_t storage;
T thread_data[ItemsPerThread];
const int tid = static_cast<int>(cub::RowMajorTid(BlockDimX, BlockDimY, BlockDimZ));
const int thread_offset = tid * ItemsPerThread;
for (int item = 0; item < ItemsPerThread; item++)
{
const int idx = thread_offset + item;
thread_data[item] = in[idx];
}
__syncthreads();
block_scan_t scan(storage);
action(scan, thread_data);
for (int item = 0; item < ItemsPerThread; item++)
{
const int idx = thread_offset + item;
out[idx] = thread_data[item];
}
}
template <cub::BlockScanAlgorithm Algorithm, int BlockDimX, int BlockDimY, int BlockDimZ, class T, class ActionT>
__global__ void block_scan_single_kernel(T* in, T* out, ActionT action)
{
using block_scan_t = cub::BlockScan<T, BlockDimX, Algorithm, BlockDimY, BlockDimZ>;
using storage_t = typename block_scan_t::TempStorage;
__shared__ storage_t storage;
const int tid = static_cast<int>(cub::RowMajorTid(BlockDimX, BlockDimY, BlockDimZ));
T thread_data = in[tid];
block_scan_t scan(storage);
action(scan, thread_data);
out[tid] = thread_data;
}
template <cub::BlockScanAlgorithm Algorithm,
int ItemsPerThread,
int BlockDimX,
int BlockDimY,
int BlockDimZ,
class T,
class ActionT>
void block_scan(c2h::device_vector<T>& in, c2h::device_vector<T>& out, ActionT action)
{
dim3 block_dims(BlockDimX, BlockDimY, BlockDimZ);
block_scan_kernel<Algorithm, ItemsPerThread, BlockDimX, BlockDimY, BlockDimZ, T, ActionT>
<<<1, block_dims>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), action);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
}
template <cub::BlockScanAlgorithm Algorithm, int BlockDimX, int BlockDimY, int BlockDimZ, class T, class ActionT>
void block_scan_single(c2h::device_vector<T>& in, c2h::device_vector<T>& out, ActionT action)
{
dim3 block_dims(BlockDimX, BlockDimY, BlockDimZ);
block_scan_single_kernel<Algorithm, BlockDimX, BlockDimY, BlockDimZ, T, ActionT>
<<<1, block_dims>>>(thrust::raw_pointer_cast(in.data()), thrust::raw_pointer_cast(out.data()), action);
REQUIRE(cudaSuccess == cudaPeekAtLastError());
REQUIRE(cudaSuccess == cudaDeviceSynchronize());
}
enum class scan_mode
{
exclusive,
inclusive
};
template <scan_mode Mode>
struct sum_op_t
{
template <int ItemsPerThread, class BlockScanT, class T>
__device__ void operator()(BlockScanT& scan, T (&thread_data)[ItemsPerThread]) const
{
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveSum(thread_data, thread_data);
}
else
{
scan.InclusiveSum(thread_data, thread_data);
}
}
};
template <scan_mode Mode>
struct sum_single_op_t
{
template <class BlockScanT, class T>
__device__ void operator()(BlockScanT& scan, T& thread_data) const
{
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveSum(thread_data, thread_data);
}
else
{
scan.InclusiveSum(thread_data, thread_data);
}
}
};
template <class T, scan_mode Mode>
struct min_init_value_op_t
{
T initial_value;
template <int ItemsPerThread, class BlockScanT>
__device__ void operator()(BlockScanT& scan, T (&thread_data)[ItemsPerThread]) const
{
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveScan(thread_data, thread_data, initial_value, cuda::minimum<>{});
}
else
{
scan.InclusiveScan(thread_data, thread_data, initial_value, cuda::minimum<>{});
}
}
};
template <scan_mode Mode>
struct min_op_t
{
template <int ItemsPerThread, class BlockScanT>
__device__ void operator()(BlockScanT& scan, int (&thread_data)[ItemsPerThread]) const
{
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveScan(thread_data, thread_data, cuda::minimum<>{});
}
else
{
scan.InclusiveScan(thread_data, thread_data, cuda::minimum<>{});
}
}
};
template <class T, scan_mode Mode>
struct min_init_value_aggregate_op_t
{
int m_target_thread_id;
T initial_value;
T* m_d_block_aggregate;
template <int ItemsPerThread, class BlockScanT>
__device__ void operator()(BlockScanT& scan, T (&thread_data)[ItemsPerThread]) const
{
T block_aggregate{};
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveScan(thread_data, thread_data, initial_value, cuda::minimum<>{}, block_aggregate);
}
else
{
scan.InclusiveScan(thread_data, thread_data, initial_value, cuda::minimum<>{}, block_aggregate);
}
const int tid =
cub::RowMajorTid(static_cast<int>(blockDim.x), static_cast<int>(blockDim.y), static_cast<int>(blockDim.z));
if (tid == m_target_thread_id)
{
*m_d_block_aggregate = block_aggregate;
}
}
};
template <class T, scan_mode Mode>
struct sum_aggregate_op_t
{
int m_target_thread_id;
T* m_d_block_aggregate;
template <int ItemsPerThread, class BlockScanT>
__device__ void operator()(BlockScanT& scan, T (&thread_data)[ItemsPerThread]) const
{
T block_aggregate{};
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveSum(thread_data, thread_data, block_aggregate);
}
else
{
scan.InclusiveSum(thread_data, thread_data, block_aggregate);
}
const int tid = static_cast<int>(
cub::RowMajorTid(static_cast<int>(blockDim.x), static_cast<int>(blockDim.y), static_cast<int>(blockDim.z)));
if (tid == m_target_thread_id)
{
*m_d_block_aggregate = block_aggregate;
}
}
};
template <class T, scan_mode Mode>
struct sum_prefix_op_t
{
T m_prefix;
struct block_prefix_op_t
{
int linear_tid;
T prefix;
__device__ block_prefix_op_t(int linear_tid, T prefix)
: linear_tid(linear_tid)
, prefix(prefix)
{}
__device__ T operator()(T block_aggregate)
{
T retval = (linear_tid == 0) ? prefix : T{};
prefix = prefix + block_aggregate;
return retval;
}
};
template <int ItemsPerThread, class BlockScanT>
__device__ void operator()(BlockScanT& scan, T (&thread_data)[ItemsPerThread]) const
{
const int tid = static_cast<int>(
cub::RowMajorTid(static_cast<int>(blockDim.x), static_cast<int>(blockDim.y), static_cast<int>(blockDim.z)));
block_prefix_op_t prefix_op{tid, m_prefix};
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveSum(thread_data, thread_data, prefix_op);
}
else
{
scan.InclusiveSum(thread_data, thread_data, prefix_op);
}
}
};
template <class T, scan_mode Mode>
struct min_prefix_op_t
{
T m_prefix;
static constexpr T min_identity = cuda::std::numeric_limits<T>::max();
struct block_prefix_op_t
{
int linear_tid;
T prefix;
__device__ block_prefix_op_t(int linear_tid, T prefix)
: linear_tid(linear_tid)
, prefix(prefix)
{}
__device__ T operator()(T block_aggregate)
{
T retval = (linear_tid == 0) ? prefix : min_identity;
prefix = cuda::minimum<>{}(prefix, block_aggregate);
return retval;
}
};
template <int ItemsPerThread, class BlockScanT>
__device__ void operator()(BlockScanT& scan, T (&thread_data)[ItemsPerThread]) const
{
const int tid = static_cast<int>(
cub::RowMajorTid(static_cast<int>(blockDim.x), static_cast<int>(blockDim.y), static_cast<int>(blockDim.z)));
block_prefix_op_t prefix_op{tid, m_prefix};
if constexpr (Mode == scan_mode::exclusive)
{
scan.ExclusiveScan(thread_data, thread_data, cuda::minimum<>{}, prefix_op);
}
else
{
scan.InclusiveScan(thread_data, thread_data, cuda::minimum<>{}, prefix_op);
}
}
};
template <class T, class ScanOpT>
T host_scan(scan_mode mode, c2h::host_vector<T>& result, ScanOpT scan_op, T initial_value = T{})
{
if (result.empty())
{
return {};
}
T accumulator = static_cast<T>(scan_op(initial_value, result[0]));
T block_accumulator = result[0];
if (mode == scan_mode::exclusive)
{
result[0] = initial_value;
for (std::size_t i = 1; i < result.size(); i++)
{
T tmp = result[i];
result[i] = accumulator;
accumulator = static_cast<T>(scan_op(accumulator, tmp));
block_accumulator = static_cast<T>(scan_op(block_accumulator, tmp));
}
}
else
{
result[0] = accumulator;
for (std::size_t i = 1; i < result.size(); i++)
{
accumulator = static_cast<T>(scan_op(accumulator, result[i]));
block_accumulator = static_cast<T>(scan_op(block_accumulator, result[i]));
result[i] = accumulator;
}
}
return block_accumulator;
}
// %PARAM% ALGO_TYPE alg 0:1:2
// %PARAM% TEST_MODE mode 0:1
using types = c2h::type_list<std::uint8_t, std::uint16_t, std::int32_t, std::int64_t>;
// FIXME(bgruber): uchar3 fails the test, see #3835
using vec_types = c2h::type_list<
#if _CCCL_CTK_AT_LEAST(13, 0)
ulonglong4_16a,
#else // _CCCL_CTK_AT_LEAST(13, 0)
ulonglong4,
#endif // _CCCL_CTK_AT_LEAST(13, 0)
/*uchar3,*/ short2>;
using block_dim_x = c2h::enum_type_list<int, 17, 32, 65, 96>;
using block_dim_yz = c2h::enum_type_list<int, 1, 2>;
using items_per_thread = c2h::enum_type_list<int, 1, 9>;
using single_item_per_thread = c2h::enum_type_list<int, 1>;
using algorithms =
c2h::enum_type_list<cub::BlockScanAlgorithm,
cub::BlockScanAlgorithm::BLOCK_SCAN_RAKING,
cub::BlockScanAlgorithm::BLOCK_SCAN_WARP_SCANS,
cub::BlockScanAlgorithm::BLOCK_SCAN_RAKING_MEMOIZE>;
using algorithm = c2h::enum_type_list<cub::BlockScanAlgorithm, c2h::get<ALGO_TYPE, algorithms>::value>;
#if TEST_MODE == 0
using modes = c2h::enum_type_list<scan_mode, scan_mode::inclusive>;
#else
using modes = c2h::enum_type_list<scan_mode, scan_mode::exclusive>;
#endif
template <class TestType>
struct params_t
{
using type = typename c2h::get<0, TestType>;
static constexpr int block_dim_x = c2h::get<1, TestType>::value;
static constexpr int block_dim_y = c2h::get<2, TestType>::value;
static constexpr int block_dim_z = block_dim_y;
static constexpr int items_per_thread = c2h::get<3, TestType>::value;
static constexpr int tile_size = items_per_thread * block_dim_x * block_dim_y * block_dim_z;
static constexpr cub::BlockScanAlgorithm algorithm = c2h::get<4, TestType>::value;
static constexpr scan_mode mode = c2h::get<5, TestType>::value;
};
C2H_TEST(
"Block scan works with sum", "[scan][block]", types, block_dim_x, block_dim_yz, items_per_thread, algorithm, modes)
{
using params = params_t<TestType>;
using type = typename params::type;
c2h::device_vector<type> d_out(params::tile_size);
c2h::device_vector<type> d_in(params::tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<params::algorithm, params::items_per_thread, params::block_dim_x, params::block_dim_y, params::block_dim_z>(
d_in, d_out, sum_op_t<params::mode>{});
c2h::host_vector<type> h_out = d_in;
host_scan(params::mode, h_out, std::plus<type>{});
REQUIRE_APPROX_EQ(h_out, d_out);
}
C2H_TEST("Block scan works with sum single",
"[scan][block]",
types,
block_dim_x,
block_dim_yz,
single_item_per_thread,
algorithm,
modes)
{
using params = params_t<TestType>;
using type = typename params::type;
c2h::device_vector<type> d_out(params::tile_size);
c2h::device_vector<type> d_in(params::tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan_single<params::algorithm, params::block_dim_x, params::block_dim_y, params::block_dim_z>(
d_in, d_out, sum_single_op_t<params::mode>{});
c2h::host_vector<type> h_out = d_in;
host_scan(params::mode, h_out, std::plus<type>{});
REQUIRE_APPROX_EQ(h_out, d_out);
}
C2H_TEST("Block scan works with vec types", "[scan][block]", vec_types, algorithm, modes)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 256;
constexpr int block_dim_y = 1;
constexpr int block_dim_z = 1;
constexpr int tile_size = items_per_thread * block_dim_x * block_dim_y * block_dim_z;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<1, TestType>::value;
constexpr scan_mode mode = c2h::get<2, TestType>::value;
using type = typename c2h::get<0, TestType>;
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(d_in, d_out, sum_op_t<mode>{});
c2h::host_vector<type> h_out = d_in;
host_scan(mode, h_out, std::plus<type>{});
REQUIRE(h_out == d_out);
}
C2H_TEST("Block scan works with custom types", "[scan][block]", algorithm, modes)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 256;
constexpr int block_dim_y = 1;
constexpr int block_dim_z = 1;
constexpr int tile_size = items_per_thread * block_dim_x * block_dim_y * block_dim_z;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = c2h::custom_type_t<c2h::accumulateable_t, c2h::equal_comparable_t>;
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(d_in, d_out, sum_op_t<mode>{});
c2h::host_vector<type> h_out = d_in;
host_scan(mode, h_out, std::plus<type>{});
REQUIRE(h_out == d_out);
}
C2H_TEST("Block scan returns valid block aggregate", "[scan][block]", algorithm, modes, block_dim_yz)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 64;
constexpr int block_dim_y = c2h::get<2, TestType>::value;
constexpr int block_dim_z = block_dim_y;
constexpr int threads_in_block = block_dim_x * block_dim_y * block_dim_z;
constexpr int tile_size = items_per_thread * threads_in_block;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = c2h::custom_type_t<c2h::accumulateable_t, c2h::equal_comparable_t>;
const int target_thread_id = GENERATE_COPY(take(2, random(0, threads_in_block - 1)));
c2h::device_vector<type> d_block_aggregate(1);
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(
d_in, d_out, sum_aggregate_op_t<type, mode>{target_thread_id, thrust::raw_pointer_cast(d_block_aggregate.data())});
c2h::host_vector<type> h_out = d_in;
type block_aggregate = host_scan(mode, h_out, std::plus<type>{});
REQUIRE(h_out == d_out);
REQUIRE(block_aggregate == d_block_aggregate[0]);
}
C2H_TEST("Block scan supports prefix op", "[scan][block]", algorithm, modes, block_dim_yz)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 64;
constexpr int block_dim_y = c2h::get<2, TestType>::value;
constexpr int block_dim_z = block_dim_y;
constexpr int threads_in_block = block_dim_x * block_dim_y * block_dim_z;
constexpr int tile_size = items_per_thread * threads_in_block;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = int;
const type prefix = GENERATE_COPY(take(2, random(0, tile_size)));
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(
d_in, d_out, sum_prefix_op_t<type, mode>{prefix});
c2h::host_vector<type> h_out = d_in;
host_scan(mode, h_out, std::plus<type>{}, prefix);
REQUIRE(h_out == d_out);
}
C2H_TEST("Block scan supports custom scan op", "[scan][block]", algorithm, modes, block_dim_yz)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 64;
constexpr int block_dim_y = c2h::get<2, TestType>::value;
constexpr int block_dim_z = block_dim_y;
constexpr int threads_in_block = block_dim_x * block_dim_y * block_dim_z;
constexpr int tile_size = items_per_thread * threads_in_block;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = int;
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(d_in, d_out, min_op_t<mode>{});
c2h::host_vector<type> h_out = d_in;
host_scan(
mode,
h_out,
[](type l, type r) {
return std::min(l, r);
},
INT_MAX);
if constexpr (mode == scan_mode::exclusive)
{
//! With no initial value, the output computed for *thread*\ :sub:`0` is undefined.
d_out.erase(d_out.begin());
h_out.erase(h_out.begin());
}
REQUIRE(h_out == d_out);
}
C2H_TEST("Block custom op scan works with initial value", "[scan][block]", algorithm, modes, block_dim_yz)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 64;
constexpr int block_dim_y = c2h::get<2, TestType>::value;
constexpr int block_dim_z = block_dim_y;
constexpr int threads_in_block = block_dim_x * block_dim_y * block_dim_z;
constexpr int tile_size = items_per_thread * threads_in_block;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = int;
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
const type initial_value = static_cast<type>(GENERATE_COPY(take(2, random(0, tile_size))));
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(
d_in, d_out, min_init_value_op_t<type, mode>{initial_value});
c2h::host_vector<type> h_out = d_in;
host_scan(
mode,
h_out,
[](type l, type r) {
return std::min(l, r);
},
initial_value);
REQUIRE(h_out == d_out);
}
C2H_TEST("Block custom op scan with initial value returns valid block aggregate",
"[scan][block]",
algorithm,
modes,
block_dim_yz)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 64;
constexpr int block_dim_y = c2h::get<2, TestType>::value;
constexpr int block_dim_z = block_dim_y;
constexpr int threads_in_block = block_dim_x * block_dim_y * block_dim_z;
constexpr int tile_size = items_per_thread * threads_in_block;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = int;
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
const type initial_value = static_cast<type>(GENERATE_COPY(take(2, random(0, tile_size))));
const int target_thread_id = GENERATE_COPY(take(2, random(0, threads_in_block - 1)));
c2h::device_vector<type> d_block_aggregate(1);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(
d_in,
d_out,
min_init_value_aggregate_op_t<type, mode>{
target_thread_id, initial_value, thrust::raw_pointer_cast(d_block_aggregate.data())});
c2h::host_vector<type> h_out = d_in;
type h_block_aggregate = host_scan(
mode,
h_out,
[](type l, type r) {
return std::min(l, r);
},
initial_value);
REQUIRE(h_out == d_out);
REQUIRE(h_block_aggregate == d_block_aggregate[0]);
}
C2H_TEST("Block scan supports prefix op and custom scan op", "[scan][block]", algorithm, modes, block_dim_yz)
{
constexpr int items_per_thread = 3;
constexpr int block_dim_x = 64;
constexpr int block_dim_y = c2h::get<2, TestType>::value;
constexpr int block_dim_z = block_dim_y;
constexpr int threads_in_block = block_dim_x * block_dim_y * block_dim_z;
constexpr int tile_size = items_per_thread * threads_in_block;
constexpr cub::BlockScanAlgorithm algorithm = c2h::get<0, TestType>::value;
constexpr scan_mode mode = c2h::get<1, TestType>::value;
using type = int;
const type prefix = GENERATE_COPY(take(2, random(0, tile_size)));
c2h::device_vector<type> d_out(tile_size);
c2h::device_vector<type> d_in(tile_size);
c2h::gen(C2H_SEED(10), d_in);
block_scan<algorithm, items_per_thread, block_dim_x, block_dim_y, block_dim_z>(
d_in, d_out, min_prefix_op_t<type, mode>{prefix});
c2h::host_vector<type> h_out = d_in;
host_scan(
mode,
h_out,
[](type a, type b) {
return std::min(a, b);
},
prefix);
REQUIRE(h_out == d_out);
}