[core] Add f4e2m1 element type and float4_e2m1 type (#24863)

### Details:
 - Add new element into OpenVINO `f4e2m1`
 - Add new type `float4_e2m1`

### Tickets:
 - [CVS-141565](https://jira.devtools.intel.com/browse/CVS-141565)

---------

Co-authored-by: Michal Lukaszewski <michal.lukaszewski@intel.com>
This commit is contained in:
Pawel Raasz 2024-06-10 09:15:45 +02:00 committed by GitHub
parent 1144a66a9b
commit bb75829a88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 463 additions and 13 deletions

View File

@ -1,6 +1,6 @@
# custom OpenVINO values
CppMethod: '^(operator\W+|[a-z_\d]+|signaling_NaN|quiet_NaN|OPENVINO_OP)$'
ClassName: '^([A-Z][\w]+|b?float16|float8_e4m3|float8_e5m2|numeric_limits|ngraph_error|stopwatch|unsupported_op)$'
ClassName: '^([A-Z][\w]+|b?float16|float8_e4m3|float8_e5m2|float4_e2m1|numeric_limits|ngraph_error|stopwatch|unsupported_op)$'
StructName: '^([A-Z][\w]+|element_type_traits|hash|oi_pair|stat)$'
FunctionName: '^(operator\W+|[a-z_\d]+)|PrintTo$'
Namespace: '^([a-z\d_]*|InferenceEngine)$'
@ -18,7 +18,7 @@ VariableReference: '^\w+$'
EnumName: '^[A-Z][\w]+$'
# excepts element_type
EnumConstantName: '^([A-Z\d_]+|undefined|dynamic|boolean|bf16|f16|f32|f64|i4|i8|i16|i32|i64|u1|u2|u3|u4|u6|u8|u16|u32|u64|nf4|f8e4m3|f8e5m2|string|asymmetric|align_corners|round_prefer_floor|round_prefer_ceil|floor|ceil|simple|nearest|linear|linear_onnx|cubic|area|scales|sizes|half_pixel|tf_half_pixel_for_nn|pytorch_half_pixel|asymetric)$'
EnumConstantName: '^([A-Z\d_]+|undefined|dynamic|boolean|bf16|f16|f32|f64|i4|i8|i16|i32|i64|u1|u2|u3|u4|u6|u8|u16|u32|u64|nf4|f8e4m3|f8e5m2|f4e2m1|string|asymmetric|align_corners|round_prefer_floor|round_prefer_ceil|floor|ceil|simple|nearest|linear|linear_onnx|cubic|area|scales|sizes|half_pixel|tf_half_pixel_for_nn|pytorch_half_pixel|asymetric)$'
# TODO: align
UsingDeclaration: '^.*$'
TypedefName: '^.*$'

View File

@ -205,6 +205,8 @@ typedef enum {
STRING, //!< string element type
F4E2M1, //!< f4e2m1 element type
} ov_element_type_e;
```

View File

@ -194,6 +194,7 @@ typedef enum {
F8E4M3, //!< f8e4m3 element type
F8E5M3, //!< f8e5m2 element type
STRING, //!< string element type
F4E2M1, //!< f4e2m1 element type
} ov_element_type_e;
/**

View File

@ -30,7 +30,8 @@ const std::map<ov_element_type_e, ov::element::Type> element_type_map = {
{ov_element_type_e::NF4, ov::element::nf4},
{ov_element_type_e::F8E4M3, ov::element::f8e4m3},
{ov_element_type_e::F8E5M3, ov::element::f8e5m2},
{ov_element_type_e::STRING, ov::element::string}};
{ov_element_type_e::STRING, ov::element::string},
{ov_element_type_e::F4E2M1, ov::element::f4e2m1}};
ov_element_type_e find_ov_element_type_e(ov::element::Type type) {
for (auto iter = element_type_map.begin(); iter != element_type_map.end(); iter++) {

View File

@ -29,6 +29,7 @@ const std::map<ov::element::Type, py::dtype>& ov_type_to_dtype() {
{ov::element::u4, py::dtype("uint8")}, {ov::element::nf4, py::dtype("uint8")},
{ov::element::i4, py::dtype("int8")}, {ov::element::f8e4m3, py::dtype("uint8")},
{ov::element::f8e5m2, py::dtype("uint8")}, {ov::element::string, py::dtype("bytes_")},
{ov::element::f4e2m1, py::dtype("uint8")},
};
return ov_type_to_dtype_mapping;
}

View File

@ -53,6 +53,7 @@ void regclass_graph_Type(py::module m) {
type.attr("f8e4m3") = ov::element::f8e4m3;
type.attr("f8e5m2") = ov::element::f8e5m2;
type.attr("string") = ov::element::string;
type.attr("f4e2m1") = ov::element::f4e2m1;
type.def("__hash__", &ov::element::Type::hash);
type.def("__repr__", [](const ov::element::Type& self) {

View File

@ -20,6 +20,7 @@
#include "openvino/core/rtti.hpp"
#include "openvino/core/type/bfloat16.hpp"
#include "openvino/core/type/float16.hpp"
#include "openvino/core/type/float4_e2m1.hpp"
#include "openvino/core/type/float8_e4m3.hpp"
#include "openvino/core/type/float8_e5m2.hpp"
@ -59,7 +60,8 @@ enum class Type_t {
nf4, //!< nf4 element type
f8e4m3, //!< f8e4m3 element type
f8e5m2, //!< f8e5m2 element type
string //!< string element type
string, //!< string element type
f4e2m1 //!< f4e2m1 element type
};
/// \brief Base class to define element type
@ -209,6 +211,9 @@ constexpr Type f8e5m2(Type_t::f8e5m2);
/// \brief string element type
/// \ingroup ov_element_cpp_api
constexpr Type string(Type_t::string);
/// \brief f4e2m1 element type
/// \ingroup ov_element_cpp_api
constexpr Type f4e2m1(Type_t::f4e2m1);
template <typename T>
Type from() {
@ -248,6 +253,8 @@ template <>
OPENVINO_API Type from<ov::float8_e5m2>();
template <>
OPENVINO_API Type from<std::string>();
template <>
OPENVINO_API Type from<ov::float4_e2m1>();
OPENVINO_API Type fundamental_type_for(const Type& type);

View File

@ -127,4 +127,9 @@ template <>
struct element_type_traits<element::Type_t::string> {
using value_type = std::string;
};
template <>
struct element_type_traits<element::Type_t::f4e2m1> {
using value_type = ov::float4_e2m1;
};
} // namespace ov

View File

@ -0,0 +1,151 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "openvino/core/core_visibility.hpp"
namespace ov {
/**
* @brief Class to represent the f4e2m1 type.
*/
class OPENVINO_API float4_e2m1 {
public:
float4_e2m1() = default;
float4_e2m1(float value);
template <typename I>
explicit float4_e2m1(I value) : float4_e2m1(static_cast<float>(value)) {}
template <typename T>
bool operator==(const T& other) const;
template <typename T>
bool operator!=(const T& other) const {
return !(*this == other);
}
template <typename T>
bool operator<(const T& other) const;
template <typename T>
bool operator<=(const T& other) const;
template <typename T>
bool operator>(const T& other) const;
template <typename T>
bool operator>=(const T& other) const;
template <typename T>
float4_e2m1 operator+(const T& other) const;
template <typename T>
float4_e2m1 operator+=(const T& other);
template <typename T>
float4_e2m1 operator-(const T& other) const;
template <typename T>
float4_e2m1 operator-=(const T& other);
template <typename T>
float4_e2m1 operator*(const T& other) const;
template <typename T>
float4_e2m1 operator*=(const T& other);
template <typename T>
float4_e2m1 operator/(const T& other) const;
template <typename T>
float4_e2m1 operator/=(const T& other);
operator float() const;
static constexpr float4_e2m1 from_bits(uint8_t bits) {
return float4_e2m1(bits, true);
}
uint8_t to_bits() const;
friend std::ostream& operator<<(std::ostream& out, const float4_e2m1& obj) {
out << static_cast<float>(obj);
return out;
}
private:
constexpr float4_e2m1(uint8_t x, bool) : m_value{x} {}
uint8_t m_value;
};
#if defined(_MSC_VER)
# pragma warning(push)
# pragma warning(disable : 4756)
#endif
template <typename T>
bool float4_e2m1::operator==(const T& other) const {
return (static_cast<float>(*this) == static_cast<float>(other));
}
template <typename T>
bool float4_e2m1::operator<(const T& other) const {
return (static_cast<float>(*this) < static_cast<float>(other));
}
template <typename T>
bool float4_e2m1::operator<=(const T& other) const {
return (static_cast<float>(*this) <= static_cast<float>(other));
}
template <typename T>
bool float4_e2m1::operator>(const T& other) const {
return (static_cast<float>(*this) > static_cast<float>(other));
}
template <typename T>
bool float4_e2m1::operator>=(const T& other) const {
return (static_cast<float>(*this) >= static_cast<float>(other));
}
template <typename T>
float4_e2m1 float4_e2m1::operator+(const T& other) const {
return {static_cast<float>(*this) + static_cast<float>(other)};
}
template <typename T>
float4_e2m1 float4_e2m1::operator+=(const T& other) {
return *this = *this + other;
}
template <typename T>
float4_e2m1 float4_e2m1::operator-(const T& other) const {
return {static_cast<float>(*this) - static_cast<float>(other)};
}
template <typename T>
float4_e2m1 float4_e2m1::operator-=(const T& other) {
return *this = *this - other;
}
template <typename T>
float4_e2m1 float4_e2m1::operator*(const T& other) const {
return {static_cast<float>(*this) * static_cast<float>(other)};
}
template <typename T>
float4_e2m1 float4_e2m1::operator*=(const T& other) {
return *this = *this * other;
}
template <typename T>
float4_e2m1 float4_e2m1::operator/(const T& other) const {
return {static_cast<float>(*this) / static_cast<float>(other)};
}
template <typename T>
float4_e2m1 float4_e2m1::operator/=(const T& other) {
return *this = *this / other;
}
#if defined(_MSC_VER)
# pragma warning(pop)
#endif
} // namespace ov

View File

@ -156,6 +156,7 @@ public:
case Type_t::string:
fill_data<Type_t::string>(value);
break;
case Type_t::f4e2m1:
case Type_t::undefined:
case Type_t::dynamic:
OPENVINO_THROW("unsupported type");
@ -707,6 +708,7 @@ private:
case Type_t::string:
write_buffer<Type_t::string>(source);
break;
case Type_t::f4e2m1:
case Type_t::undefined:
case Type_t::dynamic:
OPENVINO_THROW("unsupported type");

View File

@ -767,6 +767,8 @@ std::string get_precision_name(const ov::element::Type& elem_type) {
return "F8E5M2";
case ::ov::element::Type_t::string:
return "STRING";
case ::ov::element::Type_t::f4e2m1:
return "F4E2M1";
default:
OPENVINO_THROW("Unsupported precision: ", elem_type);
}

View File

@ -457,6 +457,7 @@ static std::string get_value(const std::shared_ptr<ov::op::v0::Constant>& consta
case ov::element::Type_t::i4:
case ov::element::Type_t::f8e4m3:
case ov::element::Type_t::f8e5m2:
case ov::element::Type_t::f4e2m1:
ss << constant->get_output_element_type(0).get_type_name() << " value";
break;
case ov::element::Type_t::bf16:

View File

@ -83,6 +83,8 @@ inline TypeInfo get_type_info(ov::element::Type_t type) {
return {8, true, true, true, "f8e5m2", "f8e5m2"};
case ov::element::Type_t::string:
return {8 * sizeof(std::string), false, false, false, "string", "string"};
case ov::element::Type_t::f4e2m1:
return {4, true, true, true, "f4e2m1", "f4e2m1"};
default:
OPENVINO_THROW("ov::element::Type_t not supported: ", type);
}
@ -139,6 +141,8 @@ ov::element::Type type_from_string(const std::string& type) {
return ::ov::element::Type(::ov::element::Type_t::f8e4m3);
} else if (type == "f8e5m2" || type == "F8E5M2") {
return ::ov::element::Type(::ov::element::Type_t::f8e5m2);
} else if (type == "f4e2m1" || type == "F4E2M1") {
return ::ov::element::Type(::ov::element::Type_t::f4e2m1);
} else {
OPENVINO_THROW("Incorrect type: ", type);
}
@ -151,7 +155,7 @@ std::vector<const ov::element::Type*> ov::element::Type::get_known_types() {
&ov::element::f64, &ov::element::i4, &ov::element::i8, &ov::element::i16, &ov::element::i32,
&ov::element::i64, &ov::element::u1, &ov::element::u2, &ov::element::u3, &ov::element::u4,
&ov::element::u6, &ov::element::u8, &ov::element::u16, &ov::element::u32, &ov::element::u64,
&ov::element::nf4, &ov::element::f8e4m3, &ov::element::f8e5m2, &ov::element::string};
&ov::element::nf4, &ov::element::f8e4m3, &ov::element::f8e5m2, &ov::element::string, &ov::element::f4e2m1};
return rc;
}
@ -187,6 +191,7 @@ ov::element::Type::Type(size_t bitwidth,
{ov::element::Type_t::f8e4m3, {8, true, true, true, "f8e4m3", "f8e4m3"}},
{ov::element::Type_t::f8e5m2, {8, true, true, true, "f8e5m2", "f8e5m2"}},
{ov::element::Type_t::string, {8 * sizeof(std::string), false, false, false, "string", "string"}},
{ov::element::Type_t::f8e5m2, {4, true, true, true, "f4e2m1", "f4e2m1"}},
};
for (const auto& t : elements_map) {
const TypeInfo& info = t.second;
@ -290,6 +295,10 @@ template <>
Type from<std::string>() {
return Type_t::string;
}
template <>
Type from<ov::float4_e2m1>() {
return Type_t::f4e2m1;
}
Type fundamental_type_for(const Type& type) {
switch (type) {
@ -339,6 +348,8 @@ Type fundamental_type_for(const Type& type) {
return from<element_type_traits<Type_t::nf4>::value_type>();
case Type_t::string:
return from<element_type_traits<Type_t::string>::value_type>();
case Type_t::f4e2m1:
return from<element_type_traits<Type_t::f4e2m1>::value_type>();
default:
OPENVINO_THROW("Unsupported Data type: ", type);
}
@ -359,8 +370,7 @@ std::istream& ov::element::operator>>(std::istream& in, ov::element::Type& obj)
{"U16", ov::element::u16}, {"U32", ov::element::u32}, {"U64", ov::element::u64},
{"FP32", ov::element::f32}, {"FP64", ov::element::f64}, {"FP16", ov::element::f16},
{"BIN", ov::element::u1}, {"NF4", ov::element::nf4}, {"F8E4M3", ov::element::f8e4m3},
{"F8E5M2", ov::element::f8e5m2}, {"STRING", ov::element::string},
};
{"F8E5M2", ov::element::f8e5m2}, {"STRING", ov::element::string}, {"F4E2M1", ov::element::f4e2m1}};
std::string str;
in >> str;
auto it_legacy = legacy.find(str);
@ -448,6 +458,7 @@ inline size_t compiler_byte_size(ov::element::Type_t et) {
ET_CASE(f8e4m3);
ET_CASE(f8e5m2);
ET_CASE(string);
ET_CASE(f4e2m1);
#undef ET_CASE
case ov::element::Type_t::undefined:
return 0;
@ -486,7 +497,8 @@ OPENVINO_API EnumNames<element::Type_t>& EnumNames<element::Type_t>::get() {
{"nf4", element::Type_t::nf4},
{"f8e4m3", element::Type_t::f8e4m3},
{"f8e5m2", element::Type_t::f8e5m2},
{"string", element::Type_t::string}});
{"string", element::Type_t::string},
{"f4e2m1", element::Type_t::f4e2m1}});
return enum_names;
}

View File

@ -0,0 +1,120 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "openvino/core/type/float4_e2m1.hpp"
#include <cmath>
#include <limits>
#include "openvino/reference/fake_convert.hpp"
namespace ov {
static_assert(sizeof(float4_e2m1) == 1, "type f4e2m1 must be exactly 1 byte");
static_assert(std::is_trivially_constructible<float4_e2m1, float4_e2m1>::value, "should be trivially constructible");
static_assert(std::is_trivially_copyable<float4_e2m1>::value, "must be trivially copyable");
static_assert(std::is_trivially_destructible<float4_e2m1>::value, "must be trivially destructible");
// clang-format off
static constexpr std::array<float, 16> f4e2m1_to_f32_lut{
0.0f, 0.5f,
1.0f, 1.5f,
2.0f, 3.0f,
4.0f, 6.0f,
-0.0f, -0.5f,
-1.0f, -1.5f,
-2.0f, -3.0f,
-4.0f, -6.0f};
// clang-format on
namespace {
constexpr uint32_t three_bytes_shift = 24;
union f32_t {
float value;
uint32_t bits;
};
constexpr uint8_t f4e2m1_e_size = 2; // f4e2m1 exponent bit size
constexpr uint8_t f4e2m1_e_mask = 0x06; // f4e2m1 exponent bit mask
constexpr uint8_t f4e2m1_e_bias = 1; // f4e2m1 exponent bias
constexpr uint8_t f4e2m1_e_max = 0x03; // f4e2m1 exponent max value
constexpr uint8_t f4e2m1_m_size = 1; // f4e2m1 mantissa bits size
constexpr uint8_t f4e2m1_m_mask = 0x01; // f4e2m1 mantissa bit mask
uint8_t f32_to_f4e2m1_bits(const float value) {
constexpr uint32_t f32_s_mask = 0x80000000; // f32 sign bit mask
constexpr uint32_t f32_e_mask = 0x7F800000; // f32 exponent bits mask
constexpr uint32_t f32_e_bias = 127; // f32 exponent bias
constexpr uint32_t f32_e_size = 8; // f32 exponent bits size
constexpr uint32_t f32_m_mask = 0x007fffff; // f32 mantissa bits mask
constexpr uint32_t f32_m_size = 23; // f32 mantissa bits size
constexpr uint32_t f_e_mask = f4e2m1_e_mask << three_bytes_shift; // f4 exponent bits mask (on u32)
constexpr uint32_t f_m_mask = f4e2m1_m_mask << three_bytes_shift; // f4 mantissa bits mask (on u32)
constexpr uint32_t f_m_hidden_one_mask = 0x02000000; // f4 mantissa hidden one bits mask (on u32)
constexpr uint32_t round_half = 0x01ffffff; // value for half to even round for f4
constexpr uint32_t round_norm = 0x07ffffff; // value for normal round for f4
constexpr uint32_t round_even = 0x00800000; // value for half to even round for f4
constexpr uint32_t round_odd = 0x01800000; // value for an non-half to even round for f4
const auto input = f32_t{value};
auto f4_bits = static_cast<uint8_t>((input.bits & f32_s_mask) >> (three_bytes_shift + 4U));
uint32_t f32_e_field = input.bits & f32_e_mask;
if (f32_e_field == f32_e_mask) {
f4_bits |= (f4e2m1_e_mask | f4e2m1_m_mask);
} else if (f32_e_field != 0) {
int32_t target_f_biased_exp = (f32_e_field >> f32_m_size) - (f32_e_bias - f4e2m1_e_bias);
uint32_t fractional = (input.bits & f32_m_mask) << (f32_e_size - f4e2m1_e_size - 4U);
// for normalized values round apply rounding change target fractional and biased exponent
if ((fractional & round_half) == round_odd || (fractional & round_norm) != 0) {
fractional += round_even;
if (0 != (fractional & f_e_mask)) {
fractional &= f_e_mask;
++target_f_biased_exp;
}
}
fractional &= f_m_mask;
// set exponent and mantissa on target bits
if (target_f_biased_exp > f4e2m1_e_max) {
// Use NAN as this type has no infinity
f4_bits |= (f4e2m1_e_mask | f4e2m1_m_mask);
} else if (target_f_biased_exp > 0) {
f4_bits |= (target_f_biased_exp << f4e2m1_m_size) | (fractional >> (three_bytes_shift));
} else {
// Restore the hidden 1 in target mantissa for subnormal calculation
fractional = f_m_hidden_one_mask | (input.bits & f32_m_mask) << (f32_e_size - f4e2m1_e_size - 4U);
// Will any bits be shifted off?
int32_t shift = target_f_biased_exp < -(f4e2m1_e_max) ? 0 : (1U << (1 - target_f_biased_exp));
uint32_t sticky = (fractional & (shift - 1)) ? 1 : 0;
fractional = ((1 + target_f_biased_exp) > f4e2m1_e_max - 1) ? 0 : fractional >> (1 - target_f_biased_exp);
fractional |= sticky;
// apply rounding
if (((fractional & round_half) == round_odd) || ((fractional & round_norm) != 0)) {
fractional += round_even;
}
f4_bits |= fractional >> three_bytes_shift;
}
}
return f4_bits;
}
} // namespace
float4_e2m1::float4_e2m1(const float value) : m_value(f32_to_f4e2m1_bits(value)){};
float4_e2m1::operator float() const {
return f4e2m1_to_f32_lut[m_value];
}
uint8_t float4_e2m1::to_bits() const {
return m_value;
}
} // namespace ov

View File

@ -73,6 +73,8 @@ TEST(element_type, from_string) {
EXPECT_EQ(element::Type("F8E5M2"), element::f8e5m2);
EXPECT_EQ(element::Type("string"), element::string);
EXPECT_EQ(element::Type("STRING"), element::string);
EXPECT_EQ(element::Type("F4E2M1"), element::f4e2m1);
EXPECT_EQ(element::Type("F4E2M1"), element::f4e2m1);
EXPECT_EQ(element::Type("undefined"), element::undefined);
EXPECT_EQ(element::Type("UNSPECIFIED"), element::undefined);

View File

@ -0,0 +1,138 @@
// Copyright (C) 2018-2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#include "openvino/core/type/float4_e2m1.hpp"
#include <gtest/gtest.h>
#include <climits>
#include <cmath>
#include "common_test_utils/float_util.hpp"
namespace ov {
namespace test {
template <class TContainer>
std::vector<std::tuple<int, typename TContainer::value_type>> enumerate(const TContainer& values) {
std::vector<std::tuple<int, typename TContainer::value_type>> enum_values;
int i = 0;
for (const auto& v : values) {
enum_values.emplace_back(i, v);
++i;
}
return enum_values;
}
TEST(F4E2M1Test, f32_inf) {
const auto f4 = ov::float4_e2m1(std::numeric_limits<float>::infinity());
// f4 is max as there is no infinity
EXPECT_EQ(f4.to_bits(), 0b0111);
}
TEST(F4E2M1Test, f32_minus_inf) {
const auto f4 = ov::float4_e2m1(-std::numeric_limits<float>::infinity());
// f4 is max as there is no infinity
EXPECT_EQ(f4.to_bits(), 0b1111);
}
TEST(F4E2M1Test, f32_gt_zero_round_to_f4_zero) {
const auto f4 = ov::float4_e2m1(0.218749985098838806152f);
EXPECT_EQ(f4.to_bits(), 0b0000);
}
TEST(F4E2M1Test, f32_gt_zero_round_to_f4_lowest_subnormal) {
const auto f4 = ov::float4_e2m1(0.21875f);
EXPECT_EQ(f4.to_bits(), 0b0001);
}
TEST(F4E2M1Test, f32_normal_fractional_rounding) {
const auto f4 = ov::float4_e2m1(1.75f);
EXPECT_EQ(f4.to_bits(), 0b0100);
}
TEST(F4E2M1Test, f32_normal_negative_fractional_rounding) {
const auto f4 = ov::float4_e2m1(-2.1f);
EXPECT_EQ(f4.to_bits(), 0b1100);
}
TEST(F4E2M1Test, f32_ge_f4_max_within_round_to_max) {
const auto f4 = ov::float4_e2m1(6.1f);
EXPECT_EQ(f4.to_bits(), 0b0111);
}
TEST(F4E2M1Test, f32_ge_f8_max_not_within_round_to_max) {
const auto f4 = ov::float4_e2m1(7.0f);
EXPECT_EQ(f4.to_bits(), 0b0111);
}
TEST(F4E2M1Test, f32_le_f8_lowest_within_round_to_lowest) {
const auto f4 = ov::float4_e2m1(-6.5f);
EXPECT_EQ(f4.to_bits(), 0b1111);
}
TEST(F4E2M1Test, f32_le_f8_lowest_not_within_round_to_lowest) {
const auto f4 = ov::float4_e2m1(-7.0f);
EXPECT_EQ(f4.to_bits(), 0b1111);
}
TEST(F4E2M1Test, stream_operator) {
std::stringstream s;
s << ov::float4_e2m1(-1.5f);
EXPECT_EQ(s.str(), "-1.5");
}
TEST(F4E2M1Test, to_string) {
const auto f4 = ov::float4_e2m1::from_bits(0b00000110);
EXPECT_EQ(std::to_string(f4), "4.000000");
}
// clang-format off
const auto exp_floats = std::vector<float>{
0.0f, 0.5f,
1.0f, 1.5f,
2.0f, 3.0f,
4.0f, 6.0f,
-0.0f, -0.5f,
-1.0f, -1.5f,
-2.0f, -3.0f,
-4.0f, -6.0f};
// clang-format on
using f4m2e1_params = std::tuple<int, float>;
class F4E2M1PTest : public testing::TestWithParam<f4m2e1_params> {};
INSTANTIATE_TEST_SUITE_P(convert,
F4E2M1PTest,
testing::ValuesIn(enumerate(exp_floats)),
testing::PrintToStringParamName());
TEST_P(F4E2M1PTest, f4_bits_to_f32) {
const auto& params = GetParam();
const auto& exp_value = std::get<1>(params);
const auto f4 = ov::float4_e2m1::from_bits(std::get<0>(params));
EXPECT_EQ(static_cast<float>(f4), exp_value);
}
TEST_P(F4E2M1PTest, f32_to_f4_bits) {
const auto& params = GetParam();
const auto& exp_value = std::get<0>(params);
const auto& value = std::get<1>(params);
const auto f4 = ov::float4_e2m1(value);
EXPECT_EQ(f4.to_bits(), exp_value);
}
} // namespace test
} // namespace ov

View File

@ -34,7 +34,8 @@ struct TypeMask {
_nf4 = 1 << 18,
_f8e4m3 = 1 << 19,
_f8e5m2 = 1 << 20,
_string = 1 << 21
_string = 1 << 21,
_f4e2m1 = 1 << 22
};
TypeMask(const ov::element::Type precision) : value(generateMask(precision)), precision(precision) {}
@ -81,6 +82,7 @@ private:
CASE(f8e4m3)
CASE(f8e5m2)
CASE(string)
CASE(f4e2m1)
default:
return _undefined;
}
@ -114,6 +116,7 @@ DEFINE_TYPE_ALIAS(_nf4);
DEFINE_TYPE_ALIAS(_f8e4m3);
DEFINE_TYPE_ALIAS(_f8e5m2);
DEFINE_TYPE_ALIAS(_string);
DEFINE_TYPE_ALIAS(_f4e2m1);
constexpr auto _any_float = _f64 | _f32 | _f16 | _bf16;
constexpr auto _half_float = _f16 | _bf16;
constexpr auto _quant = _u8 | _i8;

View File

@ -79,6 +79,7 @@ static ov::test::utils::InputGenerateData get_range_by_type(
}
CASE_OV_TYPE(ov::element::Type_t::f8e4m3)
CASE_OV_TYPE(ov::element::Type_t::f8e5m2)
CASE_OV_TYPE(ov::element::Type_t::f4e2m1)
CASE_OV_TYPE(ov::element::Type_t::bf16)
CASE_OV_TYPE(ov::element::Type_t::f16)
CASE_C_TYPE(ov::element::Type_t::f32)