// SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved. // SPDX-License-Identifier: BSD-3 #include #include #include #include #include #include template __global__ void warp_load_kernel(InputIteratorT input_iterator, ActionT action, int* error_counter) { using warp_load_t = cub::WarpLoad; using storage_t = typename warp_load_t::TempStorage; constexpr int tile_size = ITEMS_PER_THREAD * LOGICAL_WARP_THREADS; __shared__ storage_t storage[TOTAL_WARPS]; const int linear_tid = static_cast(threadIdx.x); const int warp_id = linear_tid / LOGICAL_WARP_THREADS; warp_load_t load(storage[warp_id]); // Test WarpLoad specialization T reg[ITEMS_PER_THREAD]; action.load(load, input_iterator + (warp_id * tile_size), reg); // Verify data was loaded as expected action.verify(reg, error_counter); } template void warp_load(InputIteratorT input_iterator, ActionT action, int* error_counter) { warp_load_kernel <<<1, TOTAL_WARPS * LOGICAL_WARP_THREADS>>>(input_iterator, action, error_counter); REQUIRE(cudaSuccess == cudaPeekAtLastError()); REQUIRE(cudaSuccess == cudaDeviceSynchronize()); } /** * @brief WarpLoad test specialisation for guarded loads */ template struct guarded_load_t { int valid_items; T oob_default; template __device__ void load(cub::WarpLoad load, InputIteratorT input, T (®)[ITEMS_PER_THREAD]) { load.Load(input, reg, valid_items, oob_default); } template __device__ void verify(T (®)[ITEMS_PER_THREAD], int* error_counter) { const auto linear_tid = cub::RowMajorTid(static_cast(blockDim.x), static_cast(blockDim.y), static_cast(blockDim.z)); const auto lane_id = linear_tid % LOGICAL_WARP_THREADS; for (int item = 0; item < ITEMS_PER_THREAD; item++) { const auto expected_value = static_cast(linear_tid * ITEMS_PER_THREAD + item); // NOLINT(bugprone-misplaced-widening-cast) const bool is_oob = LoadAlgorithm == cub::WarpLoadAlgorithm::WARP_LOAD_STRIPED ? item * LOGICAL_WARP_THREADS + lane_id >= valid_items : lane_id * ITEMS_PER_THREAD + item >= valid_items; if (is_oob) { if (reg[item] != oob_default) { atomicAdd(error_counter, 1); } } else if (reg[item] != expected_value) { atomicAdd(error_counter, 1); } } } }; /** * @brief WarpLoad test specialisation for unguarded loads */ struct unguarded_load_t { template __device__ void load(cub::WarpLoad load, InputIteratorT input, T (®)[ITEMS_PER_THREAD]) { load.Load(input, reg); } template __device__ void verify(T (®)[ITEMS_PER_THREAD], int* error_counter) { for (int item = 0; item < ITEMS_PER_THREAD; item++) { const auto expected_value = static_cast(threadIdx.x * ITEMS_PER_THREAD + item); // NOLINT(bugprone-misplaced-widening-cast) if (reg[item] != expected_value) { atomicAdd(error_counter, 1); } } } }; template c2h::device_vector generate_input() { constexpr int tile_size = LOGICAL_WARP_THREADS * ITEMS_PER_THREAD; constexpr int num_items = TOTAL_WARPS * tile_size; c2h::device_vector d_input(num_items); if constexpr (LoadAlgorithm == cub::WarpLoadAlgorithm::WARP_LOAD_STRIPED) { c2h::host_vector h_input(num_items); // In this case we need different stripe pattern, so the // items/threads parameters are swapped constexpr int FAKE_BLOCK_SIZE = ITEMS_PER_THREAD * TOTAL_WARPS; fill_striped(h_input.begin()); d_input = h_input; } else { c2h::gen(c2h::modulo_t{num_items}, d_input); } return d_input; } // %PARAM% LWT lwt 4:16:32 // %PARAM% ALGO_TYPE alg 0:1:2:3 using types = c2h::type_list; using items_per_thread = c2h::enum_type_list; using logical_warp_threads = c2h::enum_type_list; using algorithms = c2h::enum_type_list; using algorithm = c2h::enum_type_list::value>; using cache_load_modifier = c2h::enum_type_list; constexpr int guarded_load_tests_count = 30; template struct total_warps_t { private: static constexpr int max_warps = 2; static constexpr bool is_arch_warp = (logical_warp_threads == cub::detail::warp_threads); static constexpr bool is_pow_of_two = ((logical_warp_threads & (logical_warp_threads - 1)) == 0); static constexpr int total_warps = (is_arch_warp || is_pow_of_two) ? max_warps : 1; public: static constexpr int value() { return total_warps; } }; template struct params_t { using type = typename c2h::get<0, TestType>; static constexpr int logical_warp_threads = c2h::get<1, TestType>::value; static constexpr int items_per_thread = c2h::get<2, TestType>::value; static constexpr cub::WarpLoadAlgorithm algorithm = c2h::get<3, TestType>::value; static constexpr int total_warps = total_warps_t::value(); static constexpr int tile_size = logical_warp_threads * items_per_thread; static constexpr int total_item_count = total_warps * tile_size; }; C2H_TEST( "Warp load guarded range works with pointer", "[load][warp]", types, logical_warp_threads, items_per_thread, algorithm) { using params = params_t; using type = typename params::type; using delegate_t = guarded_load_t; const int valid_items = GENERATE_COPY(take(guarded_load_tests_count, random(0, params::tile_size - 1))); const auto oob_default = static_cast(valid_items); auto d_in = generate_input(); c2h::device_vector d_error_counter(1, 0); warp_load( thrust::raw_pointer_cast(d_in.data()), delegate_t{valid_items, oob_default}, thrust::raw_pointer_cast(d_error_counter.data())); const int num_errors = d_error_counter[0]; constexpr int expected_error_count = 0; REQUIRE(num_errors == expected_error_count); } C2H_TEST("Warp load guarded range works with cache modified iterator", "[load][warp]", types, logical_warp_threads, items_per_thread, algorithm, cache_load_modifier) { using params = params_t; using type = typename params::type; using delegate_t = guarded_load_t; constexpr cub::CacheLoadModifier load_modifier = c2h::get<4, TestType>::value; const int valid_items = GENERATE_COPY(take(guarded_load_tests_count, random(0, params::tile_size - 1))); const auto oob_default = static_cast(valid_items); auto d_in = generate_input(); auto in_it = cub::CacheModifiedInputIterator(thrust::raw_pointer_cast(d_in.data())); c2h::device_vector d_error_counter(1, 0); warp_load( in_it, delegate_t{valid_items, oob_default}, thrust::raw_pointer_cast(d_error_counter.data())); const auto num_errors = d_error_counter[0]; constexpr int expected_error_count = 0; REQUIRE(num_errors == expected_error_count); } C2H_TEST("Warp load unguarded range works with pointer", "[load][warp]", types, logical_warp_threads, items_per_thread, algorithm) { using params = params_t; using type = typename params::type; using delegate_t = unguarded_load_t; auto d_in = generate_input(); c2h::device_vector d_error_counter(1, 0); warp_load( thrust::raw_pointer_cast(d_in.data()), delegate_t{}, thrust::raw_pointer_cast(d_error_counter.data())); const auto num_errors = d_error_counter[0]; constexpr int expected_error_count = 0; REQUIRE(num_errors == expected_error_count); } C2H_TEST("Warp load unguarded range works with cache modified iterator", "[load][warp]", types, logical_warp_threads, items_per_thread, algorithm, cache_load_modifier) { using params = params_t; using type = typename params::type; using delegate_t = unguarded_load_t; constexpr cub::CacheLoadModifier load_modifier = c2h::get<4, TestType>::value; auto d_in = generate_input(); auto in_it = cub::CacheModifiedInputIterator(thrust::raw_pointer_cast(d_in.data())); c2h::device_vector d_error_counter(1, 0); warp_load( in_it, delegate_t{}, thrust::raw_pointer_cast(d_error_counter.data())); const auto num_errors = d_error_counter[0]; constexpr int expected_error_count = 0; REQUIRE(num_errors == expected_error_count); } #if ALGO_TYPE == 3 // Test for cub::WarpLoadAlgorithm::WARP_LOAD_VECTORIZE; C2H_TEST("Vectorized warp load with const and non-const datatype and different alignment cases", "[store][warp]", c2h::type_list, logical_warp_threads, items_per_thread, algorithm) { using params = params_t; using type = int; using input_ptr_type = typename params::type; using delegate_t = unguarded_load_t; const int offset_for_elements = GENERATE_COPY(0, 1, 2, 3, 4); auto d_in_ref = generate_input(); c2h::device_vector d_error_counter(1, 0); c2h::device_vector d_in(params::total_item_count + offset_for_elements); thrust::copy_n(d_in_ref.begin(), params::total_item_count, d_in.begin() + offset_for_elements); warp_load(thrust::raw_pointer_cast(d_in.data()) + offset_for_elements, delegate_t{}, thrust::raw_pointer_cast(d_error_counter.data())); const auto num_errors = d_error_counter[0]; constexpr int expected_error_count = 0; REQUIRE(num_errors == expected_error_count); } #endif