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

417 lines
15 KiB
C++

/*******************************************************************************
*
* MIT License
*
* Copyright (c) 2017 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.
*
*******************************************************************************/
#include <miopen/tensor_ops.hpp>
#include <tensor_util.hpp>
#include "gtest_common.hpp"
namespace {
std::vector<std::vector<size_t>> tensorALensArr = {{32, 16, 8, 4, 4}, // tensor A
{16, 20, 16, 8},
{20, 16, 8},
{1, 16, 8},
{16, 8},
{8}};
std::vector<std::vector<size_t>> tensorBLensArr = {{32, 16, 8, 4, 4}, // tensor B
{32, 16, 1, 1, 1},
{1, 16, 8, 1, 1},
{1, 1, 8, 4, 1},
{16, 20, 16, 8},
{16, 20, 16, 1},
{16, 20, 1, 1},
{16, 1, 1, 1},
{1, 20, 16, 8},
{1, 20, 16, 1},
{1, 20, 1, 1},
{1, 1, 16, 8},
{1, 1, 1, 8},
{20, 16, 8},
{20, 16, 1},
{1, 16, 8},
{1, 16, 1},
{20, 1, 1},
{16, 8},
{16, 1},
{1, 8},
{8},
{1}};
std::vector<std::vector<int64_t>> offsetsArr = {
{0, 0, 0}, {64, 32, 16}, {32, 16, 32}, {32, 16, 32}};
std::vector<std::vector<float>> alphabetaArr = {{1, 1, 0}, {-1, 1, 1}, {1.0, 0.5, 0.3}};
std::vector<std::vector<size_t>> stridesArr = {{8 * 16 * 20 * 16, 8 * 16 * 20, 8 * 16, 8, 1}};
std::vector<bool> packedArr = {true, false};
std::vector<miopenTensorOp_t> operationArr = {
miopenTensorOpAdd, miopenTensorOpMul, miopenTensorOpMin, miopenTensorOpMax};
} // namespace
struct TestCase
{
std::vector<size_t> tensorlens_ac;
std::vector<size_t> tensorlens_b;
std::vector<int64_t> offsets;
std::vector<size_t> stride_a;
std::vector<size_t> stride_b;
std::vector<size_t> stride_c;
std::vector<float> alphabeta;
bool packed;
miopenTensorOp_t operation;
};
template <typename T>
struct TensorOpsCommon : public testing::TestWithParam<TestCase>
{
void SetUp() override { prng::reset_seed(); }
void Run()
{
CreateTensors();
std::vector<T> tensorGPUData = CalculateOnGPU();
std::vector<T> tensorCPUData = CalculateOnCPU();
CompareResults(tensorGPUData, tensorCPUData);
}
private:
void CreateTensors()
{
const TestCase& testCase = GetParam();
tensorA = CreateTensor(
testCase.tensorlens_ac, testCase.stride_a, testCase.offsets[0], testCase.packed);
tensorB = CreateTensor(
testCase.tensorlens_b, testCase.stride_b, testCase.offsets[1], testCase.packed);
tensorC = CreateTensor(
testCase.tensorlens_ac, testCase.stride_c, testCase.offsets[2], testCase.packed);
}
tensor<T> CreateTensor(const std::vector<size_t>& lens,
const std::vector<size_t>& strides,
int64_t offset,
bool isPacked)
{
uint64_t max_value = miopen_type<T>{} == miopenHalf ? 5 : 17;
if(!isPacked)
{
std::vector<size_t> real_strides(strides.begin() + (strides.size() - lens.size()),
strides.end());
auto r = tensor<T>{lens, real_strides}.generate(tensor_elem_gen_integer{max_value});
r.data.resize(r.data.size() + offset);
return r;
}
else
{
return tensor<T>{lens}.generate(tensor_elem_gen_integer{max_value});
}
}
std::vector<T> CalculateOnGPU() const
{
const TestCase& testCase = GetParam();
auto&& handle = get_handle();
auto a_dev = handle.Write(tensorA.data);
auto b_dev = handle.Write(tensorB.data);
auto c_dev = handle.Write(tensorC.data);
miopen::OpTensor(handle,
testCase.operation,
&testCase.alphabeta[0],
tensorA.desc,
a_dev.get(),
&testCase.alphabeta[1],
tensorB.desc,
b_dev.get(),
&testCase.alphabeta[2],
tensorC.desc,
c_dev.get(),
testCase.offsets[0],
testCase.offsets[1],
testCase.offsets[2],
false); // it does not verify non-standard behaviour
return handle.Read<T>(c_dev, tensorC.data.size());
}
std::vector<T> CalculateOnCPU()
{
const TestCase& testCase = GetParam();
float alpha1 = testCase.alphabeta[0];
float alpha2 = testCase.alphabeta[1];
float beta = testCase.alphabeta[2];
if(testCase.operation == miopenTensorOpAdd)
{
return CalculateOnCPUDataOp([alpha1, alpha2, beta](auto& C, auto A, auto B) {
C = A * alpha1 + B * alpha2 + C * beta;
});
}
else if(testCase.operation == miopenTensorOpMul)
{
return CalculateOnCPUDataOp([alpha1, alpha2, beta](auto& C, auto A, auto B) {
C = A * alpha1 * B * alpha2 + C * beta;
});
}
else if(testCase.operation == miopenTensorOpMin)
{
return CalculateOnCPUDataOp([alpha1, alpha2, beta](auto& C, auto A, auto B) {
C = std::min(A * alpha1, B * alpha2) + C * beta;
});
}
else
{
return CalculateOnCPUDataOp([alpha1, alpha2, beta](auto& C, auto A, auto B) {
C = std::max(A * alpha1, B * alpha2) + C * beta;
});
}
}
template <typename DataOp>
std::vector<T> CalculateOnCPUDataOp(DataOp&& dataOp)
{
const TestCase& testCase = GetParam();
auto r = tensorC;
operate_over_subtensor<>(dataOp,
r.data,
tensorA.data,
tensorB.data,
r.desc,
tensorA.desc,
tensorB.desc,
testCase.offsets[2],
testCase.offsets[0],
testCase.offsets[1]);
return r.data;
}
void CompareResults(const std::vector<T>& tensorGPUData, const std::vector<T>& tensorCPUData)
{
const TestCase& testCase = GetParam();
double tolerance = 1;
if(std::is_same_v<T, half_float::half>)
{
// taken from original c-test
tolerance = 80;
}
double threshold = std::numeric_limits<T>::epsilon() * tolerance;
double error = miopen::rms_range(tensorCPUData, tensorGPUData);
ASSERT_LE(error, threshold)
<< "TensorOp: " << testCase.operation << std::endl
<< "A tensor: " << tensorA.desc.ToString() << std::endl
<< "B tensor: " << tensorB.desc.ToString() << std::endl
<< "IsPacked: " << testCase.packed << std::endl
<< "Offsets: " << testCase.offsets[0] << "," << testCase.offsets[1] << ","
<< testCase.offsets[2] << std::endl;
}
private:
tensor<T> tensorA;
tensor<T> tensorB;
tensor<T> tensorC;
};
using GPU_TernaryTensorOps_FP32 = TensorOpsCommon<float>;
using GPU_TernaryTensorOps_FP16 = TensorOpsCommon<half_float::half>;
using GPU_TernaryTensorOps_FP64 = TensorOpsCommon<double>;
namespace {
bool checkTensorsCompatibility(const std::vector<size_t>& tensorALens,
const std::vector<size_t>& tensorBLens)
{
if(tensorALens.size() != tensorBLens.size())
{
return false;
}
for(size_t idx = 0; idx < tensorBLens.size(); ++idx)
{
if((tensorBLens[idx] != 1) && (tensorALens[idx] != tensorBLens[idx]))
{
return false;
}
}
return true;
}
void AddTestCases(std::vector<TestCase>& testCases,
const std::vector<size_t>& tensorALens,
const std::vector<size_t>& tensorBLens)
{
const auto& stride_a = stridesArr[0];
const auto& stride_b = stridesArr[0];
const auto& stride_c = stridesArr[0];
for(bool packed : packedArr)
for(const auto& offsets : offsetsArr)
{
std::vector<int64_t> final_offsets{0, 0, 0};
if(!packed)
{
if(std::any_of(offsets.begin(), offsets.end(), [](int64_t o) { return o < 0; }))
continue;
final_offsets = offsets;
}
auto checkStride = [p = packed](const std::vector<size_t>& lens,
const std::vector<size_t>& strides) {
if(p)
return true;
if(lens.size() > strides.size())
return false;
// only sparsed case allowed, since all the kernels do not support the last
// dimension strides
if(strides.back() == 1)
{
// we use float here for all types because strides are independent to type
auto packedStrides =
miopen::TensorDescriptor(miopen_type<float>{}, lens).GetStrides();
return std::equal(packedStrides.rbegin(),
packedStrides.rend(),
strides.rbegin(),
[](size_t ps, size_t s) { return s >= ps; });
}
// currently tensor operations do not support non-one stride in the last dimention.
return false;
};
if(!checkStride(tensorALens, stride_a))
continue;
if(!checkStride(tensorBLens, stride_b))
continue;
if(!checkStride(tensorALens, stride_c))
continue;
for(const auto& alphabeta : alphabetaArr)
for(const auto& operation : operationArr)
{
TestCase& testCase = testCases.emplace_back();
testCase.tensorlens_ac = tensorALens;
testCase.tensorlens_b = tensorBLens;
testCase.alphabeta = alphabeta;
testCase.offsets = final_offsets;
testCase.packed = packed;
testCase.operation = operation;
testCase.stride_a = stride_a;
testCase.stride_b = stride_b;
testCase.stride_c = stride_c;
}
}
}
std::vector<TestCase> GenCases()
{
std::vector<TestCase> testCases;
for(const auto& tensorALens : tensorALensArr)
for(const auto& tensorBLens : tensorBLensArr)
{
if(!checkTensorsCompatibility(tensorALens, tensorBLens))
{
continue;
}
AddTestCases(testCases, tensorALens, tensorBLens);
}
return testCases;
}
inline auto GetCases()
{
static const auto cases = testing::ValuesIn(GenCases());
return cases;
}
// The FP32 Smoke/Standard/Full tiers run disjoint slices of GenCases(): the
// categories run cumulative tiers (standard = Smoke+Standard, comprehensive/full
// = Smoke+Standard+Full), so the slices reassemble the complete set with no case
// repeated across tiers. The FP64/FP16 suites have only a Full tier and use the
// complete GetCases() directly.
std::vector<TestCase> GenCasesRange(std::size_t begin, std::size_t end)
{
auto cases = GenCases();
begin = std::min(begin, cases.size());
end = std::min(end, cases.size());
return std::vector<TestCase>(cases.begin() + begin, cases.begin() + end);
}
inline auto GetCasesSmoke()
{
static const auto cases = testing::ValuesIn(GenCasesRange(0, 30));
return cases;
}
inline auto GetCasesStandard()
{
static const auto cases = testing::ValuesIn(GenCasesRange(30, 300));
return cases;
}
// FP32 Full tier: the cases beyond Standard (Smoke + Standard + this = all of
// GenCases()).
inline auto GetCasesFullDelta()
{
static const auto cases = testing::ValuesIn(GenCasesRange(300, GenCases().size()));
return cases;
}
} // namespace
TEST_P(GPU_TernaryTensorOps_FP32, TestFloat) { this->Run(); }
TEST_P(GPU_TernaryTensorOps_FP16, TestFloat16) { this->Run(); }
TEST_P(GPU_TernaryTensorOps_FP64, TestDouble) { this->Run(); }
// Tiered: Smoke (pre-commit) and Standard (per-CI) run subsets; Full
// (comprehensive/nightly) runs the complete set so no coverage is lost.
INSTANTIATE_TEST_SUITE_P(Smoke, GPU_TernaryTensorOps_FP32, GetCasesSmoke());
INSTANTIATE_TEST_SUITE_P(Standard, GPU_TernaryTensorOps_FP32, GetCasesStandard());
INSTANTIATE_TEST_SUITE_P(Full, GPU_TernaryTensorOps_FP32, GetCasesFullDelta());
INSTANTIATE_TEST_SUITE_P(Full, GPU_TernaryTensorOps_FP64, GetCases());
INSTANTIATE_TEST_SUITE_P(Full, GPU_TernaryTensorOps_FP16, GetCases());