Add numeric_limits for float8 types (#22228)

Add std::numeric_limits for float8 data types to provide a consistent
way to access limits of those types.

Reference:
* https://onnx.ai/onnx/technical/float8.html
This commit is contained in:
Tianlei Wu 2024-09-26 14:42:36 -07:00 committed by GitHub
parent 1942e40e05
commit 2deab75d39
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 325 additions and 2 deletions

View file

@ -102,6 +102,10 @@ struct Float8E4M3FN {
#endif
}
inline ORT_HOST_DEVICE bool IsNaN() const {
return (val & 0b01111111) == 0b01111111;
}
inline ORT_HOST_DEVICE float ToFloat() const {
#if defined(CUDA_VERSION) && CUDA_VERSION >= 11080
return __half2float(__nv_cvt_fp8_to_halfraw(val, __NV_E4M3));
@ -266,6 +270,10 @@ struct Float8E4M3FNUZ {
}
}
inline ORT_HOST_DEVICE bool IsNaN() const {
return val == 0b10000000;
}
inline ORT_HOST_DEVICE float ToFloat() const {
// This type does not exist on CUDA.
uint32_t res;
@ -416,6 +424,16 @@ struct Float8E5M2 {
#endif
}
inline ORT_HOST_DEVICE bool IsNaN() const {
// 7D, 7E, 7F are positive NaNs; FD, FE, FF are negative NaNs
return (val & 0b01111111) > 0b01111100;
}
inline ORT_HOST_DEVICE bool IsInfinity() const {
// 7C and FC are infinity
return (val & 0b01111111) == 0b01111100;
}
inline ORT_HOST_DEVICE float ToFloat() const {
#if defined(CUDA_VERSION) && CUDA_VERSION >= 11080
return __half2float(__nv_cvt_fp8_to_halfraw(val, __NV_E5M2));
@ -575,6 +593,10 @@ struct Float8E5M2FNUZ {
}
}
inline ORT_HOST_DEVICE bool IsNaN() const {
return val == 0b10000000;
}
inline ORT_HOST_DEVICE float ToFloat() const {
// This type does not exist on CUDA.
uint32_t res;
@ -648,4 +670,251 @@ inline void FloatToFloat8E5M2FNUZ(const float* flt, Float8E5M2FNUZ* blf, size_t
} // namespace onnxruntime
namespace std {
template <>
class numeric_limits<onnxruntime::Float8E4M3FN> {
public:
static constexpr onnxruntime::Float8E4M3FN lowest() {
return onnxruntime::Float8E4M3FN(0xFE, onnxruntime::Float8E4M3FN::FromBits()); // -448
}
static constexpr onnxruntime::Float8E4M3FN max() {
return onnxruntime::Float8E4M3FN(0x7E, onnxruntime::Float8E4M3FN::FromBits()); // 448
}
static constexpr onnxruntime::Float8E4M3FN min() {
return onnxruntime::Float8E4M3FN(0x08, onnxruntime::Float8E4M3FN::FromBits()); // 2^-6 = 0.015625
}
static constexpr onnxruntime::Float8E4M3FN denorm_min() {
return onnxruntime::Float8E4M3FN(0x01, onnxruntime::Float8E4M3FN::FromBits()); // 2^-9 = 0.001953125
}
static constexpr onnxruntime::Float8E4M3FN epsilon() {
return onnxruntime::Float8E4M3FN(0x20, onnxruntime::Float8E4M3FN::FromBits());
}
static constexpr onnxruntime::Float8E4M3FN round_error() {
return onnxruntime::Float8E4M3FN(0x30, onnxruntime::Float8E4M3FN::FromBits());
}
static constexpr onnxruntime::Float8E4M3FN infinity() {
// no infinity, returns quiet NaN instead
return quiet_NaN();
}
static constexpr onnxruntime::Float8E4M3FN quiet_NaN() {
return onnxruntime::Float8E4M3FN(0x7F, onnxruntime::Float8E4M3FN::FromBits());
}
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 = true;
static constexpr bool has_signaling_NaN = false;
static constexpr auto has_denorm = true;
static constexpr auto has_denorm_loss = true;
static constexpr auto round_style = round_to_nearest;
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = true;
static constexpr bool is_modulo = false;
static constexpr int digits = 4;
static constexpr int digits10 = 0;
static constexpr int max_digits10 = 3;
static constexpr int radix = 2;
static constexpr int min_exponent = -5;
static constexpr int min_exponent10 = -1;
static constexpr int max_exponent = 8;
static constexpr int max_exponent10 = 2;
static constexpr auto traps = false;
static constexpr auto tinyness_before = false;
};
template <>
class numeric_limits<onnxruntime::Float8E5M2> {
public:
static constexpr onnxruntime::Float8E5M2 lowest() {
return onnxruntime::Float8E5M2(0xFB, onnxruntime::Float8E5M2::FromBits()); // -57344.0
}
static constexpr onnxruntime::Float8E5M2 max() {
return onnxruntime::Float8E5M2(0x7B, onnxruntime::Float8E5M2::FromBits()); // 57344.0
}
static constexpr onnxruntime::Float8E5M2 min() {
return onnxruntime::Float8E5M2(0x4, onnxruntime::Float8E5M2::FromBits()); // 2^-14 = 0.00006103515
}
static constexpr onnxruntime::Float8E5M2 denorm_min() {
return onnxruntime::Float8E5M2(0x01, onnxruntime::Float8E5M2::FromBits()); // 2^-16 = 0.00001525878
}
static constexpr onnxruntime::Float8E5M2 epsilon() {
return onnxruntime::Float8E5M2(0x34, onnxruntime::Float8E5M2::FromBits());
}
static constexpr onnxruntime::Float8E5M2 round_error() {
return onnxruntime::Float8E5M2(0x38, onnxruntime::Float8E5M2::FromBits());
}
static constexpr onnxruntime::Float8E5M2 infinity() {
return onnxruntime::Float8E5M2(0x7C, onnxruntime::Float8E5M2::FromBits());
}
static constexpr onnxruntime::Float8E5M2 quiet_NaN() {
return onnxruntime::Float8E5M2(0x7F, onnxruntime::Float8E5M2::FromBits());
}
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 = true;
static constexpr bool has_quiet_NaN = true;
static constexpr bool has_signaling_NaN = false;
static constexpr auto has_denorm = true;
static constexpr auto has_denorm_loss = true;
static constexpr auto round_style = round_to_nearest;
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = true;
static constexpr bool is_modulo = false;
static constexpr int digits = 3;
static constexpr int digits10 = 0;
static constexpr int max_digits10 = 2;
static constexpr int radix = 2;
static constexpr int min_exponent = -13;
static constexpr int min_exponent10 = -4;
static constexpr int max_exponent = 16;
static constexpr int max_exponent10 = 4;
static constexpr auto traps = false;
static constexpr auto tinyness_before = false;
};
template <>
class numeric_limits<onnxruntime::Float8E4M3FNUZ> {
public:
static constexpr onnxruntime::Float8E4M3FNUZ lowest() {
return onnxruntime::Float8E4M3FNUZ(0xFF, onnxruntime::Float8E4M3FNUZ::FromBits()); // -240.0
}
static constexpr onnxruntime::Float8E4M3FNUZ max() {
return onnxruntime::Float8E4M3FNUZ(0x7F, onnxruntime::Float8E4M3FNUZ::FromBits()); // 240.0
}
static constexpr onnxruntime::Float8E4M3FNUZ min() {
return onnxruntime::Float8E4M3FNUZ(0x08, onnxruntime::Float8E4M3FNUZ::FromBits()); // 2^-7 = 0.0078125
}
static constexpr onnxruntime::Float8E4M3FNUZ denorm_min() {
return onnxruntime::Float8E4M3FNUZ(0x01, onnxruntime::Float8E4M3FNUZ::FromBits()); // 2^-10 = 0.0009765625
}
static constexpr onnxruntime::Float8E4M3FNUZ epsilon() {
return onnxruntime::Float8E4M3FNUZ(0x28, onnxruntime::Float8E4M3FNUZ::FromBits());
}
static constexpr onnxruntime::Float8E4M3FNUZ round_error() {
return onnxruntime::Float8E4M3FNUZ(0x38, onnxruntime::Float8E4M3FNUZ::FromBits());
}
static constexpr onnxruntime::Float8E4M3FNUZ infinity() {
// no infinity, returns quiet NaN instead
return quiet_NaN();
}
static constexpr onnxruntime::Float8E4M3FNUZ quiet_NaN() {
return onnxruntime::Float8E4M3FNUZ(0x80, onnxruntime::Float8E4M3FNUZ::FromBits());
}
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 = true;
static constexpr bool has_signaling_NaN = false;
static constexpr auto has_denorm = true;
static constexpr auto has_denorm_loss = true;
static constexpr auto round_style = round_to_nearest;
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = true;
static constexpr bool is_modulo = false;
static constexpr int digits = 4;
static constexpr int digits10 = 0;
static constexpr int max_digits10 = 3;
static constexpr int radix = 2;
static constexpr int min_exponent = -6;
static constexpr int min_exponent10 = -1;
static constexpr int max_exponent = 8;
static constexpr int max_exponent10 = 2;
static constexpr auto traps = false;
static constexpr auto tinyness_before = false;
};
template <>
class numeric_limits<onnxruntime::Float8E5M2FNUZ> {
public:
static constexpr onnxruntime::Float8E5M2FNUZ lowest() {
return onnxruntime::Float8E5M2FNUZ(0xFF, onnxruntime::Float8E5M2FNUZ::FromBits()); // -57344.0
}
static constexpr onnxruntime::Float8E5M2FNUZ max() {
return onnxruntime::Float8E5M2FNUZ(0x7F, onnxruntime::Float8E5M2FNUZ::FromBits()); // 57344.0
}
static constexpr onnxruntime::Float8E5M2FNUZ min() {
return onnxruntime::Float8E5M2FNUZ(0x04, onnxruntime::Float8E5M2FNUZ::FromBits()); // 2^-15 = 0.00003051757
}
static constexpr onnxruntime::Float8E5M2FNUZ denorm_min() {
return onnxruntime::Float8E5M2FNUZ(0x01, onnxruntime::Float8E5M2FNUZ::FromBits()); // 2^-17 = 0.00000762939
}
static constexpr onnxruntime::Float8E5M2FNUZ epsilon() {
return onnxruntime::Float8E5M2FNUZ(0x34, onnxruntime::Float8E5M2FNUZ::FromBits());
}
static constexpr onnxruntime::Float8E5M2FNUZ round_error() {
return onnxruntime::Float8E5M2FNUZ(0x38, onnxruntime::Float8E5M2FNUZ::FromBits());
}
static constexpr onnxruntime::Float8E5M2FNUZ infinity() {
// no infinity, returns quiet NaN instead
return quiet_NaN();
}
static constexpr onnxruntime::Float8E5M2FNUZ quiet_NaN() {
return onnxruntime::Float8E5M2FNUZ(0x80, onnxruntime::Float8E5M2FNUZ::FromBits());
}
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 = true;
static constexpr bool has_signaling_NaN = false;
static constexpr auto has_denorm = true;
static constexpr auto has_denorm_loss = true;
static constexpr auto round_style = round_to_nearest;
static constexpr bool is_iec559 = false;
static constexpr bool is_bounded = true;
static constexpr bool is_modulo = false;
static constexpr int digits = 3;
static constexpr int digits10 = 0;
static constexpr int max_digits10 = 2;
static constexpr int radix = 2;
static constexpr int min_exponent = -14;
static constexpr int min_exponent10 = -4;
static constexpr int max_exponent = 16;
static constexpr int max_exponent10 = 4;
static constexpr auto traps = false;
static constexpr auto tinyness_before = false;
};
} // namespace std
#endif // DISABLE_FLOAT8_TYPES

View file

@ -62,8 +62,8 @@ Status Clip_6<T>::ComputeInternal(OpKernelContext* ctx) const {
template <typename T>
struct Clip::ComputeImpl {
void operator()(cudaStream_t stream, const Tensor* X, const Tensor* min, const Tensor* max, Tensor* Y) const {
auto min_default = std::numeric_limits<T>::lowest();
auto max_default = std::numeric_limits<T>::max();
constexpr T min_default = std::numeric_limits<T>::lowest();
constexpr T max_default = std::numeric_limits<T>::max();
const T* min_data = nullptr;
const T* max_data = nullptr;

View file

@ -8,6 +8,7 @@
#include "core/framework/data_types.h"
#include "core/framework/data_types_internal.h"
#include "core/framework/float16.h"
#include "core/framework/float8.h"
#include "core/graph/onnx_protobuf.h"
#include "gtest/gtest.h"
@ -754,6 +755,59 @@ TEST_F(DataTypeTest, BFloat16NormalSubnormal) {
EXPECT_FALSE(std::isnormal(float_from_largest_subnormal));
}
#if !defined(DISABLE_FLOAT8_TYPES)
TEST_F(DataTypeTest, Float8TestNAN) {
const auto fp8_e4m3fn_nan = std::numeric_limits<onnxruntime::Float8E4M3FN>::quiet_NaN();
EXPECT_TRUE(fp8_e4m3fn_nan.IsNaN());
EXPECT_TRUE(std::isnan(fp8_e4m3fn_nan.ToFloat()));
const auto fp8_e5m2_nan = std::numeric_limits<onnxruntime::Float8E5M2>::quiet_NaN();
EXPECT_TRUE(fp8_e5m2_nan.IsNaN());
EXPECT_TRUE(std::isnan(fp8_e5m2_nan.ToFloat()));
const auto fp8_e4m3fnuz_nan = std::numeric_limits<onnxruntime::Float8E4M3FNUZ>::quiet_NaN();
EXPECT_TRUE(fp8_e4m3fnuz_nan.IsNaN());
EXPECT_TRUE(std::isnan(fp8_e4m3fnuz_nan.ToFloat()));
const auto fp8_e5m2fnuz_nan = std::numeric_limits<onnxruntime::Float8E5M2FNUZ>::quiet_NaN();
EXPECT_TRUE(fp8_e5m2fnuz_nan.IsNaN());
EXPECT_TRUE(std::isnan(fp8_e5m2fnuz_nan.ToFloat()));
}
TEST_F(DataTypeTest, Float8TestInf) {
const auto fp8_e5m2_inf = std::numeric_limits<onnxruntime::Float8E5M2>::infinity();
EXPECT_TRUE(fp8_e5m2_inf.IsInfinity());
EXPECT_TRUE(std::isinf(fp8_e5m2_inf.ToFloat()));
EXPECT_FALSE(std::numeric_limits<onnxruntime::Float8E4M3FN>::has_infinity);
EXPECT_FALSE(std::numeric_limits<onnxruntime::Float8E4M3FNUZ>::has_infinity);
EXPECT_FALSE(std::numeric_limits<onnxruntime::Float8E5M2FNUZ>::has_infinity);
}
TEST_F(DataTypeTest, Float8TestLimits) {
constexpr float abs_tolerance = 1e-6f;
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FN>::min().ToFloat(), 0.015625f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FN>::max().ToFloat(), 448.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FN>::lowest().ToFloat(), -448.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FN>::denorm_min().ToFloat(), 0.001953125f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2>::min().ToFloat(), 0.00006103515f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2>::max().ToFloat(), 57344.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2>::lowest().ToFloat(), -57344.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2>::denorm_min().ToFloat(), 0.00001525878f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FNUZ>::min().ToFloat(), 0.0078125f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FNUZ>::max().ToFloat(), 240.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FNUZ>::lowest().ToFloat(), -240.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E4M3FNUZ>::denorm_min().ToFloat(), 0.0009765625f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2FNUZ>::min().ToFloat(), 0.00003051757f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2FNUZ>::max().ToFloat(), 57344.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2FNUZ>::lowest().ToFloat(), -57344.0f, abs_tolerance);
EXPECT_NEAR(std::numeric_limits<onnxruntime::Float8E5M2FNUZ>::denorm_min().ToFloat(), 0.00000762939f, abs_tolerance);
}
#endif
TEST_F(DataTypeTest, DataUtilsTest) {
using namespace ONNX_NAMESPACE::Utils;
// Test simple seq