Files
TheRock/rocm-libraries/projects/hipdnn/test_sdk/tests/utilities/TestDynamicTolerancesMatmul.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

396 lines
15 KiB
C++

// Copyright © Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <cmath>
#include <gtest/gtest.h>
#include <hipdnn_data_sdk/types.hpp>
#include <hipdnn_data_sdk/utilities/ShapeUtilities.hpp>
#include <hipdnn_data_sdk/utilities/Tensor.hpp>
#include <hipdnn_test_sdk/utilities/DynamicTolerances.hpp>
#include <vector>
using namespace hipdnn_data_sdk::types;
template <typename Out, typename In, typename Comp>
struct TypeTriple
{
using OutputType = Out;
using InputType = In;
using ComputeType = Comp;
};
// =================================================================================================
// TestCalculateMatmulTolerance
// =================================================================================================
using namespace hipdnn_test_sdk::utilities::matmul;
struct MatmulToleranceTestCase
{
std::vector<int64_t> aDims;
std::vector<int64_t> bDims;
std::vector<double> aRowValues;
std::vector<double> bRowValues;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const MatmulToleranceTestCase& tc)
{
os << "aDims: [";
for(size_t i = 0; i < tc.aDims.size(); ++i)
{
os << tc.aDims[i] << (i < tc.aDims.size() - 1 ? ", " : "");
}
os << "], bDims: [";
for(size_t i = 0; i < tc.bDims.size(); ++i)
{
os << tc.bDims[i] << (i < tc.bDims.size() - 1 ? ", " : "");
}
os << "], aRowValues: [";
for(size_t i = 0; i < tc.aRowValues.size(); ++i)
{
os << tc.aRowValues[i] << (i < tc.aRowValues.size() - 1 ? ", " : "");
}
os << "], bRowValues: [";
for(size_t i = 0; i < tc.bRowValues.size(); ++i)
{
os << tc.bRowValues[i] << (i < tc.bRowValues.size() - 1 ? ", " : "");
}
os << "], expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
namespace
{
// Helper to create a tensor where each row is filled with a per-row constant value.
// rowValues[i] specifies the constant value for all elements in row i.
// This ensures different rows have different sums, exercising the max-row-sum logic
// in computeMatrixInfNorm.
// Supports batched (>2D) tensors via iterateAlongDimensions.
template <typename T>
hipdnn_data_sdk::utilities::Tensor<T>
createTensorFromRowValues(const std::vector<int64_t>& dims,
const std::vector<double>& rowValues)
{
using hipdnn_data_sdk::utilities::iterateAlongDimensions;
hipdnn_data_sdk::utilities::Tensor<T> tensor(dims);
auto cols = dims.back();
// outerDims = [batch..., rows] — everything except the last dim (cols)
auto outerDims = std::vector<int64_t>(dims.begin(), dims.end() - 1);
iterateAlongDimensions(outerDims, [&](const std::vector<int64_t>& outerIndices) {
auto row = outerIndices.back();
auto fullIndices = outerIndices;
fullIndices.push_back(0);
for(int64_t j = 0; j < cols; ++j)
{
fullIndices.back() = j;
tensor.setHostValue(static_cast<T>(rowValues[static_cast<size_t>(row)]), fullIndices);
}
});
return tensor;
}
} // namespace
using hipdnn_test_sdk::utilities::computeGamma;
template <typename T>
std::vector<MatmulToleranceTestCase> getMatmulToleranceTestCases();
// Float / Float / Float (High Precision: Linear)
// Tolerance = gamma(K, u_float) * ||A||_inf * ||B||_inf
// Row-varying values ensure ||A||_inf != entry_sum / rows
template <>
std::vector<MatmulToleranceTestCase> getMatmulToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto bRowValues100 = std::vector<double>(100, 1.0);
return {// K=1: A=2x1, rows={1,2}. ||A||_inf=2, B=1x2, rows={1}. ||B||_inf=2
{{2, 1}, {1, 2}, {1.0, 2.0}, {1.0}, computeGamma(1, u) * 2.0 * 2.0},
// K=3: A=2x3, rows={1,2}. ||A||_inf=6, B=3x4, rows={1,3,0.5}. ||B||_inf=12
{{2, 3}, {3, 4}, {1.0, 2.0}, {1.0, 3.0, 0.5}, computeGamma(3, u) * 6.0 * 12.0},
// K=10: A=2x10, rows={1,2}. ||A||_inf=20, B=10x2, rows={1..5,1..5}. ||B||_inf=10
{{2, 10},
{10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, u) * 20.0 * 10.0},
// K=100: A=2x100, rows={1,2}. ||A||_inf=200, B=100x2, all 1.0. ||B||_inf=2
{{2, 100}, {100, 2}, {1.0, 2.0}, bRowValues100, computeGamma(100, u) * 200.0 * 2.0},
// Batched K=3: A={2,2,3}, B={2,3,4}. Same row values as 2D K=3 case.
// Batch dim doesn't change max row sum: ||A||_inf=6, ||B||_inf=12
{{2, 2, 3}, {2, 3, 4}, {1.0, 2.0}, {1.0, 3.0, 0.5}, computeGamma(3, u) * 6.0 * 12.0},
// Batched K=10: A={3,2,10}, B={3,10,2}. 3 batches.
// ||A||_inf=20, ||B||_inf=10 (same as 2D K=10)
{{3, 2, 10},
{3, 10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, u) * 20.0 * 10.0}};
}
// Float / Double / Float (Input casting error)
// Tolerance = gamma(K, u_float) * ||A||_inf * ||B||_inf + 2 * ||A||_inf * ||B||_inf * u_float
template <>
std::vector<MatmulToleranceTestCase> getMatmulToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
return {
// K=1: ||A||_inf=2, ||B||_inf=2. Tol = gamma(1,u)*4 + 2*4*u
{{2, 1}, {1, 2}, {1.0, 2.0}, {1.0}, computeGamma(1, u) * 2.0 * 2.0 + 2.0 * 2.0 * 2.0 * u},
// K=10: ||A||_inf=20, ||B||_inf=10. Tol = gamma(10,u)*200 + 2*200*u
{{2, 10},
{10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, u) * 20.0 * 10.0 + 2.0 * 20.0 * 10.0 * u}};
}
// Half / Float / Float (Output casting error)
// Tolerance = gamma(K, u_float) * ||A||_inf * ||B||_inf + ||A||_inf * ||B||_inf * u_half
template <>
std::vector<MatmulToleranceTestCase> getMatmulToleranceTestCases<TypeTriple<half, float, float>>()
{
auto uFloat = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// K=1: ||A||_inf=2, ||B||_inf=2. Tol = gamma(1,uFloat)*4 + 4*uHalf
{{2, 1},
{1, 2},
{1.0, 2.0},
{1.0},
computeGamma(1, uFloat) * 2.0 * 2.0 + 2.0 * 2.0 * uHalf},
// K=10: ||A||_inf=20, ||B||_inf=10. Tol = gamma(10,uFloat)*200 + 200*uHalf
{{2, 10},
{10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, uFloat) * 20.0 * 10.0 + 20.0 * 10.0 * uHalf}};
}
// Half / Half / Half (Low Precision: Statistical)
// Tolerance = gamma(K, u_half) * ||A||_inf * ||B||_inf
template <>
std::vector<MatmulToleranceTestCase> getMatmulToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
auto bRowValues100 = std::vector<double>(100, 1.0);
return {// K=1: ||A||_inf=2, ||B||_inf=2
{{2, 1}, {1, 2}, {1.0, 2.0}, {1.0}, computeGamma(1, u) * 2.0 * 2.0},
// K=10: ||A||_inf=20, ||B||_inf=10
{{2, 10},
{10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, u) * 20.0 * 10.0},
// K=100: ||A||_inf=200, ||B||_inf=2
{{2, 100}, {100, 2}, {1.0, 2.0}, bRowValues100, computeGamma(100, u) * 200.0 * 2.0}};
}
// Bfloat16 / Float / Float (Output casting error)
// Tolerance = gamma(K, u_float) * ||A||_inf * ||B||_inf + ||A||_inf * ||B||_inf * u_bf16
template <>
std::vector<MatmulToleranceTestCase>
getMatmulToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto uFloat = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// K=1: ||A||_inf=2, ||B||_inf=2. Tol = gamma(1,uFloat)*4 + 4*uBf16
{{2, 1},
{1, 2},
{1.0, 2.0},
{1.0},
computeGamma(1, uFloat) * 2.0 * 2.0 + 2.0 * 2.0 * uBf16},
// K=10: ||A||_inf=20, ||B||_inf=10. Tol = gamma(10,uFloat)*200 + 200*uBf16
{{2, 10},
{10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, uFloat) * 20.0 * 10.0 + 20.0 * 10.0 * uBf16}};
}
// Bfloat16 / Bfloat16 / Bfloat16 (Low Precision: Statistical)
// Tolerance = gamma(K, u_bf16) * ||A||_inf * ||B||_inf
template <>
std::vector<MatmulToleranceTestCase>
getMatmulToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto bRowValues50 = std::vector<double>(50, 1.0);
return {// K=1: ||A||_inf=2, ||B||_inf=2
{{2, 1}, {1, 2}, {1.0, 2.0}, {1.0}, computeGamma(1, u) * 2.0 * 2.0},
// K=10: ||A||_inf=20, ||B||_inf=10
{{2, 10},
{10, 2},
{1.0, 2.0},
{1.0, 2.0, 3.0, 4.0, 5.0, 1.0, 2.0, 3.0, 4.0, 5.0},
computeGamma(10, u) * 20.0 * 10.0},
// K=50: ||A||_inf=100, ||B||_inf=2 (K=100 exceeds gamma>=0.5 for bf16)
{{2, 50}, {50, 2}, {1.0, 2.0}, bRowValues50, computeGamma(50, u) * 100.0 * 2.0}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateMatmulTolerance : public ::testing::TestWithParam<MatmulToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& tc = GetParam();
if(tc.expectThrow)
{
auto a = createTensorFromRowValues<In>(tc.aDims, tc.aRowValues);
auto b = createTensorFromRowValues<In>(tc.bDims, tc.bRowValues);
EXPECT_THROW((calculateMatmulTolerance<Out, In, Comp>(a, b)), std::exception) << tc;
}
else
{
auto a = createTensorFromRowValues<In>(tc.aDims, tc.aRowValues);
auto b = createTensorFromRowValues<In>(tc.bDims, tc.bRowValues);
auto tolerance = calculateMatmulTolerance<Out, In, Comp>(a, b);
EXPECT_NEAR(tolerance, static_cast<float>(tc.expectedTolerance), 1e-10f) << tc;
}
}
};
using TestCalculateMatmulToleranceFp32 = TestCalculateMatmulTolerance<float, float, float>;
TEST_P(TestCalculateMatmulToleranceFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalculateMatmulToleranceFp32,
::testing::ValuesIn(getMatmulToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalculateMatmulToleranceInputDouble = TestCalculateMatmulTolerance<float, double, float>;
TEST_P(TestCalculateMatmulToleranceInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalculateMatmulToleranceInputDouble,
::testing::ValuesIn(getMatmulToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalculateMatmulToleranceComputeFloatFp16
= TestCalculateMatmulTolerance<half, float, float>;
TEST_P(TestCalculateMatmulToleranceComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalculateMatmulToleranceComputeFloatFp16,
::testing::ValuesIn(getMatmulToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalculateMatmulToleranceFp16 = TestCalculateMatmulTolerance<half, half, half>;
TEST_P(TestCalculateMatmulToleranceFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalculateMatmulToleranceFp16,
::testing::ValuesIn(getMatmulToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalculateMatmulToleranceComputeFloatBfp16
= TestCalculateMatmulTolerance<bfloat16, float, float>;
TEST_P(TestCalculateMatmulToleranceComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalculateMatmulToleranceComputeFloatBfp16,
::testing::ValuesIn(getMatmulToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalculateMatmulToleranceBfp16
= TestCalculateMatmulTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalculateMatmulToleranceBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalculateMatmulToleranceBfp16,
::testing::ValuesIn(getMatmulToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// Test dimension validation
TEST(TestCalculateMatmulTolerance, ThrowsOnInvalidDimensions)
{
// Mismatched dimensions: A is 2x3, B is 4x5 (3 != 4)
auto a = createTensorFromRowValues<float>({2, 3}, {1.0, 2.0});
auto b = createTensorFromRowValues<float>({4, 5}, {1.0, 2.0, 3.0, 4.0});
EXPECT_THROW((calculateMatmulTolerance<float, float, float>(a, b)), std::invalid_argument);
}
TEST(TestCalculateMatmulTolerance, ThrowsOn1dTensorA)
{
// A is 1D (vector), B is 2D — must reject A
hipdnn_data_sdk::utilities::Tensor<float> a({4});
auto b = createTensorFromRowValues<float>({4, 2}, {1.0, 1.0, 1.0, 1.0});
EXPECT_THROW((calculateMatmulTolerance<float, float, float>(a, b)), std::invalid_argument);
}
TEST(TestCalculateMatmulTolerance, ThrowsOn1dTensorB)
{
// A is 2D, B is 1D (vector) — must reject B
auto a = createTensorFromRowValues<float>({2, 4}, {1.0, 2.0});
hipdnn_data_sdk::utilities::Tensor<float> b({4});
EXPECT_THROW((calculateMatmulTolerance<float, float, float>(a, b)), std::invalid_argument);
}
// Note: K=0 validation test removed. Tensor constructor's validateAllPositive() rejects
// dimension <= 0 before calculateMatmulTolerance is reached, so K=0 cannot be tested
// through the public API with real tensors.
// Test that large K with low-precision type causes gamma >= 0.5 overflow
TEST(TestCalculateMatmulTolerance, ThrowsOnSingularity)
{
// For bfloat16, epsilon = 2^-7 ≈ 7.81e-3
// K=100: nU = 2*100*7.81e-3 = 1.562 >= 0.01 → statistical bound
// gamma = 6 * sqrt(200) * 7.81e-3 ≈ 0.663 >= 0.5 → overflow
auto bRowValues100 = std::vector<double>(100, 1.0);
auto a = createTensorFromRowValues<bfloat16>({2, 100}, {1.0, 2.0});
auto b = createTensorFromRowValues<bfloat16>({100, 2}, bRowValues100);
EXPECT_THROW((calculateMatmulTolerance<bfloat16, bfloat16, bfloat16>(a, b)),
std::overflow_error);
}
// Test that extreme values cause output overflow
TEST(TestCalculateMatmulTolerance, ThrowsOnOutputOverflow)
{
// Create matrices with very large values that will cause tolerance > half::max
// A = 2x10, rows={1e5, 1e5}. ||A||_inf = 10 * 1e5 = 1e6
// B = 10x2, all rows 1e5. ||B||_inf = 2 * 1e5 = 2e5
// Product will exceed half max (65504)
auto bRowValues10 = std::vector<double>(10, 1.0e5);
auto a = createTensorFromRowValues<float>({2, 10}, {1.0e5, 1.0e5});
auto b = createTensorFromRowValues<float>({10, 2}, bRowValues10);
// OutputType = half, so max ≈ 65504
EXPECT_THROW((calculateMatmulTolerance<half, float, float>(a, b)), std::overflow_error);
}