// SPDX-License-Identifier: MIT // Copyright (c) 2018-2022, Advanced Micro Devices, Inc. All rights reserved. #include #include #include #include #include "ck/ck.hpp" #include "ck/tensor_operation/gpu/device/tensor_layout.hpp" #include "ck/tensor_operation/gpu/device/gemm_specialization.hpp" #include "ck/tensor_operation/gpu/device/device_grouped_gemm_xdl.hpp" #include "ck/tensor_operation/gpu/element/element_wise_operation.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/reference_tensor_operation/cpu/reference_gemm.hpp" template using S = ck::Sequence; using F16 = ck::half_t; using F32 = float; using Row = ck::tensor_layout::gemm::RowMajor; using Col = ck::tensor_layout::gemm::ColumnMajor; using PassThrough = ck::tensor_operation::element_wise::PassThrough; using ADataType = F16; using BDataType = F16; using AccDataType = F32; using CShuffleDataType = F16; using DsDataType = ck::Tuple<>; using EDataType = F16; using ALayout = Row; using BLayout = Col; using DsLayout = ck::Tuple<>; using ELayout = Row; using AElementOp = PassThrough; using BElementOp = PassThrough; using CDEElementOp = PassThrough; static constexpr auto GemmDefault = ck::tensor_operation::device::GemmSpecialization::Default; using DeviceGemmInstance = ck::tensor_operation::device::DeviceGroupedGemm_Xdl // clang-format off //######| ALayout| BLayout| DsLayout| ELayout| AData| BData| AccData| CShuffle| DsData| EData| A| B| CDE| GEMM| NumGemmK| Block| MPer| NPer| KPer| AK1| BK1| MPer| NPer| MXdl| NXdl| ABlockTransfer| ABlockTransfer| ABlockTransfer| ABlockTransfer| ABlockTransfer| ABlockTransfer| ABlockLds| BBlockTransfer| BBlockTransfer| BBlockTransfer| BlockTransfer| BBlockTransfer| BBlockTransfer| BBlockLds| CShuffle| CShuffle| CBlockTransferClusterLengths| CBlockTransfer| //######| | | | | Type| Type| Type| DataType| Type| Type| Elementwise| Elementwise| Elementwise| Spacialization| Prefetch| Size| Block| Block| Block| | | XDL| XDL| Per| Per| ThreadCluster| ThreadCluster| SrcAccessOrder| SrcVectorDim| SrcScalar| DstScalar| AddExtraM| ThreadCluster| ThreadCluster| SrcAccessOrder| SrcVectorDim| SrcScalar| DstScalar| AddExtraN| MXdlPerWave| NXdlPerWave| _MBlock_MWaveMPerXdl| ScalarPerVector| //######| | | | | | | | | | | Operation| Operation| Operation| | Stage| | | | | | | | | Wave| Wave| Lengths_K0_M_K1| ArrangeOrder| | | PerVector| PerVector_K1| | Lengths_K0_N_K1| ArrangeOrder| | | PerVector| PerVector_K1| | PerShuffle| PerShuffle| _NBlock_NWaveNPerXdl| _NWaveNPerXdl| //######| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | < ALayout, BLayout, DsLayout, ELayout, ADataType, BDataType, AccDataType, CShuffleDataType, DsDataType, EDataType, AElementOp, BElementOp, CDEElementOp, GemmDefault, 1, 256, 256, 128, 32, 8, 8, 32, 32, 4, 2, S<4, 64, 1>, S<1, 0, 2>, S<1, 0, 2>, 2, 8, 8, 1, S<4, 64, 1>, S<1, 0, 2>, S<1, 0, 2>, 2, 8, 8, 1, 1, 1, S<1, 32, 1, 8>, 8>; // clang-format on using ReferenceGemmInstance = ck::tensor_operation::host::ReferenceGemm; int main(int argc, char* argv[]) { bool do_verification = true; int init_method = 1; bool time_kernel = false; if(argc == 4) { do_verification = std::stoi(argv[1]); init_method = std::stoi(argv[2]); time_kernel = std::stoi(argv[3]); } else { printf("arg1: verification (0=no, 1=yes)\n"); printf("arg2: initialization (0=no init, 1=integer value, 2=decimal value)\n"); printf("arg3: time kernel (0=n0, 1=yes)\n"); exit(0); } int group_count = rand() % 16 + 1; // GEMM shape std::vector gemm_descs; std::vector p_a, p_b; std::vector p_c; gemm_descs.reserve(group_count); for(int i = 0; i < group_count; i++) { int M = 256 + 256 * i; int N = 128 + 128 * i; int K = 64 + 64 * i; int stride_A = K; int stride_B = K; int stride_C = N; gemm_descs.push_back({M, N, K, stride_A, stride_B, stride_C, {}}); } auto f_host_tensor_descriptor = [](std::size_t row, std::size_t col, std::size_t stride, auto layout) { if(std::is_same::value) { return HostTensorDescriptor(std::vector({row, col}), std::vector({stride, 1})); } else { return HostTensorDescriptor(std::vector({row, col}), std::vector({1, stride})); } }; std::vector> a_tensors; std::vector> b_tensors; std::vector> c_host_tensors; std::vector> c_device_tensors; a_tensors.reserve(group_count); b_tensors.reserve(group_count); c_host_tensors.reserve(group_count); c_device_tensors.reserve(group_count); using DeviceMemPtr = std::unique_ptr; std::vector a_tensors_device, b_tensors_device, c_tensors_device; a_tensors_device.reserve(group_count); b_tensors_device.reserve(group_count); c_tensors_device.reserve(group_count); std::size_t flop = 0, num_btype = 0; for(std::size_t i = 0; i < gemm_descs.size(); i++) { a_tensors.push_back(Tensor(f_host_tensor_descriptor( gemm_descs[i].M_, gemm_descs[i].K_, gemm_descs[i].stride_A_, ALayout{}))); b_tensors.push_back(Tensor(f_host_tensor_descriptor( gemm_descs[i].K_, gemm_descs[i].N_, gemm_descs[i].stride_B_, BLayout{}))); c_host_tensors.push_back(Tensor(f_host_tensor_descriptor( gemm_descs[i].M_, gemm_descs[i].N_, gemm_descs[i].stride_C_, ELayout{}))); c_device_tensors.push_back(Tensor(f_host_tensor_descriptor( gemm_descs[i].M_, gemm_descs[i].N_, gemm_descs[i].stride_C_, ELayout{}))); std::cout << "gemm[" << i << "] a_m_k: " << a_tensors[i].mDesc << " b_k_n: " << b_tensors[i].mDesc << " c_m_n: " << c_device_tensors[i].mDesc << std::endl; flop += std::size_t(2) * gemm_descs[i].M_ * gemm_descs[i].K_ * gemm_descs[i].N_; num_btype += sizeof(ADataType) * a_tensors[i].mDesc.GetElementSize() + sizeof(BDataType) * b_tensors[i].mDesc.GetElementSize() + sizeof(EDataType) * c_device_tensors[i].mDesc.GetElementSize(); switch(init_method) { case 0: break; case 1: a_tensors[i].GenerateTensorValue(GeneratorTensor_2{-5, 5}); b_tensors[i].GenerateTensorValue(GeneratorTensor_2{-5, 5}); break; case 2: a_tensors[i].GenerateTensorValue(GeneratorTensor_3{0.0, 1.0}); b_tensors[i].GenerateTensorValue(GeneratorTensor_3{-0.5, 0.5}); break; default: a_tensors[i].GenerateTensorValue(GeneratorTensor_Sequential<0>{}); b_tensors[i].GenerateTensorValue(GeneratorTensor_Sequential<1>{}); } } for(std::size_t i = 0; i < gemm_descs.size(); i++) { a_tensors_device.emplace_back(std::make_unique( sizeof(ADataType) * a_tensors[i].mDesc.GetElementSpaceSize())); b_tensors_device.emplace_back(std::make_unique( sizeof(BDataType) * b_tensors[i].mDesc.GetElementSpaceSize())); c_tensors_device.emplace_back(std::make_unique( sizeof(EDataType) * c_device_tensors[i].mDesc.GetElementSpaceSize())); a_tensors_device[i]->ToDevice(a_tensors[i].mData.data()); b_tensors_device[i]->ToDevice(b_tensors[i].mData.data()); p_a.push_back(a_tensors_device[i]->GetDeviceBuffer()); p_b.push_back(b_tensors_device[i]->GetDeviceBuffer()); p_c.push_back(c_tensors_device[i]->GetDeviceBuffer()); } auto a_element_op = AElementOp{}; auto b_element_op = BElementOp{}; auto c_element_op = CDEElementOp{}; auto gemm = DeviceGemmInstance{}; auto invoker = gemm.MakeInvoker(); std::vector> p_Ds = {}; // do GEMM auto argument = gemm.MakeArgument( p_a, p_b, p_Ds, p_c, gemm_descs, a_element_op, b_element_op, c_element_op); DeviceMem gemm_desc_workspace(gemm.GetWorkSpaceSize(&argument)); gemm.SetWorkSpacePointer(&argument, gemm_desc_workspace.GetDeviceBuffer()); if(!gemm.IsSupportedArgument(argument)) { throw std::runtime_error( "wrong! device_gemm with the specified compilation parameters does " "not support this GEMM problem"); } float ave_time = invoker.Run(argument, StreamConfig{nullptr, time_kernel}); float tflops = static_cast(flop) / 1.E9 / ave_time; float gb_per_sec = num_btype / 1.E6 / ave_time; std::cout << "Perf: " << ave_time << " ms, " << tflops << " TFlops, " << gb_per_sec << " GB/s, " << gemm.GetTypeString() << std::endl; bool pass = true; if(do_verification) { for(std::size_t i = 0; i < gemm_descs.size(); i++) { c_tensors_device[i]->FromDevice(c_device_tensors[i].mData.data()); auto ref_gemm = ReferenceGemmInstance{}; auto ref_invoker = ref_gemm.MakeInvoker(); auto ref_argument = ref_gemm.MakeArgument(a_tensors[i], b_tensors[i], c_host_tensors[i], a_element_op, b_element_op, c_element_op); ref_invoker.Run(ref_argument); pass &= ck::utils::check_err(c_device_tensors[i].mData, c_host_tensors[i].mData); } } return pass ? 0 : 1; }