/******************************************************************************* * * MIT License * * Copyright (C) 2023-2026 Advanced Micro Devices, Inc. All rights reserved. * * 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. * *******************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include // hiptensor includes #include #include #include #include #include "data_types.hpp" #include "device/common.hpp" #include "hip_resource.hpp" #include "type_traits.hpp" #define HIPTENSOR_FREE_DEVICE(ptr) \ if(ptr != nullptr) \ { \ CHECK_HIP_ERROR(hipFree(ptr)); \ } #define HIPTENSOR_FREE_HOST(ptr) \ if(ptr != nullptr) \ { \ CHECK_HIP_ERROR(hipHostFree(ptr)); \ } inline double getEpsilon(hiptensorComputeDescriptor_t id) { auto toDouble = [](auto const& val) { return static_cast(static_cast(val)); }; if(id == HIPTENSOR_COMPUTE_DESC_16F) { return std::pow(2, -10); // F16 machine epsilon (std::numeric_limits<_Float16> isn't valid on Windows MSVC) } else if(id == HIPTENSOR_COMPUTE_DESC_16BF) { return std::pow(2, -7); // BF16 machine epsilon (std::numeric_limits isn't valid on Windows MSVC) } else if(id == HIPTENSOR_COMPUTE_DESC_32F) { return toDouble(std::numeric_limits::epsilon()); } else if(id == HIPTENSOR_COMPUTE_DESC_64F) { return toDouble(std::numeric_limits::epsilon()); } else if(id == HIPTENSOR_COMPUTE_DESC_8U) { return 0; } else if(id == HIPTENSOR_COMPUTE_DESC_8I) { return 0; } else if(id == HIPTENSOR_COMPUTE_DESC_32U) { return 0; } else if(id == HIPTENSOR_COMPUTE_DESC_32I) { return 0; } else if(id == HIPTENSOR_COMPUTE_DESC_C32F) { return toDouble(std::numeric_limits::epsilon()); } else if(id == HIPTENSOR_COMPUTE_DESC_C64F) { return toDouble(std::numeric_limits::epsilon()); } else { #if !NDEBUG std::cout << "Unhandled hiptensorComputeDescriptor_t: " << id << std::endl; #endif // !NDEBUG return 0; } } inline bool isF16Supported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx908") != std::string::npos) || (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos) || (deviceName.find("gfx1100") != std::string::npos) || (deviceName.find("gfx1101") != std::string::npos) || (deviceName.find("gfx1102") != std::string::npos) || (deviceName.find("gfx1103") != std::string::npos) || (deviceName.find("gfx1150") != std::string::npos) || (deviceName.find("gfx1151") != std::string::npos) || (deviceName.find("gfx1152") != std::string::npos) || (deviceName.find("gfx1153") != std::string::npos) || (deviceName.find("gfx1200") != std::string::npos) || (deviceName.find("gfx1201") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isF32Supported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx908") != std::string::npos) || (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos) || (deviceName.find("gfx1100") != std::string::npos) || (deviceName.find("gfx1101") != std::string::npos) || (deviceName.find("gfx1102") != std::string::npos) || (deviceName.find("gfx1103") != std::string::npos) || (deviceName.find("gfx1150") != std::string::npos) || (deviceName.find("gfx1151") != std::string::npos) || (deviceName.find("gfx1152") != std::string::npos) || (deviceName.find("gfx1153") != std::string::npos) || (deviceName.find("gfx1200") != std::string::npos) || (deviceName.find("gfx1201") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isF64Supported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isF16F16MatrixCoreSupported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx1100") != std::string::npos) || (deviceName.find("gfx1101") != std::string::npos) || (deviceName.find("gfx1102") != std::string::npos) || (deviceName.find("gfx1103") != std::string::npos) || (deviceName.find("gfx1150") != std::string::npos) || (deviceName.find("gfx1151") != std::string::npos) || (deviceName.find("gfx1152") != std::string::npos) || (deviceName.find("gfx1153") != std::string::npos) || (deviceName.find("gfx1200") != std::string::npos) || (deviceName.find("gfx1201") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isF32F16MatrixCoreSupported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx908") != std::string::npos) || (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos) || (deviceName.find("gfx1100") != std::string::npos) || (deviceName.find("gfx1101") != std::string::npos) || (deviceName.find("gfx1102") != std::string::npos) || (deviceName.find("gfx1103") != std::string::npos) || (deviceName.find("gfx1150") != std::string::npos) || (deviceName.find("gfx1151") != std::string::npos) || (deviceName.find("gfx1152") != std::string::npos) || (deviceName.find("gfx1153") != std::string::npos) || (deviceName.find("gfx1200") != std::string::npos) || (deviceName.find("gfx1201") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isF32F32MatrixCoreSupported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx908") != std::string::npos) || (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isF16F32MatrixCoreSupported() { return isF32F32MatrixCoreSupported(); } inline bool isF64F64MatrixCoreSupported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos); } inline bool isF64F32MatrixCoreSupported() { hipDevice_t mHandle; hipDeviceProp_t mProps; CHECK_HIP_ERROR(hipGetDevice(&mHandle)); CHECK_HIP_ERROR(hipGetDeviceProperties(&mProps, mHandle)); std::string deviceName(mProps.gcnArchName); return (deviceName.find("gfx90a") != std::string::npos) || (deviceName.find("gfx942") != std::string::npos) || (deviceName.find("gfx950") != std::string::npos) || (deviceName.find("gfx1250") != std::string::npos); } inline bool isDataType16Bits(hiptensorDataType_t dataType) { return (dataType == HIPTENSOR_R_16F || dataType == HIPTENSOR_R_16BF || dataType == HIPTENSOR_C_16F || dataType == HIPTENSOR_C_16BF); } inline bool isDataType32Bits(hiptensorDataType_t dataType) { return (dataType == HIPTENSOR_R_32F || dataType == HIPTENSOR_C_32F); } inline bool isDataType64Bits(hiptensorDataType_t dataType) { return (dataType == HIPTENSOR_R_64F || dataType == HIPTENSOR_C_64F); } inline bool isDataTypeComplex(hiptensorDataType_t dataType) { return (dataType == HIPTENSOR_C_32F || dataType == HIPTENSOR_C_64F || dataType == HIPTENSOR_C_16F || dataType == HIPTENSOR_C_16BF); } template ::value>::type, typename intT2, class = typename std::enable_if::value>::type> static constexpr intT1 ceilDiv(const intT1 numerator, const intT2 divisor) { return (numerator + divisor - 1) / divisor; } template auto getProduct(const Container& container, typename Container::value_type init = typename Container::value_type{1}) { return std::accumulate(std::begin(container), std::end(container), init, std::multiplies{}); } // fill kernel for 'elementSize' elements template __host__ static inline void fillLaunchKernel(DataType* data, uint32_t elementSize, uint32_t seed) { auto blockDim = dim3(1024, 1, 1); auto gridDim = dim3(ceilDiv(elementSize, blockDim.x), 1, 1); hipLaunchKernelGGL((fillKernel), gridDim, blockDim, 0, 0, data, elementSize, seed); } // fill kernel for 'elementSize' elements with positive values template __host__ static inline void fillPositiveValLaunchKernel(DataType* data, uint32_t elementSize, uint32_t seed) { auto blockDim = dim3(1024, 1, 1); auto gridDim = dim3(ceilDiv(elementSize, blockDim.x), 1, 1); hipLaunchKernelGGL( (fillPositiveValKernel), gridDim, blockDim, 0, 0, data, elementSize, seed); } // fill kernel wrapper for 'elementSize' elements with a specific value template __host__ static inline void fillValLaunchKernel(DataType* data, uint32_t elementSize, DataType value) { auto blockDim = dim3(1024, 1, 1); auto gridDim = dim3(ceilDiv(elementSize, blockDim.x), 1, 1); hipLaunchKernelGGL( (fillValKernel), gridDim, blockDim, 0, 0, data, elementSize, value); } template std::pair compareEqual(DDataType const* deviceD, DDataType const* hostD, std::size_t elementsD, hiptensorComputeDescriptor_t computeType, double tolerance = 0.0) { bool retval = true; double max_relative_error = 0.0; auto toDouble = [](DDataType const& val) { return static_cast(static_cast(val)); }; bool isInf = false; bool isNaN = false; std::mutex writeMutex; #pragma omp parallel for for(int i = 0; i < elementsD; ++i) { auto valDevice = deviceD[i]; auto valHost = hostD[i]; auto numerator = fabs(toDouble(valDevice) - toDouble(valHost)); auto divisor = fabs(toDouble(valDevice)) + fabs(toDouble(valHost)) + 1.0; if(std::isinf(numerator) || std::isinf(divisor)) { #pragma omp atomic isInf |= true; } else { auto relative_error = numerator / divisor; if(std::isnan(relative_error)) { #pragma omp atomic isNaN |= true; } else if(relative_error > max_relative_error) { const std::lock_guard guard(writeMutex); // Double check in case of stall if(relative_error > max_relative_error) { max_relative_error = relative_error; } } } if(isInf || isNaN) { i = elementsD; } } if(tolerance == 0.0) { // use the same default tolerance value as CK if(computeType == HIPTENSOR_COMPUTE_DESC_16BF || std::is_same_v) { const double epsilon = std::pow(2, -7); tolerance = epsilon * 2; } else if(computeType == HIPTENSOR_COMPUTE_DESC_16F || std::is_same_v) { const double epsilon = std::pow(2, -10); tolerance = epsilon * 2; } else { tolerance = 1e-5; } } if(isInf) { retval = false; max_relative_error = std::numeric_limits::infinity(); } else if(isNaN) { retval = false; max_relative_error = std::numeric_limits::signaling_NaN(); } else if(max_relative_error > tolerance) { retval = false; } return std::make_pair(retval, max_relative_error); } template std::pair compareEqualLaunchKernel(DDataType* deviceD, DDataType* hostD, std::size_t elementsD, hiptensorComputeDescriptor_t computeType, double tolerance = 0.0) { static hiptensor::HipResource::DevicePtrT relativeErrorPtr; static std::size_t relativeErrorSize = 0; auto blockDim = dim3(1024, 1, 1); auto gridDim = dim3(ceilDiv(elementsD, blockDim.x), 1, 1); double maxRelativeError; std::size_t requiredErrorSize = elementsD * sizeof(double); if(requiredErrorSize > relativeErrorSize) { hiptensor::HipResource::reallocDevice(relativeErrorPtr, requiredErrorSize); relativeErrorSize = requiredErrorSize; } hipEvent_t syncEvent; CHECK_HIP_ERROR(hipEventCreate(&syncEvent)); // Calculate the relative error for each element of Tensor D hipLaunchKernelGGL((compareEqualKernel), gridDim, blockDim, 0, 0, deviceD, hostD, static_cast(relativeErrorPtr.get()), elementsD); CHECK_HIP_ERROR(hipEventRecord(syncEvent)); CHECK_HIP_ERROR(hipEventSynchronize(syncEvent)); // Determine the maximum relative error blockDim = dim3(512, 1, 1); uint32_t maxElements = 1024; uint32_t offset = 1; for(uint32_t i = elementsD; i > 1; i = ceilDiv(i, maxElements)) { gridDim = dim3(ceilDiv(i, maxElements), 1, 1); auto elements = i > maxElements ? maxElements : i; hipLaunchKernelGGL((maxReduceKernel), gridDim, blockDim, 0, 0, static_cast(relativeErrorPtr.get()), elements, offset, elementsD); CHECK_HIP_ERROR(hipEventRecord(syncEvent)); CHECK_HIP_ERROR(hipEventSynchronize(syncEvent)); offset = offset * maxElements; } CHECK_HIP_ERROR(hipEventDestroy(syncEvent)); CHECK_HIP_ERROR(hipMemcpy(&maxRelativeError, static_cast(relativeErrorPtr.get()), sizeof(double), hipMemcpyDeviceToHost)); bool retval = true; bool isNaN = std::isnan(maxRelativeError); auto toDouble = [](DDataType const& val) { return static_cast(static_cast(val)); }; if(tolerance == 0.0) { // use the same default tolerance value as CK if(computeType == HIPTENSOR_COMPUTE_DESC_16BF || std::is_same_v) { const double epsilon = std::pow(2, -7); tolerance = epsilon * 2; } else if(computeType == HIPTENSOR_COMPUTE_DESC_16F || std::is_same_v) { const double epsilon = std::pow(2, -10); tolerance = epsilon * 2; } else { tolerance = 1e-5; } } if(isNaN) { retval = false; maxRelativeError = std::numeric_limits::signaling_NaN(); } else if(maxRelativeError > (tolerance)) { retval = false; } return std::make_pair(retval, maxRelativeError); } namespace hiptensor { template struct has_const_iterator { private: typedef char yes; typedef struct { char array[2]; } no; template static yes test(typename C::const_iterator*); template static no test(...); public: static const bool value = sizeof(test(0)) == sizeof(yes); typedef T type; }; template struct has_begin_end { template static char (&f( typename std::enable_if(&C::begin)), typename C::const_iterator (C::*)() const>::value, void>::type*))[1]; template static char (&f(...))[2]; template static char (&g( typename std::enable_if(&C::end)), typename C::const_iterator (C::*)() const>::value, void>::type*))[1]; template static char (&g(...))[2]; static bool const beg_value = sizeof(f(0)) == 1; static bool const end_value = sizeof(g(0)) == 1; }; template struct is_container : std::integral_constant::value && has_begin_end::beg_value && has_begin_end::end_value> { }; // Write the content of arrays as value of a CSV field // do not use ',' since it is field separator in CSV // Example: arrays = [[1, 2], [3, 4] // Output: '[[1 2] [3 4]]' // Main print function template std::ostream& printContainerInCsv(Container const& container, std::ostream& outStream) { outStream << "["; for(int i = 0; i < container.size(); i++) { if constexpr(is_container::value) { printContainerInCsv(container[i], outStream); } else { outStream << container[i]; if(i != container.size() - 1) { outStream << ' '; } } } outStream << "] "; return outStream; } // Print a std::vector> template std::ostream& printVectorInCsv(std::vector> const& container, std::ostream& outStream) { outStream << "["; for(const auto& vec : container) { outStream << "["; for(const auto& element : vec) { outStream << element; if(&element != &vec.back()) { outStream << " "; } } outStream << "]"; if(&vec != &container.back()) { outStream << " "; } } outStream << "]"; return outStream; } } inline std::ostream& operator<<(std::ostream& os, const hipFloatComplex& fc) { std::string seperator = (hipCimagf(fc) >= 0) ? " + " : ""; return os << hipCrealf(fc) << seperator << hipCimagf(fc) << "i"; } inline std::ostream& operator<<(std::ostream& os, const hipDoubleComplex& dc) { std::string seperator = (hipCimag(dc) >= 0) ? " + " : ""; return os << hipCreal(dc) << seperator << hipCimag(dc) << "i"; } template void hiptensorPrintArrayElements(std::ostream& stream, T* vec, size_t size) { int index = 0; while(index != size) { if(index == size - 1) { stream << vec[index]; } else { stream << vec[index] << ", "; } index++; } } template void hiptensorPrintVectorElements(const std::vector& vec, std::string sep = " ") { for(auto& elem : vec) { std::cout << elem; if(&elem != &vec.back()) { std::cout << sep; } } } template void hiptensorPrintElementsToFile(std::ofstream& fs, F* output, size_t size, std::string sep = " ") { if(!fs.is_open()) { std::cout << "File not found!\n"; return; } for(int i = 0; i < size; i++) { if(i == size - 1) { fs << static_cast(output[i]); } else { fs << static_cast(output[i]) << sep; } } return; } namespace std { static ostream& operator<<(ostream& os, const hiptensorTensorDescriptor_t desc) { os << "dim " << desc->mLengths.size() << ", "; os << "lengths {"; hiptensorPrintVectorElements(desc->mLengths, ", "); os << "}, "; os << "strides {"; hiptensorPrintVectorElements(desc->mStrides, ", "); os << "}"; return os; } } namespace std { template ostream& operator<<(ostream& os, const std::vector& vec) { os << "[ "; for(auto i = 0; i < vec.size(); i++) { if(i < vec.size() - 1) { os << vec[i] << ", "; } else { os << vec[i]; } } os << " ]"; return os; } }