/******************************************************************************* * * MIT License * * Copyright (c) 2020-2026 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * *******************************************************************************/ #include #include #include #include "gtest_common.hpp" #include "random.hpp" #include "test_parameter_name_generator.hpp" #include "workspace.hpp" namespace { using TestCase = std::tuple>, NamedParameter, NamedParameter, NamedParameter, NamedParameter>; template void cpu_tensor_reorder(T* dst, const T* src, uint64_t dim_0, uint64_t dim_1, uint64_t dim_2, uint64_t dim_3, uint64_t order_0, uint64_t order_1, uint64_t order_2, uint64_t order_3) { const uint64_t src_dim[4] = {dim_0, dim_1, dim_2, dim_3}; const uint64_t dst_dim[4] = { src_dim[order_0], src_dim[order_1], src_dim[order_2], src_dim[order_3]}; const uint64_t src_stride[4] = { src_dim[1] * src_dim[2] * src_dim[3], src_dim[2] * src_dim[3], src_dim[3], 1}; const uint64_t dst_stride[4] = { dst_dim[1] * dst_dim[2] * dst_dim[3], dst_dim[2] * dst_dim[3], dst_dim[3], 1}; uint64_t itr_src_dim[4] = {0, 0, 0, 0}; uint64_t itr_dst_dim[4] = {0, 0, 0, 0}; for(itr_src_dim[0] = 0; itr_src_dim[0] < src_dim[0]; itr_src_dim[0]++) { for(itr_src_dim[1] = 0; itr_src_dim[1] < src_dim[1]; itr_src_dim[1]++) { for(itr_src_dim[2] = 0; itr_src_dim[2] < src_dim[2]; itr_src_dim[2]++) { for(itr_src_dim[3] = 0; itr_src_dim[3] < src_dim[3]; itr_src_dim[3]++) { itr_dst_dim[0] = itr_src_dim[order_0]; itr_dst_dim[1] = itr_src_dim[order_1]; itr_dst_dim[2] = itr_src_dim[order_2]; itr_dst_dim[3] = itr_src_dim[order_3]; const uint64_t idx_src = itr_src_dim[0] * src_stride[0] + itr_src_dim[1] * src_stride[1] + itr_src_dim[2] * src_stride[2] + itr_src_dim[3] * src_stride[3]; const uint64_t idx_dst = itr_dst_dim[0] * dst_stride[0] + itr_dst_dim[1] * dst_stride[1] + itr_dst_dim[2] * dst_stride[2] + itr_dst_dim[3] * dst_stride[3]; dst[idx_dst] = src[idx_src]; } } } } } template struct cpu_reorder { static void run(T* dst, const T* src, uint64_t dim_0, uint64_t dim_1, uint64_t dim_2, uint64_t dim_3, uint64_t order_0, uint64_t order_1, uint64_t order_2, uint64_t order_3) { cpu_tensor_reorder( dst, src, dim_0, dim_1, dim_2, dim_3, order_0, order_1, order_2, order_3); } }; struct reorder_str { static std::string get(uint32_t order_0, uint32_t order_1, uint32_t order_2, uint32_t order_3) { return ("r" + std::to_string(order_0) + std::to_string(order_1) + std::to_string(order_2) + std::to_string(order_3)); } }; std::string supported_reorder_to_string(uint32_t order_0, uint32_t order_1, uint32_t order_2, uint32_t order_3) { std::string layout_string("N/A"); // NOLINTBEGIN(*-braces-around-statements) if((order_0 == 0) && (order_1 == 1) && (order_2 == 3) && (order_3 == 2)) layout_string = "r0132"; else if((order_0 == 0) && (order_1 == 2) && (order_2 == 1) && (order_3 == 3)) layout_string = "r0213"; else if((order_0 == 0) && (order_1 == 2) && (order_2 == 3) && (order_3 == 1)) layout_string = "r0231"; else if((order_0 == 0) && (order_1 == 3) && (order_2 == 1) && (order_3 == 2)) layout_string = "r0312"; else if((order_0 == 0) && (order_1 == 3) && (order_2 == 2) && (order_3 == 1)) layout_string = "r0321"; else if((order_0 == 1) && (order_1 == 0) && (order_2 == 2) && (order_3 == 3)) layout_string = "r1023"; else if((order_0 == 1) && (order_1 == 0) && (order_2 == 3) && (order_3 == 2)) layout_string = "r1032"; else if((order_0 == 1) && (order_1 == 2) && (order_2 == 0) && (order_3 == 3)) layout_string = "r1203"; else if((order_0 == 1) && (order_1 == 2) && (order_2 == 3) && (order_3 == 0)) layout_string = "r1230"; else if((order_0 == 1) && (order_1 == 3) && (order_2 == 0) && (order_3 == 2)) layout_string = "r1302"; else if((order_0 == 1) && (order_1 == 3) && (order_2 == 2) && (order_3 == 0)) layout_string = "r1320"; else if((order_0 == 2) && (order_1 == 0) && (order_2 == 1) && (order_3 == 3)) layout_string = "r2013"; else if((order_0 == 2) && (order_1 == 0) && (order_2 == 3) && (order_3 == 1)) layout_string = "r2031"; else if((order_0 == 2) && (order_1 == 1) && (order_2 == 0) && (order_3 == 3)) layout_string = "r2103"; else if((order_0 == 2) && (order_1 == 1) && (order_2 == 3) && (order_3 == 0)) layout_string = "r2130"; else if((order_0 == 2) && (order_1 == 3) && (order_2 == 0) && (order_3 == 1)) layout_string = "r2301"; else if((order_0 == 2) && (order_1 == 3) && (order_2 == 1) && (order_3 == 0)) layout_string = "r2310"; else if((order_0 == 3) && (order_1 == 0) && (order_2 == 1) && (order_3 == 2)) layout_string = "r3012"; else if((order_0 == 3) && (order_1 == 0) && (order_2 == 2) && (order_3 == 1)) layout_string = "r3021"; else if((order_0 == 3) && (order_1 == 1) && (order_2 == 0) && (order_3 == 2)) layout_string = "r3102"; else if((order_0 == 3) && (order_1 == 1) && (order_2 == 2) && (order_3 == 0)) layout_string = "r3120"; else if((order_0 == 3) && (order_1 == 2) && (order_2 == 0) && (order_3 == 1)) layout_string = "r3201"; else if((order_0 == 3) && (order_1 == 2) && (order_2 == 1) && (order_3 == 0)) layout_string = "r3210"; else MIOPEN_THROW("Unsupported reorder layout"); // NOLINTEND(*-braces-around-statements) return layout_string; } static const constexpr int RAND_INTEGER_MAX = 120; static const constexpr int RAND_INTEGER_MIN = -88; template void rand_tensor_integer(tensor& t, int max = RAND_INTEGER_MAX, int min = RAND_INTEGER_MIN) { // use integer to random. for(size_t i = 0; i < t.data.size(); i++) t[i] = static_cast(prng::gen_A_to_B(min, max)); } template bool compare_equal(T r1, T r2) { return r1 == r2; } template <> bool compare_equal(double r1, double r2) { return miopen::float_equal(r1, r2); } template <> bool compare_equal(float r1, float r2) { return miopen::float_equal(r1, r2); } struct reorder_invoke_param : public miopen::InvokeParams { ConstData_t src = nullptr; Data_t dst = nullptr; [[maybe_unused]] reorder_invoke_param(ConstData_t src_, Data_t dst_) : src(src_), dst(dst_) {} [[maybe_unused]] reorder_invoke_param(miopen::InvokeType type_, ConstData_t src_, Data_t dst_) : InvokeParams{type_}, src(src_), dst(dst_) { } Data_t GetWorkspace() const { return nullptr; } std::size_t GetWorkspaceSize() const { return 0; } }; inline auto GenCases() { static const std::vector> all_possible_order{ {0, 1, 3, 2}, {0, 2, 1, 3}, {0, 2, 3, 1}, {0, 3, 1, 2}, {0, 3, 2, 1}, {1, 0, 2, 3}, {1, 0, 3, 2}, {1, 2, 0, 3}, {1, 2, 3, 0}, {1, 3, 0, 2}, {1, 3, 2, 0}, {2, 0, 1, 3}, {2, 0, 3, 1}, {2, 1, 0, 3}, {2, 1, 3, 0}, {2, 3, 0, 1}, {2, 3, 1, 0}, {3, 0, 1, 2}, {3, 0, 2, 1}, {3, 1, 0, 2}, {3, 1, 2, 0}, {3, 2, 0, 1}, {3, 2, 1, 0}}; return testing::Combine( MakeNamedParameterCollectionValues>("order", all_possible_order, ","), MakeNamedParameterValues( "dim0", 1U, 2U, static_cast(prng::gen_off_range(3, 4))), MakeNamedParameterValues( "dim1", 3U, 8U, static_cast(prng::gen_off_range(15, 13))), MakeNamedParameterValues( "dim2", 1U, 9U, static_cast(prng::gen_off_range(29, 13))), MakeNamedParameterValues( "dim3", 1U, 9U, static_cast(prng::gen_off_range(29, 13)))); } inline auto GetCases() { static const auto cases = GenCases(); return cases; } } // namespace template struct tensor_reorder_test : public testing::TestWithParam { void SetUp() override { prng::reset_seed(); std::tie(order, dim0, dim1, dim2, dim3) = GetParam(); } // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) void Run() { const int tensor_sz = dim0 * dim1 * dim2 * dim3; const std::vector tensor_len({static_cast(dim0), static_cast(dim1), static_cast(dim2), static_cast(dim3)}); std::vector tensor_strides; std::string layout_default = miopen::tensor_layout_get_default(4); std::string layout_string = miopen::TensorDescriptor::LayoutEnumToStr(miopenTensorNCHW); std::string reorder_string = supported_reorder_to_string(order[0], order[1], order[2], order[3]); miopen::tensor_layout_to_strides(tensor_len, layout_default, layout_string, tensor_strides); tensor t_src(tensor_len, tensor_strides); tensor t_dst(tensor_len, tensor_strides); tensor t_dst_gpu(tensor_len, tensor_strides); rand_tensor_integer(t_src); auto& handle = get_handle(); miopen::ExecutionContext ctx; ctx.SetStream(&handle); const auto reorder_sol = MakeTensorReorderAttributes( ctx, miopen_type{}, dim0, dim1, dim2, dim3, order[0], order[1], order[2], order[3]); ASSERT_NE(reorder_sol, nullptr); size_t workspace_size = reorder_sol->IsSkippable() ? sizeof(T) * tensor_sz : reorder_sol->GetOutputTensorSize(); Workspace wspace{workspace_size}; auto src_dev = handle.Write(t_src.data); const auto invoke_param = reorder_invoke_param{src_dev.get(), wspace.ptr()}; std::vector opArgs = reorder_sol->GetKernelArg(); std::optional invoker_factory( [=](const std::vector& kernels) mutable { return [=](const miopen::Handle& handle_, const miopen::AnyInvokeParams& primitive_param) mutable { decltype(auto) invoke_params = primitive_param.CastTo(); const auto k = handle_.Run(kernels[0]); opArgs[0] = OpKernelArg(invoke_params.dst); opArgs[1] = OpKernelArg(invoke_params.src); k(opArgs); }; }); const std::vector construction_params{ reorder_sol->GetKernelInfo()}; if(invoker_factory.has_value()) { const auto invoker = handle.PrepareInvoker(*invoker_factory, construction_params); // run gpu invoker(handle, invoke_param); // run cpu cpu_reorder::run(t_dst.data.data(), t_src.data.data(), dim0, dim1, dim2, dim3, order[0], order[1], order[2], order[3]); } invoker_factory.reset(); t_dst_gpu.data = wspace.Read(); // we expect excact match, since use integer VerifyTensor(t_dst_gpu, t_dst); } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) private: std::vector order; uint32_t dim0{0}; uint32_t dim1{0}; uint32_t dim2{0}; uint32_t dim3{0}; void VerifyTensor(const tensor& t_gpu, const tensor& t_cpu) { ASSERT_EQ(t_gpu.data.size(), t_cpu.data.size()); const auto idx = miopen::mismatch_idx(t_gpu.data, t_cpu.data, compare_equal); const bool valid_result = idx >= miopen::range_distance(t_cpu); std::cout << "[" << reorder_str::get(order[0], order[1], order[2], order[3]) << ", b" << (sizeof(T) * 8) << " ] " << "dim0:" << dim0 << ", dim1:" << dim1 << ", dim2:" << dim2 << ", dim3:" << dim3 << ", valid:" << valid_result << std::endl; EXPECT_TRUE(valid_result) << "diff at:" << idx << ", gpu:" << t_gpu[idx] << ", cpu:" << t_cpu[idx] << std::endl; } }; struct TestNameGenerator { std::string operator()(const auto& info) { const auto& [order, dim0, dim1, dim2, dim3] = info.param; std::stringstream ss; std::string str; ss << "order_" << GetRangeAsString(order(), ",") << "_dim0_" << dim0() << "_dim1_" << dim1() << "_dim2_" << dim2() << "_dim3_" << dim3() << "_test_id_" << info.index; str = ss.str(); // Name format only supports letters, numbers and underscores. std::transform(str.begin(), str.end(), str.begin(), [](char c) -> char { return (c == '.') ? 'p' : (std::isalnum(c) ? c : '_'); }); return str; } }; using GPU_TensorReorder_I8 = tensor_reorder_test; using GPU_TensorReorder_FP16 = tensor_reorder_test; using GPU_TensorReorder_FP32 = tensor_reorder_test; using GPU_TensorReorder_FP64 = tensor_reorder_test; TEST_P(GPU_TensorReorder_I8, TestInt8) { this->Run(); } TEST_P(GPU_TensorReorder_FP16, TestFloat16) { this->Run(); } TEST_P(GPU_TensorReorder_FP32, TestFloat32) { this->Run(); } TEST_P(GPU_TensorReorder_FP64, TestFloat64) { this->Run(); } INSTANTIATE_TEST_SUITE_P(Smoke, GPU_TensorReorder_I8, GetCases(), TestNameGenerator{}); INSTANTIATE_TEST_SUITE_P(Smoke, GPU_TensorReorder_FP16, GetCases(), TestNameGenerator{}); INSTANTIATE_TEST_SUITE_P(Smoke, GPU_TensorReorder_FP32, GetCases(), TestNameGenerator{}); INSTANTIATE_TEST_SUITE_P(Smoke, GPU_TensorReorder_FP64, GetCases(), TestNameGenerator{});