/* * Copyright 2008-2013 NVIDIA Corporation * Modifications Copyright© 2019-2025 Advanced Micro Devices, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include "test_param_fixtures.hpp" #include "test_utils.hpp" using VectorTestsParams = ::testing::Types< Params>, Params>, Params>, Params>, Params>>, Params>, Params>, Params>, Params>, Params>>, Params>, Params>>; TESTS_DEFINE(ReduceTests, FullTestsParams); TESTS_DEFINE(ReduceIntegerTests, UnsignedIntegerTestsParams); TESTS_DEFINE(ReducePrimitiveTests, NumericalTestsParams); TESTS_DEFINE(ReduceVectorUnitTests, VectorTestsParams); template struct plus_mod_10 { THRUST_HOST_DEVICE T operator()(T lhs, T rhs) const { return ((lhs % 10) + (rhs % 10)) % 10; } }; template struct is_equal_div_10_reduce { THRUST_HOST_DEVICE bool operator()(const T x, const T& y) const { return ((int) x / 10) == ((int) y / 10); } }; TYPED_TEST(ReduceVectorUnitTests, TestReduceSimple) { using Vector = typename TestFixture::input_type; using T = typename Vector::value_type; SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); Vector v{1, -2, 3}; // no initializer ASSERT_EQ(thrust::reduce(v.begin(), v.end()), 2); // with initializer ASSERT_EQ(thrust::reduce(v.begin(), v.end(), (T) 10), 12); } template int reduce(my_system& system, InputIterator, InputIterator) { system.validate_dispatch(); return 13; } TEST(ReduceTests, TestReduceDispatchExplicit) { SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); thrust::device_vector vec; my_system sys(0); thrust::reduce(sys, vec.begin(), vec.end()); ASSERT_EQ(true, sys.is_valid()); } template int reduce(my_tag, InputIterator, InputIterator) { return 13; } TEST(ReduceTests, TestReduceDispatchImplicit) { SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); thrust::device_vector vec; int result = thrust::reduce(thrust::retag(vec.begin()), thrust::retag(vec.end())); ASSERT_EQ(13, result); } TYPED_TEST(ReducePrimitiveTests, TestReduce) { using T = typename TestFixture::input_type; SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); for (auto size : get_sizes()) { SCOPED_TRACE(testing::Message() << "with size= " << size); for (auto seed : get_seeds()) { SCOPED_TRACE(testing::Message() << "with seed= " << seed); thrust::host_vector h_data = get_random_data(size, get_default_limits::min(), get_default_limits::max(), seed); thrust::device_vector d_data = h_data; T init = 13; T h_result = thrust::reduce(h_data.begin(), h_data.end(), init); T d_result = thrust::reduce(d_data.begin(), d_data.end(), init); test_equality(h_result, d_result, size - 1); } } } TYPED_TEST(ReduceTests, TestReduceMixedTypes) { using Vector = typename TestFixture::input_type; using T = typename Vector::value_type; SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); // make sure we get types for default args and operators correct if constexpr (std::is_floating_point::value) { Vector float_input{1.5, 2.5, 3.5, 4.5}; // float -> int should use using plus operator by default ASSERT_EQ(thrust::reduce(float_input.begin(), float_input.end(), (int) 0), 10); } else { Vector int_input{1, 2, 3, 4}; // int -> float should use using plus operator by default ASSERT_EQ(thrust::reduce(int_input.begin(), int_input.end(), (float) 0.5), 10.5); } } TYPED_TEST(ReduceIntegerTests, TestReduceWithOperator) { using T = typename TestFixture::input_type; SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); for (auto size : get_sizes()) { SCOPED_TRACE(testing::Message() << "with size= " << size); for (auto seed : get_seeds()) { SCOPED_TRACE(testing::Message() << "with seed= " << seed); thrust::host_vector h_data = get_random_data(size, get_default_limits::min(), get_default_limits::max(), seed); thrust::device_vector d_data = h_data; T init = 3; T cpu_result = thrust::reduce(h_data.begin(), h_data.end(), init, plus_mod_10()); T gpu_result = thrust::reduce(d_data.begin(), d_data.end(), init, plus_mod_10()); ASSERT_EQ(cpu_result, gpu_result); } } } template struct plus_mod3 { T* table; plus_mod3(T* table) : table(table) {} THRUST_HOST_DEVICE T operator()(T a, T b) { return table[(int) (a + b)]; } }; TYPED_TEST(ReduceTests, TestReduceWithIndirection) { // add numbers modulo 3 with external lookup table using Vector = typename TestFixture::input_type; using T = typename Vector::value_type; SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); Vector data{0, 1, 2, 1, 2, 0, 1}; Vector table{0, 1, 2, 0, 1, 2}; T result = thrust::reduce(data.begin(), data.end(), T(0), plus_mod3(thrust::raw_pointer_cast(&table[0]))); ASSERT_EQ(result, T(1)); } TYPED_TEST(ReducePrimitiveTests, TestReduceCountingIterator) { using T = typename TestFixture::input_type; SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); size_t const n = 15 * sizeof(T); ASSERT_LE(T(n), truncate_to_max_representable(n)); thrust::counting_iterator h_first = thrust::make_counting_iterator(0); thrust::counting_iterator d_first = thrust::make_counting_iterator(0); T init = random_integer(); T h_result = thrust::reduce(h_first, h_first + n, init); T d_result = thrust::reduce(d_first, d_first + n, init); // we use ASSERT_NEAR because we're testing floating point types if (std::is_floating_point::value) { ASSERT_NEAR(h_result, d_result, h_result * 0.01); } else { ASSERT_EQ(h_result, d_result); } } void TestReduceWithBigIndexesHelper(int magnitude) { thrust::constant_iterator begin(1); thrust::constant_iterator end = begin + (1ll << magnitude); ASSERT_EQ(thrust::distance(begin, end), 1ll << magnitude); long long result = thrust::reduce(thrust::device, begin, end); ASSERT_EQ(result, 1ll << magnitude); } TEST(ReduceTests, TestReduceWithBigIndexes) { SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); TestReduceWithBigIndexesHelper(30); #ifndef THRUST_FORCE_32_BIT_OFFSET_TYPE TestReduceWithBigIndexesHelper(31); TestReduceWithBigIndexesHelper(32); TestReduceWithBigIndexesHelper(33); #endif } __global__ THRUST_HIP_LAUNCH_BOUNDS_DEFAULT void ReduceKernel(int const N, int* in_array, int* result) { if (threadIdx.x == 0) { thrust::device_ptr in_begin(in_array); thrust::device_ptr in_end(in_array + N); result[0] = thrust::reduce(thrust::hip::par, in_begin, in_end); } } TEST(ReduceTests, TestReduceDevice) { SCOPED_TRACE(testing::Message() << "with device_id= " << test::set_device_from_ctest()); for (auto size : get_sizes()) { SCOPED_TRACE(testing::Message() << "with size= " << size); for (auto seed : get_seeds()) { SCOPED_TRACE(testing::Message() << "with seed= " << seed); thrust::host_vector h_data = get_random_data(size, 0, 15, seed); thrust::device_vector d_data = h_data; thrust::device_vector d_output(1, 0); int h_output = thrust::reduce(h_data.begin(), h_data.end()); hipLaunchKernelGGL( ReduceKernel, dim3(1, 1, 1), dim3(128, 1, 1), 0, 0, size, thrust::raw_pointer_cast(&d_data[0]), thrust::raw_pointer_cast(&d_output[0])); HIP_CHECK(hipGetLastError()); ASSERT_EQ(h_output, d_output[0]); } } }