From 12a8883c48b8ac03595bb4c5eb79d2fa53ff4599 Mon Sep 17 00:00:00 2001 From: arai713 <67439843+arai713@users.noreply.github.com> Date: Mon, 18 Dec 2023 19:35:00 -0800 Subject: [PATCH] Hip tensor permute unit test (#1068) * adding files for F32 example * adding functioning implementation with scalar multiplication and unary operator support * added fp 16 type check in unary square * updating scalar multiplication as an operator * functioning version with scalar operator * changing strides for col major * updated column major implementation * working column major implementation * cleaned up comments, rearranged/renamed files * small edits to 3d transpose profiler * adding test/profiler/instance files for hipTensor permute unit test * added more test instances * cleaned up errors, randomized input tensor, added more instances * turned off time printouts * removed conflicting transpose profiler * rearranged some files --- .../elementwise_permute_4D_fp16_col.cpp | 11 +- .../elementwise_permute_4D_fp32_col.cpp | 4 +- .../gpu/permute_scale.hpp | 77 +++++++ .../gpu/permute_scale/CMakeLists.txt | 2 + .../device_permute_scale_instances.cpp | 56 +++++ .../device_transpose_instances_3d.cpp | 8 - test/CMakeLists.txt | 1 + test/permute_scale/CMakeLists.txt | 6 + test/permute_scale/test_permute_scale.cpp | 36 +++ .../permute_scale/test_permute_scale_impl.hpp | 212 ++++++++++++++++++ 10 files changed, 399 insertions(+), 14 deletions(-) create mode 100644 library/include/ck/library/tensor_operation_instance/gpu/permute_scale.hpp create mode 100644 library/src/tensor_operation_instance/gpu/permute_scale/CMakeLists.txt create mode 100644 library/src/tensor_operation_instance/gpu/permute_scale/device_permute_scale_instances.cpp create mode 100644 test/permute_scale/CMakeLists.txt create mode 100644 test/permute_scale/test_permute_scale.cpp create mode 100644 test/permute_scale/test_permute_scale_impl.hpp diff --git a/example/44_elementwise_permute/elementwise_permute_4D_fp16_col.cpp b/example/44_elementwise_permute/elementwise_permute_4D_fp16_col.cpp index 9ed078f77e..f496d26a8a 100644 --- a/example/44_elementwise_permute/elementwise_permute_4D_fp16_col.cpp +++ b/example/44_elementwise_permute/elementwise_permute_4D_fp16_col.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "ck/ck.hpp" #include "ck/tensor_operation/gpu/element/binary_element_wise_operation.hpp" @@ -48,10 +49,8 @@ void host_elementwise4D(HostTensorB& B_nhwc, for(std::size_t n = 0; n < N; ++n) { ADataType tmp_val; - // auto a_val = A_nchw(n, c, h, w); auto a_val = A_nchw.mData[(n) + (c * N) + (h * C * N) + (w * H * C * N)]; functor_b(tmp_val, a_val); - // functor_a(B_nhwc(n, h, w, c), scale * tmp_val); functor_a(B_nhwc.mData[(n) + (c * W * H * N) + (h * N) + (w * H * N)], scale * tmp_val); } @@ -62,12 +61,14 @@ int main() bool do_verification = true; bool time_kernel = true; - std::vector nchw = {4, 2, 1, 8}; - std::vector nhwc = {4, 1, 8, 2}; + std::vector nchw = {16, 8, 32, 64}; + std::vector nhwc = {16, 32, 64, 8}; Tensor a(nchw); Tensor b(nhwc); float scale = 1.f; auto i = 0; + std::mt19937 gen(11939); + std::uniform_int_distribution dis(0, 1); for(std::size_t w = 0; w < a.mDesc.GetLengths()[3]; ++w) for(std::size_t h = 0; h < a.mDesc.GetLengths()[2]; ++h) for(std::size_t c = 0; c < a.mDesc.GetLengths()[1]; ++c) @@ -75,7 +76,7 @@ int main() { a.mData[(n * nchw[1] * nchw[2] * nchw[3]) + (c * nchw[2] * nchw[3]) + (h * nchw[3]) + w] = i; - i++; + i = dis(gen); } DeviceMem a_device_buf(sizeof(ADataType) * a.mDesc.GetElementSpaceSize()); diff --git a/example/44_elementwise_permute/elementwise_permute_4D_fp32_col.cpp b/example/44_elementwise_permute/elementwise_permute_4D_fp32_col.cpp index be8894f2b2..619f481357 100644 --- a/example/44_elementwise_permute/elementwise_permute_4D_fp32_col.cpp +++ b/example/44_elementwise_permute/elementwise_permute_4D_fp32_col.cpp @@ -67,6 +67,8 @@ int main() float scale = 1.f; auto i = 0; + std::mt19937 gen(11939); + std::uniform_int_distribution dis(0, 1); for(std::size_t w = 0; w < a.mDesc.GetLengths()[3]; ++w) for(std::size_t h = 0; h < a.mDesc.GetLengths()[2]; ++h) for(std::size_t c = 0; c < a.mDesc.GetLengths()[1]; ++c) @@ -74,7 +76,7 @@ int main() { a.mData[(n * nchw[1] * nchw[2] * nchw[3]) + (c * nchw[2] * nchw[3]) + (h * nchw[3]) + w] = i; - i++; + i = dis(gen); } DeviceMem a_device_buf(sizeof(ADataType) * a.mDesc.GetElementSpaceSize()); diff --git a/library/include/ck/library/tensor_operation_instance/gpu/permute_scale.hpp b/library/include/ck/library/tensor_operation_instance/gpu/permute_scale.hpp new file mode 100644 index 0000000000..6ea1244c57 --- /dev/null +++ b/library/include/ck/library/tensor_operation_instance/gpu/permute_scale.hpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2023, Advanced Micro Devices, Inc. All rights reserved. + +#pragma once + +#include + +#include "ck/ck.hpp" +#include "ck/tensor_operation/gpu/device/tensor_layout.hpp" +#include "ck/tensor_operation/gpu/device/device_elementwise_scale.hpp" +#include "ck/tensor_operation/gpu/element/element_wise_operation.hpp" + +#include "ck/library/tensor_operation_instance/device_operation_instance_factory.hpp" + +namespace ck { +namespace tensor_operation { +namespace device { +namespace instance { + +void add_device_permute_scale_f16_instances( + std::vector, + ck::Tuple, + PassThrough, + element_wise::UnarySquare, + Scale, + 4>>>&); + +void add_device_permute_scale_f32_instances( + std::vector, + ck::Tuple, + PassThrough, + element_wise::UnarySquare, + Scale, + 4>>>&); + +template +struct DeviceOperationInstanceFactory< + ck::tensor_operation::device::DeviceElementwise> +{ + using DeviceOp = DeviceElementwise; + + static auto GetInstances() + { + std::vector> op_ptrs; + if constexpr(is_same_v> && + is_same_v>) + { + add_device_permute_scale_f32_instances(op_ptrs); + } + else if constexpr(is_same_v> && + is_same_v>) + { + add_device_permute_scale_f16_instances(op_ptrs); + } + return op_ptrs; + } +}; + +} // namespace instance +} // namespace device +} // namespace tensor_operation +} // namespace ck diff --git a/library/src/tensor_operation_instance/gpu/permute_scale/CMakeLists.txt b/library/src/tensor_operation_instance/gpu/permute_scale/CMakeLists.txt new file mode 100644 index 0000000000..8b45c1ab07 --- /dev/null +++ b/library/src/tensor_operation_instance/gpu/permute_scale/CMakeLists.txt @@ -0,0 +1,2 @@ +add_instance_library(device_permute_scale_instance + device_permute_scale_instances.cpp) diff --git a/library/src/tensor_operation_instance/gpu/permute_scale/device_permute_scale_instances.cpp b/library/src/tensor_operation_instance/gpu/permute_scale/device_permute_scale_instances.cpp new file mode 100644 index 0000000000..fbbedd52e8 --- /dev/null +++ b/library/src/tensor_operation_instance/gpu/permute_scale/device_permute_scale_instances.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2023, Advanced Micro Devices, Inc. All rights reserved. + +#include "ck/ck.hpp" +#include "ck/tensor_operation/gpu/device/impl/device_elementwise_scale_impl.hpp" +#include "ck/utility/data_type.hpp" + +#include "ck/library/tensor_operation_instance/add_device_operation_instance.hpp" + +namespace ck { +namespace tensor_operation { +namespace device { +namespace instance { + +using F16 = ck::half_t; +using F32 = float; + +using Pass = ck::tensor_operation::element_wise::PassThrough; +using UnaryOp = ck::tensor_operation::element_wise::UnarySquare; +using Scale = ck::tensor_operation::element_wise::Scale; + +// clang-format off +using device_permute_scale_f16_instances = + std::tuple < + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 1, ck::Sequence<1>, ck::Sequence<1>>, + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 8, ck::Sequence<1>, ck::Sequence<1>>, + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 4, ck::Sequence<1>, ck::Sequence<1>>, + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 2, ck::Sequence<1>, ck::Sequence<1>> + >; + +using device_permute_scale_f32_instances = std::tuple< + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 1, ck::Sequence<1>, ck::Sequence<1>>, + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 8, ck::Sequence<1>, ck::Sequence<1>>, + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 4, ck::Sequence<1>, ck::Sequence<1>>, + DeviceElementwiseImpl, ck::Tuple, Pass, UnaryOp, Scale, 4, 2, ck::Sequence<1>, ck::Sequence<1>> + >; +// clang-format on + +void add_device_permute_scale_f16_instances( + std::vector, ck::Tuple, Pass, UnaryOp, Scale, 4>>>& instances) +{ + add_device_operation_instances(instances, device_permute_scale_f16_instances{}); +} + +void add_device_permute_scale_f32_instances( + std::vector, ck::Tuple, Pass, UnaryOp, Scale, 4>>>& instances) +{ + add_device_operation_instances(instances, device_permute_scale_f32_instances{}); +} + +} // namespace instance +} // namespace device +} // namespace tensor_operation +} // namespace ck diff --git a/library/src/tensor_operation_instance/gpu/transpose/device_transpose_instances_3d.cpp b/library/src/tensor_operation_instance/gpu/transpose/device_transpose_instances_3d.cpp index 4efeb81885..0357af149c 100644 --- a/library/src/tensor_operation_instance/gpu/transpose/device_transpose_instances_3d.cpp +++ b/library/src/tensor_operation_instance/gpu/transpose/device_transpose_instances_3d.cpp @@ -19,22 +19,14 @@ void add_device_transpose_f16_instances( std::vector, ck::Tuple, PassThrough, 5>>>& instances) { -#ifdef CK_ENABLE_FP16 add_device_operation_instances(instances, device_transpose_f16_instances{}); -#else - ignore = instances; -#endif } void add_device_transpose_f32_instances( std::vector, ck::Tuple, PassThrough, 5>>>& instances) { -#ifdef CK_ENABLE_FP32 add_device_operation_instances(instances, device_transpose_f32_instances{}); -#else - ignore = instances; -#endif } } // namespace instance diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6f7e18b0e7..94c5f2750f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -150,6 +150,7 @@ add_subdirectory(batched_gemm_multi_d) add_subdirectory(grouped_convnd_bwd_data) add_subdirectory(conv_tensor_rearrange) add_subdirectory(transpose) +add_subdirectory(permute_scale) add_subdirectory(wrapper) if(GPU_TARGETS MATCHES "gfx11") add_subdirectory(wmma_op) diff --git a/test/permute_scale/CMakeLists.txt b/test/permute_scale/CMakeLists.txt new file mode 100644 index 0000000000..be6aaf94aa --- /dev/null +++ b/test/permute_scale/CMakeLists.txt @@ -0,0 +1,6 @@ +add_custom_target(test_permute) +add_gtest_executable(test_permute_scale test_permute_scale.cpp) +if(result EQUAL 0) + target_link_libraries(test_permute_scale PRIVATE utility device_permute_scale_instance) + add_dependencies(test_permute test_permute_scale) +endif() diff --git a/test/permute_scale/test_permute_scale.cpp b/test/permute_scale/test_permute_scale.cpp new file mode 100644 index 0000000000..518d3fc87a --- /dev/null +++ b/test/permute_scale/test_permute_scale.cpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2023, Advanced Micro Devices, Inc. All rights reserved. + +#include "gtest/gtest.h" +#include "test_permute_scale_impl.hpp" + +using F16 = ck::half_t; +using F32 = float; +using ck::index_t; + +template +class TestPermute : public ::testing::Test +{ + protected: + using ADataType = std::tuple_element_t<0, Tuple>; + using BDataType = std::tuple_element_t<1, Tuple>; + + void Run() + { + std::vector> lengths = { + {4, 2, 1, 8}, {1, 1, 1, 1}, {16, 8, 32, 64}, {32, 64, 128, 128}}; + + for(auto length : lengths) + { + bool success = + ck::test_permute_scale_impl(true, 2, false, false, length); + EXPECT_TRUE(success); + } + } +}; + +using KernelTypes = ::testing::Types, std::tuple>; + +TYPED_TEST_SUITE(TestPermute, KernelTypes); +TYPED_TEST(TestPermute, Test_FP16) { this->Run(); } +TYPED_TEST(TestPermute, Test_FP32) { this->Run(); } diff --git a/test/permute_scale/test_permute_scale_impl.hpp b/test/permute_scale/test_permute_scale_impl.hpp new file mode 100644 index 0000000000..3837e7ef5a --- /dev/null +++ b/test/permute_scale/test_permute_scale_impl.hpp @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2018-2023, Advanced Micro Devices, Inc. All rights reserved. + +#pragma once + +#include +#include + +#include "ck/ck.hpp" +#include "ck/tensor_operation/gpu/device/tensor_layout.hpp" +#include "ck/tensor_operation/gpu/device/device_elementwise_scale.hpp" +#include "ck/tensor_operation/gpu/element/element_wise_operation.hpp" +#include "ck/tensor_operation/gpu/device/impl/device_elementwise_scale_impl.hpp" + +#include "ck/library/tensor_operation_instance/gpu/permute_scale.hpp" + +#include "ck/library/utility/check_err.hpp" +#include "ck/library/utility/device_memory.hpp" +#include "ck/library/utility/host_tensor.hpp" +#include "ck/library/utility/host_tensor_generator.hpp" +#include "ck/library/utility/literals.hpp" + +namespace ck { +template +void host_elementwise4D(HostTensorB& B_nhwc, + const HostTensorA& A_nchw, + FunctorA functor_a, + FunctorB functor_b, + float scale) +{ + std::size_t N = A_nchw.mDesc.GetLengths()[0]; + std::size_t C = A_nchw.mDesc.GetLengths()[1]; + std::size_t H = A_nchw.mDesc.GetLengths()[2]; + std::size_t W = A_nchw.mDesc.GetLengths()[3]; + for(std::size_t w = 0; w < W; ++w) + for(std::size_t h = 0; h < H; ++h) + for(std::size_t c = 0; c < C; ++c) + for(std::size_t n = 0; n < N; ++n) + { + using tmp_type = ck::remove_reference_t; + tmp_type tmp_val = 0; + auto a_val = A_nchw.mData[(n) + (c * N) + (h * C * N) + (w * H * C * N)]; + functor_b(tmp_val, a_val); + functor_a(B_nhwc.mData[(n) + (c * W * H * N) + (h * N) + (w * H * N)], + scale * tmp_val); + } +} + +template +bool test_permute_scale_impl(int do_verification, + int init_method, + bool do_log, + bool time_kernel, + std::vector lengths) +{ + bool pass = true; + + using ElementOp = ck::tensor_operation::element_wise::PassThrough; + using UnaryOp = ck::tensor_operation::element_wise::UnarySquare; + using Scale = ck::tensor_operation::element_wise::Scale; + float scale = 2.f; + + index_t N = lengths[0]; + index_t C = lengths[1]; + index_t H = lengths[2]; + index_t W = lengths[3]; + + std::vector nchw = {N, C, H, W}; + std::vector nhwc = {N, H, W, C}; + Tensor a(nchw); + Tensor b(nhwc); + Tensor host_b(nhwc); + + std::array ab_lengths; + + std::array a_strides = {1, + static_cast(nchw[0]), + static_cast(nchw[0] * nchw[1]), + static_cast(nchw[0] * nchw[1] * nchw[2])}; + + std::array b_strides = {1, + static_cast(nhwc[0] * nhwc[1] * nhwc[2]), + static_cast(nhwc[0]), + static_cast(nhwc[0] * nhwc[1])}; + ck::ranges::copy(nchw, ab_lengths.begin()); + + std::cout << "A: " << a.mDesc << std::endl; + std::cout << "B: " << b.mDesc << std::endl; + + switch(init_method) + { + case 0: break; + case 1: a.GenerateTensorValue(GeneratorTensor_2{-1, 2}); break; + default: // a.GenerateTensorValue(GeneratorTensor_3{0.0, 1.0} + std::mt19937 gen(11939); + std::uniform_int_distribution dis(0, 1); + auto i = 0; + for(std::size_t w = 0; w < a.mDesc.GetLengths()[3]; ++w) + for(std::size_t h = 0; h < a.mDesc.GetLengths()[2]; ++h) + for(std::size_t c = 0; c < a.mDesc.GetLengths()[1]; ++c) + for(std::size_t n = 0; n < a.mDesc.GetLengths()[0]; ++n) + { + a.mData[(n * nchw[1] * nchw[2] * nchw[3]) + (c * nchw[2] * nchw[3]) + + (h * nchw[3]) + w] = i; + i = dis(gen); + } + } + + DeviceMem a_device_buf(sizeof(ADataType) * a.mDesc.GetElementSpaceSize()); + DeviceMem b_device_buf(sizeof(BDataType) * b.mDesc.GetElementSpaceSize()); + + a_device_buf.ToDevice(a.mData.data()); + + std::array input = {a_device_buf.GetDeviceBuffer()}; + std::array output = {b_device_buf.GetDeviceBuffer()}; + using DeviceOp = ck::tensor_operation::device::DeviceElementwise, + ck::Tuple, + ElementOp, + UnaryOp, + Scale, + NumDim>; + + // get device op instances + const auto op_ptrs = ck::tensor_operation::device::instance::DeviceOperationInstanceFactory< + DeviceOp>::GetInstances(); + + std::cout << "found " << op_ptrs.size() << " instances" << std::endl; + + std::string best_instance_name; + float best_ave_time = std::numeric_limits::max(); + float best_gb_per_sec = 0; + float best_tflops = 0; + + if(do_verification) + { + host_elementwise4D(host_b, a, ElementOp{}, UnaryOp{}, scale); + } + + for(auto& op_ptr : op_ptrs) + { + auto argument_ptr = op_ptr->MakeArgumentPointer(ab_lengths, + {a_strides}, + {b_strides}, + input, + output, + ElementOp{}, + UnaryOp{}, + Scale{scale}); + + auto invoker_ptr = op_ptr->MakeInvokerPointer(); + + if(op_ptr->IsSupportedArgument(argument_ptr.get())) + { + b_device_buf.SetZero(); + + invoker_ptr->Run(argument_ptr.get(), StreamConfig{nullptr, false}); + + if(do_verification) + { + b_device_buf.FromDevice(b.mData.data()); + + pass &= ck::utils::check_err( + b.mData, host_b.mData, "Error: Incorrect results b", 1e-3, 1e-3); + + if(do_log) + { + LogRangeAsType(std::cout << "a : ", a.mData, ",") << std::endl; + LogRangeAsType(std::cout << "b: ", b.mData, ",") << std::endl; + } + } + + std::string op_name = op_ptr->GetTypeString(); + + float ave_time = + invoker_ptr->Run(argument_ptr.get(), StreamConfig{nullptr, time_kernel}); + + std::size_t flop = std::size_t(2) * nchw[0] * nchw[1] * nchw[2] * nchw[3]; + + std::size_t num_btype = sizeof(ADataType) * (nchw[0] * nchw[1] * nchw[2] * nchw[3]) + + sizeof(BDataType) * (nchw[0] * nchw[1] * nchw[2] * nchw[3]); + + float tflops = static_cast(flop) / 1.E9 / ave_time; + + float gb_per_sec = num_btype / 1.E6 / ave_time; + + std::cout << "Perf: " << std::setw(10) << ave_time << " ms, " << tflops << " TFlops, " + << gb_per_sec << " GB/s, " << op_name << std::endl; + + if(tflops > best_tflops) + { + best_instance_name = op_name; + best_tflops = tflops; + best_ave_time = ave_time; + best_gb_per_sec = gb_per_sec; + } + } + else + { + std::cout << op_ptr->GetTypeString() << " does not support this problem" << std::endl; + } + } + if(time_kernel) + { + LogRange(std::cout << "length = ", lengths, ",") << ", "; + std::cout << "best perf = " << best_ave_time << " ms, " << best_gb_per_sec << " GB/s, " + << best_instance_name << std::endl; + } + + return true; +} + +} // namespace ck