libeigen/eigen!2705 Co-authored-by: Rasmus Munk Larsen <rmlarsen@gmail.com> Co-authored-by: Rasmus Munk Larsen <rlarsen@nvidia.com>
124 lines
5.0 KiB
C++
124 lines
5.0 KiB
C++
// SPDX-FileCopyrightText: The Eigen Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
#include "main.h"
|
|
#include "fp_control.h"
|
|
|
|
template <typename RealScalar>
|
|
EIGEN_DONT_INLINE RealScalar underflow_probe() {
|
|
volatile RealScalar normal_min = (std::numeric_limits<RealScalar>::min)();
|
|
volatile RealScalar one_half = RealScalar(0.5);
|
|
return normal_min * one_half;
|
|
}
|
|
|
|
#if defined(EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC) && EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC
|
|
EIGEN_DONT_INLINE half fp16_underflow_probe(half normal_min, half one_half) { return normal_min * one_half; }
|
|
|
|
// Volatile dispatch prevents constant-propagation clones from bypassing the changed FPCR.
|
|
typedef half (*Fp16UnderflowProbe)(half, half);
|
|
static Fp16UnderflowProbe volatile fp16_underflow_probe_runtime = &fp16_underflow_probe;
|
|
|
|
void scoped_flush_to_zero_fp16() {
|
|
const half normal_min = (std::numeric_limits<half>::min)();
|
|
const half one_half(0.5f);
|
|
const half underflow_before = fp16_underflow_probe_runtime(normal_min, one_half);
|
|
bool flush_to_zero_supported = false;
|
|
{
|
|
Eigen::ScopedFlushToZero flush_to_zero;
|
|
flush_to_zero_supported = flush_to_zero.isSupported();
|
|
VERIFY(flush_to_zero_supported);
|
|
VERIFY_IS_EQUAL(static_cast<float>(fp16_underflow_probe_runtime(normal_min, one_half)), 0.0f);
|
|
}
|
|
VERIFY_IS_EQUAL(static_cast<float>(fp16_underflow_probe_runtime(normal_min, one_half)),
|
|
static_cast<float>(underflow_before));
|
|
}
|
|
#endif
|
|
|
|
template <typename RealScalar>
|
|
void stable_normalize_fastmath() {
|
|
typedef Matrix<RealScalar, 2, 1> Vector2;
|
|
typedef std::complex<RealScalar> Complex;
|
|
typedef Matrix<Complex, 1, 1> ComplexVector1;
|
|
using std::sqrt;
|
|
|
|
const RealScalar highest = (std::numeric_limits<RealScalar>::max)();
|
|
const RealScalar smallest = (std::numeric_limits<RealScalar>::min)();
|
|
const RealScalar inv_sqrt_two = RealScalar(1) / sqrt(RealScalar(2));
|
|
const Vector2 expected = Vector2::Constant(inv_sqrt_two);
|
|
|
|
Vector2 large = Vector2::Constant(highest);
|
|
VERIFY_IS_APPROX(large.stableNormalized(), expected);
|
|
large.stableNormalize();
|
|
VERIFY_IS_APPROX(large, expected);
|
|
|
|
Vector2 small = Vector2::Constant(smallest);
|
|
VERIFY_IS_APPROX(small.stableNormalized(), expected);
|
|
small.stableNormalize();
|
|
VERIFY_IS_APPROX(small, expected);
|
|
|
|
ComplexVector1 complex_large;
|
|
complex_large(0) = Complex(highest, highest);
|
|
const Complex expected_complex(inv_sqrt_two, inv_sqrt_two);
|
|
VERIFY_IS_APPROX(complex_large.stableNormalized()(0), expected_complex);
|
|
complex_large.stableNormalize();
|
|
VERIFY_IS_APPROX(complex_large(0), expected_complex);
|
|
}
|
|
|
|
template <typename RealScalar>
|
|
void stable_norm_ftz_boundaries() {
|
|
typedef Matrix<RealScalar, 2, 1> Vector2;
|
|
typedef Matrix<RealScalar, Dynamic, 1> VectorX;
|
|
using std::sqrt;
|
|
|
|
const RealScalar normal_min = (std::numeric_limits<RealScalar>::min)();
|
|
const RealScalar inv_sqrt_two = RealScalar(1) / sqrt(RealScalar(2));
|
|
const Vector2 expected_normalized = Vector2::Constant(inv_sqrt_two);
|
|
Vector2 reciprocal_boundary = Vector2::Constant(RealScalar(1) / normal_min);
|
|
|
|
const Index block_size = 4096;
|
|
const RealScalar large = RealScalar(128) * sqrt(normal_min);
|
|
const RealScalar small = RealScalar(0.5) * sqrt(normal_min);
|
|
VectorX mixed = VectorX::Constant(block_size, small);
|
|
mixed(0) = large;
|
|
const RealScalar expected_norm = large * sqrt(RealScalar(1) + RealScalar(block_size - 1) / RealScalar(65536));
|
|
|
|
const RealScalar underflow_before = underflow_probe<RealScalar>();
|
|
bool flush_to_zero_supported = false;
|
|
{
|
|
Eigen::ScopedFlushToZero flush_to_zero;
|
|
flush_to_zero_supported = flush_to_zero.isSupported();
|
|
if (flush_to_zero_supported) VERIFY_IS_EQUAL(underflow_probe<RealScalar>(), RealScalar(0));
|
|
VERIFY_IS_APPROX(reciprocal_boundary.stableNormalized(), expected_normalized);
|
|
reciprocal_boundary.stableNormalize();
|
|
VERIFY_IS_APPROX(reciprocal_boundary, expected_normalized);
|
|
VERIFY_IS_APPROX(mixed.stableNorm(), expected_norm);
|
|
}
|
|
if (flush_to_zero_supported) VERIFY_IS_EQUAL(underflow_probe<RealScalar>(), underflow_before);
|
|
}
|
|
|
|
template <typename RealScalar>
|
|
void stable_norm_multiblock_fastmath() {
|
|
typedef Matrix<RealScalar, Dynamic, 1> VectorX;
|
|
using std::sqrt;
|
|
|
|
const Index block_size = 4096;
|
|
const RealScalar scale = sqrt((std::numeric_limits<RealScalar>::max)());
|
|
VectorX input = VectorX::Zero(2 * block_size);
|
|
input(0) = scale;
|
|
input.tail(block_size).setConstant(scale / RealScalar(128));
|
|
const RealScalar expected = scale * sqrt(RealScalar(1.25));
|
|
VERIFY_IS_APPROX(input.stableNorm(), expected);
|
|
}
|
|
|
|
EIGEN_DECLARE_TEST(stable_norm_fastmath) {
|
|
CALL_SUBTEST_1(stable_normalize_fastmath<float>());
|
|
CALL_SUBTEST_2(stable_normalize_fastmath<double>());
|
|
CALL_SUBTEST_1(stable_norm_multiblock_fastmath<float>());
|
|
CALL_SUBTEST_2(stable_norm_multiblock_fastmath<double>());
|
|
CALL_SUBTEST_1(stable_norm_ftz_boundaries<float>());
|
|
CALL_SUBTEST_2(stable_norm_ftz_boundaries<double>());
|
|
#if defined(EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC) && EIGEN_HAS_ARM64_FP16_SCALAR_ARITHMETIC
|
|
CALL_SUBTEST_1(scoped_flush_to_zero_fp16());
|
|
#endif
|
|
}
|