Files
project_6/upstream_ref/cuda_gemm_optimization/profile_utils.cuh
Claude 9ca33cf4d5 upstream: add GEMM kernel references from 4 repos for BI-V100 porting
Sources (all CUDA 10.2 compatible, no CUTLASS/Triton dependency):
- leimao/CUDA-GEMM-Optimization: v00-v07, fp16 WMMA variant, double buffered
- siboehm/SGEMM_CUDA: kernel 1-12, warp tiling + double buffering
- wangzyon/NVIDIA_SGEMM_PRACTICE: kernel 1-7
- edtallison/sgemm-cuda: kernel 1-12 (reimplementation with notes)

Key porting issue: ALL kernels hardcode WARPSIZE=32.
BI-V100 has warp_size=64. Need to:
1. Replace all 32U / WARPSIZE constants with 64
2. Adjust warp subtile decomposition (WMITER, WNITER, WSUBM, WSUBN)
3. Adjust shared memory bank conflict avoidance (may have different bank count)
4. Test __shfl_down_sync with mask=0xFFFFFFFFFFFFFFFF (64-bit)
2026-08-14 15:11:57 +00:00

400 lines
15 KiB
Plaintext

#ifndef PROFILE_UTILS_CUH
#define PROFILE_UTILS_CUH
#include <cassert>
#include <cmath>
#include <functional>
#include <iostream>
#include <random>
#include "cuda_gemm.hpp"
#include "cuda_gemm_utils.cuh"
#include <cublas_v2.h>
#include <cuda_fp16.h>
#include <cuda_runtime.h>
template <typename T>
float measure_performance(std::function<T(cudaStream_t)> bound_function,
cudaStream_t stream, size_t num_repeats = 100,
size_t num_warmups = 100)
{
cudaEvent_t start, stop;
float time;
CHECK_CUDA_ERROR(cudaEventCreate(&start));
CHECK_CUDA_ERROR(cudaEventCreate(&stop));
for (size_t i{0}; i < num_warmups; ++i)
{
bound_function(stream);
}
CHECK_CUDA_ERROR(cudaStreamSynchronize(stream));
CHECK_CUDA_ERROR(cudaEventRecord(start, stream));
for (size_t i{0}; i < num_repeats; ++i)
{
bound_function(stream);
}
CHECK_CUDA_ERROR(cudaEventRecord(stop, stream));
CHECK_CUDA_ERROR(cudaEventSynchronize(stop));
CHECK_LAST_CUDA_ERROR();
CHECK_CUDA_ERROR(cudaEventElapsedTime(&time, start, stop));
CHECK_CUDA_ERROR(cudaEventDestroy(start));
CHECK_CUDA_ERROR(cudaEventDestroy(stop));
float const latency{time / num_repeats};
return latency;
}
#define CHECK_CUBLASS_ERROR(val) check_cublass((val), #val, __FILE__, __LINE__)
void check_cublass(cublasStatus_t err, const char* const func,
const char* const file, const int line)
{
if (err != CUBLAS_STATUS_SUCCESS)
{
std::cerr << "cuBLAS Error at: " << file << ":" << line << std::endl;
std::cerr << cublasGetStatusString(err) << std::endl;
std::exit(EXIT_FAILURE);
}
}
// Determine CUDA data type from type.
template <typename T,
typename std::enable_if<std::is_same<T, float>::value ||
std::is_same<T, double>::value ||
std::is_same<T, __half>::value,
bool>::type = true>
constexpr cudaDataType_t cuda_data_type_trait()
{
if (std::is_same<T, float>::value)
{
return CUDA_R_32F;
}
else if (std::is_same<T, double>::value)
{
return CUDA_R_64F;
}
else if (std::is_same<T, __half>::value)
{
return CUDA_R_16F;
}
else
{
throw std::runtime_error("Unsupported data type.");
}
}
template <typename T,
typename std::enable_if<std::is_same<T, float>::value ||
std::is_same<T, double>::value ||
std::is_same<T, __half>::value,
bool>::type = true>
void launch_gemm_cublas(size_t m, size_t n, size_t k, T const* alpha,
T const* A, size_t lda, T const* B, size_t ldb,
T const* beta, T* C, size_t ldc, cublasHandle_t handle)
{
// Non-TensorCore algorithm?
constexpr cublasGemmAlgo_t algo{CUBLAS_GEMM_DEFAULT};
constexpr cudaDataType_t data_type{cuda_data_type_trait<T>()};
// All the matrix are in row-major order.
// https://docs.nvidia.com/cuda/cublas/#cublasgemmex
// A: m x k row-major -> A: k x m column-major non-transposed
// B: k x n row-major -> B: n x k column-major non-transposed
// C: m x n row-major -> C: n x m column-major non-transposed
// Thus, without padding, the leading dimension of the matrix in row-major
// order is the number of columns, i.e., k for A, n for B, and n for C.
// Row-major order: C = AB + C
// Column-major order: C = BA + C
// The cuBLAS API requires the leading dimension of the matrix in
// column-major order. This API call looks non-intuitive, but it is correct.
CHECK_CUBLASS_ERROR(cublasGemmEx(
handle, CUBLAS_OP_N, CUBLAS_OP_N, n, m, k, alpha, B, data_type, ldb, A,
data_type, lda, beta, C, data_type, ldc, data_type, algo));
}
template <typename T,
typename std::enable_if<std::is_same<T, float>::value ||
std::is_same<T, double>::value,
bool>::type = true>
void launch_gemm_cpu(size_t m, size_t n, size_t k, T const* alpha, T const* A,
size_t lda, T const* B, size_t ldb, T const* beta, T* C,
size_t ldc)
{
// Compute GEMM using CPU.
for (size_t i{0U}; i < m; ++i)
{
for (size_t j{0U}; j < n; ++j)
{
T sum{static_cast<T>(0)};
for (size_t l{0U}; l < k; ++l)
{
sum += A[i * lda + l] * B[l * ldb + j];
}
C[i * ldc + j] = (*alpha) * sum + (*beta) * C[i * ldc + j];
}
}
}
// Many different implementations have been tried for FP16 GEMM on CPU.
// There is always a discrepancy between the results from CPU and GPU (cuBLAS or
// custom kernel).
template <typename T, typename std::enable_if<std::is_same<T, __half>::value,
bool>::type = true>
void launch_gemm_cpu(size_t m, size_t n, size_t k, T const* alpha, T const* A,
size_t lda, T const* B, size_t ldb, T const* beta, T* C,
size_t ldc)
{
// Compute GEMM using CPU.
for (size_t i{0U}; i < m; ++i)
{
for (size_t j{0U}; j < n; ++j)
{
float sum{0.0f};
for (size_t l{0U}; l < k; ++l)
{
sum += __half2float(__hmul(A[i * lda + l], B[l * ldb + j]));
}
C[i * ldc + j] = __float2half(__half2float(*alpha) * sum +
__half2float(*beta) *
__half2float(C[i * ldc + j]));
}
}
}
template <typename T>
bool all_close(T const* C, T const* C_ref, size_t m, size_t n, size_t ldc,
T abs_tol, double rel_tol)
{
bool status{true};
for (size_t i{0U}; i < m; ++i)
{
for (size_t j{0U}; j < n; ++j)
{
double const C_val{static_cast<double>(C[i * ldc + j])};
double const C_ref_val{static_cast<double>(C_ref[i * ldc + j])};
double const diff{C_val - C_ref_val};
double const diff_val{std::abs(diff)};
if (diff_val >
std::max(static_cast<double>(abs_tol),
static_cast<double>(std::abs(C_ref_val)) * rel_tol))
{
std::cout << "C[" << i << ", " << j << "] = " << C_val
<< " C_ref[" << i << ", " << j << "] = " << C_ref_val
<< " Abs Diff: " << diff_val
<< " Abs Diff Threshold: "
<< static_cast<double>(abs_tol)
<< " Rel->Abs Diff Threshold: "
<< static_cast<double>(
static_cast<double>(std::abs(C_ref_val)) *
rel_tol)
<< std::endl;
status = false;
return status;
}
}
}
return status;
}
void print_device_info()
{
int device_id{0};
cudaGetDevice(&device_id);
cudaDeviceProp device_prop;
cudaGetDeviceProperties(&device_prop, device_id);
std::cout << "Device Name: " << device_prop.name << std::endl;
float const memory_size{static_cast<float>(device_prop.totalGlobalMem) /
(1 << 30)};
std::cout << "Memory Size: " << memory_size << " GB" << std::endl;
float const peak_bandwidth{
static_cast<float>(2.0f * device_prop.memoryClockRate *
(device_prop.memoryBusWidth / 8) / 1.0e6)};
std::cout << "Peak Bandwitdh: " << peak_bandwidth << " GB/s" << std::endl;
std::cout << std::endl;
}
template <typename T>
float compute_effective_bandwidth(size_t m, size_t n, size_t k, float latency)
{
return ((m * k + k * n + m * n) * sizeof(T)) / (latency * 1e-3) / 1e9;
}
float compute_effective_tflops(size_t m, size_t n, size_t k, float latency)
{
return (2.0 * m * k * n) / (latency * 1e-3) / 1e12;
}
template <typename T,
typename std::enable_if<std::is_same<T, float>::value ||
std::is_same<T, double>::value ||
std::is_same<T, __half>::value,
bool>::type = true>
void random_initialize_matrix(T* A, size_t m, size_t n, size_t lda,
unsigned int seed = 0U)
{
std::default_random_engine eng(seed);
// The best way to verify is to use integer values.
std::uniform_int_distribution<int> dis(0, 5);
// std::uniform_real_distribution<float> dis(-1.0f, 1.0f);
auto const rand = [&dis, &eng]() { return dis(eng); };
for (size_t i{0U}; i < m; ++i)
{
for (size_t j{0U}; j < n; ++j)
{
A[i * lda + j] = static_cast<T>(rand());
}
}
}
void print_performance_result(size_t m, size_t n, size_t k, float latency)
{
float const effective_bandwidth{
compute_effective_bandwidth<float>(m, n, k, latency)};
float const effective_tflops{compute_effective_tflops(m, n, k, latency)};
std::cout << "Latency: " << latency << " ms" << std::endl;
std::cout << "Effective Bandwidth: " << effective_bandwidth << " GB/s"
<< std::endl;
std::cout << "Effective TFLOPS: " << effective_tflops << " TFLOPS"
<< std::endl;
}
template <typename T,
typename std::enable_if<std::is_same<T, float>::value ||
std::is_same<T, double>::value ||
std::is_same<T, __half>::value,
bool>::type = true>
std::pair<float, float> profile_gemm(
size_t m, size_t n, size_t k, size_t lda, size_t ldb, size_t ldc,
std::function<void(size_t, size_t, size_t, T const*, T const*, size_t,
T const*, size_t, T const*, T*, size_t, cudaStream_t)>
gemm_kernel_launch_function,
T abs_tol, double rel_tol, size_t num_repeats = 10, size_t num_warmups = 10,
unsigned int seed = 0U)
{
T const alpha{static_cast<T>(1.0)};
T const beta{static_cast<T>(0.0)};
// Create CUDA stream.
cudaStream_t stream;
CHECK_CUDA_ERROR(cudaStreamCreate(&stream));
// Allocate memory on host.
T* A_host{nullptr};
T* B_host{nullptr};
T* C_host{nullptr};
T* C_host_ref{nullptr};
T* C_host_from_device{nullptr};
CHECK_CUDA_ERROR(cudaMallocHost(&A_host, m * lda * sizeof(T)));
CHECK_CUDA_ERROR(cudaMallocHost(&B_host, k * ldb * sizeof(T)));
CHECK_CUDA_ERROR(cudaMallocHost(&C_host, m * ldc * sizeof(T)));
CHECK_CUDA_ERROR(cudaMallocHost(&C_host_ref, m * ldc * sizeof(T)));
CHECK_CUDA_ERROR(cudaMallocHost(&C_host_from_device, m * ldc * sizeof(T)));
// Initialize matrix A and B.
random_initialize_matrix(A_host, m, k, lda);
random_initialize_matrix(B_host, k, n, ldb);
random_initialize_matrix(C_host, m, n, ldc);
// Allocate memory on device.
T* A_device{nullptr};
T* B_device{nullptr};
T* C_device{nullptr};
CHECK_CUDA_ERROR(cudaMalloc(&A_device, m * lda * sizeof(T)));
CHECK_CUDA_ERROR(cudaMalloc(&B_device, k * ldb * sizeof(T)));
CHECK_CUDA_ERROR(cudaMalloc(&C_device, m * ldc * sizeof(T)));
// Copy matrix A and B from host to device.
CHECK_CUDA_ERROR(cudaMemcpy(A_device, A_host, m * lda * sizeof(T),
cudaMemcpyHostToDevice));
CHECK_CUDA_ERROR(cudaMemcpy(B_device, B_host, k * ldb * sizeof(T),
cudaMemcpyHostToDevice));
CHECK_CUDA_ERROR(cudaMemcpy(C_device, C_host, m * ldc * sizeof(T),
cudaMemcpyHostToDevice));
CHECK_CUDA_ERROR(cudaMemcpy(C_host_ref, C_host, m * ldc * sizeof(T),
cudaMemcpyHostToHost));
// Create cuBLAS handle.
cublasHandle_t handle;
CHECK_CUBLASS_ERROR(cublasCreate(&handle));
CHECK_CUBLASS_ERROR(cublasSetStream(handle, stream));
// Compute reference output using cuBLAS.
launch_gemm_cublas<T>(m, n, k, &alpha, A_device, lda, B_device, ldb, &beta,
C_device, ldc, handle);
CHECK_CUDA_ERROR(cudaStreamSynchronize(stream));
// Copy matrix C from device to host.
CHECK_CUDA_ERROR(cudaMemcpy(C_host_ref, C_device, m * ldc * sizeof(T),
cudaMemcpyDeviceToHost));
// // Compute reference output using CPU.
// std::cout << "Computing reference output using CPU..." << std::endl;
// launch_gemm_cpu<T>(m, n, k, &alpha, A_host, lda, B_host, ldb, &beta,
// C_host_ref, ldc);
// std::cout << "Done." << std::endl;
// Launch CUDA GEMM.
CHECK_CUDA_ERROR(cudaMemcpy(C_device, C_host, m * ldc * sizeof(T),
cudaMemcpyHostToDevice));
// Verify the correctness of CUDA GEMM.
gemm_kernel_launch_function(m, n, k, &alpha, A_device, lda, B_device, ldb,
&beta, C_device, ldc, stream);
// launch_gemm_cublas<T>(m, n, k, &alpha, A_device, lda, B_device, ldb,
// &beta,
// C_device, ldc, handle);
CHECK_CUDA_ERROR(cudaStreamSynchronize(stream));
CHECK_CUDA_ERROR(cudaMemcpy(C_host_from_device, C_device,
m * ldc * sizeof(T), cudaMemcpyDeviceToHost));
assert(all_close<T>(C_host_from_device, C_host_ref, m, n, ldc, abs_tol,
rel_tol));
// Launch cuBLAS GEMM.
float const latency_cublas{measure_performance<void>(
[&](cudaStream_t stream)
{
launch_gemm_cublas<T>(m, n, k, &alpha, A_device, lda, B_device, ldb,
&beta, C_device, ldc, handle);
return;
},
stream, num_repeats, num_warmups)};
float const latency_cuda_gemm{measure_performance<void>(
[&](cudaStream_t stream)
{
gemm_kernel_launch_function(m, n, k, &alpha, A_device, lda,
B_device, ldb, &beta, C_device, ldc,
stream);
return;
},
stream, num_repeats, num_warmups)};
// Release resources.
CHECK_CUDA_ERROR(cudaFree(A_device));
CHECK_CUDA_ERROR(cudaFree(B_device));
CHECK_CUDA_ERROR(cudaFree(C_device));
CHECK_CUDA_ERROR(cudaFreeHost(A_host));
CHECK_CUDA_ERROR(cudaFreeHost(B_host));
CHECK_CUDA_ERROR(cudaFreeHost(C_host));
CHECK_CUDA_ERROR(cudaFreeHost(C_host_ref));
CHECK_CUDA_ERROR(cudaFreeHost(C_host_from_device));
CHECK_CUBLASS_ERROR(cublasDestroy(handle));
CHECK_CUDA_ERROR(cudaStreamDestroy(stream));
std::cout << "cuBLAS GEMM Kernel Performance" << std::endl;
print_performance_result(m, n, k, latency_cublas);
std::cout << "Custom GEMM Kernel Performance" << std::endl;
print_performance_result(m, n, k, latency_cuda_gemm);
std::cout << "Custom GEMM VS cuBLAS GEMM Performance: "
<< latency_cublas / latency_cuda_gemm * 100.0f << "%"
<< std::endl;
return std::pair<float, float>{latency_cublas, latency_cuda_gemm};
}
#endif // PROFILE_UTILS_CUH