// Copyright (c) Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #if MIOPEN_USE_HIPBLASLT #include // Only enable BF8 support if hipBLASLt is 0.8 or above #if MIOPEN_HIPBLASLT_VERSION_FLAT >= 8000 #define ENABLE_HIPBLASLT_BF8 #endif #endif #if MIOPEN_USE_ROCBLAS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-macros" #define ROCBLAS_BETA_FEATURES_API 1 #pragma clang diagnostic pop #include #if MIOPEN_ROCBLAS_VERSION_FLAT < 2045000 #include #else #include /// rocblas_gemm_ex3 supports F8 datatypes. #ifdef _WIN32 #define USE_ROCBLAS_GEMM_EX3 \ ((MIOPEN_ROCBLAS_VERSION_FLAT >= 3000000 && MIOPEN_ROCBLAS_VERSION_FLAT < 5000000) && \ ROCBLAS_BETA_FEATURES_API) #else #define USE_ROCBLAS_GEMM_EX3 \ ((MIOPEN_ROCBLAS_VERSION_FLAT >= 2047000 && MIOPEN_ROCBLAS_VERSION_FLAT < 5000000) && \ ROCBLAS_BETA_FEATURES_API) #endif #endif #include #endif #include #include // std::ignore #if MIOPEN_USE_ROCBLAS /// Avoid warnings "The workspace_size and workspace arguments are obsolete" and /// "disabled expansion of recursive macro" injected by rocblas headers. #define AVOID_ROCBLAS_WRAPPERS_204 (MIOPEN_ROCBLAS_VERSION_FLAT >= 2004000) /// Maintain API compatibility for versions not supporting FP16 alternate implementations #define USE_GEMM_FLAGS_FP16_ALT_IMPL (MIOPEN_ROCBLAS_VERSION_FLAT >= 2043000) /// Some 2.42 versions have rocblas_gemm_flags_fp16_alt_impl, but /// some do not, and that leads to build errors. /// Let's pass literal value as a workaround; there should be no harm. #define USE_GEMM_FLAGS_FP16_ALT_IMPL_242 (MIOPEN_ROCBLAS_VERSION_FLAT == 2042000) static inline uint32_t FlagsForRocblasFp32Fp16Call(const miopen::GemmDescriptor& desc) // bool gfx90aFp16Alt) { #if USE_GEMM_FLAGS_FP16_ALT_IMPL return desc.gfx90a_alt_impl ? rocblas_gemm_flags_fp16_alt_impl : 0; #elif USE_GEMM_FLAGS_FP16_ALT_IMPL_242 return desc.gfx90a_alt_impl ? 0x4 : 0; #else std::ignore = desc; MIOPEN_LOG_W("The gfx90aFp16Alt is not supported by rocBlas"); return 0; #endif #if USE_GEMM_FLAGS_FP16_ALT_IMPL_242 // -warning: macro is not used #endif } #if USE_ROCBLAS_GEMM_EX3 static inline rocblas_computetype rocBlasComputeType_ex3(const miopen::GemmDescriptor& desc) { if(desc.a_cast_type == miopenFloat8_fnuz && desc.b_cast_type == miopenFloat8_fnuz) { return rocblas_compute_type_f8_f8_f32; } else if(desc.a_cast_type == miopenFloat8_fnuz && desc.b_cast_type == miopenBFloat8_fnuz) { return rocblas_compute_type_f8_bf8_f32; } else if(desc.a_cast_type == miopenBFloat8_fnuz && desc.b_cast_type == miopenFloat8_fnuz) { return rocblas_compute_type_bf8_f8_f32; } else if(desc.a_cast_type == miopenBFloat8_fnuz && desc.b_cast_type == miopenBFloat8_fnuz) { return rocblas_compute_type_bf8_bf8_f32; } else { return rocblas_compute_type_f32; } } #endif static inline rocblas_datatype rocBlasComputeType(const miopen::GemmDescriptor& desc) { if(desc.dataType == miopenInt8) return rocblas_datatype::rocblas_datatype_i32_r; else return rocblas_datatype::rocblas_datatype_f32_r; } auto rocBlasDataType(miopenDataType_t data_type) { /// \todo Not all supported data types are handled here. /// This is fine so far because this function is used only with FP16/F8. #if USE_ROCBLAS_GEMM_EX3 if(data_type == miopenFloat8_fnuz) return rocblas_datatype::rocblas_datatype_f8_r; if(data_type == miopenBFloat8_fnuz) return rocblas_datatype::rocblas_datatype_bf8_r; #endif if(data_type == miopenHalf) return rocblas_datatype::rocblas_datatype_f16_r; MIOPEN_THROW(miopenStatusInternalError, "Invalid data type passed"); } template rocblas_status miopen_rocblas_gemm_ex3(const miopen::Handle& handle, const miopen::GemmDescriptor& gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset) { rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; // cppcheck-suppress redundantInitialization #if USE_ROCBLAS_GEMM_EX3 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdisabled-macro-expansion" #pragma clang diagnostic ignored "-Wdeprecated-declarations" float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; auto flags = FlagsForRocblasFp32Fp16Call(gemm_desc); if(gemm_desc.conv_attributes.fp8rounding_mode.Get() == miopenF8RoundingModeStochastic) flags = flags | rocblas_gemm_flags::rocblas_gemm_flags_stochastic_rounding; rb_status = // cppcheck-suppress redundantInitialization rocblas_gemm_ex3(handle.rhandle().get(), gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocBlasDataType(gemm_desc.dataType), gemm_desc.lda, static_cast(B) + b_offset, rocBlasDataType(gemm_desc.dataType), gemm_desc.ldb, &beta, static_cast(C) + c_offset, rocBlasDataType(gemm_desc.dataType), gemm_desc.ldc, static_cast(C) + c_offset, rocBlasDataType(gemm_desc.dataType), gemm_desc.ldc, rocBlasComputeType_ex3(gemm_desc), rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, flags); // gfx90a_alt_impl)); return rb_status; #pragma clang diagnostic pop #else std::ignore = A; std::ignore = a_offset; std::ignore = B; std::ignore = b_offset; std::ignore = C; std::ignore = c_offset; #endif MIOPEN_THROW(miopenStatusInternalError, "An appropriate version of rocBLAS is required for this op"); std::ignore = handle; std::ignore = gemm_desc; return rb_status; } template auto miopen_rocblas_gemm_ex(const miopen::Handle& handle, const miopen::GemmDescriptor& gemm_desc, Ts... xs) { std::ignore = handle; std::ignore = gemm_desc; #if AVOID_ROCBLAS_WRAPPERS_204 return (rocblas_gemm_ex)(handle.rhandle().get(), xs...); #else std::size_t zero = 0; return rocblas_gemm_ex(handle.rhandle().get(), xs..., &zero, nullptr); #endif } template auto miopen_rocblas_gemm_strided_batched_ex(Ts... xs) { #if AVOID_ROCBLAS_WRAPPERS_204 return (rocblas_gemm_strided_batched_ex)(xs...); #else std::size_t zero = 0; return rocblas_gemm_strided_batched_ex(xs..., &zero, nullptr); #endif } template rocblas_status miopen_rocblas_gemm_strided_batched_ex3(const miopen::Handle& handle, const miopen::GemmDescriptor& gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset) { rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; // Until there is a batched counter part to the ex3 rocBlas call we need to iterate over the // batched GEMM for(int bCount = 0; bCount < gemm_desc.batch_count; ++bCount) { rb_status = miopen_rocblas_gemm_ex3(handle, gemm_desc, A, a_offset + (bCount * gemm_desc.strideA), B, b_offset + (bCount * gemm_desc.strideB), C, c_offset + (bCount * gemm_desc.strideC)); } return rb_status; } #endif // MIOPEN_USE_ROCBLAS MIOPEN_DECLARE_ENV_VAR_UINT64(MIOPEN_GEMM_ENFORCE_BACKEND) namespace miopen { bool IsFP8Supported(const std::string& device_name) { #if defined(USE_ROCBLAS_GEMM_EX3) && USE_ROCBLAS_GEMM_EX3 return device_name == "gfx942" || miopen::StartsWith(device_name, "gfx95"); #else std::ignore = device_name; return false; #endif } std::ostream& operator<<(std::ostream& stream, const GemmDescriptor& gemm_desc) { return stream << "{" << "isColMajor " << gemm_desc.isColMajor << ", " << "transA " << gemm_desc.transA << ", " << "transB " << gemm_desc.transB << ", " << "m " << gemm_desc.m << ", " << "n " << gemm_desc.n << ", " << "k " << gemm_desc.k << ", " << "lda " << gemm_desc.lda << ", " << "ldb " << gemm_desc.ldb << ", " << "ldc " << gemm_desc.ldc << ", " << "batch_count " << gemm_desc.batch_count << ", " << "strideA " << gemm_desc.strideA << ", " << "strideB " << gemm_desc.strideB << ", " << "strideC " << gemm_desc.strideC << ", " << "alpha " << gemm_desc.alpha << ", " << "beta " << gemm_desc.beta << ", " << "dataType " << GetDataType(gemm_desc.dataType) << ", " << "a_cast_type " << GetDataType(gemm_desc.a_cast_type) << ", " << "b_cast_type " << GetDataType(gemm_desc.b_cast_type) << "} "; } #if MIOPEN_USE_ROCBLAS inline rocblas_atomics_mode DisableRocblasAtomics(const miopen::Handle& handle) { MIOPEN_LOG_I2(""); rocblas_atomics_mode cur_mode; [[maybe_unused]] rocblas_status status = rocblas_get_atomics_mode(handle.rhandle().get(), &cur_mode); assert(status == rocblas_status::rocblas_status_success); if(cur_mode == rocblas_atomics_allowed) { status = rocblas_set_atomics_mode(handle.rhandle().get(), rocblas_atomics_not_allowed); assert(status == rocblas_status::rocblas_status_success); } return cur_mode; } inline void SetRocblasAtomics(const miopen::Handle& handle, rocblas_atomics_mode mode) { MIOPEN_LOG_I2(""); [[maybe_unused]] rocblas_status status = rocblas_set_atomics_mode(handle.rhandle().get(), mode); assert(status == rocblas_status::rocblas_status_success); } #endif inline void ProfilingRecordStart(const Handle& handle, HipEventPtr& start, HipEventPtr& stop) { start = make_hip_event(); stop = make_hip_event(); (void)hipEventRecord(start.get(), handle.GetStream()); } inline void ProfilingRecordStop(const Handle& handle, HipEventPtr& start, HipEventPtr& stop) { (void)hipEventRecord(stop.get(), handle.GetStream()); (void)hipEventSynchronize(stop.get()); float mS = 0; (void)hipEventElapsedTime(&mS, start.get(), stop.get()); handle.ResetKernelTime(); handle.AccumKernelTime(mS); } #if MIOPEN_USE_HIPBLASLT struct HipBLASLtMemoryHandles { hipblasLtMatrixLayout_t matA, matB, matC, matD; hipblasLtMatmulDesc_t matmul; hipblasLtMatmulPreference_t pref; HipBLASLtMemoryHandles() : matA(nullptr), matB(nullptr), matC(nullptr), matD(nullptr), matmul(nullptr), pref(nullptr) { } ~HipBLASLtMemoryHandles() { hipblasLtMatrixLayoutDestroy(matA); hipblasLtMatrixLayoutDestroy(matB); hipblasLtMatrixLayoutDestroy(matC); hipblasLtMatrixLayoutDestroy(matD); hipblasLtMatmulDescDestroy(matmul); hipblasLtMatmulPreferenceDestroy(pref); } }; static inline void check_hipblas_status(hipblasStatus_t status) { if(status != hipblasStatus_t::HIPBLAS_STATUS_SUCCESS) { std::cout << "error msg" << std::endl; MIOPEN_THROW(miopenStatusInternalError, "hipBlasLt error encountered"); } } template static void miopen_hipblasLt_gemm(const miopen::Handle& handle, const miopen::GemmDescriptor& gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, hipDataType hip_type_AB, Data_t C, std::size_t c_offset, hipDataType hip_type_C, bool skip_batches) { HipBLASLtMemoryHandles hipBLASLtHandles; if(gemm_desc.transA) { check_hipblas_status(hipblasLtMatrixLayoutCreate( &hipBLASLtHandles.matA, hip_type_AB, gemm_desc.k, gemm_desc.m, gemm_desc.lda)); } else { check_hipblas_status(hipblasLtMatrixLayoutCreate( &hipBLASLtHandles.matA, hip_type_AB, gemm_desc.m, gemm_desc.k, gemm_desc.lda)); } if(gemm_desc.transB) { check_hipblas_status(hipblasLtMatrixLayoutCreate( &hipBLASLtHandles.matB, hip_type_AB, gemm_desc.n, gemm_desc.k, gemm_desc.ldb)); } else { check_hipblas_status(hipblasLtMatrixLayoutCreate( &hipBLASLtHandles.matB, hip_type_AB, gemm_desc.k, gemm_desc.n, gemm_desc.ldb)); } check_hipblas_status(hipblasLtMatrixLayoutCreate( &hipBLASLtHandles.matC, hip_type_C, gemm_desc.m, gemm_desc.n, gemm_desc.ldc)); check_hipblas_status(hipblasLtMatrixLayoutCreate( &hipBLASLtHandles.matD, hip_type_C, gemm_desc.m, gemm_desc.n, gemm_desc.ldc)); if(gemm_desc.batch_count > 1 && !skip_batches) { check_hipblas_status(hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matA, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &gemm_desc.batch_count, sizeof(gemm_desc.batch_count))); check_hipblas_status( hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matA, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &gemm_desc.strideA, sizeof(gemm_desc.strideA))); check_hipblas_status(hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matB, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &gemm_desc.batch_count, sizeof(gemm_desc.batch_count))); check_hipblas_status( hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matB, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &gemm_desc.strideB, sizeof(gemm_desc.strideB))); check_hipblas_status(hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matC, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &gemm_desc.batch_count, sizeof(gemm_desc.batch_count))); check_hipblas_status( hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matC, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &gemm_desc.strideC, sizeof(gemm_desc.strideC))); check_hipblas_status(hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matD, HIPBLASLT_MATRIX_LAYOUT_BATCH_COUNT, &gemm_desc.batch_count, sizeof(gemm_desc.batch_count))); check_hipblas_status( hipblasLtMatrixLayoutSetAttribute(hipBLASLtHandles.matD, HIPBLASLT_MATRIX_LAYOUT_STRIDED_BATCH_OFFSET, &gemm_desc.strideC, sizeof(gemm_desc.strideC))); } check_hipblas_status( hipblasLtMatmulDescCreate(&hipBLASLtHandles.matmul, HIPBLAS_COMPUTE_32F, HIP_R_32F)); hipblasOperation_t opTypeA = gemm_desc.transA ? HIPBLAS_OP_T : HIPBLAS_OP_N; hipblasOperation_t opTypeB = gemm_desc.transB ? HIPBLAS_OP_T : HIPBLAS_OP_N; check_hipblas_status(hipblasLtMatmulDescSetAttribute( hipBLASLtHandles.matmul, HIPBLASLT_MATMUL_DESC_TRANSA, &opTypeA, sizeof(opTypeA))); check_hipblas_status(hipblasLtMatmulDescSetAttribute( hipBLASLtHandles.matmul, HIPBLASLT_MATMUL_DESC_TRANSB, &opTypeB, sizeof(opTypeB))); hipblasLtEpilogue_t epilogue = HIPBLASLT_EPILOGUE_DEFAULT; check_hipblas_status(hipblasLtMatmulDescSetAttribute( hipBLASLtHandles.matmul, HIPBLASLT_MATMUL_DESC_EPILOGUE, &epilogue, sizeof(epilogue))); /// \todo Need to request additional workspace for optimal gemm performance, and pass down /// workspace size & pointer. --BrianHarrisonAMD June 2024 size_t max_workspace_size = 0; void* workspace = nullptr; check_hipblas_status(hipblasLtMatmulPreferenceCreate(&hipBLASLtHandles.pref)); check_hipblas_status( hipblasLtMatmulPreferenceSetAttribute(hipBLASLtHandles.pref, HIPBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES, &max_workspace_size, sizeof(max_workspace_size))); const int requestSolutions = 1; hipblasLtMatmulHeuristicResult_t heuristicResult[requestSolutions]; int returnedAlgoCount = 0; check_hipblas_status(hipblasLtMatmulAlgoGetHeuristic(handle.HipblasLtHandle().get(), hipBLASLtHandles.matmul, hipBLASLtHandles.matA, hipBLASLtHandles.matB, hipBLASLtHandles.matC, hipBLASLtHandles.matD, hipBLASLtHandles.pref, requestSolutions, heuristicResult, &returnedAlgoCount)); if(returnedAlgoCount == 0) { MIOPEN_THROW(miopenStatusInternalError, "no solution found for hipBLASLt hipBLASLtHandles.matmul"); } float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; const void* aData = static_cast(A) + a_offset; const void* bData = static_cast(B) + b_offset; const void* cData = static_cast(C) + c_offset; void* dData = static_cast(C) + c_offset; { HipEventProfiler profiler(handle); check_hipblas_status(hipblasLtMatmul(handle.HipblasLtHandle().get(), hipBLASLtHandles.matmul, &alpha, aData, hipBLASLtHandles.matA, bData, hipBLASLtHandles.matB, &beta, cData, hipBLASLtHandles.matC, dData, hipBLASLtHandles.matD, &heuristicResult[0].algo, workspace, max_workspace_size, handle.GetStream())); } } static void call_miopen_hipblasLt_gemm(const miopen::Handle& handle, const miopen::GemmDescriptor& gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset, bool skip_batches) { switch(gemm_desc.dataType) { case miopenInt8: { MIOPEN_THROW(miopenStatusInternalError, "miopenInt8 is not supported for hipBLASLt"); } break; case miopenInt32: { MIOPEN_THROW(miopenStatusInternalError, "miopenInt32 is not supported for hipBLASLt"); } break; case miopenHalf: { miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, HIP_R_16F, C, c_offset, HIP_R_16F, skip_batches); } break; case miopenBFloat16: { miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, HIP_R_16BF, C, c_offset, HIP_R_16BF, skip_batches); } break; case miopenFloat: { miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, HIP_R_32F, C, c_offset, HIP_R_32F, skip_batches); } break; case miopenFloat8_fnuz: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; if(is_gfx942) { miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, HIP_R_8F_E4M3_FNUZ, C, c_offset, HIP_R_8F_E4M3_FNUZ, skip_batches); } else { MIOPEN_THROW(miopenStatusInternalError, "miopenFloat8_fnuz is only supported for hipBlasLt on gfx942"); } } break; case miopenBFloat8_fnuz: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; if(is_gfx942) { #ifdef ENABLE_HIPBLASLT_BF8 miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, HIP_R_8F_E5M2_FNUZ, C, c_offset, HIP_R_8F_E5M2_FNUZ, skip_batches); #else MIOPEN_THROW( miopenStatusInternalError, "miopenBFloat8_fnuz is not supported for this version of hipBlasLt on gfx942"); #endif } else { MIOPEN_THROW(miopenStatusInternalError, "miopenBFloat8_fnuz is only supported for hipBlasLt on gfx942"); } } break; case miopenDouble: { MIOPEN_THROW(miopenStatusInternalError, "miopenDouble is not supported for hipBlasLt"); } break; case miopenInt64: { MIOPEN_THROW(miopenStatusInternalError, "miopenInt64 is not supported for hipBlasLt"); } break; } } #endif // hacks: control GEMM backend by enviroment variable and build option // very nasty static GemmBackend_t enforce_gemm_backend(GemmBackend_t gemm_backend_preferred) { GemmBackend_t gemm_backend_env = GemmBackend_t::nogemmbackend; // enforce backend based on env variable // I have left the commented lines here to preserve values for the enforce and hint at why are // they 1, 3, and 5 switch(env::value(MIOPEN_GEMM_ENFORCE_BACKEND)) { #if MIOPEN_USE_ROCBLAS case 1: gemm_backend_env = GemmBackend_t::rocblas; break; #endif // case 2: gemm_backend_env = GemmBackend_t::miopengemm; break; case 3: gemm_backend_env = GemmBackend_t::nogemmbackend; break; // case 4: gemm_backend_env = GemmBackend_t::miopentensile; break; #if MIOPEN_USE_HIPBLASLT case 5: gemm_backend_env = GemmBackend_t::hipblaslt; break; #endif default: gemm_backend_env = gemm_backend_preferred; } return gemm_backend_env; } miopenStatus_t CallGemm(const Handle& handle, GemmDescriptor gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset, GemmBackend_t gemm_backend) { MIOPEN_LOG_I2("gemm_desc: " << gemm_desc); gemm_backend = enforce_gemm_backend(gemm_backend); if(!gemm_desc.isColMajor) { gemm_desc.isColMajor = !gemm_desc.isColMajor; std::swap(A, B); std::swap(a_offset, b_offset); std::swap(gemm_desc.a_cast_type, gemm_desc.b_cast_type); std::swap(gemm_desc.transA, gemm_desc.transB); std::swap(gemm_desc.m, gemm_desc.n); std::swap(gemm_desc.lda, gemm_desc.ldb); } switch(gemm_backend) { case GemmBackend_t::nogemmbackend: return miopenStatusNotImplemented; case GemmBackend_t::rocblas: { #if MIOPEN_USE_ROCBLAS MIOPEN_LOG_I2("rocBLAS"); HipEventPtr start = nullptr; HipEventPtr stop = nullptr; if(handle.IsProfilingEnabled()) { ProfilingRecordStart(handle, start, stop); } rocblas_atomics_mode cur_mode = rocblas_atomics_mode::rocblas_atomics_allowed; // default value from rocblas if(gemm_desc.deterministic) cur_mode = DisableRocblasAtomics(handle); rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; switch(gemm_desc.dataType) { case miopenInt8: { assert(gemm_desc.k % 4 == 0); auto alpha = int(gemm_desc.alpha); auto beta = int(gemm_desc.beta); rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_i8_r, gemm_desc.lda, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_i8_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_i32_r, gemm_desc.ldc, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_i32_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), // rocblas_datatype::rocblas_datatype_i32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } break; case miopenInt32: break; case miopenHalf: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; // We need ex3 API if any of the dataType or the cast type is an 8-bit floating type const auto needs_ex3 = [&]() { if((gemm_desc.dataType == miopenFloat8_fnuz || gemm_desc.dataType == miopenBFloat8_fnuz) || (gemm_desc.a_cast_type == miopenFloat8_fnuz || gemm_desc.a_cast_type == miopenBFloat8_fnuz) || (gemm_desc.b_cast_type == miopenBFloat8_fnuz || gemm_desc.b_cast_type == miopenFloat8_fnuz)) { return true; } else { return false; } }(); // ex3 API only works on the gfx942 ASIC; if(needs_ex3) { if(is_gfx942) { rb_status = miopen_rocblas_gemm_ex3( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset); } else { MIOPEN_THROW(miopenStatusInternalError, "8-bit floating types are only supported on gfx942"); } } else { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.lda, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldc, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, FlagsForRocblasFp32Fp16Call(gemm_desc)); // gfx90a_alt_impl)); } } break; case miopenBFloat16: { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.lda, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldc, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } break; case miopenFloat: { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.lda, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), // rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } break; case miopenFloat8_fnuz: case miopenBFloat8_fnuz: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; if(is_gfx942) { rb_status = miopen_rocblas_gemm_ex3( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset); } else { MIOPEN_THROW(miopenStatusInternalError, "8-bit floating types are only supported on gfx942"); } }; break; case miopenDouble: { MIOPEN_THROW(miopenStatusInternalError, "miopenDouble data type not supported by rocBLAS."); }; break; case miopenInt64: { MIOPEN_THROW(miopenStatusInternalError, "miopenInt64 is not currently supported."); } break; } if(handle.IsProfilingEnabled()) ProfilingRecordStop(handle, start, stop); if(rb_status != rocblas_status::rocblas_status_success) MIOPEN_THROW(miopenStatusInternalError, "rocBlas error encountered"); if(gemm_desc.deterministic) SetRocblasAtomics(handle, cur_mode); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } case GemmBackend_t::hipblaslt: { #if MIOPEN_USE_HIPBLASLT call_miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset, true); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } } return miopenStatusUnknownError; } miopenStatus_t CallGemm(const Handle& handle, GemmDescriptor gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset, miopenDataType_t cType, GemmBackend_t gemm_backend) { // If C/D type matches A/B type, delegate to the standard overload if(cType == gemm_desc.dataType) return CallGemm(handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset, gemm_backend); MIOPEN_LOG_I2("gemm_desc: " << gemm_desc << " cType: " << GetDataType(cType)); gemm_backend = enforce_gemm_backend(gemm_backend); if(!gemm_desc.isColMajor) { gemm_desc.isColMajor = !gemm_desc.isColMajor; std::swap(A, B); std::swap(a_offset, b_offset); std::swap(gemm_desc.a_cast_type, gemm_desc.b_cast_type); std::swap(gemm_desc.transA, gemm_desc.transB); std::swap(gemm_desc.m, gemm_desc.n); std::swap(gemm_desc.lda, gemm_desc.ldb); } switch(gemm_backend) { case GemmBackend_t::nogemmbackend: return miopenStatusNotImplemented; case GemmBackend_t::rocblas: { #if MIOPEN_USE_ROCBLAS MIOPEN_LOG_I2("rocBLAS mixed-precision C/D"); HipEventPtr start = nullptr; HipEventPtr stop = nullptr; if(handle.IsProfilingEnabled()) { ProfilingRecordStart(handle, start, stop); } rocblas_atomics_mode cur_mode = rocblas_atomics_mode::rocblas_atomics_allowed; if(gemm_desc.deterministic) cur_mode = DisableRocblasAtomics(handle); rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; // Currently only bf16 A/B with fp32 C/D is supported if(gemm_desc.dataType == miopenBFloat16 && cType == miopenFloat) { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.lda, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } else { MIOPEN_THROW(miopenStatusInternalError, "CallGemm with cType: unsupported dataType/cType combination"); } if(handle.IsProfilingEnabled()) ProfilingRecordStop(handle, start, stop); if(rb_status != rocblas_status::rocblas_status_success) MIOPEN_THROW(miopenStatusInternalError, "rocBlas error encountered"); if(gemm_desc.deterministic) SetRocblasAtomics(handle, cur_mode); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } case GemmBackend_t::hipblaslt: { MIOPEN_THROW(miopenStatusInternalError, "CallGemm with cType: hipblaslt backend not supported"); } } return miopenStatusUnknownError; } miopenStatus_t CallGemmStridedBatched(const Handle& handle, GemmDescriptor gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset, GemmBackend_t gemm_backend) { MIOPEN_LOG_I2("gemm_desc: " << gemm_desc); gemm_backend = enforce_gemm_backend(gemm_backend); if(!gemm_desc.isColMajor) { gemm_desc.isColMajor = !gemm_desc.isColMajor; std::swap(A, B); std::swap(a_offset, b_offset); std::swap(gemm_desc.a_cast_type, gemm_desc.b_cast_type); std::swap(gemm_desc.transA, gemm_desc.transB); std::swap(gemm_desc.m, gemm_desc.n); std::swap(gemm_desc.lda, gemm_desc.ldb); std::swap(gemm_desc.strideA, gemm_desc.strideB); } switch(gemm_backend) { case GemmBackend_t::nogemmbackend: return miopenStatusNotImplemented; case GemmBackend_t::rocblas: { #if MIOPEN_USE_ROCBLAS MIOPEN_LOG_I2("rocBLAS"); HipEventPtr start = nullptr; HipEventPtr stop = nullptr; if(handle.IsProfilingEnabled()) { ProfilingRecordStart(handle, start, stop); } rocblas_atomics_mode cur_mode = rocblas_atomics_mode::rocblas_atomics_allowed; // default value from rocblas if(gemm_desc.deterministic) cur_mode = DisableRocblasAtomics(handle); rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; switch(gemm_desc.dataType) { case miopenInt8: { assert(gemm_desc.k % 4 == 0); auto alpha = int(gemm_desc.alpha); auto beta = int(gemm_desc.beta); rb_status = miopen_rocblas_gemm_strided_batched_ex( handle.rhandle().get(), gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_i8_r, gemm_desc.lda, gemm_desc.strideA, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_i8_r, gemm_desc.ldb, gemm_desc.strideB, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_i32_r, gemm_desc.ldc, gemm_desc.strideC, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_i32_r, gemm_desc.ldc, gemm_desc.strideC, gemm_desc.batch_count, rocblas_datatype::rocblas_datatype_i32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } break; case miopenInt32: break; case miopenHalf: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; // We need ex3 API if any of the dataType or the cast type is an 8-bit floating type const auto needs_ex3 = [&]() { if((gemm_desc.dataType == miopenFloat8_fnuz || gemm_desc.dataType == miopenBFloat8_fnuz) || (gemm_desc.a_cast_type == miopenFloat8_fnuz || gemm_desc.a_cast_type == miopenBFloat8_fnuz) || (gemm_desc.b_cast_type == miopenBFloat8_fnuz || gemm_desc.b_cast_type == miopenFloat8_fnuz)) { return true; } else { return false; } }(); // ex3 API only works on the gfx942 ASIC; if(needs_ex3) { if(is_gfx942) { rb_status = miopen_rocblas_gemm_strided_batched_ex3( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset); } else { MIOPEN_THROW(miopenStatusInternalError, "8-bit floating types are only supported on gfx942"); } } else { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_strided_batched_ex( handle.rhandle().get(), gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.lda, gemm_desc.strideA, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldb, gemm_desc.strideB, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldc, gemm_desc.strideC, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldc, gemm_desc.strideC, gemm_desc.batch_count, rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, FlagsForRocblasFp32Fp16Call(gemm_desc)); } } break; case miopenBFloat16: { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_strided_batched_ex( handle.rhandle().get(), gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.lda, gemm_desc.strideA, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldb, gemm_desc.strideB, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldc, gemm_desc.strideC, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldc, gemm_desc.strideC, gemm_desc.batch_count, rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } break; case miopenFloat: { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_strided_batched_ex( handle.rhandle().get(), gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.lda, gemm_desc.strideA, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldb, gemm_desc.strideB, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, gemm_desc.strideC, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, gemm_desc.strideC, gemm_desc.batch_count, rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } break; case miopenFloat8_fnuz: case miopenBFloat8_fnuz: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; if(is_gfx942) { rb_status = miopen_rocblas_gemm_strided_batched_ex3( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset); } else { MIOPEN_THROW(miopenStatusInternalError, "8-bit floating types are only supported on gfx942"); } break; } case miopenDouble: { MIOPEN_THROW(miopenStatusInternalError, "miopenDouble data type not supported by rocBLAS."); } break; case miopenInt64: { MIOPEN_THROW(miopenStatusInternalError, "miopenInt64 is not currently supported."); } break; } if(handle.IsProfilingEnabled()) ProfilingRecordStop(handle, start, stop); if(rb_status != rocblas_status::rocblas_status_success) MIOPEN_THROW(miopenStatusInternalError, "rocBlas error encountered"); if(gemm_desc.deterministic) SetRocblasAtomics(handle, cur_mode); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } case GemmBackend_t::hipblaslt: { #if MIOPEN_USE_HIPBLASLT call_miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset, false); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } } return miopenStatusUnknownError; } miopenStatus_t CallGemmStridedBatched(const Handle& handle, GemmDescriptor gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset, miopenDataType_t cType, GemmBackend_t gemm_backend) { // If C/D type matches A/B type, delegate to the standard overload if(cType == gemm_desc.dataType) return CallGemmStridedBatched( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset, gemm_backend); MIOPEN_LOG_I2("gemm_desc: " << gemm_desc << " cType: " << GetDataType(cType)); gemm_backend = enforce_gemm_backend(gemm_backend); if(!gemm_desc.isColMajor) { gemm_desc.isColMajor = !gemm_desc.isColMajor; std::swap(A, B); std::swap(a_offset, b_offset); std::swap(gemm_desc.a_cast_type, gemm_desc.b_cast_type); std::swap(gemm_desc.transA, gemm_desc.transB); std::swap(gemm_desc.m, gemm_desc.n); std::swap(gemm_desc.lda, gemm_desc.ldb); std::swap(gemm_desc.strideA, gemm_desc.strideB); } switch(gemm_backend) { case GemmBackend_t::nogemmbackend: return miopenStatusNotImplemented; case GemmBackend_t::rocblas: { #if MIOPEN_USE_ROCBLAS MIOPEN_LOG_I2("rocBLAS mixed-precision C/D (strided batched)"); HipEventPtr start = nullptr; HipEventPtr stop = nullptr; if(handle.IsProfilingEnabled()) { ProfilingRecordStart(handle, start, stop); } rocblas_atomics_mode cur_mode = rocblas_atomics_mode::rocblas_atomics_allowed; if(gemm_desc.deterministic) cur_mode = DisableRocblasAtomics(handle); rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; // Currently only bf16 A/B with fp32 C/D is supported if(gemm_desc.dataType == miopenBFloat16 && cType == miopenFloat) { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; rb_status = miopen_rocblas_gemm_strided_batched_ex( handle.rhandle().get(), gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.lda, gemm_desc.strideA, static_cast(B) + b_offset, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldb, gemm_desc.strideB, &beta, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, gemm_desc.strideC, static_cast(C) + c_offset, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, gemm_desc.strideC, gemm_desc.batch_count, rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } else { MIOPEN_THROW( miopenStatusInternalError, "CallGemmStridedBatched with cType: unsupported dataType/cType combination"); } if(handle.IsProfilingEnabled()) ProfilingRecordStop(handle, start, stop); if(rb_status != rocblas_status::rocblas_status_success) MIOPEN_THROW(miopenStatusInternalError, "rocBlas error encountered"); if(gemm_desc.deterministic) SetRocblasAtomics(handle, cur_mode); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } case GemmBackend_t::hipblaslt: { MIOPEN_THROW(miopenStatusInternalError, "CallGemmStridedBatched with cType: hipblaslt backend not supported"); } } return miopenStatusUnknownError; } miopenStatus_t CallGemmStridedBatchedSequential(const Handle& handle, GemmDescriptor gemm_desc, ConstData_t A, std::size_t a_offset, ConstData_t B, std::size_t b_offset, Data_t C, std::size_t c_offset, GemmBackend_t gemm_backend) { MIOPEN_LOG_I2("gemm_desc: " << gemm_desc); gemm_backend = enforce_gemm_backend(gemm_backend); if(!gemm_desc.isColMajor) { gemm_desc.isColMajor = !gemm_desc.isColMajor; std::swap(A, B); std::swap(a_offset, b_offset); std::swap(gemm_desc.a_cast_type, gemm_desc.b_cast_type); std::swap(gemm_desc.transA, gemm_desc.transB); std::swap(gemm_desc.m, gemm_desc.n); std::swap(gemm_desc.lda, gemm_desc.ldb); std::swap(gemm_desc.strideA, gemm_desc.strideB); } switch(gemm_backend) { case GemmBackend_t::nogemmbackend: return miopenStatusNotImplemented; case GemmBackend_t::rocblas: { #if MIOPEN_USE_ROCBLAS MIOPEN_LOG_I2("rocBLAS"); HipEventPtr start = nullptr; HipEventPtr stop = nullptr; if(handle.IsProfilingEnabled()) { ProfilingRecordStart(handle, start, stop); } rocblas_atomics_mode cur_mode = rocblas_atomics_mode::rocblas_atomics_allowed; // default value from rocblas if(gemm_desc.deterministic) { cur_mode = DisableRocblasAtomics(handle); } rocblas_status rb_status = rocblas_status::rocblas_status_internal_error; switch(gemm_desc.dataType) { case miopenInt8: { assert(gemm_desc.k % 4 == 0); auto alpha = int(gemm_desc.alpha); auto beta = int(gemm_desc.beta); for(int i = 0; i < gemm_desc.batch_count; ++i) { rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset + i * gemm_desc.strideA, rocblas_datatype::rocblas_datatype_i8_r, gemm_desc.lda, static_cast(B) + b_offset + i * gemm_desc.strideB, rocblas_datatype::rocblas_datatype_i8_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_i32_r, gemm_desc.ldc, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_i32_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), // rocblas_datatype::rocblas_datatype_i32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } } break; case miopenInt32: break; case miopenHalf: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; // We need ex3 API if any of the dataType or the cast type is an 8-bit floating type const auto needs_ex3 = [&]() { if((gemm_desc.dataType == miopenFloat8_fnuz || gemm_desc.dataType == miopenBFloat8_fnuz) || (gemm_desc.a_cast_type == miopenFloat8_fnuz || gemm_desc.a_cast_type == miopenBFloat8_fnuz) || (gemm_desc.b_cast_type == miopenBFloat8_fnuz || gemm_desc.b_cast_type == miopenFloat8_fnuz)) { return true; } else { return false; } }(); // ex3 API only works on the gfx942 ASIC; if(needs_ex3) { if(is_gfx942) { rb_status = miopen_rocblas_gemm_strided_batched_ex3( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset); } else { MIOPEN_THROW(miopenStatusInternalError, "8-bit floating types are only supported on gfx942"); } } else { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; for(int i = 0; i < gemm_desc.batch_count; ++i) { rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset + i * gemm_desc.strideA, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.lda, static_cast(B) + b_offset + i * gemm_desc.strideB, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldc, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_f16_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), // rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, FlagsForRocblasFp32Fp16Call(gemm_desc)); } } } break; case miopenBFloat16: { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; for(int i = 0; i < gemm_desc.batch_count; ++i) { rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset + i * gemm_desc.strideA, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.lda, static_cast(B) + b_offset + i * gemm_desc.strideB, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldc, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_bf16_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), // rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } } break; case miopenFloat: { float alpha = gemm_desc.alpha; float beta = gemm_desc.beta; for(int i = 0; i < gemm_desc.batch_count; ++i) { rb_status = miopen_rocblas_gemm_ex( handle, gemm_desc, gemm_desc.transA ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.transB ? rocblas_operation_transpose : rocblas_operation_none, gemm_desc.m, gemm_desc.n, gemm_desc.k, &alpha, static_cast(A) + a_offset + i * gemm_desc.strideA, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.lda, static_cast(B) + b_offset + i * gemm_desc.strideB, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldb, &beta, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, static_cast(C) + c_offset + i * gemm_desc.strideC, rocblas_datatype::rocblas_datatype_f32_r, gemm_desc.ldc, rocBlasComputeType(gemm_desc), // rocblas_datatype::rocblas_datatype_f32_r, rocblas_gemm_algo::rocblas_gemm_algo_standard, 0, 0); } } break; case miopenFloat8_fnuz: case miopenBFloat8_fnuz: { const auto is_gfx942 = handle.GetDeviceName() == "gfx942"; if(is_gfx942) { rb_status = miopen_rocblas_gemm_strided_batched_ex3( handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset); } else { MIOPEN_THROW(miopenStatusInternalError, "8-bit floating types are only supported on gfx942"); } break; } case miopenDouble: { MIOPEN_THROW(miopenStatusInternalError, "miopenDouble data type not supported by rocBLAS."); } break; case miopenInt64: { MIOPEN_THROW(miopenStatusInternalError, "miopenInt64 is not currently supported."); } break; } if(handle.IsProfilingEnabled()) ProfilingRecordStop(handle, start, stop); if(rb_status != rocblas_status::rocblas_status_success) MIOPEN_THROW(miopenStatusInternalError, "rocBlas error encountered"); if(gemm_desc.deterministic) SetRocblasAtomics(handle, cur_mode); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } case GemmBackend_t::hipblaslt: { #if MIOPEN_USE_HIPBLASLT // todo bharriso - find out if we need to support iterative variant, or if using regular // batching is alright. call_miopen_hipblasLt_gemm(handle, gemm_desc, A, a_offset, B, b_offset, C, c_offset, false); return miopenStatusSuccess; #else return miopenStatusNotImplemented; #endif } } return miopenStatusUnknownError; } // y = w * Im2Col(x) GemmDescriptor CreateGemmDescriptorConvFwd(const TensorDescriptor& wDesc, const TensorDescriptor& xDesc, const TensorDescriptor& yDesc) { #ifndef NDEBUG assert(wDesc.GetType() == xDesc.GetType()); if(wDesc.GetType() != miopenInt8) assert(wDesc.GetType() == yDesc.GetType()); #endif int in_c = xDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto wei_spatial = wDesc.GetLengths() | std::views::drop(2) | std::views::take(wDesc.GetLengths().size() - 2); auto out_spatial = yDesc.GetLengths() | std::views::drop(2) | std::views::take(yDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = (wDesc.GetType() == miopenInt8); int m = wei_k; int n = std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = in_c * std::accumulate(wei_spatial.begin(), wei_spatial.end(), 1, std::multiplies()); int lda = k; int ldb = wDesc.GetType() == miopenInt8 ? k : n; int ldc = n; int batch_count = 1; auto strideA = static_cast(0); auto strideB = static_cast(0); auto strideC = static_cast(0); float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // dx = Col2Im(transpose(w) * dy) GemmDescriptor CreateGemmDescriptorConvBwdData(const TensorDescriptor& wDesc, const TensorDescriptor& dyDesc, const TensorDescriptor& dxDesc) { #ifndef NDEBUG assert(wDesc.GetType() == dxDesc.GetType() && wDesc.GetType() == dyDesc.GetType()); #endif int in_c = dxDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto wei_spatial = wDesc.GetLengths() | std::views::drop(2) | std::views::take(wDesc.GetLengths().size() - 2); auto out_spatial = dyDesc.GetLengths() | std::views::drop(2) | std::views::take(dyDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = true; bool transB = false; int m = in_c * std::accumulate(wei_spatial.begin(), wei_spatial.end(), 1, std::multiplies()); int n = std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = wei_k; int lda = m; int ldb = n; int ldc = n; int batch_count = 1; auto strideA = static_cast(0); auto strideB = static_cast(0); auto strideC = static_cast(0); float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, dxDesc.GetType(), false}; } // dw = dy * transpose(Im2Col(x)) GemmDescriptor CreateGemmDescriptorConvBwdWeight(const TensorDescriptor& dyDesc, const TensorDescriptor& xDesc, const TensorDescriptor& dwDesc) { #ifndef NDEBUG assert(dwDesc.GetType() == xDesc.GetType() && dwDesc.GetType() == dyDesc.GetType()); #endif std::size_t in_c = xDesc.GetLengths()[1]; std::size_t wei_k = dwDesc.GetLengths()[0]; auto wei_spatial = dwDesc.GetLengths() | std::views::drop(2) | std::views::take(dwDesc.GetLengths().size() - 2); auto out_spatial = dyDesc.GetLengths() | std::views::drop(2) | std::views::take(dyDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = true; int m = wei_k; int n = static_cast(in_c) * std::accumulate(wei_spatial.begin(), wei_spatial.end(), 1, std::multiplies()); int k = std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int lda = k; int ldb = k; int ldc = n; int batch_count = 1; auto strideA = static_cast(0); auto strideB = static_cast(0); auto strideC = static_cast(0); float alpha = 1.; float beta = 1.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // y = CNHW2NCHW(w * NCHW2CNHW(x)) GemmDescriptor CreateGemmDescriptorConvCNHWFwd(const TensorDescriptor& wDesc, const TensorDescriptor& xDesc, const TensorDescriptor& yDesc) { #ifndef NDEBUG assert(wDesc.GetType() == xDesc.GetType()); if(wDesc.GetType() != miopenInt8) assert(wDesc.GetType() == yDesc.GetType()); #endif int in_n = xDesc.GetLengths()[0]; int in_c = xDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto out_spatial = yDesc.GetLengths() | std::views::drop(2) | std::views::take(yDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = (wDesc.GetType() == miopenInt8); int m = wei_k; int n = in_n * std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = in_c; int lda = k; int ldb = wDesc.GetType() == miopenInt8 ? k : n; int ldc = n; int batch_count = 1; auto strideA = static_cast(0); auto strideB = static_cast(0); auto strideC = static_cast(0); float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // dx = CNHW2NCHW(transpose(w) * NCHW2CNHW(dy)) GemmDescriptor CreateGemmDescriptorConvCNHWBwdData(const TensorDescriptor& wDesc, const TensorDescriptor& dyDesc, const TensorDescriptor& dxDesc) { #ifndef NDEBUG assert(wDesc.GetType() == dxDesc.GetType() && wDesc.GetType() == dyDesc.GetType()); #endif int in_n = dxDesc.GetLengths()[0]; int in_c = dxDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto out_spatial = dyDesc.GetLengths() | std::views::drop(2) | std::views::take(dyDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = true; bool transB = false; int m = in_c; int n = in_n * std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = wei_k; int lda = m; int ldb = n; int ldc = n; int batch_count = 1; auto strideA = static_cast(0); auto strideB = static_cast(0); auto strideC = static_cast(0); float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, dxDesc.GetType(), false}; } // y[i] = w * x[i], i is batch id GemmDescriptor CreateGemmStridedBatchedDescriptorConv1x1Fwd(const TensorDescriptor& wDesc, const TensorDescriptor& xDesc, const TensorDescriptor& yDesc) { #ifndef NDEBUG assert(wDesc.GetType() == xDesc.GetType()); if(wDesc.GetType() != miopenInt8) assert(wDesc.GetType() == yDesc.GetType()); #else (void)yDesc; #endif int in_n = xDesc.GetLengths()[0]; int in_c = xDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto in_spatial = xDesc.GetLengths() | std::views::drop(2) | std::views::take(xDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = (wDesc.GetType() == miopenInt8); int m = wei_k; int n = std::accumulate(in_spatial.begin(), in_spatial.end(), 1, std::multiplies()); int k = in_c; int lda = k; int ldb = wDesc.GetType() == miopenInt8 ? k : n; int ldc = n; int batch_count = in_n; auto strideA = static_cast(0); auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // dx[i] = transpose(w) * dy[i], i is batch id GemmDescriptor CreateGemmStridedBatchedDescriptorConv1x1BwdData(const TensorDescriptor& wDesc, const TensorDescriptor& dyDesc, const TensorDescriptor& dxDesc) { #ifndef NDEBUG assert(wDesc.GetType() == dxDesc.GetType() && wDesc.GetType() == dyDesc.GetType()); #else (void)dyDesc; #endif int in_n = dxDesc.GetLengths()[0]; int in_c = dxDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto in_spatial = dxDesc.GetLengths() | std::views::drop(2) | std::views::take(dxDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = true; bool transB = false; int m = in_c; int n = std::accumulate(in_spatial.begin(), in_spatial.end(), 1, std::multiplies()); int k = wei_k; int lda = m; int ldb = n; int ldc = n; int batch_count = in_n; auto strideA = static_cast(0); auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 0; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, dxDesc.GetType(), false}; } // dw = sum_over_batch(dy[i] * transpose(x[i])), i is batch id GemmDescriptor CreateGemmStridedBatchedDescriptorConv1x1BwdWeight(const TensorDescriptor& dyDesc, const TensorDescriptor& xDesc, const TensorDescriptor& dwDesc) { #ifndef NDEBUG assert(dwDesc.GetType() == xDesc.GetType() && dwDesc.GetType() == dyDesc.GetType()); #else (void)dyDesc; #endif int in_n = xDesc.GetLengths()[0]; int in_c = xDesc.GetLengths()[1]; int wei_k = dwDesc.GetLengths()[0]; auto in_spatial = xDesc.GetLengths() | std::views::drop(2) | std::views::take(xDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = true; int m = wei_k; int n = in_c; int k = std::accumulate(in_spatial.begin(), in_spatial.end(), 1, std::multiplies()); int lda = k; int ldb = k; int ldc = n; int batch_count = in_n; auto strideA = static_cast(m) * k; auto strideB = static_cast(k) * n; auto strideC = static_cast(0); float alpha = 1.; float beta = 1.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // y = w * Im2Col(x) GemmDescriptor CreateGemmDescriptorGroupConvFwd(const TensorDescriptor& wDesc, const TensorDescriptor& xDesc, const TensorDescriptor& yDesc, int groupCount) { #ifndef NDEBUG assert(wDesc.GetType() == xDesc.GetType() && wDesc.GetType() == yDesc.GetType()); #endif int in_c = xDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto wei_spatial = wDesc.GetLengths() | std::views::drop(2) | std::views::take(wDesc.GetLengths().size() - 2); auto out_spatial = yDesc.GetLengths() | std::views::drop(2) | std::views::take(yDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = false; int m = wei_k / groupCount; int n = std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = (in_c / groupCount) * std::accumulate(wei_spatial.begin(), wei_spatial.end(), 1, std::multiplies()); int lda = k; int ldb = n; int ldc = n; int batch_count = groupCount; auto strideA = static_cast(m) * k; auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // dx = Col2Im(transpose(w) * dy) GemmDescriptor CreateGemmDescriptorGroupConvBwdData(const TensorDescriptor& wDesc, const TensorDescriptor& dyDesc, const TensorDescriptor& dxDesc, int groupCount) { #ifndef NDEBUG assert(wDesc.GetType() == dxDesc.GetType() && wDesc.GetType() == dyDesc.GetType()); #endif int in_c = dxDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto wei_spatial = wDesc.GetLengths() | std::views::drop(2) | std::views::take(wDesc.GetLengths().size() - 2); auto out_spatial = dyDesc.GetLengths() | std::views::drop(2) | std::views::take(dyDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = true; bool transB = false; int m = (in_c / groupCount) * std::accumulate(wei_spatial.begin(), wei_spatial.end(), 1, std::multiplies()); int n = std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = wei_k / groupCount; int lda = m; int ldb = n; int ldc = n; int batch_count = groupCount; auto strideA = static_cast(m) * k; auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, dxDesc.GetType(), false}; } // dw = dy * transpose(Im2Col(x)) GemmDescriptor CreateGemmDescriptorGroupConvBwdWeight(const TensorDescriptor& dyDesc, const TensorDescriptor& xDesc, const TensorDescriptor& dwDesc, int groupCount) { #ifndef NDEBUG assert(dwDesc.GetType() == xDesc.GetType() && dwDesc.GetType() == dyDesc.GetType()); #endif int in_c = xDesc.GetLengths()[1]; int wei_k = dwDesc.GetLengths()[0]; auto wei_spatial = dwDesc.GetLengths() | std::views::drop(2) | std::views::take(dwDesc.GetLengths().size() - 2); auto out_spatial = dyDesc.GetLengths() | std::views::drop(2) | std::views::take(dyDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = true; int m = wei_k / groupCount; int n = (in_c / groupCount) * std::accumulate(wei_spatial.begin(), wei_spatial.end(), 1, std::multiplies()); int k = std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int lda = k; int ldb = k; int ldc = n; int batch_count = groupCount; auto strideA = static_cast(m) * k; auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 1.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // y = CNHW2NCHW(w * NCHW2CNHW(x)) GemmDescriptor CreateGemmDescriptorGroupConvCNHWFwd(const TensorDescriptor& wDesc, const TensorDescriptor& xDesc, const TensorDescriptor& yDesc, int groupCount) { #ifndef NDEBUG assert(wDesc.GetType() == xDesc.GetType() && wDesc.GetType() == yDesc.GetType()); #endif int in_n = xDesc.GetLengths()[0]; int in_c = xDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto out_spatial = yDesc.GetLengths() | std::views::drop(2) | std::views::take(yDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = false; bool transB = false; int m = wei_k / groupCount; int n = in_n * std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = in_c / groupCount; int lda = k; int ldb = n; int ldc = n; int batch_count = groupCount; auto strideA = static_cast(m) * k; auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, xDesc.GetType(), false}; } // dx = CNHW2NCHW(transpose(w) * NCHW2CNHW(dy)) GemmDescriptor CreateGemmDescriptorGroupConvCNHWBwdData(const TensorDescriptor& wDesc, const TensorDescriptor& dyDesc, const TensorDescriptor& dxDesc, int groupCount) { #ifndef NDEBUG assert(wDesc.GetType() == dxDesc.GetType() && wDesc.GetType() == dyDesc.GetType()); #endif int in_n = dxDesc.GetLengths()[0]; int in_c = dxDesc.GetLengths()[1]; int wei_k = wDesc.GetLengths()[0]; auto out_spatial = dyDesc.GetLengths() | std::views::drop(2) | std::views::take(dyDesc.GetLengths().size() - 2); bool isColMajor = false; bool transA = true; bool transB = false; int m = in_c / groupCount; int n = in_n * std::accumulate(out_spatial.begin(), out_spatial.end(), 1, std::multiplies()); int k = wei_k / groupCount; int lda = m; int ldb = n; int ldc = n; int batch_count = groupCount; auto strideA = static_cast(m) * k; auto strideB = static_cast(k) * n; auto strideC = static_cast(m) * n; float alpha = 1.; float beta = 0.; return GemmDescriptor{isColMajor, transA, transB, m, n, k, lda, ldb, ldc, batch_count, strideA, strideB, strideC, alpha, beta, dxDesc.GetType(), false}; } } // namespace miopen