Files
TheRock/rocm-libraries/projects/miopen/test/gtest/cbna_inference.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

377 lines
14 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <algorithm>
#include <cctype>
#include <limits>
#include <vector>
#include <gtest/gtest.h>
#include <half/half.hpp>
#include <miopen/fusion.hpp>
#include <miopen/fusion/fusion_invoke_params.hpp>
#include <miopen/miopen.h>
#include "../conv_common.hpp"
#include "../fusionHost.hpp"
#include "conv_test_base.hpp"
#include "get_handle.hpp"
#include "gtest_common.hpp"
#include "tensor_util.hpp"
namespace {
using float16 = half_float::half;
struct CbnaParamNameGenerator
{
template <typename ParamType>
std::string operator()(const testing::TestParamInfo<ParamType>& info) const
{
std::string name = testing::PrintToString(info.param);
std::transform(name.begin(), name.end(), name.begin(), [](const char c) {
return std::isalnum(static_cast<unsigned char>(c)) ? c : '_';
});
if(name.empty())
name = "param";
return "case_" + std::to_string(info.index) + "_" + name;
}
};
using ptr_FusionPlanDesc = MIOPEN_MANAGE_PTR(miopenFusionPlanDescriptor_t, miopenDestroyFusionPlan);
using ptr_FusionPlanArgs = MIOPEN_MANAGE_PTR(miopenOperatorArgs_t, miopenDestroyOperatorArgs);
ptr_FusionPlanDesc GetManagedFusionPlanDesc(miopenTensorDescriptor_t inputDesc)
{
miopenFusionPlanDescriptor_t fusePlanDesc;
miopenCreateFusionPlan(&fusePlanDesc, miopenVerticalFusion, inputDesc);
return ptr_FusionPlanDesc{fusePlanDesc};
}
ptr_FusionPlanArgs GetManageFusionPlanArgs()
{
miopenOperatorArgs_t fusionArgs;
miopenCreateOperatorArgs(&fusionArgs);
return ptr_FusionPlanArgs{fusionArgs};
}
template <class T>
struct verify_forward_cbna
{
tensor<T> input;
tensor<T> weights;
miopenConvolutionDescriptor_t filter;
tensor<T> bias{};
miopenTensorDescriptor_t inputDesc{};
miopenTensorDescriptor_t weightsDesc{};
miopenTensorDescriptor_t outputDesc{};
miopenTensorDescriptor_t biasDesc{};
tensor<T> bnscale{};
tensor<T> bnbias{};
tensor<T> estMean{};
tensor<T> estVariance{};
miopenFusionPlanDescriptor_t fusionplan;
miopenBatchNormMode_t bnmode;
double epsilon;
size_t workspace_size = 0;
verify_forward_cbna(miopenFusionPlanDescriptor_t pfusionplan,
tensor<T>& pinput,
tensor<T>& pweights,
miopen::ConvolutionDescriptor& pfilter,
tensor<T>& pbias,
tensor<T>& pbnscale,
const tensor<T>& pbnbias,
const tensor<T>& pestMean,
const tensor<T>& pestVariance,
miopenBatchNormMode_t pbnmode,
size_t pworkspace_size)
{
input = pinput;
inputDesc = &pinput.desc;
weights = pweights;
weightsDesc = &pweights.desc;
bias = pbias;
biasDesc = &pbias.desc;
filter = &pfilter;
bnscale = pbnscale;
bnbias = pbnbias;
estMean = pestMean;
estVariance = pestVariance;
bnmode = pbnmode;
fusionplan = pfusionplan;
epsilon = 1.0e-5;
workspace_size = pworkspace_size;
}
tensor<T> cpu() const
{
auto rout = get_output_tensor(miopen::deref(filter), input, weights);
auto bout = rout;
auto aout = rout;
std::fill(bout.begin(), bout.end(), 0.);
std::fill(aout.begin(), aout.end(), 0.);
convHostForward(input, rout, weights, 1, bias, filter);
if(bnmode == miopenBNPerActivation)
{
batchNormPerActivHostInference(
rout, bout, bnscale, bnbias, epsilon, estMean, estVariance);
}
else
{
batchNormSpatialHostInference(
rout, bout, bnscale, bnbias, epsilon, estMean, estVariance);
}
activationHostInfer(miopenActivationRELU, 0.5, 0.5, 0.5, bout.data, aout.data);
return aout;
}
tensor<T> gpu() const
{
auto&& handle = get_handle();
auto rout = get_output_tensor(miopen::deref(filter), input, weights);
auto in_dev = handle.Write(input.data);
auto wei_dev = handle.Write(weights.data);
auto b_dev = handle.Write(bias.data);
auto out_dev = handle.Write(rout.data);
auto sc_dev = handle.Write(bnscale.data);
auto sh_dev = handle.Write(bnbias.data);
auto m_dev = handle.Write(estMean.data);
auto v_dev = handle.Write(estVariance.data);
auto workspace_dev =
workspace_size > 0 ? handle.Write(std::vector<char>(workspace_size)) : nullptr;
miopenFusionOpDescriptor_t convoOp = nullptr;
miopenFusionOpDescriptor_t biasOp = nullptr;
miopenFusionOpDescriptor_t bnOp = nullptr;
miopenFusionOpDescriptor_t activOp = nullptr;
auto ptr_fusionargs = GetManageFusionPlanArgs();
double alpha = 1.0, beta = 0.0;
miopenFusionPlanGetOp(fusionplan, 0, &convoOp);
miopenFusionPlanGetOp(fusionplan, 1, &biasOp);
miopenFusionPlanGetOp(fusionplan, 2, &bnOp);
miopenFusionPlanGetOp(fusionplan, 3, &activOp);
miopenSetOpArgsConvForward(ptr_fusionargs.get(), convoOp, &alpha, &beta, wei_dev.get());
miopenSetOpArgsBiasForward(ptr_fusionargs.get(), biasOp, &alpha, &beta, b_dev.get());
miopenSetOpArgsBatchNormInference(ptr_fusionargs.get(),
bnOp,
&alpha,
&beta,
sc_dev.get(),
sh_dev.get(),
m_dev.get(),
v_dev.get(),
epsilon);
miopenSetOpArgsActivForward(ptr_fusionargs.get(), activOp, &alpha, &beta, 0.5, 0.5, 0.5);
miopenExecuteFusionPlan_v2(&handle,
fusionplan,
inputDesc,
in_dev.get(),
&rout.desc,
out_dev.get(),
ptr_fusionargs.get(),
workspace_dev.get(),
workspace_size);
rout.data = handle.Read<T>(out_dev, rout.data.size());
return rout;
}
};
template <typename T, typename Verifier>
void VerifyAndValidate(const Verifier& verifier)
{
auto cpu_result = verifier.cpu();
auto gpu_result = verifier.gpu();
EXPECT_EQ(miopen::range_distance(cpu_result), miopen::range_distance(gpu_result));
using value_type = T;
const double tolerance = 80.0;
const double threshold = std::numeric_limits<value_type>::epsilon() * tolerance;
const double rms_error = miopen::rms_range(cpu_result, gpu_result);
EXPECT_LE(rms_error, threshold)
<< "RMS error: " << rms_error << " exceeds threshold: " << threshold;
}
struct CbnaTestCase
{
std::vector<int> input_dims; // [N, C, H, W]
std::vector<int> weights_dims; // [K, C, H, W]
std::vector<int>
pads_strides_dilations; // [pad_h, pad_w, stride_h, stride_w, dilation_h, dilation_w]
bool bias_mode;
std::string pad_mode;
bool test_activ;
int activ_mode;
int batchnorm_mode;
friend std::ostream& operator<<(std::ostream& os, const CbnaTestCase& tc)
{
return os << "input: [" << tc.input_dims[0] << "," << tc.input_dims[1] << ","
<< tc.input_dims[2] << "," << tc.input_dims[3] << "] weights: ["
<< tc.weights_dims[0] << "," << tc.weights_dims[1] << "," << tc.weights_dims[2]
<< "," << tc.weights_dims[3] << "] bias:" << tc.bias_mode
<< " pad_mode:" << tc.pad_mode << " activ:" << tc.test_activ;
}
};
std::vector<CbnaTestCase> GetCbnaTestCases()
{
std::vector<CbnaTestCase> result;
auto input_shapes = get_inputs<int>(0);
auto weight_shapes = get_weights<int>(0);
for(const auto& in : input_shapes)
{
for(auto stride : {1, 2})
{
int fpad_h = (stride == 2) ? 1 : 0;
int fpad_w = (stride == 2) ? 1 : 0;
for(const auto& wei : weight_shapes)
{
if(in[1] != wei[1])
continue;
result.push_back({{1, in[1], in[2], in[3]},
{wei[0], wei[1], wei[2], wei[3]},
{fpad_h, fpad_w, stride, stride, 1, 1},
true,
"default",
true,
3,
1});
}
}
}
return result;
}
template <typename T>
void RunCbnaInferenceTest(const CbnaTestCase& test_case)
{
uint64_t max_value = miopen_type<T>{} == miopenHalf ? 5 : 17;
tensor<T> input{static_cast<size_t>(test_case.input_dims[0]),
static_cast<size_t>(test_case.input_dims[1]),
static_cast<size_t>(test_case.input_dims[2]),
static_cast<size_t>(test_case.input_dims[3])};
input.generate(tensor_elem_gen_integer{max_value});
tensor<T> weights{static_cast<size_t>(test_case.weights_dims[0]),
static_cast<size_t>(test_case.weights_dims[1]),
static_cast<size_t>(test_case.weights_dims[2]),
static_cast<size_t>(test_case.weights_dims[3])};
weights.generate(tensor_elem_gen_integer{max_value});
int input_c, input_h, input_w, wei_c, wei_k, wei_h, wei_w;
std::tie(wei_k, wei_c, wei_h, wei_w) = miopen::tien<4>(weights.desc.GetLengths());
std::tie(std::ignore, input_c, input_h, input_w) = miopen::tien<4>(input.desc.GetLengths());
EXPECT_EQ(input_c, wei_c) << "Aborting due to incorrect test config";
if(input_c != wei_c)
{
return;
}
miopen::ConvolutionDescriptor filter;
filter.mode = miopenConvolution;
filter.paddingMode = miopenPaddingDefault;
filter.pads[0] = test_case.pads_strides_dilations[0];
filter.pads[1] = test_case.pads_strides_dilations[1];
filter.strides[0] = test_case.pads_strides_dilations[2];
filter.strides[1] = test_case.pads_strides_dilations[3];
filter.dilations[0] = test_case.pads_strides_dilations[4];
filter.dilations[1] = test_case.pads_strides_dilations[5];
auto&& handle = get_handle();
auto ptr_fusionplan = GetManagedFusionPlanDesc(&input.desc);
miopenFusionOpDescriptor_t convoOp = nullptr;
miopenFusionOpDescriptor_t biasOp = nullptr;
miopenFusionOpDescriptor_t bnOp = nullptr;
miopenFusionOpDescriptor_t activOp = nullptr;
miopenCreateOpConvForward(ptr_fusionplan.get(), &convoOp, &filter, &weights.desc);
auto output = get_output_tensor(filter, input, weights);
tensor<T> bias{1, output.desc.GetLengths()[1], 1, 1};
bias.generate(tensor_elem_gen_integer{max_value});
miopenCreateOpBiasForward(ptr_fusionplan.get(), &biasOp, &bias.desc);
miopenBatchNormMode_t bnmode = miopenBNSpatial;
auto derivedBnDesc = miopen::TensorDescriptor{};
miopen::DeriveBNTensorDescriptor(derivedBnDesc, output.desc, bnmode);
tensor<T> bnscale{derivedBnDesc.GetLengths()};
bnscale.generate(tensor_elem_gen_integer{max_value});
tensor<T> bnbias{derivedBnDesc.GetLengths()};
bnbias.generate(tensor_elem_gen_integer{max_value});
tensor<T> estMean{derivedBnDesc.GetLengths()};
estMean.generate(tensor_elem_gen_integer{max_value});
tensor<T> estVariance{derivedBnDesc.GetLengths()};
estVariance.generate(tensor_elem_gen_integer{max_value});
miopenCreateOpBatchNormInference(ptr_fusionplan.get(), &bnOp, bnmode, &bnscale.desc);
miopenCreateOpActivationForward(ptr_fusionplan.get(), &activOp, miopenActivationRELU);
// A few basic dimension checks that we expect to be caught.
const bool valid_test_case = wei_h > 2 * filter.pads[0] && wei_w > 2 * filter.pads[1] &&
input_h >= (2 * filter.pads[0] + wei_h) &&
input_w >= (2 * filter.pads[1] + wei_w);
miopenStatus_t miopenError = miopenCompileFusionPlan(&handle, ptr_fusionplan.get());
size_t workspace_size = 0;
if(miopenError == miopenStatusSuccess)
{
miopenFusionPlanGetWorkSpaceSize(
&handle, ptr_fusionplan.get(), &workspace_size, miopenConvolutionFwdAlgoImplicitGEMM);
}
if(miopenError != miopenStatusSuccess)
{
std::stringstream ss;
ss << "CBNA Inference plan not supported for: " << test_case;
GTEST_SKIP() << ss.str();
}
else if(valid_test_case)
{
VerifyAndValidate<T>(verify_forward_cbna<T>{ptr_fusionplan.get(),
input,
weights,
filter,
bias,
bnscale,
bnbias,
estMean,
estVariance,
bnmode,
workspace_size});
}
else
{
GTEST_SKIP() << "Test case dimensions do not meet requirements";
}
}
class GPU_CbnaInference_FP32 : public testing::TestWithParam<CbnaTestCase>
{
void SetUp() override { prng::reset_seed(); }
};
} // namespace
TEST_P(GPU_CbnaInference_FP32, FloatTest_cbna_inference)
{
RunCbnaInferenceTest<float>(GetParam());
}
INSTANTIATE_TEST_SUITE_P(Smoke,
GPU_CbnaInference_FP32,
testing::ValuesIn(GetCbnaTestCases()),
CbnaParamNameGenerator{});