From ed789e176193da5e700c8e5014cc4ded391edbda Mon Sep 17 00:00:00 2001 From: Arne Juul Date: Fri, 22 Jan 2021 11:24:15 +0000 Subject: add BrainFloat16 "float with less precision" --- vespalib/CMakeLists.txt | 1 + .../src/tests/util/brain_float16/CMakeLists.txt | 9 ++ .../util/brain_float16/brain_float16_test.cpp | 81 ++++++++++++++++ vespalib/src/vespa/vespalib/cppunit/.gitignore | 3 - vespalib/src/vespa/vespalib/util/CMakeLists.txt | 1 + vespalib/src/vespa/vespalib/util/brain_float16.cpp | 3 + vespalib/src/vespa/vespalib/util/brain_float16.h | 104 +++++++++++++++++++++ 7 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 vespalib/src/tests/util/brain_float16/CMakeLists.txt create mode 100644 vespalib/src/tests/util/brain_float16/brain_float16_test.cpp delete mode 100644 vespalib/src/vespa/vespalib/cppunit/.gitignore create mode 100644 vespalib/src/vespa/vespalib/util/brain_float16.cpp create mode 100644 vespalib/src/vespa/vespalib/util/brain_float16.h diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt index 2db3c89dfb5..3fd9ee8e209 100644 --- a/vespalib/CMakeLists.txt +++ b/vespalib/CMakeLists.txt @@ -130,6 +130,7 @@ vespa_define_module( src/tests/tutorial/simple src/tests/tutorial/threads src/tests/typify + src/tests/util/brain_float16 src/tests/util/generationhandler src/tests/util/generationhandler_stress src/tests/util/md5 diff --git a/vespalib/src/tests/util/brain_float16/CMakeLists.txt b/vespalib/src/tests/util/brain_float16/CMakeLists.txt new file mode 100644 index 00000000000..e975abbf44a --- /dev/null +++ b/vespalib/src/tests/util/brain_float16/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(vespalib_brain_float16_test_app TEST + SOURCES + brain_float16_test.cpp + DEPENDS + vespalib + GTest::GTest +) +vespa_add_test(NAME vespalib_brain_float16_test_app COMMAND vespalib_brain_float16_test_app) diff --git a/vespalib/src/tests/util/brain_float16/brain_float16_test.cpp b/vespalib/src/tests/util/brain_float16/brain_float16_test.cpp new file mode 100644 index 00000000000..4b7b0ee7897 --- /dev/null +++ b/vespalib/src/tests/util/brain_float16/brain_float16_test.cpp @@ -0,0 +1,81 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include +#include +#include +#include + +using namespace vespalib; + +using Limits = std::numeric_limits; + +TEST(BrainFloat16Test, constants_check) { + EXPECT_EQ(0x1.0p-7, (1.0/128.0)); + + float n_min = Limits::min(); + float d_min = Limits::denorm_min(); + float eps = Limits::epsilon(); + float big = Limits::max(); + float low = Limits::lowest(); + + EXPECT_EQ(n_min, 0x1.0p-126); + EXPECT_EQ(d_min, 0x1.0p-133); + EXPECT_EQ(eps, 0x1.0p-7); + EXPECT_EQ(big, 0x1.FEp127); + EXPECT_EQ(low, -big); + + EXPECT_EQ(n_min, std::numeric_limits::min()); + EXPECT_EQ(d_min, n_min / 128.0); + EXPECT_GT(eps, std::numeric_limits::epsilon()); + + BrainFloat16 try_epsilon = 1.0f + eps; + EXPECT_GT(try_epsilon.to_float(), 1.0f); + BrainFloat16 try_half_epsilon = 1.0f + (0.5f * eps); + EXPECT_EQ(try_half_epsilon.to_float(), 1.0f); + + EXPECT_LT(big, std::numeric_limits::max()); + EXPECT_GT(low, std::numeric_limits::lowest()); + + printf("bfloat16 epsilon: %.10g (float has %.20g)\n", eps, std::numeric_limits::epsilon()); + printf("bfloat16 norm_min: %.20g (float has %.20g)\n", n_min, std::numeric_limits::min()); + printf("bfloat16 denorm_min: %.20g (float has %.20g)\n", d_min, std::numeric_limits::denorm_min()); + printf("bfloat16 max: %.20g (float has %.20g)\n", big, std::numeric_limits::max()); + printf("bfloat16 lowest: %.20g (float has %.20g)\n", low, std::numeric_limits::lowest()); +} + +TEST(BrainFloat16Test, traits_check) { + EXPECT_TRUE(std::is_trivially_constructible::value); + EXPECT_TRUE(std::is_trivially_move_constructible::value); + EXPECT_TRUE(std::is_trivially_default_constructible::value); + EXPECT_TRUE((std::is_trivially_assignable::value)); + EXPECT_TRUE(std::is_trivially_move_assignable::value); + EXPECT_TRUE(std::is_trivially_copy_assignable::value); + EXPECT_TRUE(std::is_trivially_copyable::value); + EXPECT_TRUE(std::is_trivially_destructible::value); + EXPECT_TRUE(std::is_trivial::value); + EXPECT_TRUE(std::is_swappable::value); + EXPECT_TRUE(std::has_unique_object_representations::value); +} + +TEST(BrainFloat16Test, check_special_values) { + float f_inf = std::numeric_limits::infinity(); + float f_neg = -f_inf; + float f_nan = std::numeric_limits::quiet_NaN(); + BrainFloat16 b_inf = f_inf; + BrainFloat16 b_neg = f_neg; + BrainFloat16 b_nan = f_nan; + double d_inf = b_inf; + double d_neg = b_neg; + double d_nan = b_nan; + EXPECT_EQ(d_inf, std::numeric_limits::infinity()); + EXPECT_EQ(d_neg, -std::numeric_limits::infinity()); + EXPECT_TRUE(std::isnan(d_nan)); + float f_from_b_inf = b_inf; + float f_from_b_neg = b_neg; + float f_from_b_nan = b_nan; + EXPECT_EQ(memcmp(&f_inf, &f_from_b_inf, sizeof(float)), 0); + EXPECT_EQ(memcmp(&f_neg, &f_from_b_neg, sizeof(float)), 0); + EXPECT_EQ(memcmp(&f_nan, &f_from_b_nan, sizeof(float)), 0); +} + +GTEST_MAIN_RUN_ALL_TESTS() diff --git a/vespalib/src/vespa/vespalib/cppunit/.gitignore b/vespalib/src/vespa/vespalib/cppunit/.gitignore deleted file mode 100644 index 583460ae288..00000000000 --- a/vespalib/src/vespa/vespalib/cppunit/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.So -.depend -Makefile diff --git a/vespalib/src/vespa/vespalib/util/CMakeLists.txt b/vespalib/src/vespa/vespalib/util/CMakeLists.txt index 64c27482e00..15878ebb68f 100644 --- a/vespalib/src/vespa/vespalib/util/CMakeLists.txt +++ b/vespalib/src/vespa/vespalib/util/CMakeLists.txt @@ -10,6 +10,7 @@ vespa_add_library(vespalib_vespalib_util OBJECT backtrace.cpp barrier.cpp benchmark_timer.cpp + brain_float16.cpp blockingthreadstackexecutor.cpp box.cpp child_process.cpp diff --git a/vespalib/src/vespa/vespalib/util/brain_float16.cpp b/vespalib/src/vespa/vespalib/util/brain_float16.cpp new file mode 100644 index 00000000000..fc856e04a25 --- /dev/null +++ b/vespalib/src/vespa/vespalib/util/brain_float16.cpp @@ -0,0 +1,3 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include "brain_float16.h" diff --git a/vespalib/src/vespa/vespalib/util/brain_float16.h b/vespalib/src/vespa/vespalib/util/brain_float16.h new file mode 100644 index 00000000000..190f6be8450 --- /dev/null +++ b/vespalib/src/vespa/vespalib/util/brain_float16.h @@ -0,0 +1,104 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#pragma once + +#include +#include +#include + +namespace vespalib { + +class BrainFloat16 { +private: + uint16_t _bits; + struct TwoU16 { + uint16_t u1; + uint16_t u2; + }; +public: + constexpr BrainFloat16(float value) noexcept : _bits(float_to_bits(value)) {} + BrainFloat16() noexcept = default; + ~BrainFloat16() noexcept = default; + constexpr BrainFloat16(const BrainFloat16 &other) noexcept = default; + constexpr BrainFloat16(BrainFloat16 &&other) noexcept = default; + constexpr BrainFloat16& operator=(const BrainFloat16 &other) noexcept = default; + constexpr BrainFloat16& operator=(BrainFloat16 &&other) noexcept = default; + + constexpr operator float () const noexcept { return bits_to_float(_bits); } + + constexpr float to_float() const noexcept { return bits_to_float(_bits); } + + constexpr void assign(float value) noexcept { _bits = float_to_bits(value); } + + static constexpr uint16_t float_to_bits(float value) noexcept { + TwoU16 both{0,0}; + static_assert(sizeof(TwoU16) == sizeof(float)); + memcpy(&both, &value, sizeof(float)); + if constexpr (std::endian::native == std::endian::big) { + return both.u1; + } else if constexpr (std::endian::native == std::endian::little) { + return both.u2; + } else { + return 0; + } + } + + static constexpr float bits_to_float(uint16_t bits) noexcept { + TwoU16 both{0,0}; + if constexpr (std::endian::native == std::endian::big) { + both.u1 = bits; + } else if constexpr (std::endian::native == std::endian::little) { + both.u2 = bits; + } else { + return 0.0; + } + float result = 0.0; + static_assert(sizeof(TwoU16) == sizeof(float)); + memcpy(&result, &both, sizeof(float)); + return result; + } +}; + +} + +namespace std { +template<> class numeric_limits { +public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = true; + static constexpr bool is_integer = false; + static constexpr bool is_exact = false; + static constexpr bool has_infinity = false; + static constexpr bool has_quiet_NaN = false; + static constexpr bool has_signaling_NaN = false; + static constexpr bool has_denorm = true; + static constexpr bool has_denorm_loss = false; + static constexpr bool is_iec559 = false; + static constexpr bool is_bounded = true; + static constexpr bool is_modulo = false; + static constexpr bool traps = false; + static constexpr bool tinyness_before = false; + + static constexpr std::float_round_style round_style = std::round_toward_zero; + static constexpr int radix = 2; + + static constexpr int digits = 8; + static constexpr int digits10 = 2; + static constexpr int max_digits10 = 4; + + static constexpr int min_exponent = -125; + static constexpr int min_exponent10 = -2; + + static constexpr int max_exponent = 128; + static constexpr int max_exponent10 = 38; + + static constexpr vespalib::BrainFloat16 denorm_min() noexcept { return 0x1.0p-133; } + static constexpr vespalib::BrainFloat16 epsilon() noexcept { return 0x1.0p-7; } + static constexpr vespalib::BrainFloat16 lowest() noexcept { return -0x1.FEp127; } + static constexpr vespalib::BrainFloat16 max() noexcept { return 0x1.FEp127; } + static constexpr vespalib::BrainFloat16 min() noexcept { return 0x1.0p-126; } + static constexpr vespalib::BrainFloat16 round_error() noexcept { return 1.0; } + +}; + +} -- cgit v1.2.3