Files
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

2910 lines
108 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/CpuFpReferenceValidation.hpp>
#include <hipdnn_test_sdk/utilities/DynamicTolerancesBatchNorm.hpp>
#include <hipdnn_test_sdk/utilities/DynamicTolerancesCommon.hpp>
#include <vector>
using namespace hipdnn_test_sdk::utilities::batchnorm;
using hipdnn_data_sdk::types::bfloat16;
using hipdnn_data_sdk::types::half;
using hipdnn_test_sdk::utilities::computeGamma;
template <typename Out, typename In, typename Comp>
struct TypeTriple
{
using OutputType = Out;
using InputType = In;
using ComputeType = Comp;
};
// =================================================================================================
// TestCalculateBatchnormTrainingTolerance
// =================================================================================================
struct BnTrainToleranceTestCase
{
double xMin;
double xMax;
double scaleMin;
double scaleMax;
double biasMin;
double biasMax;
int64_t nElementsPerChannel;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnTrainToleranceTestCase& tc)
{
os << "xMin: " << tc.xMin << ", xMax: " << tc.xMax << ", scaleMin: " << tc.scaleMin
<< ", scaleMax: " << tc.scaleMax << ", biasMin: " << tc.biasMin
<< ", biasMax: " << tc.biasMax << ", NHW: " << tc.nElementsPerChannel
<< ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnTrainToleranceTestCase> getBnTrainToleranceTestCases();
// BN Training tolerance formula (no casting, maxAbsX>0):
// accTol = (gamma + 2u) * S (Higham + 2u for div/sub variance ops)
// propTol = accTol/(2*S) * scale + gamma * scale + 6u * scale + u * bias
// = ((gamma + 2u)/2 + gamma + 6u) * maxAbsScale + u * maxAbsBias
// = (3/2*gamma + u + 6u) * maxAbsScale + u * maxAbsBias
// = (3/2*gamma + 7u) * maxAbsScale + u * maxAbsBias
// Output casting: maxOutputMagnitude = maxAbsScale * maxAbsX * invVarEst + maxAbsBias
// where invVarEst = 1/sqrt(max(maxAbsX^2, 1e-5) + 1e-5)
// With input casting (InputType=double, ComputeType=float):
// accTol = (gamma + 2u + u) * S -> variance path = (gamma + 3u)/2
// total = ((gamma + 3u)/2 + gamma + 6u) * maxAbsScale + u * maxAbsBias
// = (3/2*gamma + 3u/2 + 6u) * maxAbsScale + u * maxAbsBias
// Helper to compute output cast contribution for y tolerance
static double yOutputCast(double maxAbsX,
double maxAbsScale,
double maxAbsBias,
double outputEpsilon,
double computeEpsilon,
double epsBn = 1e-5)
{
if(outputEpsilon <= computeEpsilon)
{
return 0.0;
}
const double varEst = std::max(maxAbsX * maxAbsX, epsBn);
const double invVarEst = 1.0 / std::sqrt(varEst + epsBn);
const double maxOut = (maxAbsX > 0.0 ? maxAbsScale * maxAbsX * invVarEst : 0.0) + maxAbsBias;
return maxOut * outputEpsilon;
}
// Float / Float / Float
template <>
std::vector<BnTrainToleranceTestCase>
getBnTrainToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0, 0.0, true},
// NHW=1, x=[-1,1], scale=[-1,1], no bias
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1, (1.5 * gamma(1) + 7.0 * u) * 1.0},
// NHW=10, x=[-1,1], scale=[-1,1], no bias
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, (1.5 * gamma(10) + 7.0 * u) * 1.0},
// NHW=10, x=[-1000,1000], scale=[-1000,1000], no bias
// Tolerance scales with |scale|, not |x| (normalization cancels |x|)
{-1000.0, 1000.0, -1000.0, 1000.0, 0.0, 0.0, 10, (1.5 * gamma(10) + 7.0 * u) * 1000.0},
// NHW=3, x=[-1,1], scale=[-1,1], bias=[-0.5,0.5]
{-1.0, 1.0, -1.0, 1.0, -0.5, 0.5, 3, (1.5 * gamma(3) + 7.0 * u) * 1.0 + 0.5 * u}};
}
// Float / Double / Float (Input casting: inputEpsilon(double) < epsilon(float))
// accTol = (gamma + 2u) * S + u * S (input cast) = (gamma + 3u) * S
// variance path = (gamma + 3u)/2, then + gamma (mean) + 6u (ops)
// total = (3/2*gamma + 3u/2 + 6u) * scale
template <>
std::vector<BnTrainToleranceTestCase>
getBnTrainToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0, 0.0, true},
// NHW=1
{-1.0,
1.0,
-1.0,
1.0,
0.0,
0.0,
1,
((gamma(1) + 3.0 * u) / 2.0 + gamma(1) + 6.0 * u) * 1.0},
// NHW=10
{-1.0,
1.0,
-1.0,
1.0,
0.0,
0.0,
10,
((gamma(10) + 3.0 * u) / 2.0 + gamma(10) + 6.0 * u) * 1.0},
// Zero input: all terms vanish
{0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10, 0.0}};
}
// Half / Float / Float (Output casting: outputEpsilon(half=2^-10) > epsilon(float=2^-23))
// maxOutputMagnitude = maxAbsScale * maxAbsX * invVarEst (conservative, not assuming xHat~O(1))
template <>
std::vector<BnTrainToleranceTestCase> getBnTrainToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
auto yCast = [&](double maxAbsX, double maxAbsScale, double maxAbsBias) {
return yOutputCast(maxAbsX, maxAbsScale, maxAbsBias, uHalf, u);
};
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0, 0.0, true},
// NHW=1: base + output cast
{-1.0,
1.0,
-1.0,
1.0,
0.0,
0.0,
1,
(1.5 * gamma(1) + 7.0 * u) * 1.0 + yCast(1.0, 1.0, 0.0)},
// NHW=10
{-1.0,
1.0,
-1.0,
1.0,
0.0,
0.0,
10,
(1.5 * gamma(10) + 7.0 * u) * 1.0 + yCast(1.0, 1.0, 0.0)},
// Zero input: output cast from maxOutputMagnitude=0 vanishes
{0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10, 0.0}};
}
// Half / Half / Half
template <>
std::vector<BnTrainToleranceTestCase> getBnTrainToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1, (1.5 * gamma(1) + 7.0 * u) * 1.0},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, (1.5 * gamma(10) + 7.0 * u) * 1.0},
// Zero input
{0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10, 0.0}};
}
// Bfloat16 / Float / Float (Output casting: outputEpsilon(bf16=2^-7) > epsilon(float=2^-23))
template <>
std::vector<BnTrainToleranceTestCase>
getBnTrainToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
auto yCast = [&](double maxAbsX, double maxAbsScale, double maxAbsBias) {
return yOutputCast(maxAbsX, maxAbsScale, maxAbsBias, uBf16, u);
};
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0, 0.0, true},
// NHW=1
{-1.0,
1.0,
-1.0,
1.0,
0.0,
0.0,
1,
(1.5 * gamma(1) + 7.0 * u) * 1.0 + yCast(1.0, 1.0, 0.0)},
// NHW=10
{-1.0,
1.0,
-1.0,
1.0,
0.0,
0.0,
10,
(1.5 * gamma(10) + 7.0 * u) * 1.0 + yCast(1.0, 1.0, 0.0)},
// Zero input
{0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10, 0.0}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnTrainToleranceTestCase>
getBnTrainToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1, (1.5 * gamma(1) + 7.0 * u) * 1.0},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, (1.5 * gamma(10) + 7.0 * u) * 1.0},
// Zero input
{0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10, 0.0}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnTrainTolerance : public ::testing::TestWithParam<BnTrainToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW(
(calculateBatchnormTrainingTolerance<Out, In, Comp>(params.xMin,
params.xMax,
params.scaleMin,
params.scaleMax,
params.biasMin,
params.biasMax,
params.nElementsPerChannel)),
std::invalid_argument);
}
else
{
auto tol
= calculateBatchnormTrainingTolerance<Out, In, Comp>(params.xMin,
params.xMax,
params.scaleMin,
params.scaleMax,
params.biasMin,
params.biasMax,
params.nElementsPerChannel);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnTrainTolFp32 = TestCalculateBnTrainTolerance<float, float, float>;
TEST_P(TestCalcBnTrainTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnTrainTolFp32,
::testing::ValuesIn(getBnTrainToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnTrainTolInputDouble = TestCalculateBnTrainTolerance<float, double, float>;
TEST_P(TestCalcBnTrainTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnTrainTolInputDouble,
::testing::ValuesIn(getBnTrainToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnTrainTolComputeFloatFp16 = TestCalculateBnTrainTolerance<half, float, float>;
TEST_P(TestCalcBnTrainTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnTrainTolComputeFloatFp16,
::testing::ValuesIn(getBnTrainToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnTrainTolFp16 = TestCalculateBnTrainTolerance<half, half, half>;
TEST_P(TestCalcBnTrainTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnTrainTolFp16,
::testing::ValuesIn(getBnTrainToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnTrainTolComputeFloatBfp16 = TestCalculateBnTrainTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnTrainTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnTrainTolComputeFloatBfp16,
::testing::ValuesIn(getBnTrainToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnTrainTolBfp16 = TestCalculateBnTrainTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnTrainTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnTrainTolBfp16,
::testing::ValuesIn(getBnTrainToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// TestCalculateBatchnormMeanTolerance
// =================================================================================================
struct BnMeanToleranceTestCase
{
double xMin;
double xMax;
int64_t nElementsPerChannel;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnMeanToleranceTestCase& tc)
{
os << "xMin: " << tc.xMin << ", xMax: " << tc.xMax << ", NHW: " << tc.nElementsPerChannel
<< ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnMeanToleranceTestCase> getBnMeanToleranceTestCases();
// Mean tolerance formula (no casting):
// tolerance = (gamma + u) * maxAbsX
// With input casting (double->float):
// tolerance += computeEpsilon * maxAbsX (from inputCastError / NHW)
// total = (gamma + 2u) * maxAbsX
// Float / Float / Float
template <>
std::vector<BnMeanToleranceTestCase> getBnMeanToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, (gamma(1) + u) * 1.0},
// NHW=10
{-1.0, 1.0, 10, (gamma(10) + u) * 1.0},
// Large x range
{-1000.0, 1000.0, 10, (gamma(10) + u) * 1000.0},
// Zero input
{0.0, 0.0, 10, 0.0}};
}
// Float / Double / Float
template <>
std::vector<BnMeanToleranceTestCase> getBnMeanToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1: input casting adds u * maxAbsX
{-1.0, 1.0, 1, (gamma(1) + 2.0 * u) * 1.0},
// NHW=10
{-1.0, 1.0, 10, (gamma(10) + 2.0 * u) * 1.0},
// Zero input
{0.0, 0.0, 10, 0.0}};
}
// Half / Float / Float (Output casting)
template <>
std::vector<BnMeanToleranceTestCase> getBnMeanToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1: base + output casting (maxAbsX * uHalf)
{-1.0, 1.0, 1, (gamma(1) + u) * 1.0 + 1.0 * uHalf},
// NHW=10
{-1.0, 1.0, 10, (gamma(10) + u) * 1.0 + 1.0 * uHalf},
// Zero input
{0.0, 0.0, 10, 0.0}};
}
// Half / Half / Half
template <>
std::vector<BnMeanToleranceTestCase> getBnMeanToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, (gamma(1) + u) * 1.0},
// NHW=10
{-1.0, 1.0, 10, (gamma(10) + u) * 1.0},
// Zero input
{0.0, 0.0, 10, 0.0}};
}
// Bfloat16 / Float / Float (Output casting)
template <>
std::vector<BnMeanToleranceTestCase>
getBnMeanToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, (gamma(1) + u) * 1.0 + 1.0 * uBf16},
// NHW=10
{-1.0, 1.0, 10, (gamma(10) + u) * 1.0 + 1.0 * uBf16},
// Zero input
{0.0, 0.0, 10, 0.0}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnMeanToleranceTestCase>
getBnMeanToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, (gamma(1) + u) * 1.0},
// NHW=10
{-1.0, 1.0, 10, (gamma(10) + u) * 1.0},
// Zero input
{0.0, 0.0, 10, 0.0}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnMeanTolerance : public ::testing::TestWithParam<BnMeanToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW((calculateBatchnormMeanTolerance<Out, In, Comp>(
params.xMin, params.xMax, params.nElementsPerChannel)),
std::invalid_argument);
}
else
{
auto tol = calculateBatchnormMeanTolerance<Out, In, Comp>(
params.xMin, params.xMax, params.nElementsPerChannel);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnMeanTolFp32 = TestCalculateBnMeanTolerance<float, float, float>;
TEST_P(TestCalcBnMeanTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnMeanTolFp32,
::testing::ValuesIn(getBnMeanToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnMeanTolInputDouble = TestCalculateBnMeanTolerance<float, double, float>;
TEST_P(TestCalcBnMeanTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnMeanTolInputDouble,
::testing::ValuesIn(getBnMeanToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnMeanTolComputeFloatFp16 = TestCalculateBnMeanTolerance<half, float, float>;
TEST_P(TestCalcBnMeanTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnMeanTolComputeFloatFp16,
::testing::ValuesIn(getBnMeanToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnMeanTolFp16 = TestCalculateBnMeanTolerance<half, half, half>;
TEST_P(TestCalcBnMeanTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnMeanTolFp16,
::testing::ValuesIn(getBnMeanToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnMeanTolComputeFloatBfp16 = TestCalculateBnMeanTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnMeanTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnMeanTolComputeFloatBfp16,
::testing::ValuesIn(getBnMeanToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnMeanTolBfp16 = TestCalculateBnMeanTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnMeanTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnMeanTolBfp16,
::testing::ValuesIn(getBnMeanToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// TestCalculateBatchnormInvVarianceTolerance
// =================================================================================================
struct BnInvVarToleranceTestCase
{
double xMin;
double xMax;
int64_t nElementsPerChannel;
double epsilonBn;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnInvVarToleranceTestCase& tc)
{
os << "xMin: " << tc.xMin << ", xMax: " << tc.xMax << ", NHW: " << tc.nElementsPerChannel
<< ", epsilonBn: " << tc.epsilonBn << ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnInvVarToleranceTestCase> getBnInvVarToleranceTestCases();
// InvVar tolerance formula (corrected, see plan Section 15):
// deltaVariance = (3*gamma + 2u) * maxAbsX^2
// varEstimate = max(maxAbsX^2, epsilonBn)
// varPlusEps = varEstimate + epsilonBn
// tolerance = deltaVariance / (2 * varPlusEps^{3/2}) + 3u / sqrt(varPlusEps)
// With output casting: + invVarEstimate * outputEpsilon
// where invVarEstimate = 1/sqrt(varPlusEps)
// For x=0: varEstimate = epsilonBn, varPlusEps = 2*epsilonBn
// Helper to compute expected invVar tolerance
static double expectedInvVarTol(double maxAbsX,
double epsBn,
double gamma,
double u,
double outputEpsilon = 0.0,
double computeEpsilon = 0.0)
{
const double varEst = std::max(maxAbsX * maxAbsX, epsBn);
const double varPlusEps = varEst + epsBn;
const double deltaVar = (3.0 * gamma + 2.0 * u) * maxAbsX * maxAbsX;
const double invVarEst = 1.0 / std::sqrt(varPlusEps);
double tol = deltaVar / (2.0 * varPlusEps * std::sqrt(varPlusEps)) + 3.0 * u * invVarEst;
if(outputEpsilon > computeEpsilon)
{
tol += invVarEst * outputEpsilon;
}
return tol;
}
// Float / Float / Float
template <>
std::vector<BnInvVarToleranceTestCase>
getBnInvVarToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1, x=[-1,1]
{-1.0, 1.0, 1, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(1), u)},
// NHW=10
{-1.0, 1.0, 10, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(10), u)},
// Large x range: tolerance now properly scales down with maxAbsX
{-1000.0, 1000.0, 10, 1e-5, expectedInvVarTol(1000.0, 1e-5, gamma(10), u)},
// Zero input: varEstimate = epsBn, varPlusEps = 2*epsBn
{0.0, 0.0, 10, 1e-5, expectedInvVarTol(0.0, 1e-5, gamma(10), u)}};
}
// Float / Double / Float (no output casting, no input casting for invVar)
template <>
std::vector<BnInvVarToleranceTestCase>
getBnInvVarToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(1), u)},
// NHW=10
{-1.0, 1.0, 10, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(10), u)},
// Zero input
{0.0, 0.0, 10, 1e-5, expectedInvVarTol(0.0, 1e-5, gamma(10), u)}};
}
// Half / Float / Float (Output casting)
template <>
std::vector<BnInvVarToleranceTestCase>
getBnInvVarToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(1), u, uHalf, u)},
// NHW=10
{-1.0, 1.0, 10, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(10), u, uHalf, u)},
// Zero input
{0.0, 0.0, 10, 1e-5, expectedInvVarTol(0.0, 1e-5, gamma(10), u, uHalf, u)}};
}
// Half / Half / Half
template <>
std::vector<BnInvVarToleranceTestCase> getBnInvVarToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(1), u)},
// NHW=10
{-1.0, 1.0, 10, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(10), u)},
// Zero input
{0.0, 0.0, 10, 1e-5, expectedInvVarTol(0.0, 1e-5, gamma(10), u)}};
}
// Bfloat16 / Float / Float
template <>
std::vector<BnInvVarToleranceTestCase>
getBnInvVarToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(1), u, uBf16, u)},
// NHW=10
{-1.0, 1.0, 10, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(10), u, uBf16, u)},
// Zero input
{0.0, 0.0, 10, 1e-5, expectedInvVarTol(0.0, 1e-5, gamma(10), u, uBf16, u)}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnInvVarToleranceTestCase>
getBnInvVarToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(1), u)},
// NHW=10
{-1.0, 1.0, 10, 1e-5, expectedInvVarTol(1.0, 1e-5, gamma(10), u)},
// Zero input
{0.0, 0.0, 10, 1e-5, expectedInvVarTol(0.0, 1e-5, gamma(10), u)}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnInvVarTolerance : public ::testing::TestWithParam<BnInvVarToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW(
(calculateBatchnormInvVarianceTolerance<Out, In, Comp>(
params.xMin, params.xMax, params.nElementsPerChannel, params.epsilonBn)),
std::invalid_argument);
}
else
{
auto tol = calculateBatchnormInvVarianceTolerance<Out, In, Comp>(
params.xMin, params.xMax, params.nElementsPerChannel, params.epsilonBn);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnInvVarTolFp32 = TestCalculateBnInvVarTolerance<float, float, float>;
TEST_P(TestCalcBnInvVarTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInvVarTolFp32,
::testing::ValuesIn(getBnInvVarToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnInvVarTolInputDouble = TestCalculateBnInvVarTolerance<float, double, float>;
TEST_P(TestCalcBnInvVarTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInvVarTolInputDouble,
::testing::ValuesIn(getBnInvVarToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnInvVarTolComputeFloatFp16 = TestCalculateBnInvVarTolerance<half, float, float>;
TEST_P(TestCalcBnInvVarTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInvVarTolComputeFloatFp16,
::testing::ValuesIn(getBnInvVarToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnInvVarTolFp16 = TestCalculateBnInvVarTolerance<half, half, half>;
TEST_P(TestCalcBnInvVarTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInvVarTolFp16,
::testing::ValuesIn(getBnInvVarToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnInvVarTolComputeFloatBfp16 = TestCalculateBnInvVarTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnInvVarTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInvVarTolComputeFloatBfp16,
::testing::ValuesIn(getBnInvVarToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnInvVarTolBfp16 = TestCalculateBnInvVarTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnInvVarTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInvVarTolBfp16,
::testing::ValuesIn(getBnInvVarToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// Standalone Tests
// =================================================================================================
// Test that calculateBatchnormTrainingTolerance catches simulated wrong outputs
TEST(TestCalculateBnTrainTolerance, DetectsFailure)
{
// NHW=100 (shape [1,1,10,10]), x=[-1,1], scale=[-1,1]
const std::vector<int64_t> dims = {1, 1, 10, 10};
const std::vector<int64_t> strides = {100, 100, 10, 1};
auto baseline = hipdnn_data_sdk::utilities::createTensor<float>(dims, strides);
auto actualPassing = hipdnn_data_sdk::utilities::createTensor<float>(dims, strides);
auto actualFailing = hipdnn_data_sdk::utilities::createTensor<float>(dims, strides);
baseline->fillTensorWithValue(1.0f);
actualPassing->fillTensorWithValue(1.000001f); // Small error
actualFailing->fillTensorWithValue(1.2f); // Large error
auto tol = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 100);
EXPECT_LT(tol, 0.01f);
EXPECT_GT(tol, 1e-6f);
auto validator = hipdnn_test_sdk::utilities::createAllCloseValidator(
hipdnn_flatbuffers_sdk::data_objects::DataType::FLOAT, tol, 0);
bool valid = validator->allClose(*baseline, *actualPassing);
EXPECT_TRUE(valid);
valid = validator->allClose(*baseline, *actualFailing);
EXPECT_FALSE(valid);
}
// Test that tolerance throws when gamma >= 0.5
TEST(TestCalculateBnTrainTolerance, ThrowsOnSingularity)
{
// float: gamma >= 0.5 at C ~ 3e11 (same as RMSNorm)
EXPECT_THROW((calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 300000000000LL)),
std::overflow_error);
// bfloat16: gamma >= 0.5 at C ~ 69. Use C=100.
EXPECT_THROW((calculateBatchnormTrainingTolerance<bfloat16, bfloat16, bfloat16>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 100)),
std::overflow_error);
// half: gamma >= 0.5 at C ~ 4370. Use C=5000.
EXPECT_THROW((calculateBatchnormTrainingTolerance<half, half, half>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 5000)),
std::overflow_error);
}
// Test that tolerance throws when it exceeds OutputType max
TEST(TestCalculateBnTrainTolerance, ThrowsOnOutputOverflow)
{
// half output with very large scale: output cast term >> 65504
EXPECT_THROW((calculateBatchnormTrainingTolerance<half, float, float>(
-1.0, 1.0, -1e8, 1e8, 0.0, 0.0, 10)),
std::overflow_error);
}
// Test zero input produces correct minimal tolerance
TEST(TestCalculateBnTrainTolerance, ZeroInput)
{
// No bias: all terms vanish
auto tol = calculateBatchnormTrainingTolerance<float, float, float>(
0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10);
EXPECT_EQ(tol, 0.0f);
// With bias: only bias term survives
auto tolBias = calculateBatchnormTrainingTolerance<float, float, float>(
0.0, 0.0, -1.0, 1.0, -0.5, 0.5, 10);
EXPECT_GT(tolBias, 0.0f);
// Zero input with bf16 output casting: maxOutputMagnitude=0 -> no output cast
auto tolBf16 = calculateBatchnormTrainingTolerance<bfloat16, float, float>(
0.0, 0.0, -1.0, 1.0, 0.0, 0.0, 10);
EXPECT_EQ(tolBf16, 0.0f);
// Zero input with bias and half output: bias term + output cast of bias
auto uFloat = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
auto tolHalfBias = calculateBatchnormTrainingTolerance<half, float, float>(
0.0, 0.0, -1.0, 1.0, -0.5, 0.5, 10);
// Expected: maxAbsBias * epsilon + maxOutputMagnitude * outputEpsilon
// = 0.5 * uFloat + 0.5 * uHalf (maxOutputMagnitude = 0 + maxAbsBias = 0.5)
auto expectedHalfBias
= static_cast<float>(0.5 * uFloat + yOutputCast(0.0, 1.0, 0.5, uHalf, uFloat));
EXPECT_NEAR(tolHalfBias, expectedHalfBias, 1e-10f);
}
// Sanity check: tolerance scaling behavior with increasing NHW
TEST(TestCalculateBnTrainTolerance, ToleranceScalesWithNHW)
{
// FP32: tolerance should grow roughly linearly with NHW (gamma ~ NHW*u for small NHW)
auto tolC1 = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1);
auto tolC10 = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10);
auto tolC100 = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 100);
EXPECT_LT(tolC1, tolC10) << "FP32: NHW=10 should have higher tolerance than NHW=1";
EXPECT_LT(tolC10, tolC100) << "FP32: NHW=100 should have higher tolerance than NHW=10";
auto ratioFp32 = static_cast<double>(tolC100) / static_cast<double>(tolC1);
EXPECT_GT(ratioFp32, 10.0) << "FP32: ratio NHW100/NHW1 should reflect ~NHW growth";
// BF16 (statistical): tolerance should grow ~ sqrt(NHW)
auto tolBf16C1 = calculateBatchnormTrainingTolerance<bfloat16, bfloat16, bfloat16>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1);
auto tolBf16C10 = calculateBatchnormTrainingTolerance<bfloat16, bfloat16, bfloat16>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10);
auto tolBf16C50 = calculateBatchnormTrainingTolerance<bfloat16, bfloat16, bfloat16>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 50);
EXPECT_LT(tolBf16C1, tolBf16C10) << "BF16: NHW=10 should have higher tolerance than NHW=1";
EXPECT_LT(tolBf16C10, tolBf16C50) << "BF16: NHW=50 should have higher tolerance than NHW=10";
auto ratioBf16 = static_cast<double>(tolBf16C10) / static_cast<double>(tolBf16C1);
EXPECT_GT(ratioBf16, 1.5) << "BF16: ratio NHW10/NHW1 should reflect sqrt(NHW) growth";
}
// Sanity: mean tolerance scales with NHW
TEST(TestCalculateBnMeanTolerance, ToleranceScalesWithNHW)
{
auto tol1 = calculateBatchnormMeanTolerance<float, float, float>(-1.0, 1.0, 1);
auto tol10 = calculateBatchnormMeanTolerance<float, float, float>(-1.0, 1.0, 10);
auto tol100 = calculateBatchnormMeanTolerance<float, float, float>(-1.0, 1.0, 100);
EXPECT_LT(tol1, tol10);
EXPECT_LT(tol10, tol100);
}
// Sanity: mean zero input
TEST(TestCalculateBnMeanTolerance, ZeroInput)
{
auto tol = calculateBatchnormMeanTolerance<float, float, float>(0.0, 0.0, 10);
EXPECT_EQ(tol, 0.0f);
}
// Sanity: invVar tolerance scales with NHW
TEST(TestCalculateBnInvVarTolerance, ToleranceScalesWithNHW)
{
auto tol1 = calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 1);
auto tol10 = calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 10);
auto tol100 = calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 100);
EXPECT_LT(tol1, tol10);
EXPECT_LT(tol10, tol100);
}
// Sanity: invVar zero input gives finite positive tolerance
TEST(TestCalculateBnInvVarTolerance, ZeroInput)
{
auto tol = calculateBatchnormInvVarianceTolerance<float, float, float>(0.0, 0.0, 10);
EXPECT_GT(tol, 0.0f);
EXPECT_LT(tol, 1.0f); // Should be a reasonable small value
}
// Asymmetric x range: maxAbsX = max(|xMin|, |xMax|)
TEST(TestCalculateBnTrainTolerance, AsymmetricXRange)
{
// xMin=-0.5, xMax=2.0 => maxAbsX=2.0 (same as symmetric [-2,2])
auto tolAsym = calculateBatchnormTrainingTolerance<float, float, float>(
-0.5, 2.0, -1.0, 1.0, 0.0, 0.0, 10);
auto tolSym = calculateBatchnormTrainingTolerance<float, float, float>(
-2.0, 2.0, -1.0, 1.0, 0.0, 0.0, 10);
// Both have same maxAbsX=2.0, so tolerance should be identical
EXPECT_EQ(tolAsym, tolSym);
// One-sided: xMin=0, xMax=5.0 => maxAbsX=5.0
auto tolOneSided = calculateBatchnormTrainingTolerance<float, float, float>(
0.0, 5.0, -1.0, 1.0, 0.0, 0.0, 10);
auto tolSymFive = calculateBatchnormTrainingTolerance<float, float, float>(
-5.0, 5.0, -1.0, 1.0, 0.0, 0.0, 10);
EXPECT_EQ(tolOneSided, tolSymFive);
// Mean tolerance: same behavior
auto meanAsym = calculateBatchnormMeanTolerance<float, float, float>(-0.5, 2.0, 10);
auto meanSym = calculateBatchnormMeanTolerance<float, float, float>(-2.0, 2.0, 10);
EXPECT_EQ(meanAsym, meanSym);
// InvVar tolerance: same behavior
auto invVarAsym = calculateBatchnormInvVarianceTolerance<float, float, float>(-0.5, 2.0, 10);
auto invVarSym = calculateBatchnormInvVarianceTolerance<float, float, float>(-2.0, 2.0, 10);
EXPECT_EQ(invVarAsym, invVarSym);
}
// Scale and bias both zero: y tolerance should be zero
TEST(TestCalculateBnTrainTolerance, ZeroScaleAndBias)
{
auto tol = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 10);
EXPECT_EQ(tol, 0.0f);
// With half output casting: maxOutputMagnitude = 0 + 0 = 0, so cast term also 0
auto tolHalf = calculateBatchnormTrainingTolerance<half, float, float>(
-1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 10);
EXPECT_EQ(tolHalf, 0.0f);
}
// Negative NHW should throw (validates the < 1 check catches negatives)
TEST(TestCalculateBnTrainTolerance, ThrowsOnNegativeNHW)
{
EXPECT_THROW((calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, -1)),
std::invalid_argument);
EXPECT_THROW((calculateBatchnormMeanTolerance<float, float, float>(-1.0, 1.0, -1)),
std::invalid_argument);
EXPECT_THROW((calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, -1)),
std::invalid_argument);
}
// InvVar: small x where eps_bn dominates variance estimate
TEST(TestCalculateBnInvVarTolerance, SmallXEpsilonDominates)
{
// maxAbsX = 0.001, eps_bn = 1e-5
// maxAbsX^2 = 1e-6 < eps_bn = 1e-5, so varEstimate = eps_bn
// varPlusEps = 2 * eps_bn = 2e-5
// This tests that the variance floor prevents underestimation
auto tol = calculateBatchnormInvVarianceTolerance<float, float, float>(-0.001, 0.001, 10, 1e-5);
EXPECT_GT(tol, 0.0f);
// Compare with zero-input case: should be similar magnitude since eps_bn dominates both
auto tolZero = calculateBatchnormInvVarianceTolerance<float, float, float>(0.0, 0.0, 10, 1e-5);
EXPECT_GT(tolZero, 0.0f);
// Small-x tolerance should be >= zero-input tolerance (has additional deltaVar contribution)
EXPECT_GE(tol, tolZero);
// InvVar tolerance should decrease as maxAbsX increases (larger var -> less amplification)
auto tolLargeX
= calculateBatchnormInvVarianceTolerance<float, float, float>(-10.0, 10.0, 10, 1e-5);
EXPECT_LT(tolLargeX, tol) << "Larger x should give smaller invVar tolerance";
}
// InvVar: tolerance properly scales down for large maxAbsX
TEST(TestCalculateBnInvVarTolerance, LargeXScalesDown)
{
auto tol1 = calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 10);
auto tol100 = calculateBatchnormInvVarianceTolerance<float, float, float>(-100.0, 100.0, 10);
auto tol1000 = calculateBatchnormInvVarianceTolerance<float, float, float>(-1000.0, 1000.0, 10);
// Tolerance should decrease as maxAbsX increases
EXPECT_LT(tol100, tol1) << "x=100 should give smaller invVar tolerance than x=1";
EXPECT_LT(tol1000, tol100) << "x=1000 should give smaller invVar tolerance than x=100";
// The ratio should reflect the 1/maxAbsX scaling in the gamma term
auto ratio = static_cast<double>(tol1) / static_cast<double>(tol100);
EXPECT_GT(ratio, 10.0) << "Tolerance ratio should reflect ~1/maxAbsX scaling";
}
// Mean and invVar also throw on singularity
TEST(TestCalculateBnMeanTolerance, ThrowsOnSingularity)
{
EXPECT_THROW((calculateBatchnormMeanTolerance<bfloat16, bfloat16, bfloat16>(-1.0, 1.0, 100)),
std::overflow_error);
}
TEST(TestCalculateBnInvVarTolerance, ThrowsOnSingularity)
{
EXPECT_THROW(
(calculateBatchnormInvVarianceTolerance<bfloat16, bfloat16, bfloat16>(-1.0, 1.0, 100)),
std::overflow_error);
}
// epsilonBn <= 0 should throw for both y and invVar
TEST(TestCalculateBnTrainTolerance, ThrowsOnInvalidEpsilon)
{
EXPECT_THROW((calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, 0.0)),
std::invalid_argument);
EXPECT_THROW((calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, -1e-5)),
std::invalid_argument);
}
TEST(TestCalculateBnInvVarTolerance, ThrowsOnInvalidEpsilon)
{
EXPECT_THROW((calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 10, 0.0)),
std::invalid_argument);
EXPECT_THROW(
(calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 10, -1e-5)),
std::invalid_argument);
}
// InvVar: tolerance changes with different epsilon values
TEST(TestCalculateBnInvVarTolerance, NonDefaultEpsilon)
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma10 = computeGamma(10, u);
// Large epsilon (1e-3): larger variance floor -> smaller invVar -> smaller tolerance
auto tolLargeEps
= calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 10, 1e-3);
auto expectedLargeEps = expectedInvVarTol(1.0, 1e-3, gamma10, u);
EXPECT_NEAR(
tolLargeEps,
static_cast<float>(expectedLargeEps),
std::max(static_cast<float>(expectedLargeEps) * 0.01f, std::numeric_limits<float>::min()));
// Small epsilon (1e-8): varEstimate = maxAbsX^2 dominates, epsilon barely matters
auto tolSmallEps
= calculateBatchnormInvVarianceTolerance<float, float, float>(-1.0, 1.0, 10, 1e-8);
auto expectedSmallEps = expectedInvVarTol(1.0, 1e-8, gamma10, u);
EXPECT_NEAR(
tolSmallEps,
static_cast<float>(expectedSmallEps),
std::max(static_cast<float>(expectedSmallEps) * 0.01f, std::numeric_limits<float>::min()));
// With maxAbsX=0: epsilon dominates entirely, larger eps -> smaller tolerance
auto tolZeroLargeEps
= calculateBatchnormInvVarianceTolerance<float, float, float>(0.0, 0.0, 10, 1e-3);
auto tolZeroSmallEps
= calculateBatchnormInvVarianceTolerance<float, float, float>(0.0, 0.0, 10, 1e-8);
EXPECT_LT(tolZeroLargeEps, tolZeroSmallEps)
<< "Larger eps_bn -> larger variance floor -> smaller invVar tolerance for zero input";
}
// =================================================================================================
// TestCalculateBatchnormInferenceTolerance
// =================================================================================================
struct BnInfToleranceTestCase
{
double xMin;
double xMax;
double meanMin;
double meanMax;
double invVarMin;
double invVarMax;
double scaleMin;
double scaleMax;
double biasMin;
double biasMax;
double expectedTolerance;
friend std::ostream& operator<<(std::ostream& os, const BnInfToleranceTestCase& tc)
{
os << "xMin: " << tc.xMin << ", xMax: " << tc.xMax << ", meanMin: " << tc.meanMin
<< ", meanMax: " << tc.meanMax << ", invVarMin: " << tc.invVarMin
<< ", invVarMax: " << tc.invVarMax << ", scaleMin: " << tc.scaleMin
<< ", scaleMax: " << tc.scaleMax << ", biasMin: " << tc.biasMin
<< ", biasMax: " << tc.biasMax << ", expectedTolerance: " << tc.expectedTolerance;
return os;
}
};
template <typename T>
std::vector<BnInfToleranceTestCase> getBnInfToleranceTestCases();
// BN Inference tolerance formula (no casting, invVar variant):
// maxAbsDiff = maxAbsX + maxAbsMean (triangle inequality)
// P = maxAbsDiff * maxAbsInvVar * maxAbsScale
// chainTol = 3u * P
// biasTol = u * (P + maxAbsBias)
// total = 4u * P + u * maxAbsBias
// With input casting (InputType=double, ComputeType=float):
// total += computeEpsilon * P (input cast for 1 tensor)
// = (4u + u) * P + u * maxAbsBias = 5u * P + u * B
// With output casting (OutputType=half/bf16):
// maxOutMag = P + maxAbsBias
// total += maxOutMag * outputEpsilon
// Helper to compute expected inference tolerance.
// inputTypeEpsilon/outputTypeEpsilon are the epsilon of the InputType/OutputType respectively.
// They are used only to detect whether casting occurs (by comparing against computeEpsilon).
// The actual casting error magnitude is always bounded by the destination type's epsilon.
static double expectedBnInfTol(double maxAbsX,
double maxAbsMean,
double maxAbsInvVar,
double maxAbsScale,
double maxAbsBias,
double computeEpsilon,
double inputTypeEpsilon = 0.0,
double outputTypeEpsilon = 0.0)
{
const double maxAbsDiff = maxAbsX + maxAbsMean;
const double mainProduct = maxAbsDiff * maxAbsInvVar * maxAbsScale;
double tol = 3.0 * computeEpsilon * mainProduct; // chain ops
tol += computeEpsilon * (mainProduct + maxAbsBias); // bias addition
// Input casting: higher-precision input downcast to compute type
if(inputTypeEpsilon > 0.0 && inputTypeEpsilon < computeEpsilon)
{
tol += computeEpsilon * mainProduct;
}
// Output casting: compute type downcast to lower-precision output
if(outputTypeEpsilon > computeEpsilon)
{
tol += (mainProduct + maxAbsBias) * outputTypeEpsilon;
}
return tol;
}
// Float / Float / Float
template <>
std::vector<BnInfToleranceTestCase> getBnInfToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
// Test ranges matching BatchnormFwdTensorBundle: x=[0,1], mean=[0,1], invVar=[1,3], scale=[0,1], bias=[0,1]
return {// Standard test: x=[0,1], mean=[0,1], invVar=[1,3], scale=[0,1], bias=[0,1]
// maxAbsDiff = 1+1=2, P = 2*3*1=6
{0.0,
1.0,
0.0,
1.0,
1.0,
3.0,
0.0,
1.0,
0.0,
1.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 1.0, u)},
// Symmetric x, no bias
{-1.0,
1.0,
-1.0,
1.0,
1.0,
3.0,
-1.0,
1.0,
0.0,
0.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 0.0, u)},
// Zero input: P=0, only bias tolerance survives
{0.0,
0.0,
0.0,
0.0,
1.0,
3.0,
-1.0,
1.0,
-0.5,
0.5,
expectedBnInfTol(0.0, 0.0, 3.0, 1.0, 0.5, u)},
// All zero: tolerance = 0
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0},
// Large invVar: P scales with invVar
{-1.0,
1.0,
-1.0,
1.0,
1.0,
10.0,
-1.0,
1.0,
0.0,
0.0,
expectedBnInfTol(1.0, 1.0, 10.0, 1.0, 0.0, u)}};
}
// Float / Double / Float (Input casting: inputEpsilon(double) < epsilon(float), adds u*P)
template <>
std::vector<BnInfToleranceTestCase> getBnInfToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uDouble = std::numeric_limits<double>::epsilon();
return {// Standard
{0.0,
1.0,
0.0,
1.0,
1.0,
3.0,
0.0,
1.0,
0.0,
1.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 1.0, u, uDouble)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
1.0,
3.0,
-1.0,
1.0,
0.0,
0.0,
expectedBnInfTol(0.0, 0.0, 3.0, 1.0, 0.0, u, uDouble)}};
}
// Half / Float / Float (Output casting: uHalf > uFloat)
template <>
std::vector<BnInfToleranceTestCase> getBnInfToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
1.0,
3.0,
0.0,
1.0,
0.0,
1.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 1.0, u, 0.0, uHalf)},
// Zero input with bias: output cast from bias magnitude
{0.0,
0.0,
0.0,
0.0,
1.0,
3.0,
-1.0,
1.0,
-0.5,
0.5,
expectedBnInfTol(0.0, 0.0, 3.0, 1.0, 0.5, u, 0.0, uHalf)}};
}
// Half / Half / Half
template <>
std::vector<BnInfToleranceTestCase> getBnInfToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
1.0,
3.0,
0.0,
1.0,
0.0,
1.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 1.0, u)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
1.0,
3.0,
-1.0,
1.0,
0.0,
0.0,
expectedBnInfTol(0.0, 0.0, 3.0, 1.0, 0.0, u)}};
}
// Bfloat16 / Float / Float (Output casting: uBf16 > uFloat)
template <>
std::vector<BnInfToleranceTestCase> getBnInfToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
1.0,
3.0,
0.0,
1.0,
0.0,
1.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 1.0, u, 0.0, uBf16)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
1.0,
3.0,
-1.0,
1.0,
0.0,
0.0,
expectedBnInfTol(0.0, 0.0, 3.0, 1.0, 0.0, u, 0.0, uBf16)}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnInfToleranceTestCase>
getBnInfToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
1.0,
3.0,
0.0,
1.0,
0.0,
1.0,
expectedBnInfTol(1.0, 1.0, 3.0, 1.0, 1.0, u)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
1.0,
3.0,
-1.0,
1.0,
0.0,
0.0,
expectedBnInfTol(0.0, 0.0, 3.0, 1.0, 0.0, u)}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnInfTolerance : public ::testing::TestWithParam<BnInfToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
auto tol = calculateBatchnormInferenceTolerance<Out, In, Comp>(params.xMin,
params.xMax,
params.meanMin,
params.meanMax,
params.invVarMin,
params.invVarMax,
params.scaleMin,
params.scaleMax,
params.biasMin,
params.biasMax);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
};
using TestCalcBnInfTolFp32 = TestCalculateBnInfTolerance<float, float, float>;
TEST_P(TestCalcBnInfTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfTolFp32,
::testing::ValuesIn(getBnInfToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnInfTolInputDouble = TestCalculateBnInfTolerance<float, double, float>;
TEST_P(TestCalcBnInfTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfTolInputDouble,
::testing::ValuesIn(getBnInfToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnInfTolComputeFloatFp16 = TestCalculateBnInfTolerance<half, float, float>;
TEST_P(TestCalcBnInfTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfTolComputeFloatFp16,
::testing::ValuesIn(getBnInfToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnInfTolFp16 = TestCalculateBnInfTolerance<half, half, half>;
TEST_P(TestCalcBnInfTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfTolFp16,
::testing::ValuesIn(getBnInfToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnInfTolComputeFloatBfp16 = TestCalculateBnInfTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnInfTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfTolComputeFloatBfp16,
::testing::ValuesIn(getBnInfToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnInfTolBfp16 = TestCalculateBnInfTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnInfTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfTolBfp16,
::testing::ValuesIn(getBnInfToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// TestCalculateBatchnormInferenceWithVarianceTolerance
// =================================================================================================
struct BnInfVarToleranceTestCase
{
double xMin;
double xMax;
double meanMin;
double meanMax;
double varMin;
double varMax;
double scaleMin;
double scaleMax;
double biasMin;
double biasMax;
double epsilonBn;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnInfVarToleranceTestCase& tc)
{
os << "xMin: " << tc.xMin << ", xMax: " << tc.xMax << ", meanMin: " << tc.meanMin
<< ", meanMax: " << tc.meanMax << ", varMin: " << tc.varMin << ", varMax: " << tc.varMax
<< ", scaleMin: " << tc.scaleMin << ", scaleMax: " << tc.scaleMax
<< ", biasMin: " << tc.biasMin << ", biasMax: " << tc.biasMax
<< ", epsBn: " << tc.epsilonBn << ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnInfVarToleranceTestCase> getBnInfVarToleranceTestCases();
// BN Inference with variance tolerance formula (no casting):
// maxAbsDiff = maxAbsX + maxAbsMean
// smallestVar = varMin (must be >= 0, throws otherwise)
// maxAbsInvVar = 1/sqrt(smallestVar + epsBn)
// P = maxAbsDiff * maxAbsInvVar * maxAbsScale
// chainTol = 6u * P (3 for invVar computation + 3 for normalize)
// biasTol = u * (P + maxAbsBias)
// total = 7u * P + u * maxAbsBias
// Helper to compute expected inference with variance tolerance.
// See expectedBnInfTol for inputTypeEpsilon/outputTypeEpsilon semantics.
static double expectedBnInfVarTol(double maxAbsX,
double maxAbsMean,
double varMin,
double maxAbsScale,
double maxAbsBias,
double epsBn,
double computeEpsilon,
double inputTypeEpsilon = 0.0,
double outputTypeEpsilon = 0.0)
{
const double maxAbsDiff = maxAbsX + maxAbsMean;
const double maxAbsInvVar = 1.0 / std::sqrt(varMin + epsBn);
const double mainProduct = maxAbsDiff * maxAbsInvVar * maxAbsScale;
double tol = 6.0 * computeEpsilon * mainProduct; // 6 chain ops
tol += computeEpsilon * (mainProduct + maxAbsBias); // bias addition
// Input casting: higher-precision input downcast to compute type
if(inputTypeEpsilon > 0.0 && inputTypeEpsilon < computeEpsilon)
{
tol += computeEpsilon * mainProduct;
}
// Output casting: compute type downcast to lower-precision output
if(outputTypeEpsilon > computeEpsilon)
{
tol += (mainProduct + maxAbsBias) * outputTypeEpsilon;
}
return tol;
}
// Float / Float / Float
template <>
std::vector<BnInfVarToleranceTestCase>
getBnInfVarToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
// Test ranges matching BatchnormFwdWithVarianceTensorBundle: x=[0,1], mean=[0,1], var=[0.1,1], scale=[0,1], bias=[0,1]
return {// Standard: var=[0.1,1.0], invVar = 1/sqrt(0.1+1e-5), P = 2*invVar*1
{0.0,
1.0,
0.0,
1.0,
0.1,
1.0,
0.0,
1.0,
0.0,
1.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 1.0, 1e-5, u)},
// Symmetric ranges
{-1.0,
1.0,
-1.0,
1.0,
0.1,
1.0,
-1.0,
1.0,
0.0,
0.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 0.0, 1e-5, u)},
// Zero input: P=0, only bias tolerance survives
{0.0,
0.0,
0.0,
0.0,
0.1,
1.0,
-1.0,
1.0,
-0.5,
0.5,
1e-5,
expectedBnInfVarTol(0.0, 0.0, 0.1, 1.0, 0.5, 1e-5, u)},
// All zero (except epsBn): invVar = 1/sqrt(epsBn), but P=0
{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1e-5, 0.0},
// eps = 0 => throws
{0.0, 1.0, 0.0, 1.0, 0.1, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, true},
// eps < 0 => throws
{0.0, 1.0, 0.0, 1.0, 0.1, 1.0, 0.0, 1.0, 0.0, 1.0, -1e-5, 0.0, true}};
}
// Float / Double / Float
template <>
std::vector<BnInfVarToleranceTestCase>
getBnInfVarToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uDouble = std::numeric_limits<double>::epsilon();
return {// Standard
{0.0,
1.0,
0.0,
1.0,
0.1,
1.0,
0.0,
1.0,
0.0,
1.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 1.0, 1e-5, u, uDouble)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
0.1,
1.0,
-1.0,
1.0,
0.0,
0.0,
1e-5,
expectedBnInfVarTol(0.0, 0.0, 0.1, 1.0, 0.0, 1e-5, u, uDouble)}};
}
// Half / Float / Float
template <>
std::vector<BnInfVarToleranceTestCase>
getBnInfVarToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
0.1,
1.0,
0.0,
1.0,
0.0,
1.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 1.0, 1e-5, u, 0.0, uHalf)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
0.1,
1.0,
-1.0,
1.0,
0.0,
0.0,
1e-5,
expectedBnInfVarTol(0.0, 0.0, 0.1, 1.0, 0.0, 1e-5, u, 0.0, uHalf)}};
}
// Half / Half / Half
template <>
std::vector<BnInfVarToleranceTestCase> getBnInfVarToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
0.1,
1.0,
0.0,
1.0,
0.0,
1.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 1.0, 1e-5, u)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
0.1,
1.0,
-1.0,
1.0,
0.0,
0.0,
1e-5,
expectedBnInfVarTol(0.0, 0.0, 0.1, 1.0, 0.0, 1e-5, u)}};
}
// Bfloat16 / Float / Float
template <>
std::vector<BnInfVarToleranceTestCase>
getBnInfVarToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
0.1,
1.0,
0.0,
1.0,
0.0,
1.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 1.0, 1e-5, u, 0.0, uBf16)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
0.1,
1.0,
-1.0,
1.0,
0.0,
0.0,
1e-5,
expectedBnInfVarTol(0.0, 0.0, 0.1, 1.0, 0.0, 1e-5, u, 0.0, uBf16)}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnInfVarToleranceTestCase>
getBnInfVarToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// Standard
{0.0,
1.0,
0.0,
1.0,
0.1,
1.0,
0.0,
1.0,
0.0,
1.0,
1e-5,
expectedBnInfVarTol(1.0, 1.0, 0.1, 1.0, 1.0, 1e-5, u)},
// Zero input
{0.0,
0.0,
0.0,
0.0,
0.1,
1.0,
-1.0,
1.0,
0.0,
0.0,
1e-5,
expectedBnInfVarTol(0.0, 0.0, 0.1, 1.0, 0.0, 1e-5, u)}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnInfVarTolerance : public ::testing::TestWithParam<BnInfVarToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW(
(calculateBatchnormInferenceWithVarianceTolerance<Out, In, Comp>(params.xMin,
params.xMax,
params.meanMin,
params.meanMax,
params.varMin,
params.varMax,
params.scaleMin,
params.scaleMax,
params.biasMin,
params.biasMax,
params.epsilonBn)),
std::invalid_argument);
}
else
{
auto tol
= calculateBatchnormInferenceWithVarianceTolerance<Out, In, Comp>(params.xMin,
params.xMax,
params.meanMin,
params.meanMax,
params.varMin,
params.varMax,
params.scaleMin,
params.scaleMax,
params.biasMin,
params.biasMax,
params.epsilonBn);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnInfVarTolFp32 = TestCalculateBnInfVarTolerance<float, float, float>;
TEST_P(TestCalcBnInfVarTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfVarTolFp32,
::testing::ValuesIn(getBnInfVarToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnInfVarTolInputDouble = TestCalculateBnInfVarTolerance<float, double, float>;
TEST_P(TestCalcBnInfVarTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfVarTolInputDouble,
::testing::ValuesIn(getBnInfVarToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnInfVarTolComputeFloatFp16 = TestCalculateBnInfVarTolerance<half, float, float>;
TEST_P(TestCalcBnInfVarTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfVarTolComputeFloatFp16,
::testing::ValuesIn(getBnInfVarToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnInfVarTolFp16 = TestCalculateBnInfVarTolerance<half, half, half>;
TEST_P(TestCalcBnInfVarTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfVarTolFp16,
::testing::ValuesIn(getBnInfVarToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnInfVarTolComputeFloatBfp16 = TestCalculateBnInfVarTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnInfVarTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfVarTolComputeFloatBfp16,
::testing::ValuesIn(getBnInfVarToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnInfVarTolBfp16 = TestCalculateBnInfVarTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnInfVarTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnInfVarTolBfp16,
::testing::ValuesIn(getBnInfVarToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// Standalone Tests - BN Inference
// =================================================================================================
// BN Inference: zero scale and bias gives zero tolerance
TEST(TestCalculateBnInfTolerance, ZeroScaleAndBias)
{
auto tol = calculateBatchnormInferenceTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 1.0, 3.0, 0.0, 0.0, 0.0, 0.0);
EXPECT_EQ(tol, 0.0f);
// With half output: maxOutputMagnitude = 0, output cast = 0
auto tolHalf = calculateBatchnormInferenceTolerance<half, float, float>(
-1.0, 1.0, -1.0, 1.0, 1.0, 3.0, 0.0, 0.0, 0.0, 0.0);
EXPECT_EQ(tolHalf, 0.0f);
}
// BN Inference: tolerance scales with invVar magnitude
TEST(TestCalculateBnInfTolerance, ScalesWithInvVar)
{
auto tolSmall = calculateBatchnormInferenceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 0.0);
auto tolLarge = calculateBatchnormInferenceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 10.0, -1.0, 1.0, 0.0, 0.0);
EXPECT_LT(tolSmall, tolLarge)
<< "Larger invVar should produce larger tolerance (P scales with invVar)";
}
// BN Inference: tolerance scales with scale magnitude
TEST(TestCalculateBnInfTolerance, ScalesWithScale)
{
auto tolSmall = calculateBatchnormInferenceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 3.0, 0.0, 0.1, 0.0, 0.0);
auto tolLarge = calculateBatchnormInferenceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 3.0, -1.0, 1.0, 0.0, 0.0);
EXPECT_LT(tolSmall, tolLarge) << "Larger scale should produce larger tolerance";
}
// BN Inference: asymmetric x range
TEST(TestCalculateBnInfTolerance, AsymmetricXRange)
{
auto tolAsym = calculateBatchnormInferenceTolerance<float, float, float>(
-0.5, 2.0, 0.0, 0.0, 1.0, 3.0, -1.0, 1.0, 0.0, 0.0);
auto tolSym = calculateBatchnormInferenceTolerance<float, float, float>(
-2.0, 2.0, 0.0, 0.0, 1.0, 3.0, -1.0, 1.0, 0.0, 0.0);
// Both have maxAbsX=2.0, so tolerance should be identical
EXPECT_EQ(tolAsym, tolSym);
}
// BN Inference: variance variant throws on invalid epsilon
TEST(TestCalculateBnInfVarTolerance, ThrowsOnInvalidEpsilon)
{
EXPECT_THROW((calculateBatchnormInferenceWithVarianceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 0.1, 1.0, -1.0, 1.0, 0.0, 0.0, 0.0)),
std::invalid_argument);
EXPECT_THROW((calculateBatchnormInferenceWithVarianceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 0.1, 1.0, -1.0, 1.0, 0.0, 0.0, -1e-5)),
std::invalid_argument);
}
// BN Inference: variance variant throws on negative varianceMin
TEST(TestCalculateBnInfVarTolerance, ThrowsOnNegativeVariance)
{
EXPECT_THROW((calculateBatchnormInferenceWithVarianceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, -0.1, 1.0, -1.0, 1.0, 0.0, 0.0, 1e-5)),
std::invalid_argument);
}
// BN Inference with variance: tolerance larger than invVar variant (more ops)
TEST(TestCalculateBnInfVarTolerance, LargerThanInvVarVariant)
{
// For comparable inputs, the variance variant should produce a larger tolerance
// because it has 6 chain ops vs 3 for the invVar variant.
// Use var=1.0 so invVar = 1/sqrt(1+1e-5) ~ 1.0 (comparable to invVar range [1,1])
auto tolInvVar = calculateBatchnormInferenceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 0.0);
auto tolVar = calculateBatchnormInferenceWithVarianceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 1e-5);
EXPECT_LT(tolInvVar, tolVar)
<< "Variance variant should have larger tolerance due to more chain ops";
}
// BN Inference with variance: smaller variance -> larger tolerance (larger invVar)
TEST(TestCalculateBnInfVarTolerance, SmallVarianceLargerTolerance)
{
auto tolLargeVar = calculateBatchnormInferenceWithVarianceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 1.0, 10.0, -1.0, 1.0, 0.0, 0.0, 1e-5);
auto tolSmallVar = calculateBatchnormInferenceWithVarianceTolerance<float, float, float>(
-1.0, 1.0, 0.0, 0.0, 0.01, 0.1, -1.0, 1.0, 0.0, 0.0, 1e-5);
EXPECT_LT(tolLargeVar, tolSmallVar) << "Smaller variance -> larger invVar -> larger tolerance";
}
// BN Inference: output overflow detection
TEST(TestCalculateBnInfTolerance, ThrowsOnOutputOverflow)
{
// half output with very large invVar and scale: output magnitude >> 65504
EXPECT_THROW((calculateBatchnormInferenceTolerance<half, float, float>(
-1.0, 1.0, 0.0, 0.0, 1e4, 1e4, 1e4, 1e4, 0.0, 0.0)),
std::overflow_error);
}
// Y tolerance: non-default epsilon affects output casting magnitude
TEST(TestCalculateBnTrainTolerance, NonDefaultEpsilon)
{
// For maxAbsX=1, scale=1, the main tolerance (3/2*gamma + 7u) is independent
// of eps_bn. Only the output casting magnitude changes via invVarEst.
auto tolDefault = calculateBatchnormTrainingTolerance<half, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, 1e-5);
auto tolLargeEps = calculateBatchnormTrainingTolerance<half, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, 1e-3);
// Larger eps_bn -> smaller invVarEst -> smaller maxOutputMagnitude -> smaller output cast
EXPECT_LT(tolLargeEps, tolDefault) << "Larger eps_bn should reduce output casting contribution";
// For float/float/float (no output casting), eps_bn has no effect
auto tolFp32Default = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, 1e-5);
auto tolFp32LargeEps = calculateBatchnormTrainingTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, 1e-3);
EXPECT_EQ(tolFp32Default, tolFp32LargeEps)
<< "Without output casting, eps_bn should not affect y tolerance";
}
// =================================================================================================
// TestCalculateBatchnormBackwardDbiasTolerance
// =================================================================================================
struct BnBwdDbiasToleranceTestCase
{
double dyMin;
double dyMax;
int64_t nElementsPerChannel;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnBwdDbiasToleranceTestCase& tc)
{
os << "dyMin: " << tc.dyMin << ", dyMax: " << tc.dyMax
<< ", NHW: " << tc.nElementsPerChannel << ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnBwdDbiasToleranceTestCase> getBnBwdDbiasToleranceTestCases();
// dbias tolerance formula (no casting):
// tolerance = gamma_NHW * max|dy|
// With input casting (InputType=double, ComputeType=float):
// tolerance += computeEpsilon * NHW * max|dy| (input cast for 1 tensor)
// With output casting (OutputType=half/bf16):
// tolerance += NHW * max|dy| * outputEpsilon
// Float / Float / Float
template <>
std::vector<BnBwdDbiasToleranceTestCase>
getBnBwdDbiasToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, gamma(1) * 1.0},
// NHW=10
{-1.0, 1.0, 10, gamma(10) * 1.0},
// Large dy range (scales linearly with max|dy|)
{-1000.0, 1000.0, 10, gamma(10) * 1000.0},
// Zero dy (tolerance=0)
{0.0, 0.0, 10, 0.0}};
}
// Float / Double / Float (Input casting: inputEpsilon(double) < epsilon(float))
// tolerance = gamma * max|dy| + u * NHW * max|dy|
template <>
std::vector<BnBwdDbiasToleranceTestCase>
getBnBwdDbiasToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1: gamma * 1 + u * 1 * 1
{-1.0, 1.0, 1, gamma(1) * 1.0 + u * 1.0 * 1.0},
// NHW=10
{-1.0, 1.0, 10, gamma(10) * 1.0 + u * 10.0 * 1.0},
// Zero dy
{0.0, 0.0, 10, 0.0}};
}
// Half / Float / Float (Output casting: outputEpsilon(half) > epsilon(float))
// tolerance = gamma * max|dy| + NHW * max|dy| * uHalf
template <>
std::vector<BnBwdDbiasToleranceTestCase>
getBnBwdDbiasToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, gamma(1) * 1.0 + 1.0 * 1.0 * uHalf},
// NHW=10
{-1.0, 1.0, 10, gamma(10) * 1.0 + 10.0 * 1.0 * uHalf},
// Zero dy
{0.0, 0.0, 10, 0.0}};
}
// Half / Half / Half
template <>
std::vector<BnBwdDbiasToleranceTestCase>
getBnBwdDbiasToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, gamma(1) * 1.0},
// NHW=10
{-1.0, 1.0, 10, gamma(10) * 1.0},
// Zero dy
{0.0, 0.0, 10, 0.0}};
}
// Bfloat16 / Float / Float (Output casting: outputEpsilon(bf16) > epsilon(float))
template <>
std::vector<BnBwdDbiasToleranceTestCase>
getBnBwdDbiasToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, gamma(1) * 1.0 + 1.0 * 1.0 * uBf16},
// NHW=10
{-1.0, 1.0, 10, gamma(10) * 1.0 + 10.0 * 1.0 * uBf16},
// Zero dy
{0.0, 0.0, 10, 0.0}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnBwdDbiasToleranceTestCase>
getBnBwdDbiasToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
auto gamma = [&](int64_t c) { return computeGamma(static_cast<uint64_t>(c), u); };
return {// NHW = 0 => throws
{-1.0, 1.0, 0, 0.0, true},
// NHW=1
{-1.0, 1.0, 1, gamma(1) * 1.0},
// NHW=10
{-1.0, 1.0, 10, gamma(10) * 1.0},
// Zero dy
{0.0, 0.0, 10, 0.0}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnBwdDbiasTolerance
: public ::testing::TestWithParam<BnBwdDbiasToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW((calculateBatchnormBackwardDbiasTolerance<Out, In, Comp>(
params.dyMin, params.dyMax, params.nElementsPerChannel)),
std::invalid_argument);
}
else
{
auto tol = calculateBatchnormBackwardDbiasTolerance<Out, In, Comp>(
params.dyMin, params.dyMax, params.nElementsPerChannel);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnBwdDbiasTolFp32 = TestCalculateBnBwdDbiasTolerance<float, float, float>;
TEST_P(TestCalcBnBwdDbiasTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDbiasTolFp32,
::testing::ValuesIn(getBnBwdDbiasToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnBwdDbiasTolInputDouble = TestCalculateBnBwdDbiasTolerance<float, double, float>;
TEST_P(TestCalcBnBwdDbiasTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDbiasTolInputDouble,
::testing::ValuesIn(getBnBwdDbiasToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnBwdDbiasTolComputeFloatFp16 = TestCalculateBnBwdDbiasTolerance<half, float, float>;
TEST_P(TestCalcBnBwdDbiasTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDbiasTolComputeFloatFp16,
::testing::ValuesIn(getBnBwdDbiasToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnBwdDbiasTolFp16 = TestCalculateBnBwdDbiasTolerance<half, half, half>;
TEST_P(TestCalcBnBwdDbiasTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDbiasTolFp16,
::testing::ValuesIn(getBnBwdDbiasToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnBwdDbiasTolComputeFloatBfp16
= TestCalculateBnBwdDbiasTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnBwdDbiasTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDbiasTolComputeFloatBfp16,
::testing::ValuesIn(getBnBwdDbiasToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnBwdDbiasTolBfp16 = TestCalculateBnBwdDbiasTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnBwdDbiasTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDbiasTolBfp16,
::testing::ValuesIn(
getBnBwdDbiasToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// TestCalculateBatchnormBackwardDscaleTolerance
// =================================================================================================
struct BnBwdDscaleToleranceTestCase
{
double dyMin;
double dyMax;
double xMin;
double xMax;
int64_t nElementsPerChannel;
double epsilonBn;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnBwdDscaleToleranceTestCase& tc)
{
os << "dyMin: " << tc.dyMin << ", dyMax: " << tc.dyMax << ", xMin: " << tc.xMin
<< ", xMax: " << tc.xMax << ", NHW: " << tc.nElementsPerChannel
<< ", epsBn: " << tc.epsilonBn << ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnBwdDscaleToleranceTestCase> getBnBwdDscaleToleranceTestCases();
// dscale tolerance formula (no casting, maxAbsDy > 0):
// varEst = max(maxAbsX^2, epsBn)
// invVarEst = 1/sqrt(varEst + epsBn)
// maxAbsXHat = maxAbsX * invVarEst
// tolerance = (gamma + u) * maxAbsXHat * max|dy|
// With input casting (double->float): + 2 * u * NHW * maxAbsXHat * max|dy|
// With output casting: + NHW * maxAbsXHat * max|dy| * outputEpsilon
// Helper to compute expected dscale tolerance
static double expectedDscaleTol(double maxAbsDy,
double maxAbsX,
int64_t nhw,
double epsBn,
double u,
double outputEpsilon = 0.0,
double computeEpsilon = 0.0,
double inputEpsilon = 0.0)
{
if(maxAbsDy == 0.0)
{
return 0.0;
}
auto gamma = computeGamma(static_cast<uint64_t>(nhw), u);
const double varEst = std::max(maxAbsX * maxAbsX, epsBn);
const double invVarEst = 1.0 / std::sqrt(varEst + epsBn);
const double maxAbsXHat = maxAbsX * invVarEst;
const double signalBound = static_cast<double>(nhw) * maxAbsXHat * maxAbsDy;
double tol = (gamma + u) * maxAbsXHat * maxAbsDy;
// Input casting (factor=2 for two-tensor product)
if(inputEpsilon > 0.0 && inputEpsilon < computeEpsilon)
{
tol += 2.0 * computeEpsilon * signalBound;
}
// Output casting
if(outputEpsilon > computeEpsilon && computeEpsilon > 0.0)
{
tol += signalBound * outputEpsilon;
}
return tol;
}
// Float / Float / Float
template <>
std::vector<BnBwdDscaleToleranceTestCase>
getBnBwdDscaleToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDscaleTol(1.0, 1.0, 1, 1e-5, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDscaleTol(1.0, 1.0, 10, 1e-5, u)},
// Large x range
{-1.0, 1.0, -1000.0, 1000.0, 10, 1e-5, expectedDscaleTol(1.0, 1000.0, 10, 1e-5, u)},
// Zero dy (tolerance=0)
{0.0, 0.0, -1.0, 1.0, 10, 1e-5, 0.0},
// Zero x (xHat=0 -> tol=0 from function since dy>0 but xHat=0 means all products are 0)
{-1.0, 1.0, 0.0, 0.0, 10, 1e-5, expectedDscaleTol(1.0, 0.0, 10, 1e-5, u)},
// Invalid epsilon => throws
{-1.0, 1.0, -1.0, 1.0, 10, 0.0, 0.0, true}};
}
// Float / Double / Float (Input casting: inputEpsilon(double) < epsilon(float))
template <>
std::vector<BnBwdDscaleToleranceTestCase>
getBnBwdDscaleToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uDouble = std::numeric_limits<double>::epsilon();
return {
// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDscaleTol(1.0, 1.0, 1, 1e-5, u, 0.0, u, uDouble)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDscaleTol(1.0, 1.0, 10, 1e-5, u, 0.0, u, uDouble)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Half / Float / Float (Output casting: outputEpsilon(half) > epsilon(float))
template <>
std::vector<BnBwdDscaleToleranceTestCase>
getBnBwdDscaleToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDscaleTol(1.0, 1.0, 1, 1e-5, u, uHalf, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDscaleTol(1.0, 1.0, 10, 1e-5, u, uHalf, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Half / Half / Half
template <>
std::vector<BnBwdDscaleToleranceTestCase>
getBnBwdDscaleToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDscaleTol(1.0, 1.0, 1, 1e-5, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDscaleTol(1.0, 1.0, 10, 1e-5, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Bfloat16 / Float / Float (Output casting)
template <>
std::vector<BnBwdDscaleToleranceTestCase>
getBnBwdDscaleToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDscaleTol(1.0, 1.0, 1, 1e-5, u, uBf16, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDscaleTol(1.0, 1.0, 10, 1e-5, u, uBf16, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnBwdDscaleToleranceTestCase>
getBnBwdDscaleToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDscaleTol(1.0, 1.0, 1, 1e-5, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDscaleTol(1.0, 1.0, 10, 1e-5, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnBwdDscaleTolerance
: public ::testing::TestWithParam<BnBwdDscaleToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW((calculateBatchnormBackwardDscaleTolerance<Out, In, Comp>(
params.dyMin,
params.dyMax,
params.xMin,
params.xMax,
params.nElementsPerChannel,
params.epsilonBn)),
std::invalid_argument);
}
else
{
auto tol = calculateBatchnormBackwardDscaleTolerance<Out, In, Comp>(
params.dyMin,
params.dyMax,
params.xMin,
params.xMax,
params.nElementsPerChannel,
params.epsilonBn);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnBwdDscaleTolFp32 = TestCalculateBnBwdDscaleTolerance<float, float, float>;
TEST_P(TestCalcBnBwdDscaleTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDscaleTolFp32,
::testing::ValuesIn(getBnBwdDscaleToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnBwdDscaleTolInputDouble = TestCalculateBnBwdDscaleTolerance<float, double, float>;
TEST_P(TestCalcBnBwdDscaleTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDscaleTolInputDouble,
::testing::ValuesIn(getBnBwdDscaleToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnBwdDscaleTolComputeFloatFp16
= TestCalculateBnBwdDscaleTolerance<half, float, float>;
TEST_P(TestCalcBnBwdDscaleTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDscaleTolComputeFloatFp16,
::testing::ValuesIn(getBnBwdDscaleToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnBwdDscaleTolFp16 = TestCalculateBnBwdDscaleTolerance<half, half, half>;
TEST_P(TestCalcBnBwdDscaleTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDscaleTolFp16,
::testing::ValuesIn(getBnBwdDscaleToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnBwdDscaleTolComputeFloatBfp16
= TestCalculateBnBwdDscaleTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnBwdDscaleTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDscaleTolComputeFloatBfp16,
::testing::ValuesIn(getBnBwdDscaleToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnBwdDscaleTolBfp16 = TestCalculateBnBwdDscaleTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnBwdDscaleTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDscaleTolBfp16,
::testing::ValuesIn(
getBnBwdDscaleToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// TestCalculateBatchnormBackwardDxTolerance
// =================================================================================================
struct BnBwdDxToleranceTestCase
{
double dyMin;
double dyMax;
double xMin;
double xMax;
double scaleMin;
double scaleMax;
int64_t nElementsPerChannel;
double epsilonBn;
double expectedTolerance;
bool expectThrow = false;
friend std::ostream& operator<<(std::ostream& os, const BnBwdDxToleranceTestCase& tc)
{
os << "dyMin: " << tc.dyMin << ", dyMax: " << tc.dyMax << ", xMin: " << tc.xMin
<< ", xMax: " << tc.xMax << ", scaleMin: " << tc.scaleMin
<< ", scaleMax: " << tc.scaleMax << ", NHW: " << tc.nElementsPerChannel
<< ", epsBn: " << tc.epsilonBn << ", expectedTolerance: " << tc.expectedTolerance
<< ", expectThrow: " << (tc.expectThrow ? "true" : "false");
return os;
}
};
template <typename T>
std::vector<BnBwdDxToleranceTestCase> getBnBwdDxToleranceTestCases();
// dx tolerance formula (no casting):
// varEst = max(maxAbsX^2, epsBn), invVarEst = 1/sqrt(varEst + epsBn)
// maxAbsXHat = maxAbsX * invVarEst
// scalarCoef = maxAbsScale * invVarEst
// deltaMeanDy = (gamma + u) * max|dy|
// deltaMeanDyXhat = (gamma + 2u) * maxAbsXHat * max|dy|
// maxAbsE = (2 + maxAbsXHat^2) * max|dy|
// tolerance = scalarCoef * (deltaMeanDy + maxAbsXHat * deltaMeanDyXhat + 3u * maxAbsE)
// + 2u * scalarCoef * maxAbsE
// Helper to compute expected dx tolerance
static double expectedDxTol(double maxAbsDy,
double maxAbsX,
double maxAbsScale,
int64_t nhw,
double epsBn,
double u,
double outputEpsilon = 0.0,
double computeEpsilon = 0.0,
double inputEpsilon = 0.0)
{
if(maxAbsDy == 0.0)
{
return 0.0;
}
auto gamma = computeGamma(static_cast<uint64_t>(nhw), u);
const double varEst = std::max(maxAbsX * maxAbsX, epsBn);
const double invVarEst = 1.0 / std::sqrt(varEst + epsBn);
const double maxAbsXHat = maxAbsX * invVarEst;
const double scalarCoef = maxAbsScale * invVarEst;
const double deltaMeanDy = (gamma + u) * maxAbsDy;
const double deltaMeanDyXhat = (gamma + 2.0 * u) * maxAbsXHat * maxAbsDy;
const double maxAbsE = (2.0 + maxAbsXHat * maxAbsXHat) * maxAbsDy;
double tol = scalarCoef * (deltaMeanDy + maxAbsXHat * deltaMeanDyXhat + 3.0 * u * maxAbsE)
+ 2.0 * u * scalarCoef * maxAbsE;
const double maxAbsDx = scalarCoef * maxAbsE;
// Input casting (factor=2)
if(inputEpsilon > 0.0 && inputEpsilon < computeEpsilon)
{
tol += 2.0 * computeEpsilon * maxAbsDx;
}
// Output casting
if(outputEpsilon > computeEpsilon && computeEpsilon > 0.0)
{
tol += maxAbsDx * outputEpsilon;
}
return tol;
}
// Float / Float / Float
template <>
std::vector<BnBwdDxToleranceTestCase>
getBnBwdDxToleranceTestCases<TypeTriple<float, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDxTol(1.0, 1.0, 1.0, 1, 1e-5, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDxTol(1.0, 1.0, 1.0, 10, 1e-5, u)},
// Zero dy (tol=0)
{0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, 0.0},
// Zero scale (scalarCoef=0 -> tol=0)
{-1.0, 1.0, -1.0, 1.0, 0.0, 0.0, 10, 1e-5, expectedDxTol(1.0, 1.0, 0.0, 10, 1e-5, u)},
// Invalid epsilon => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 10, 0.0, 0.0, true}};
}
// Float / Double / Float (Input casting)
template <>
std::vector<BnBwdDxToleranceTestCase>
getBnBwdDxToleranceTestCases<TypeTriple<float, double, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uDouble = std::numeric_limits<double>::epsilon();
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0,
1.0,
-1.0,
1.0,
-1.0,
1.0,
1,
1e-5,
expectedDxTol(1.0, 1.0, 1.0, 1, 1e-5, u, 0.0, u, uDouble)},
// NHW=10
{-1.0,
1.0,
-1.0,
1.0,
-1.0,
1.0,
10,
1e-5,
expectedDxTol(1.0, 1.0, 1.0, 10, 1e-5, u, 0.0, u, uDouble)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Half / Float / Float (Output casting)
template <>
std::vector<BnBwdDxToleranceTestCase> getBnBwdDxToleranceTestCases<TypeTriple<half, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uHalf = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0,
1.0,
-1.0,
1.0,
-1.0,
1.0,
1,
1e-5,
expectedDxTol(1.0, 1.0, 1.0, 1, 1e-5, u, uHalf, u)},
// NHW=10
{-1.0,
1.0,
-1.0,
1.0,
-1.0,
1.0,
10,
1e-5,
expectedDxTol(1.0, 1.0, 1.0, 10, 1e-5, u, uHalf, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Half / Half / Half
template <>
std::vector<BnBwdDxToleranceTestCase> getBnBwdDxToleranceTestCases<TypeTriple<half, half, half>>()
{
auto u = static_cast<double>(std::numeric_limits<half>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDxTol(1.0, 1.0, 1.0, 1, 1e-5, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDxTol(1.0, 1.0, 1.0, 10, 1e-5, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Bfloat16 / Float / Float (Output casting)
template <>
std::vector<BnBwdDxToleranceTestCase>
getBnBwdDxToleranceTestCases<TypeTriple<bfloat16, float, float>>()
{
auto u = static_cast<double>(std::numeric_limits<float>::epsilon());
auto uBf16 = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0,
1.0,
-1.0,
1.0,
-1.0,
1.0,
1,
1e-5,
expectedDxTol(1.0, 1.0, 1.0, 1, 1e-5, u, uBf16, u)},
// NHW=10
{-1.0,
1.0,
-1.0,
1.0,
-1.0,
1.0,
10,
1e-5,
expectedDxTol(1.0, 1.0, 1.0, 10, 1e-5, u, uBf16, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
// Bfloat16 / Bfloat16 / Bfloat16
template <>
std::vector<BnBwdDxToleranceTestCase>
getBnBwdDxToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()
{
auto u = static_cast<double>(std::numeric_limits<bfloat16>::epsilon());
return {// NHW = 0 => throws
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 0, 1e-5, 0.0, true},
// NHW=1
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1, 1e-5, expectedDxTol(1.0, 1.0, 1.0, 1, 1e-5, u)},
// NHW=10
{-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, expectedDxTol(1.0, 1.0, 1.0, 10, 1e-5, u)},
// Zero dy
{0.0, 0.0, -1.0, 1.0, -1.0, 1.0, 10, 1e-5, 0.0}};
}
template <typename Out, typename In, typename Comp>
class TestCalculateBnBwdDxTolerance : public ::testing::TestWithParam<BnBwdDxToleranceTestCase>
{
protected:
void verifyTolerance()
{
const auto& params = GetParam();
if(params.expectThrow)
{
EXPECT_THROW(
(calculateBatchnormBackwardDxTolerance<Out, In, Comp>(params.dyMin,
params.dyMax,
params.xMin,
params.xMax,
params.scaleMin,
params.scaleMax,
params.nElementsPerChannel,
params.epsilonBn)),
std::invalid_argument);
}
else
{
auto tol
= calculateBatchnormBackwardDxTolerance<Out, In, Comp>(params.dyMin,
params.dyMax,
params.xMin,
params.xMax,
params.scaleMin,
params.scaleMax,
params.nElementsPerChannel,
params.epsilonBn);
auto expected = static_cast<float>(params.expectedTolerance);
EXPECT_NEAR(
tol, expected, std::max(expected * 0.01f, std::numeric_limits<float>::min()));
}
}
};
using TestCalcBnBwdDxTolFp32 = TestCalculateBnBwdDxTolerance<float, float, float>;
TEST_P(TestCalcBnBwdDxTolFp32, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDxTolFp32,
::testing::ValuesIn(getBnBwdDxToleranceTestCases<TypeTriple<float, float, float>>()));
using TestCalcBnBwdDxTolInputDouble = TestCalculateBnBwdDxTolerance<float, double, float>;
TEST_P(TestCalcBnBwdDxTolInputDouble, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDxTolInputDouble,
::testing::ValuesIn(getBnBwdDxToleranceTestCases<TypeTriple<float, double, float>>()));
using TestCalcBnBwdDxTolComputeFloatFp16 = TestCalculateBnBwdDxTolerance<half, float, float>;
TEST_P(TestCalcBnBwdDxTolComputeFloatFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDxTolComputeFloatFp16,
::testing::ValuesIn(getBnBwdDxToleranceTestCases<TypeTriple<half, float, float>>()));
using TestCalcBnBwdDxTolFp16 = TestCalculateBnBwdDxTolerance<half, half, half>;
TEST_P(TestCalcBnBwdDxTolFp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDxTolFp16,
::testing::ValuesIn(getBnBwdDxToleranceTestCases<TypeTriple<half, half, half>>()));
using TestCalcBnBwdDxTolComputeFloatBfp16 = TestCalculateBnBwdDxTolerance<bfloat16, float, float>;
TEST_P(TestCalcBnBwdDxTolComputeFloatBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDxTolComputeFloatBfp16,
::testing::ValuesIn(getBnBwdDxToleranceTestCases<TypeTriple<bfloat16, float, float>>()));
using TestCalcBnBwdDxTolBfp16 = TestCalculateBnBwdDxTolerance<bfloat16, bfloat16, bfloat16>;
TEST_P(TestCalcBnBwdDxTolBfp16, VerifyTolerance)
{
this->verifyTolerance();
}
INSTANTIATE_TEST_SUITE_P(
Smoke,
TestCalcBnBwdDxTolBfp16,
::testing::ValuesIn(getBnBwdDxToleranceTestCases<TypeTriple<bfloat16, bfloat16, bfloat16>>()));
// =================================================================================================
// Standalone Tests - BN Backward
// =================================================================================================
// dbias tolerance is simpler than dscale (pure sum vs dot-product)
TEST(TestCalculateBnBwdDbiasTolerance, DbiasToleranceSmallerThanDscale)
{
auto dbiasTol = calculateBatchnormBackwardDbiasTolerance<float, float, float>(-1.0, 1.0, 100);
auto dscaleTol
= calculateBatchnormBackwardDscaleTolerance<float, float, float>(-1.0, 1.0, -1.0, 1.0, 100);
EXPECT_LT(dbiasTol, dscaleTol)
<< "dbias (pure sum) should have smaller tolerance than dscale (dot-product)";
}
// Doubling scale approximately doubles dx tolerance
TEST(TestCalculateBnBwdDxTolerance, DxToleranceScalesWithScale)
{
auto tolScale1 = calculateBatchnormBackwardDxTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 100);
auto tolScale2 = calculateBatchnormBackwardDxTolerance<float, float, float>(
-1.0, 1.0, -1.0, 1.0, -2.0, 2.0, 100);
// scalarCoef = scale * invVar, so doubling scale should approximately double tolerance
auto ratio = static_cast<double>(tolScale2) / static_cast<double>(tolScale1);
EXPECT_GT(ratio, 1.8) << "Doubling scale should approximately double dx tolerance";
EXPECT_LT(ratio, 2.2) << "Ratio should be close to 2.0";
}