[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:
parent
1144a66a9b
commit
bb75829a88
|
|
@ -1,6 +1,6 @@
|
||||||
# custom OpenVINO values
|
# custom OpenVINO values
|
||||||
CppMethod: '^(operator\W+|[a-z_\d]+|signaling_NaN|quiet_NaN|OPENVINO_OP)$'
|
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)$'
|
StructName: '^([A-Z][\w]+|element_type_traits|hash|oi_pair|stat)$'
|
||||||
FunctionName: '^(operator\W+|[a-z_\d]+)|PrintTo$'
|
FunctionName: '^(operator\W+|[a-z_\d]+)|PrintTo$'
|
||||||
Namespace: '^([a-z\d_]*|InferenceEngine)$'
|
Namespace: '^([a-z\d_]*|InferenceEngine)$'
|
||||||
|
|
@ -18,7 +18,7 @@ VariableReference: '^\w+$'
|
||||||
|
|
||||||
EnumName: '^[A-Z][\w]+$'
|
EnumName: '^[A-Z][\w]+$'
|
||||||
# excepts element_type
|
# 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
|
# TODO: align
|
||||||
UsingDeclaration: '^.*$'
|
UsingDeclaration: '^.*$'
|
||||||
TypedefName: '^.*$'
|
TypedefName: '^.*$'
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,8 @@ typedef enum {
|
||||||
|
|
||||||
STRING, //!< string element type
|
STRING, //!< string element type
|
||||||
|
|
||||||
|
F4E2M1, //!< f4e2m1 element type
|
||||||
|
|
||||||
} ov_element_type_e;
|
} ov_element_type_e;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ typedef enum {
|
||||||
F8E4M3, //!< f8e4m3 element type
|
F8E4M3, //!< f8e4m3 element type
|
||||||
F8E5M3, //!< f8e5m2 element type
|
F8E5M3, //!< f8e5m2 element type
|
||||||
STRING, //!< string element type
|
STRING, //!< string element type
|
||||||
|
F4E2M1, //!< f4e2m1 element type
|
||||||
} ov_element_type_e;
|
} ov_element_type_e;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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::NF4, ov::element::nf4},
|
||||||
{ov_element_type_e::F8E4M3, ov::element::f8e4m3},
|
{ov_element_type_e::F8E4M3, ov::element::f8e4m3},
|
||||||
{ov_element_type_e::F8E5M3, ov::element::f8e5m2},
|
{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) {
|
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++) {
|
for (auto iter = element_type_map.begin(); iter != element_type_map.end(); iter++) {
|
||||||
|
|
|
||||||
|
|
@ -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::u4, py::dtype("uint8")}, {ov::element::nf4, py::dtype("uint8")},
|
||||||
{ov::element::i4, py::dtype("int8")}, {ov::element::f8e4m3, 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::f8e5m2, py::dtype("uint8")}, {ov::element::string, py::dtype("bytes_")},
|
||||||
|
{ov::element::f4e2m1, py::dtype("uint8")},
|
||||||
};
|
};
|
||||||
return ov_type_to_dtype_mapping;
|
return ov_type_to_dtype_mapping;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ void regclass_graph_Type(py::module m) {
|
||||||
type.attr("f8e4m3") = ov::element::f8e4m3;
|
type.attr("f8e4m3") = ov::element::f8e4m3;
|
||||||
type.attr("f8e5m2") = ov::element::f8e5m2;
|
type.attr("f8e5m2") = ov::element::f8e5m2;
|
||||||
type.attr("string") = ov::element::string;
|
type.attr("string") = ov::element::string;
|
||||||
|
type.attr("f4e2m1") = ov::element::f4e2m1;
|
||||||
|
|
||||||
type.def("__hash__", &ov::element::Type::hash);
|
type.def("__hash__", &ov::element::Type::hash);
|
||||||
type.def("__repr__", [](const ov::element::Type& self) {
|
type.def("__repr__", [](const ov::element::Type& self) {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include "openvino/core/rtti.hpp"
|
#include "openvino/core/rtti.hpp"
|
||||||
#include "openvino/core/type/bfloat16.hpp"
|
#include "openvino/core/type/bfloat16.hpp"
|
||||||
#include "openvino/core/type/float16.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_e4m3.hpp"
|
||||||
#include "openvino/core/type/float8_e5m2.hpp"
|
#include "openvino/core/type/float8_e5m2.hpp"
|
||||||
|
|
||||||
|
|
@ -59,7 +60,8 @@ enum class Type_t {
|
||||||
nf4, //!< nf4 element type
|
nf4, //!< nf4 element type
|
||||||
f8e4m3, //!< f8e4m3 element type
|
f8e4m3, //!< f8e4m3 element type
|
||||||
f8e5m2, //!< f8e5m2 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
|
/// \brief Base class to define element type
|
||||||
|
|
@ -209,6 +211,9 @@ constexpr Type f8e5m2(Type_t::f8e5m2);
|
||||||
/// \brief string element type
|
/// \brief string element type
|
||||||
/// \ingroup ov_element_cpp_api
|
/// \ingroup ov_element_cpp_api
|
||||||
constexpr Type string(Type_t::string);
|
constexpr Type string(Type_t::string);
|
||||||
|
/// \brief f4e2m1 element type
|
||||||
|
/// \ingroup ov_element_cpp_api
|
||||||
|
constexpr Type f4e2m1(Type_t::f4e2m1);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
Type from() {
|
Type from() {
|
||||||
|
|
@ -248,6 +253,8 @@ template <>
|
||||||
OPENVINO_API Type from<ov::float8_e5m2>();
|
OPENVINO_API Type from<ov::float8_e5m2>();
|
||||||
template <>
|
template <>
|
||||||
OPENVINO_API Type from<std::string>();
|
OPENVINO_API Type from<std::string>();
|
||||||
|
template <>
|
||||||
|
OPENVINO_API Type from<ov::float4_e2m1>();
|
||||||
|
|
||||||
OPENVINO_API Type fundamental_type_for(const Type& type);
|
OPENVINO_API Type fundamental_type_for(const Type& type);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -127,4 +127,9 @@ template <>
|
||||||
struct element_type_traits<element::Type_t::string> {
|
struct element_type_traits<element::Type_t::string> {
|
||||||
using value_type = std::string;
|
using value_type = std::string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct element_type_traits<element::Type_t::f4e2m1> {
|
||||||
|
using value_type = ov::float4_e2m1;
|
||||||
|
};
|
||||||
} // namespace ov
|
} // namespace ov
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -156,6 +156,7 @@ public:
|
||||||
case Type_t::string:
|
case Type_t::string:
|
||||||
fill_data<Type_t::string>(value);
|
fill_data<Type_t::string>(value);
|
||||||
break;
|
break;
|
||||||
|
case Type_t::f4e2m1:
|
||||||
case Type_t::undefined:
|
case Type_t::undefined:
|
||||||
case Type_t::dynamic:
|
case Type_t::dynamic:
|
||||||
OPENVINO_THROW("unsupported type");
|
OPENVINO_THROW("unsupported type");
|
||||||
|
|
@ -707,6 +708,7 @@ private:
|
||||||
case Type_t::string:
|
case Type_t::string:
|
||||||
write_buffer<Type_t::string>(source);
|
write_buffer<Type_t::string>(source);
|
||||||
break;
|
break;
|
||||||
|
case Type_t::f4e2m1:
|
||||||
case Type_t::undefined:
|
case Type_t::undefined:
|
||||||
case Type_t::dynamic:
|
case Type_t::dynamic:
|
||||||
OPENVINO_THROW("unsupported type");
|
OPENVINO_THROW("unsupported type");
|
||||||
|
|
|
||||||
|
|
@ -767,6 +767,8 @@ std::string get_precision_name(const ov::element::Type& elem_type) {
|
||||||
return "F8E5M2";
|
return "F8E5M2";
|
||||||
case ::ov::element::Type_t::string:
|
case ::ov::element::Type_t::string:
|
||||||
return "STRING";
|
return "STRING";
|
||||||
|
case ::ov::element::Type_t::f4e2m1:
|
||||||
|
return "F4E2M1";
|
||||||
default:
|
default:
|
||||||
OPENVINO_THROW("Unsupported precision: ", elem_type);
|
OPENVINO_THROW("Unsupported precision: ", elem_type);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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::i4:
|
||||||
case ov::element::Type_t::f8e4m3:
|
case ov::element::Type_t::f8e4m3:
|
||||||
case ov::element::Type_t::f8e5m2:
|
case ov::element::Type_t::f8e5m2:
|
||||||
|
case ov::element::Type_t::f4e2m1:
|
||||||
ss << constant->get_output_element_type(0).get_type_name() << " value";
|
ss << constant->get_output_element_type(0).get_type_name() << " value";
|
||||||
break;
|
break;
|
||||||
case ov::element::Type_t::bf16:
|
case ov::element::Type_t::bf16:
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,8 @@ inline TypeInfo get_type_info(ov::element::Type_t type) {
|
||||||
return {8, true, true, true, "f8e5m2", "f8e5m2"};
|
return {8, true, true, true, "f8e5m2", "f8e5m2"};
|
||||||
case ov::element::Type_t::string:
|
case ov::element::Type_t::string:
|
||||||
return {8 * sizeof(std::string), false, false, false, "string", "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:
|
default:
|
||||||
OPENVINO_THROW("ov::element::Type_t not supported: ", type);
|
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);
|
return ::ov::element::Type(::ov::element::Type_t::f8e4m3);
|
||||||
} else if (type == "f8e5m2" || type == "F8E5M2") {
|
} else if (type == "f8e5m2" || type == "F8E5M2") {
|
||||||
return ::ov::element::Type(::ov::element::Type_t::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 {
|
} else {
|
||||||
OPENVINO_THROW("Incorrect type: ", type);
|
OPENVINO_THROW("Incorrect type: ", type);
|
||||||
}
|
}
|
||||||
|
|
@ -147,11 +151,11 @@ ov::element::Type type_from_string(const std::string& type) {
|
||||||
|
|
||||||
std::vector<const ov::element::Type*> ov::element::Type::get_known_types() {
|
std::vector<const ov::element::Type*> ov::element::Type::get_known_types() {
|
||||||
std::vector<const ov::element::Type*> rc = {
|
std::vector<const ov::element::Type*> rc = {
|
||||||
&ov::element::dynamic, &ov::element::boolean, &ov::element::bf16, &ov::element::f16, &ov::element::f32,
|
&ov::element::dynamic, &ov::element::boolean, &ov::element::bf16, &ov::element::f16, &ov::element::f32,
|
||||||
&ov::element::f64, &ov::element::i4, &ov::element::i8, &ov::element::i16, &ov::element::i32,
|
&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::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::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;
|
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::f8e4m3, {8, true, true, true, "f8e4m3", "f8e4m3"}},
|
||||||
{ov::element::Type_t::f8e5m2, {8, true, true, true, "f8e5m2", "f8e5m2"}},
|
{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::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) {
|
for (const auto& t : elements_map) {
|
||||||
const TypeInfo& info = t.second;
|
const TypeInfo& info = t.second;
|
||||||
|
|
@ -290,6 +295,10 @@ template <>
|
||||||
Type from<std::string>() {
|
Type from<std::string>() {
|
||||||
return Type_t::string;
|
return Type_t::string;
|
||||||
}
|
}
|
||||||
|
template <>
|
||||||
|
Type from<ov::float4_e2m1>() {
|
||||||
|
return Type_t::f4e2m1;
|
||||||
|
}
|
||||||
|
|
||||||
Type fundamental_type_for(const Type& type) {
|
Type fundamental_type_for(const Type& type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
@ -339,6 +348,8 @@ Type fundamental_type_for(const Type& type) {
|
||||||
return from<element_type_traits<Type_t::nf4>::value_type>();
|
return from<element_type_traits<Type_t::nf4>::value_type>();
|
||||||
case Type_t::string:
|
case Type_t::string:
|
||||||
return from<element_type_traits<Type_t::string>::value_type>();
|
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:
|
default:
|
||||||
OPENVINO_THROW("Unsupported Data type: ", type);
|
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},
|
{"U16", ov::element::u16}, {"U32", ov::element::u32}, {"U64", ov::element::u64},
|
||||||
{"FP32", ov::element::f32}, {"FP64", ov::element::f64}, {"FP16", ov::element::f16},
|
{"FP32", ov::element::f32}, {"FP64", ov::element::f64}, {"FP16", ov::element::f16},
|
||||||
{"BIN", ov::element::u1}, {"NF4", ov::element::nf4}, {"F8E4M3", ov::element::f8e4m3},
|
{"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;
|
std::string str;
|
||||||
in >> str;
|
in >> str;
|
||||||
auto it_legacy = legacy.find(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(f8e4m3);
|
||||||
ET_CASE(f8e5m2);
|
ET_CASE(f8e5m2);
|
||||||
ET_CASE(string);
|
ET_CASE(string);
|
||||||
|
ET_CASE(f4e2m1);
|
||||||
#undef ET_CASE
|
#undef ET_CASE
|
||||||
case ov::element::Type_t::undefined:
|
case ov::element::Type_t::undefined:
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -486,7 +497,8 @@ OPENVINO_API EnumNames<element::Type_t>& EnumNames<element::Type_t>::get() {
|
||||||
{"nf4", element::Type_t::nf4},
|
{"nf4", element::Type_t::nf4},
|
||||||
{"f8e4m3", element::Type_t::f8e4m3},
|
{"f8e4m3", element::Type_t::f8e4m3},
|
||||||
{"f8e5m2", element::Type_t::f8e5m2},
|
{"f8e5m2", element::Type_t::f8e5m2},
|
||||||
{"string", element::Type_t::string}});
|
{"string", element::Type_t::string},
|
||||||
|
{"f4e2m1", element::Type_t::f4e2m1}});
|
||||||
return enum_names;
|
return enum_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -73,6 +73,8 @@ TEST(element_type, from_string) {
|
||||||
EXPECT_EQ(element::Type("F8E5M2"), element::f8e5m2);
|
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("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("undefined"), element::undefined);
|
||||||
EXPECT_EQ(element::Type("UNSPECIFIED"), element::undefined);
|
EXPECT_EQ(element::Type("UNSPECIFIED"), element::undefined);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -34,7 +34,8 @@ struct TypeMask {
|
||||||
_nf4 = 1 << 18,
|
_nf4 = 1 << 18,
|
||||||
_f8e4m3 = 1 << 19,
|
_f8e4m3 = 1 << 19,
|
||||||
_f8e5m2 = 1 << 20,
|
_f8e5m2 = 1 << 20,
|
||||||
_string = 1 << 21
|
_string = 1 << 21,
|
||||||
|
_f4e2m1 = 1 << 22
|
||||||
};
|
};
|
||||||
|
|
||||||
TypeMask(const ov::element::Type precision) : value(generateMask(precision)), precision(precision) {}
|
TypeMask(const ov::element::Type precision) : value(generateMask(precision)), precision(precision) {}
|
||||||
|
|
@ -81,6 +82,7 @@ private:
|
||||||
CASE(f8e4m3)
|
CASE(f8e4m3)
|
||||||
CASE(f8e5m2)
|
CASE(f8e5m2)
|
||||||
CASE(string)
|
CASE(string)
|
||||||
|
CASE(f4e2m1)
|
||||||
default:
|
default:
|
||||||
return _undefined;
|
return _undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +116,7 @@ DEFINE_TYPE_ALIAS(_nf4);
|
||||||
DEFINE_TYPE_ALIAS(_f8e4m3);
|
DEFINE_TYPE_ALIAS(_f8e4m3);
|
||||||
DEFINE_TYPE_ALIAS(_f8e5m2);
|
DEFINE_TYPE_ALIAS(_f8e5m2);
|
||||||
DEFINE_TYPE_ALIAS(_string);
|
DEFINE_TYPE_ALIAS(_string);
|
||||||
|
DEFINE_TYPE_ALIAS(_f4e2m1);
|
||||||
constexpr auto _any_float = _f64 | _f32 | _f16 | _bf16;
|
constexpr auto _any_float = _f64 | _f32 | _f16 | _bf16;
|
||||||
constexpr auto _half_float = _f16 | _bf16;
|
constexpr auto _half_float = _f16 | _bf16;
|
||||||
constexpr auto _quant = _u8 | _i8;
|
constexpr auto _quant = _u8 | _i8;
|
||||||
|
|
|
||||||
|
|
@ -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::f8e4m3)
|
||||||
CASE_OV_TYPE(ov::element::Type_t::f8e5m2)
|
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::bf16)
|
||||||
CASE_OV_TYPE(ov::element::Type_t::f16)
|
CASE_OV_TYPE(ov::element::Type_t::f16)
|
||||||
CASE_C_TYPE(ov::element::Type_t::f32)
|
CASE_C_TYPE(ov::element::Type_t::f32)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue