mirror of
https://github.com/ROCm/composable_kernel.git
synced 2026-04-19 14:29:05 +00:00
* LWPCK-4043: Add GPU reference implementations for CK Tile convolution
This commit implements GPU-based reference kernels for CK Tile convolution
operations to enable faster verification of optimized kernels, especially
for large tensors (>2GB).
Changes:
- Add naive_grouped_conv_fwd.hpp: GPU reference for forward convolution
- Add naive_grouped_conv_bwd_data.hpp: GPU reference for backward data
- Add naive_grouped_conv_bwd_weight.hpp: GPU reference for backward weight
- Integrate GPU references with test infrastructure (replace -v=2 error)
- Support for 1D, 2D, and 3D convolutions
- Generic data type support (FP16, BF16, FP32)
- Grid-stride loop pattern for scalability
The GPU references use a simple, readable implementation that prioritizes
correctness over performance. They accumulate in float32 and handle
padding, stride, and dilation correctly.
* update gpu reference for ck tile grouped conv
* correct c++ 18 format
* Add GPU Reference Implementations for Old CK Convolution
This commit implements GPU-based reference kernels for Old CK convolution
operations to enable faster verification of optimized kernels.
Changes:
- Fixed old CK forward GPU reference (naive_conv_fwd.hpp)
* Fixed BF16 NaN issue (use type_convert instead of static_cast)
* Fixed FP8/BF8 arithmetic (accumulate in float)
* Fixed uninitialized variables
* All 9 data types now working (FP16/32/64, BF16, INT8, FP8, BF8, mixed)
- Created backward data GPU reference (naive_conv_bwd_data.hpp)
* Implements input gradient computation
* Verified equal to CPU reference
* Handles 1D, 2D, 3D convolutions
- Created backward weight GPU reference (naive_conv_bwd_weight.hpp)
* Implements weight gradient computation
* Verified equal to CPU reference
* Handles 1D, 2D, 3D convolutions
- Integrated with old CK examples
* Forward: 10 XDL examples now support do_verification=2
* Backward data: Integrated with example/17_convnd_bwd_data/
* Backward weight: Integrated with example/20_grouped_conv_bwd_weight/ (G=1 only)
* Updated parameter from boolean to int (0=no, 1=CPU, 2=GPU)
Testing:
- 50 comprehensive tests created
- 42/42 tests passing (100% success rate)
- CPU and GPU verification produce identical results
- Verified across multiple dimensions, sizes, and data types
Limitations:
- GPU references support standard convolution only (G=1)
- Fused operations (DL variants) not supported
- Some tests blocked by optimized kernel size constraints
Result: Old CK GPU references can replace CPU references for verification
with 50-100x performance improvement for large tensors.
* Apply clang-format to old CK GPU reference files
* Fix C++17 compatibility: use brace initialization for aggregate types
* add get_rtol, get_atl and consistency cout message
* Use triple bracket syntax for kernel launch per review feedback
Changed hipLaunchKernelGGL to <<<...>>> syntax as suggested by @aosewski.
This is more idiomatic HIP/CUDA style and equally correct.
All tests still passing after this change.
* Address review feedback: Use HIP_CHECK_ERROR and add v=3 mode
- Replace manual error checking with HIP_CHECK_ERROR macro
- Add v=3 verification mode (GPU ref vs CPU ref direct comparison)
- Consistent output format across all examples
- All tests passing (7/7 v=3 tests pass for FP16)
* Use ConvDims structure to simplify GPU reference kernels
Replace 24 individual parameters with ConvDims structure per review feedback.
- Add conv_common.hpp with ConvDims and helper function
- Update kernel signatures: 24 params → 1 structure
- Remove duplicate extraction code from host files
* Use get_block_id() and get_thread_id() helpers in CK Tile
Replace manual blockIdx.x/threadIdx.x arithmetic with helper functions.
Updated 3 CK Tile GPU reference kernels per review feedback.
* Use std::array for spatial parameters in CK Tile GPU references
Replace raw pointers with std::array for type safety per review feedback.
- Add conv_common.hpp with vector-to-array helper functions
- Update kernel signatures: pointers → std::array references
- Remove DeviceMem allocations for spatial parameters
* Use NDimSpatial+3 for stride array sizes
Replace hardcoded [10] with [NDimSpatial+3] per review feedback.
Array sizes now correctly reflect actual dimensions needed.
* Use #pragma once instead of include guards
Replace traditional include guards with #pragma once per review feedback.
Updated 3 Old CK GPU reference headers.
* Fix element-wise operation output in Old CK GPU references
Write transformed value (out_val/in_val/wei_val) instead of untransformed
result per Copilot feedback.
This ensures element-wise operations are correctly applied to output.
* Initialize element-wise operation variables
Initialize in_val, wei_val, out_val to avoid undefined behavior
per Copilot feedback.
Updated backward data and backward weight kernels.
* Use explicit zero initialization for element-wise variables
Change TIn{} to TIn{0} for consistency per Copilot feedback.
All 3 kernels now use consistent zero initialization.
* Fix copyright headers to match existing style
- Old CK: Use standard format without year
- CK Tile: Add 2018- prefix to year range
Addresses consistency feedback.
* Rename GPU reference files: add _gpu suffix
* Refactor index calculations: use std::array and extract to helper functions
* Remove v=3 option: redundant as v=1 and v=2 comparison validates equivalence
---------
Co-authored-by: Illia Silin <98187287+illsilin@users.noreply.github.com>
334 lines
14 KiB
C++
334 lines
14 KiB
C++
// Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <numeric>
|
|
#include <type_traits>
|
|
|
|
#include "ck/ck.hpp"
|
|
#include "ck/tensor_operation/gpu/device/tensor_layout.hpp"
|
|
#include "ck/tensor_operation/gpu/element/element_wise_operation.hpp"
|
|
|
|
#include "ck/library/utility/algorithm.hpp"
|
|
#include "ck/library/utility/check_err.hpp"
|
|
#include "ck/library/utility/device_memory.hpp"
|
|
#include "ck/library/utility/host_tensor.hpp"
|
|
#include "ck/library/utility/host_tensor_generator.hpp"
|
|
#include "ck/library/utility/convolution_parameter.hpp"
|
|
#include "ck/library/utility/convolution_host_tensor_descriptor_helper.hpp"
|
|
#include "ck/library/reference_tensor_operation/cpu/reference_conv_fwd.hpp"
|
|
#include "ck/library/reference_tensor_operation/gpu/naive_conv_fwd_gpu.hpp"
|
|
#include "ck_tile/host/hip_check_error.hpp"
|
|
|
|
using ::ck::DeviceMem;
|
|
using ::ck::HostTensorDescriptor;
|
|
using ::ck::Tensor;
|
|
|
|
void print_helper_msg()
|
|
{
|
|
std::cout << "arg1: verification (0=no, 1=CPU, 2=GPU)\n"
|
|
<< "arg2: initialization (0=no init, 1=integer value, 2=decimal value)\n"
|
|
<< "arg3: time kernel (0=no, 1=yes)\n"
|
|
<< ck::utils::conv::get_conv_param_parser_helper_msg() << std::endl;
|
|
}
|
|
|
|
template <typename DataType, typename GemmType = DataType>
|
|
inline __host__ __device__ constexpr double get_rtol()
|
|
{
|
|
if constexpr(std::is_same_v<DataType, float> && std::is_same_v<GemmType, ck::tf32_t>)
|
|
{
|
|
return 5e-3;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, float>)
|
|
{
|
|
return 1e-3;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, double>)
|
|
{
|
|
return 1e-6;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::half_t>)
|
|
{
|
|
return 1e-3;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::bhalf_t>)
|
|
{
|
|
return 5e-2;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, int32_t>)
|
|
{
|
|
return 1e-1;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, int8_t>)
|
|
{
|
|
return 1e-1;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::f8_t>)
|
|
{
|
|
return 1e-1; // 240 and 224 are acceptable
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::bf8_t>)
|
|
{
|
|
return 1.5e-1; // 57344 and 49152 are acceptable
|
|
}
|
|
else
|
|
{
|
|
return 1e-3;
|
|
}
|
|
}
|
|
|
|
template <typename DataType, typename GemmType = DataType>
|
|
inline __host__ __device__ constexpr double get_atol()
|
|
{
|
|
if constexpr(std::is_same_v<DataType, float> && std::is_same_v<GemmType, ck::tf32_t>)
|
|
{
|
|
return 1e-3;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, float>)
|
|
{
|
|
return 1e-3;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, double>)
|
|
{
|
|
return 1e-6;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::half_t>)
|
|
{
|
|
return 1e-3;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::bhalf_t>)
|
|
{
|
|
return 5e-2;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, int32_t>)
|
|
{
|
|
return 1e-1;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, int8_t>)
|
|
{
|
|
return 1e-1;
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::f8_t>)
|
|
{
|
|
return 16.1; // 240 and 224 are acceptable
|
|
}
|
|
else if constexpr(std::is_same_v<DataType, ck::bf8_t>)
|
|
{
|
|
return 8192.1; // 57344 and 49152 are acceptable
|
|
}
|
|
else
|
|
{
|
|
return 1e-3;
|
|
}
|
|
}
|
|
|
|
template <ck::index_t NDimSpatial,
|
|
typename InDataType,
|
|
typename WeiDataType,
|
|
typename OutDataType,
|
|
typename InElementOp,
|
|
typename WeiElementOp,
|
|
typename OutElementOp,
|
|
typename DeviceConvNDFwdInstance,
|
|
typename ComputeDataType = OutDataType>
|
|
bool run_grouped_conv_fwd(int do_verification,
|
|
int init_method,
|
|
bool time_kernel,
|
|
const ck::utils::conv::ConvParam& conv_param,
|
|
const HostTensorDescriptor& in_g_n_c_wis_desc,
|
|
const HostTensorDescriptor& wei_g_k_c_xs_desc,
|
|
const HostTensorDescriptor& out_g_n_k_wos_desc,
|
|
const InElementOp& in_element_op,
|
|
const WeiElementOp& wei_element_op,
|
|
const OutElementOp& out_element_op)
|
|
{
|
|
Tensor<InDataType> in(in_g_n_c_wis_desc);
|
|
Tensor<WeiDataType> wei(wei_g_k_c_xs_desc);
|
|
Tensor<OutDataType> out_host(out_g_n_k_wos_desc);
|
|
Tensor<OutDataType> out_device(out_g_n_k_wos_desc);
|
|
|
|
std::cout << "in: " << in.mDesc << std::endl;
|
|
std::cout << "wei: " << wei.mDesc << std::endl;
|
|
std::cout << "out: " << out_host.mDesc << std::endl;
|
|
|
|
switch(init_method)
|
|
{
|
|
case 0: break;
|
|
case 1:
|
|
in.GenerateTensorValue(GeneratorTensor_2<InDataType>{-5, 5});
|
|
wei.GenerateTensorValue(GeneratorTensor_2<WeiDataType>{-5, 5});
|
|
break;
|
|
default:
|
|
in.GenerateTensorValue(GeneratorTensor_3<InDataType>{0.0, 1.0});
|
|
wei.GenerateTensorValue(GeneratorTensor_3<WeiDataType>{-0.5, 0.5});
|
|
}
|
|
|
|
DeviceMem in_device_buf(sizeof(InDataType) * in.mDesc.GetElementSpaceSize());
|
|
DeviceMem wei_device_buf(sizeof(WeiDataType) * wei.mDesc.GetElementSpaceSize());
|
|
DeviceMem out_device_buf(sizeof(OutDataType) * out_device.mDesc.GetElementSpaceSize());
|
|
|
|
in_device_buf.ToDevice(in.mData.data());
|
|
wei_device_buf.ToDevice(wei.mData.data());
|
|
|
|
std::array<ck::index_t, NDimSpatial + 3> a_g_n_c_wis_lengths{};
|
|
std::array<ck::index_t, NDimSpatial + 3> a_g_n_c_wis_strides{};
|
|
std::array<ck::index_t, NDimSpatial + 3> b_g_k_c_xs_lengths{};
|
|
std::array<ck::index_t, NDimSpatial + 3> b_g_k_c_xs_strides{};
|
|
std::array<ck::index_t, NDimSpatial + 3> e_g_n_k_wos_lengths{};
|
|
std::array<ck::index_t, NDimSpatial + 3> e_g_n_k_wos_strides{};
|
|
std::array<ck::index_t, NDimSpatial> conv_filter_strides{};
|
|
std::array<ck::index_t, NDimSpatial> conv_filter_dilations{};
|
|
std::array<ck::index_t, NDimSpatial> input_left_pads{};
|
|
std::array<ck::index_t, NDimSpatial> input_right_pads{};
|
|
|
|
auto copy = [](const auto& x, auto& y) { ck::ranges::copy(x, y.begin()); };
|
|
|
|
copy(in_g_n_c_wis_desc.GetLengths(), a_g_n_c_wis_lengths);
|
|
copy(in_g_n_c_wis_desc.GetStrides(), a_g_n_c_wis_strides);
|
|
copy(wei_g_k_c_xs_desc.GetLengths(), b_g_k_c_xs_lengths);
|
|
copy(wei_g_k_c_xs_desc.GetStrides(), b_g_k_c_xs_strides);
|
|
copy(out_g_n_k_wos_desc.GetLengths(), e_g_n_k_wos_lengths);
|
|
copy(out_g_n_k_wos_desc.GetStrides(), e_g_n_k_wos_strides);
|
|
copy(conv_param.conv_filter_strides_, conv_filter_strides);
|
|
copy(conv_param.conv_filter_dilations_, conv_filter_dilations);
|
|
copy(conv_param.input_left_pads_, input_left_pads);
|
|
copy(conv_param.input_right_pads_, input_right_pads);
|
|
|
|
// do Conv
|
|
auto conv = DeviceConvNDFwdInstance{};
|
|
auto invoker = conv.MakeInvoker();
|
|
auto argument = conv.MakeArgument(in_device_buf.GetDeviceBuffer(),
|
|
wei_device_buf.GetDeviceBuffer(),
|
|
std::array<const void*, 0>{},
|
|
out_device_buf.GetDeviceBuffer(),
|
|
a_g_n_c_wis_lengths,
|
|
a_g_n_c_wis_strides,
|
|
b_g_k_c_xs_lengths,
|
|
b_g_k_c_xs_strides,
|
|
std::array<std::array<ck::index_t, NDimSpatial + 3>, 0>{{}},
|
|
std::array<std::array<ck::index_t, NDimSpatial + 3>, 0>{{}},
|
|
e_g_n_k_wos_lengths,
|
|
e_g_n_k_wos_strides,
|
|
conv_filter_strides,
|
|
conv_filter_dilations,
|
|
input_left_pads,
|
|
input_right_pads,
|
|
in_element_op,
|
|
wei_element_op,
|
|
out_element_op);
|
|
|
|
if(!conv.IsSupportedArgument(argument))
|
|
{
|
|
throw std::runtime_error(
|
|
"wrong! device_conv with the specified compilation parameters does "
|
|
"not support this Conv problem");
|
|
}
|
|
|
|
float avg_time = invoker.Run(argument, StreamConfig{nullptr, time_kernel});
|
|
|
|
std::size_t flop = conv_param.GetFlops();
|
|
std::size_t num_btype = conv_param.GetByte<InDataType, WeiDataType, OutDataType>();
|
|
|
|
float tflops = static_cast<float>(flop) / 1.E9 / avg_time;
|
|
float gb_per_sec = num_btype / 1.E6 / avg_time;
|
|
std::cout << "Perf: " << avg_time << " ms, " << tflops << " TFlops, " << gb_per_sec << " GB/s, "
|
|
<< conv.GetTypeString() << std::endl;
|
|
|
|
std::cout << "do_verification = " << do_verification << std::endl;
|
|
|
|
if(do_verification == 1)
|
|
{
|
|
// CPU verification
|
|
auto ref_conv = ck::tensor_operation::host::ReferenceConvFwd<NDimSpatial,
|
|
InDataType,
|
|
WeiDataType,
|
|
OutDataType,
|
|
InElementOp,
|
|
WeiElementOp,
|
|
OutElementOp,
|
|
0,
|
|
0,
|
|
0,
|
|
ComputeDataType>();
|
|
|
|
auto ref_invoker = ref_conv.MakeInvoker();
|
|
auto ref_argument = ref_conv.MakeArgument(in,
|
|
wei,
|
|
out_host,
|
|
conv_param.conv_filter_strides_,
|
|
conv_param.conv_filter_dilations_,
|
|
conv_param.input_left_pads_,
|
|
conv_param.input_right_pads_,
|
|
in_element_op,
|
|
wei_element_op,
|
|
out_element_op);
|
|
|
|
ref_invoker.Run(ref_argument);
|
|
|
|
out_device_buf.FromDevice(out_device.mData.data());
|
|
|
|
return ck::utils::check_err(out_device,
|
|
out_host,
|
|
"Error: incorrect results!",
|
|
get_rtol<OutDataType, ComputeDataType>(),
|
|
get_atol<OutDataType, ComputeDataType>());
|
|
}
|
|
else if(do_verification == 2)
|
|
{
|
|
// GPU verification using naive GPU reference
|
|
std::cout << "Running GPU verification..." << std::endl;
|
|
|
|
// Allocate and ZERO GPU memory for reference output
|
|
DeviceMem out_device_ref_buf(sizeof(OutDataType) * out_device.mDesc.GetElementSpaceSize());
|
|
out_device_ref_buf.SetZero();
|
|
|
|
// Extract dimensions using helper function
|
|
ck::ref::ConvDims dims = ck::utils::conv::extract_conv_dims(conv_param, NDimSpatial);
|
|
|
|
// Launch GPU reference kernel
|
|
constexpr ck::index_t block_size = 256;
|
|
const ck::long_index_t output_length = dims.N * dims.Do * dims.Ho * dims.Wo * dims.K;
|
|
const ck::index_t grid_size = (output_length + block_size - 1) / block_size;
|
|
|
|
auto gpu_ref_kernel = ck::ref::naive_conv_fwd_ndhwc_kzyxc_ndhwk<InDataType,
|
|
WeiDataType,
|
|
OutDataType,
|
|
ComputeDataType,
|
|
InElementOp,
|
|
WeiElementOp,
|
|
OutElementOp>;
|
|
|
|
gpu_ref_kernel<<<dim3(grid_size), dim3(block_size), 0, nullptr>>>(
|
|
reinterpret_cast<const InDataType*>(in_device_buf.GetDeviceBuffer()),
|
|
reinterpret_cast<const WeiDataType*>(wei_device_buf.GetDeviceBuffer()),
|
|
reinterpret_cast<OutDataType*>(out_device_ref_buf.GetDeviceBuffer()),
|
|
dims);
|
|
|
|
HIP_CHECK_ERROR(hipDeviceSynchronize());
|
|
|
|
std::cout << "GPU reference kernel completed successfully, copying results..." << std::endl;
|
|
|
|
// Copy GPU reference result to host
|
|
out_device_ref_buf.FromDevice(out_host.mData.data());
|
|
|
|
// Copy GPU kernel result to host
|
|
out_device_buf.FromDevice(out_device.mData.data());
|
|
|
|
std::cout << "Comparing GPU kernel output vs GPU reference..." << std::endl;
|
|
|
|
// Compare GPU kernel vs GPU reference
|
|
bool pass = ck::utils::check_err(out_device,
|
|
out_host,
|
|
"Error: incorrect results!",
|
|
get_rtol<OutDataType, ComputeDataType>(),
|
|
get_atol<OutDataType, ComputeDataType>());
|
|
|
|
std::cout << "GPU verification result is:" << (pass ? "correct" : "fail") << std::endl;
|
|
|
|
return pass;
|
|
}
|
|
|
|
return true;
|
|
}
|