Files
TheRock/rocm-libraries/projects/miopen/test/gtest/tensor_api.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

682 lines
22 KiB
C++

/*******************************************************************************
*
* MIT License
*
* Copyright (c) 2023 Advanced Micro Devices, Inc.
*
* 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.
*
*******************************************************************************/
// Test Suite for tensor API
#include <gtest/gtest.h>
#include <miopen/miopen.h>
#include <iomanip>
#include <optional>
#include <sstream>
#include <string>
#include <vector>
// Compiler uses undefined behavior sanitizer
// -fsanitize=enum (or -fsanitize=undefined)
#if(defined(__clang__) || defined(__GNUG__)) && !defined(NDEBUG)
#define UBSAN_ENABLED 1
#else
#define UBSAN_ENABLED 0
#endif
// We use out-of-range values for miopenDataType_t and miopenTensorLayout_t
#define USE_OUT_OF_RANGE_ENUM (UBSAN_ENABLED == 0)
// miopenLastDataType must be changed if new data types are added
#define miopenFirstDataType miopenHalf
#define miopenLastDataType miopenInt64
// miopenLastTensorLayout must be changed if new layouts are added
#define miopenFirstTensorLayout miopenTensorNCHW
#define miopenLastTensorLayout miopenTensorNDHWC
namespace {
using testDataType_t = int;
using testTensorLayout_t = int;
enum class TestStatus
{
Failed,
Passed,
Skipped
};
struct TensorParams
{
testDataType_t dataType;
std::optional<testTensorLayout_t> tensorLayout;
int nbDims;
int* dimsA;
int* stridesA;
friend std::ostream& operator<<(std::ostream& os, const TensorParams& tp)
{
os << "(";
os << "type:" << static_cast<int>(tp.dataType) << ",";
os << "layout:";
if(tp.tensorLayout)
os << static_cast<int>(tp.tensorLayout.value());
else
os << "none";
os << ",";
os << "ndims:" << tp.nbDims;
if(tp.nbDims > 0)
{
os << ",";
if(tp.dimsA == nullptr)
{
os << "dimsA:none";
}
else
{
for(unsigned i = 0; i < tp.nbDims; i++)
{
if(i != 0)
os << "x";
os << tp.dimsA[i];
}
}
os << ",";
if(tp.stridesA == nullptr)
{
os << "stridesA:none";
}
else
{
for(unsigned i = 0; i < tp.nbDims; i++)
{
if(i != 0)
os << "x";
os << tp.stridesA[i];
}
}
}
os << ")";
return os;
}
};
struct TestConfig
{
bool null_tensor_descriptor;
TensorParams params;
bool valid;
bool skip;
friend std::ostream& operator<<(std::ostream& os, const TestConfig& tc)
{
os << "(";
os << "skip:" << tc.skip << ",";
os << "valid:" << tc.valid << ",";
os << "null_td:" << tc.null_tensor_descriptor << ",";
os << tc.params;
os << ")";
return os;
}
};
template <class L1, class L2>
bool CompareLengths(const L1 l1, const L2 l2, int size)
{
for(int i = 0; i < size; i++)
{
if(l1[i] != l2[i])
return false;
}
return true;
}
// Set tensor descriptor
TestStatus Set4dTensorDescriptor(miopenTensorDescriptor_t tensorDesc,
const TensorParams& params,
bool check_skip)
{
if(params.tensorLayout && (params.tensorLayout != miopenTensorNCHW))
return TestStatus::Skipped;
if(params.nbDims != 4 || params.dimsA == nullptr || params.stridesA != nullptr)
return TestStatus::Skipped;
if(check_skip)
return TestStatus::Passed;
miopenStatus_t status =
miopenSet4dTensorDescriptor(tensorDesc,
static_cast<miopenDataType_t>(params.dataType),
params.dimsA[0],
params.dimsA[1],
params.dimsA[2],
params.dimsA[3]);
if(status == miopenStatusSuccess)
return TestStatus::Passed;
return TestStatus::Failed;
}
TestStatus SetNdTensorDescriptorWithLayout(miopenTensorDescriptor_t tensorDesc,
const TensorParams& params,
bool check_skip)
{
if(!params.tensorLayout || params.stridesA != nullptr)
return TestStatus::Skipped;
if(check_skip)
return TestStatus::Passed;
miopenStatus_t status = miopenSetNdTensorDescriptorWithLayout(
tensorDesc,
static_cast<miopenDataType_t>(params.dataType),
static_cast<miopenTensorLayout_t>(params.tensorLayout.value()),
params.dimsA,
params.nbDims);
if(status == miopenStatusSuccess)
return TestStatus::Passed;
return TestStatus::Failed;
}
TestStatus Set4dTensorDescriptorEx(miopenTensorDescriptor_t tensorDesc,
const TensorParams& params,
bool check_skip)
{
if(params.tensorLayout || params.nbDims != 4 || params.dimsA == nullptr ||
params.stridesA == nullptr)
{
return TestStatus::Skipped;
}
if(check_skip)
return TestStatus::Passed;
miopenStatus_t status =
miopenSet4dTensorDescriptorEx(tensorDesc,
static_cast<miopenDataType_t>(params.dataType),
params.dimsA[0],
params.dimsA[1],
params.dimsA[2],
params.dimsA[3],
params.stridesA[0],
params.stridesA[1],
params.stridesA[2],
params.stridesA[3]);
if(status == miopenStatusSuccess)
return TestStatus::Passed;
return TestStatus::Failed;
}
TestStatus SetTensorDescriptor(miopenTensorDescriptor_t tensorDesc,
const TensorParams& params,
bool check_skip)
{
if(params.tensorLayout)
return TestStatus::Skipped;
if(check_skip)
return TestStatus::Passed;
miopenStatus_t status =
miopenSetTensorDescriptor(tensorDesc,
static_cast<miopenDataType_t>(params.dataType),
params.nbDims,
params.dimsA,
params.stridesA);
if(status == miopenStatusSuccess)
return TestStatus::Passed;
return TestStatus::Failed;
}
TestStatus SetTensorDescriptorV2(miopenTensorDescriptor_t tensorDesc,
const TensorParams& params,
bool check_skip)
{
if(params.tensorLayout)
return TestStatus::Skipped;
if(check_skip)
return TestStatus::Passed;
size_t* dimsA = nullptr;
size_t* stridesA = nullptr;
std::vector<std::size_t> dims;
std::vector<std::size_t> strides;
if(params.nbDims > 0)
{
if(params.dimsA != nullptr)
{
dims = std::vector<std::size_t>(params.dimsA, params.dimsA + params.nbDims);
dimsA = dims.data();
}
if(params.stridesA != nullptr)
{
strides = std::vector<std::size_t>(params.stridesA, params.stridesA + params.nbDims);
stridesA = strides.data();
}
}
miopenStatus_t status = miopenSetTensorDescriptorV2(
tensorDesc, static_cast<miopenDataType_t>(params.dataType), params.nbDims, dimsA, stridesA);
if(status == miopenStatusSuccess)
return TestStatus::Passed;
return TestStatus::Failed;
}
const auto set_tensor_descr_funcs = {Set4dTensorDescriptor,
SetNdTensorDescriptorWithLayout,
Set4dTensorDescriptorEx,
SetTensorDescriptor,
SetTensorDescriptorV2};
// Get tensor descriptor
TestStatus Get4dTensorDescriptor(miopenTensorDescriptor_t tensorDesc, const TensorParams& params)
{
if(params.tensorLayout || params.nbDims != 4)
return TestStatus::Skipped;
if(params.dimsA == nullptr)
return TestStatus::Failed; // internal error
miopenStatus_t status;
miopenDataType_t dataType;
int dims[4], strides[4];
status = miopenGet4dTensorDescriptor(tensorDesc,
&dataType,
dims,
dims + 1,
dims + 2,
dims + 3,
strides,
strides + 1,
strides + 2,
strides + 3);
if(status != miopenStatusSuccess)
return TestStatus::Failed;
if(params.dataType != dataType || !CompareLengths(params.dimsA, dims, 4) ||
(params.stridesA != nullptr && !CompareLengths(params.stridesA, strides, 4)))
{
return TestStatus::Failed;
}
return TestStatus::Passed;
}
TestStatus GetTensorDescriptor(miopenTensorDescriptor_t tensorDesc, const TensorParams& params)
{
if(params.tensorLayout)
{
if((params.tensorLayout != miopenTensorNCHW && params.tensorLayout != miopenTensorNCDHW) ||
params.stridesA != nullptr)
{
return TestStatus::Skipped;
}
}
if(params.dimsA == nullptr)
return TestStatus::Failed; // internal error
miopenStatus_t status;
int size;
status = miopenGetTensorDescriptorSize(tensorDesc, &size);
if(status != miopenStatusSuccess || size < 0 || size != params.nbDims)
return TestStatus::Failed;
miopenDataType_t dataType;
std::vector<int> dims(size);
std::vector<int> strides(size);
status = miopenGetTensorDescriptor(tensorDesc, &dataType, dims.data(), strides.data());
if(status != miopenStatusSuccess)
return TestStatus::Failed;
if(params.dataType != dataType || !CompareLengths(params.dimsA, dims, size) ||
(params.stridesA != nullptr && !CompareLengths(params.stridesA, strides, size)))
return TestStatus::Failed;
return TestStatus::Passed;
}
const auto get_tensor_descr_funcs = {Get4dTensorDescriptor, GetTensorDescriptor};
class TestSetTensor : public ::testing::TestWithParam<TestConfig>
{
protected:
static int GetNumDimsForLayout(testTensorLayout_t layout)
{
int ndims = 0;
// clang-format off
switch(layout)
{
case miopenTensorNCHW:
case miopenTensorNHWC:
case miopenTensorCHWN:
case miopenTensorNCHWc4:
case miopenTensorNCHWc8:
case miopenTensorCHWNc4:
case miopenTensorCHWNc8:
ndims = 4;
break;
case miopenTensorNCDHW:
case miopenTensorNDHWC:
ndims = 5;
break;
}
// clang-format on
return ndims;
}
static void GenerateValidConfigs(std::vector<TestConfig>& configs)
{
static int dims[] = {4, 4, 16, 9, 16};
static int strides[] = {9216, 2304, 144, 16, 1};
static_assert(sizeof(dims) == sizeof(strides));
const auto max_ndims = sizeof(dims) / sizeof(dims[0]);
// clang-format off
for(testDataType_t datatype = miopenFirstDataType; datatype <= miopenLastDataType; datatype++)
// clang-format on
{
if(datatype == 4)
continue; // miopenInt8x4
// clang-format off
for(testTensorLayout_t layout = miopenFirstTensorLayout; layout <= miopenLastTensorLayout; layout++)
// clang-format on
{
int ndims = GetNumDimsForLayout(layout);
const TestConfig config = {
false,
{datatype, layout, ndims, dims + (max_ndims - ndims), nullptr},
true,
false};
configs.push_back(config);
}
for(int i = 0; i < 2; i++)
{
const bool use_strides = (i == 1);
for(int ndims = 1; ndims <= max_ndims; ndims++)
{
const TestConfig config = {
false,
{datatype,
std::nullopt,
ndims,
dims + (max_ndims - ndims),
use_strides ? (strides + (max_ndims - ndims)) : nullptr},
true,
false};
configs.push_back(config);
}
}
}
}
static std::vector<TestConfig> GenerateValidConfigs()
{
std::vector<TestConfig> configs;
GenerateValidConfigs(configs);
return configs;
}
static std::vector<testDataType_t> GetWrongDataTypes()
{
std::vector<testDataType_t> wrong_datatypes = {
static_cast<testDataType_t>(miopenFirstDataType) - 1,
static_cast<testDataType_t>(miopenLastDataType) + 1,
/*miopenInt8x4*/ 4};
return wrong_datatypes;
}
static std::vector<testTensorLayout_t> GetWrongLayouts(int num_dims, bool use_strides)
{
std::vector<testTensorLayout_t> wrong_layouts = {
static_cast<testTensorLayout_t>(miopenFirstTensorLayout) - 1,
static_cast<testTensorLayout_t>(miopenLastTensorLayout) + 1};
std::vector<testTensorLayout_t> layouts_4d = {miopenTensorNCHW,
miopenTensorNHWC,
miopenTensorCHWN,
miopenTensorNCHWc4,
miopenTensorNCHWc8,
miopenTensorCHWNc4,
miopenTensorCHWNc8};
std::vector<testTensorLayout_t> layouts_5d = {miopenTensorNCDHW, miopenTensorNDHWC};
if(use_strides)
{
wrong_layouts.insert(wrong_layouts.end(), layouts_4d.cbegin(), layouts_4d.cend());
wrong_layouts.insert(wrong_layouts.end(), layouts_5d.cbegin(), layouts_5d.cend());
}
else
{
if(num_dims == 4)
wrong_layouts.insert(wrong_layouts.end(), layouts_5d.cbegin(), layouts_5d.cend());
else if(num_dims == 5)
wrong_layouts.insert(wrong_layouts.end(), layouts_4d.cbegin(), layouts_4d.cend());
}
return wrong_layouts;
}
static std::vector<int> GetWrongNumDims(std::optional<testTensorLayout_t> layout)
{
std::vector<int> wrong_ndims = {-1, 0};
if(!layout)
return wrong_ndims;
auto num_dims = GetNumDimsForLayout(layout.value());
wrong_ndims.push_back(num_dims - 1);
wrong_ndims.push_back(num_dims + 1);
return wrong_ndims;
}
static void GenerateWrongConfigs(const TestConfig& valid_config,
std::vector<TestConfig>& wrong_configs)
{
const auto wrong_datatypes = GetWrongDataTypes();
const auto wrong_layouts =
GetWrongLayouts(valid_config.params.nbDims, valid_config.params.stridesA != nullptr);
const auto wrong_ndims = GetWrongNumDims(valid_config.params.tensorLayout);
static int wrong_dims[][8] = {{0, 0, 0, 0, 0, 0, 0, 0}, {-1, -1, -1, -1, -1, -1, -1, -1}};
// tensorDesc = nullptr
{
auto config = valid_config;
config.null_tensor_descriptor = true;
config.valid = false;
wrong_configs.push_back(config);
}
// wrong data type
for(auto datatype : wrong_datatypes)
{
auto config = valid_config;
config.params.dataType = datatype;
config.valid = false;
#if !USE_OUT_OF_RANGE_ENUM
if(config.params.dataType < miopenFirstDataType ||
config.params.dataType > miopenLastDataType)
{
config.skip = true;
}
#endif
wrong_configs.push_back(config);
}
// wrong layout
for(auto layout : wrong_layouts)
{
auto config = valid_config;
config.params.tensorLayout = layout;
config.valid = false;
#if !USE_OUT_OF_RANGE_ENUM
if(config.params.tensorLayout < miopenFirstTensorLayout ||
config.params.tensorLayout > miopenLastTensorLayout)
{
config.skip = true;
}
#endif
wrong_configs.push_back(config);
}
#if defined(__SANITIZE_ADDRESS__) || (defined(__has_feature) && __has_feature(address_sanitizer))
// Skip wrong dimension tests when AddressSanitizer is enabled as they trigger buffer
// overflows before reaching actual API validation, rendering the edge case tests useless.
#else
// wrong number of dimensions
for(auto ndims : wrong_ndims)
{
auto config = valid_config;
config.params.nbDims = ndims;
config.valid = false;
wrong_configs.push_back(config);
}
#endif
// dimsA = nullptr
{
auto config = valid_config;
config.params.dimsA = nullptr;
config.valid = false;
wrong_configs.push_back(config);
}
// wrong dimensions
for(auto dims : wrong_dims)
{
auto config = valid_config;
config.params.dimsA = dims;
config.valid = false;
wrong_configs.push_back(config);
}
if(valid_config.params.stridesA != nullptr)
{
// wrong strides
for(auto strides : wrong_dims)
{
auto config = valid_config;
config.params.stridesA = strides;
config.valid = false;
wrong_configs.push_back(config);
}
}
}
static std::vector<TestConfig> GenerateWrongConfigs()
{
const auto& valid_configs = GetValidConfigs();
std::vector<TestConfig> wrong_configs;
for(const auto& valid_config : valid_configs)
GenerateWrongConfigs(valid_config, wrong_configs);
return wrong_configs;
}
static void RunTest(const TestConfig& config)
{
for(const auto set_tensor_descr_func : set_tensor_descr_funcs)
{
TestStatus test_status;
miopenStatus_t status;
miopenTensorDescriptor_t desc = nullptr;
test_status = set_tensor_descr_func(nullptr, config.params, true);
if(test_status == TestStatus::Skipped)
continue;
if(!config.null_tensor_descriptor)
{
status = miopenCreateTensorDescriptor(&desc);
ASSERT_EQ(status, miopenStatusSuccess);
}
test_status = set_tensor_descr_func(desc, config.params, false);
if(config.valid)
ASSERT_NE(test_status, TestStatus::Failed);
else
ASSERT_NE(test_status, TestStatus::Passed);
if(config.valid)
{
for(const auto get_tensor_descr_func : get_tensor_descr_funcs)
{
test_status = get_tensor_descr_func(desc, config.params);
ASSERT_NE(test_status, TestStatus::Failed);
}
}
if(!config.null_tensor_descriptor)
{
status = miopenDestroyTensorDescriptor(desc);
ASSERT_EQ(status, miopenStatusSuccess);
}
}
}
static void RunTest()
{
const auto& config = GetParam();
if(config.skip)
GTEST_SKIP();
RunTest(config);
}
public:
static const std::vector<TestConfig>& GetValidConfigs()
{
static const auto configs = GenerateValidConfigs();
return configs;
}
static const std::vector<TestConfig>& GetWrongConfigs()
{
static const auto configs = GenerateWrongConfigs();
return configs;
}
};
} // namespace
using CPU_ApiTestSetTensorDescriptor_NONE = TestSetTensor;
using CPU_ApiTestSetWrongTensorDescriptor_NONE = TestSetTensor;
TEST_P(CPU_ApiTestSetTensorDescriptor_NONE, TD) { RunTest(); }
TEST_P(CPU_ApiTestSetWrongTensorDescriptor_NONE, TD) { RunTest(); }
INSTANTIATE_TEST_SUITE_P(Full,
CPU_ApiTestSetTensorDescriptor_NONE,
testing::ValuesIn(TestSetTensor::GetValidConfigs()));
INSTANTIATE_TEST_SUITE_P(Full,
CPU_ApiTestSetWrongTensorDescriptor_NONE,
testing::ValuesIn(TestSetTensor::GetWrongConfigs()));