From 33a51b0b3f013ea018322646dd2dcbe4a6e82772 Mon Sep 17 00:00:00 2001 From: barnasm1 Date: Mon, 24 Jun 2024 14:55:16 +0200 Subject: [PATCH] Integrate the f8e8m0 with Constant and Convert (#25105) ### Details: * Add f16, bf16, f32 <-> f8e8m0 conversion in Convert operator * Add f8e8m0 support in Constant operator ### Tickets: - [*CVS-141563*](https://jira.devtools.intel.com/browse/CVS-141563) --- .../python/tests/test_graph/test_constant.py | 61 ++++++++ src/core/include/openvino/op/constant.hpp | 23 ++++ .../openvino/reference/utils/type_util.hpp | 2 +- src/core/src/op/constant.cpp | 22 ++- src/core/src/op/convert.cpp | 44 ++++-- src/core/tests/constant.cpp | 130 +++++++++++++++++- .../op_reference/base_reference_test.cpp | 8 ++ .../functional/op_reference/convert_like.cpp | 46 ++++++- .../include/common_test_utils/data_utils.hpp | 4 + 9 files changed, 321 insertions(+), 19 deletions(-) diff --git a/src/bindings/python/tests/test_graph/test_constant.py b/src/bindings/python/tests/test_graph/test_constant.py index 8d9ec4f183397e..ba1c9eb061588d 100644 --- a/src/bindings/python/tests/test_graph/test_constant.py +++ b/src/bindings/python/tests/test_graph/test_constant.py @@ -473,6 +473,36 @@ def test_float_to_f8e4m3_constant(ov_type, numpy_dtype): assert np.allclose(result, target, equal_nan=True) +@pytest.mark.parametrize(("ov_type", "numpy_dtype"), [ + (Type.f32, np.float32), + (Type.f16, np.float16), +]) +def test_float_to_f8e8m0_constant(ov_type, numpy_dtype): + from openvino.runtime import opset12 as opset + import openvino as ov + data = np.array([4.75, 4.5, 5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, + 0.6, 0.7, 0.8, 0.9, 1, -0.0, 1.1, 1.2, 1.3, + 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 448, 512, np.nan], dtype=numpy_dtype) + + compressed_const = opset.constant(data, dtype=ov.Type.f8e8m0, name="f8e8m0_constant") + convert = opset.convert(compressed_const, data.dtype) + parameter = opset.parameter(ov.PartialShape([-1]), ov_type) + add_op = opset.add(parameter, convert) + model = ov.Model([add_op], [parameter]) + + compiled = ov.compile_model(model) + tensor = np.zeros(data.shape, dtype=numpy_dtype) + result = compiled(tensor)[0] + + target = [4.0, 4.0, 4.0, 0.0, 0.125, 0.25, 0.25, + 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, + 2.0, 2.0, 2.0, 2.0, 512, 512, np.nan] + target = np.array(target, dtype=numpy_dtype) + + assert np.allclose(result, target, equal_nan=True) + + @pytest.mark.parametrize(("ov_type", "numpy_dtype"), [ (Type.f32, np.float32), (Type.f16, np.float16), @@ -535,6 +565,37 @@ def test_float_to_f8e4m3_convert(ov_type, numpy_dtype): assert np.allclose(result, target, equal_nan=True) +@pytest.mark.parametrize(("ov_type", "numpy_dtype"), [ + (Type.f32, np.float32), + (Type.f16, np.float16), +]) +def test_float_to_f8e8m0_convert(ov_type, numpy_dtype): + from openvino.runtime import opset12 as opset + import openvino as ov + data = np.array([4.75, 4.5, 5.25, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, + 0.6, 0.7, 0.8, 0.9, 1, -0.0, 1.1, 1.2, 1.3, + 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 448, 512, np.nan], dtype=numpy_dtype) + + compressed_const = opset.constant(data, dtype=ov_type, name="fx_constant") + convert_to_fp8 = opset.convert(compressed_const, Type.f8e8m0) + convert_back = opset.convert(convert_to_fp8, ov_type) + parameter = opset.parameter(ov.PartialShape([-1]), ov_type) + add_op = opset.add(parameter, convert_back) + model = ov.Model([add_op], [parameter]) + + compiled = ov.compile_model(model) + tensor = np.zeros(data.shape, dtype=numpy_dtype) + result = compiled(tensor)[0] + + target = [4.0, 4.0, 4.0, 0.0, 0.125, 0.25, 0.25, + 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, + 0.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, + 2.0, 2.0, 2.0, 2.0, 512, 512, np.nan] + target = np.array(target, dtype=numpy_dtype) + + assert np.allclose(result, target, equal_nan=True) + + @pytest.mark.parametrize( ("src_dtype"), [ diff --git a/src/core/include/openvino/op/constant.hpp b/src/core/include/openvino/op/constant.hpp index 844288e1cb3f85..6f7500e69a3a95 100644 --- a/src/core/include/openvino/op/constant.hpp +++ b/src/core/include/openvino/op/constant.hpp @@ -160,6 +160,8 @@ class OPENVINO_API Constant : public Op { fill_lp_data(value); break; case Type_t::f8e8m0: + fill_data(value); + break; case Type_t::undefined: case Type_t::dynamic: OPENVINO_THROW("unsupported type"); @@ -370,6 +372,9 @@ class OPENVINO_API Constant : public Op { case Type_t::f4e2m1: cast_lp_vector(rc, num_elements_to_cast); break; + case Type_t::f8e8m0: + cast_vector(rc, num_elements_to_cast); + break; default: OPENVINO_THROW("unsupported type"); } @@ -729,6 +734,8 @@ class OPENVINO_API Constant : public Op { write_lp_buffer(source); break; case Type_t::f8e8m0: + write_buffer(source); + break; case Type_t::undefined: case Type_t::dynamic: OPENVINO_THROW("unsupported type"); @@ -865,6 +872,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(u1, long long) CONSTANT_FILL_DATA_SPECIALIZATION(u1, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(u1, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(u1, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(u1, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(u1, float16) CONSTANT_FILL_DATA_SPECIALIZATION(u1, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(u1, float) @@ -884,6 +892,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(u2, long long) CONSTANT_FILL_DATA_SPECIALIZATION(u2, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(u2, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(u2, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(u2, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(u2, float16) CONSTANT_FILL_DATA_SPECIALIZATION(u2, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(u2, float) @@ -903,6 +912,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(u3, long long) CONSTANT_FILL_DATA_SPECIALIZATION(u3, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(u3, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(u3, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(u3, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(u3, float16) CONSTANT_FILL_DATA_SPECIALIZATION(u3, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(u3, float) @@ -922,6 +932,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(u4, long long) CONSTANT_FILL_DATA_SPECIALIZATION(u4, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(u4, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(u4, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(u4, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(u4, float16) CONSTANT_FILL_DATA_SPECIALIZATION(u4, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(u4, float) @@ -941,6 +952,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(u6, long long) CONSTANT_FILL_DATA_SPECIALIZATION(u6, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(u6, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(u6, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(u6, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(u6, float16) CONSTANT_FILL_DATA_SPECIALIZATION(u6, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(u6, float) @@ -960,6 +972,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(i4, long long) CONSTANT_FILL_DATA_SPECIALIZATION(i4, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(i4, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(i4, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(i4, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(i4, float16) CONSTANT_FILL_DATA_SPECIALIZATION(i4, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(i4, float) @@ -979,6 +992,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(nf4, long long) CONSTANT_FILL_DATA_SPECIALIZATION(nf4, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(nf4, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(nf4, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(nf4, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(nf4, float16) CONSTANT_FILL_DATA_SPECIALIZATION(nf4, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(nf4, float) @@ -998,6 +1012,7 @@ CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, long long) CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, unsigned long long) CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, float8_e4m3) CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, float8_e5m2) +CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, float8_e8m0) CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, float16) CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, bfloat16) CONSTANT_FILL_DATA_SPECIALIZATION(f4e2m1, float) @@ -1148,6 +1163,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u1, float) @@ -1167,6 +1183,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u2, float) @@ -1186,6 +1203,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u3, float) @@ -1205,6 +1223,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u4, float) @@ -1224,6 +1243,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(u6, float) @@ -1243,6 +1263,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(i4, float) @@ -1262,6 +1283,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(nf4, float) @@ -1281,6 +1303,7 @@ CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, unsigned long long) CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, float8_e4m3) CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, float8_e5m2) +CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, float8_e8m0) CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, float16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, bfloat16) CONSTANT_WRITE_BUFFER_SPECIALIZATION(f4e2m1, float) diff --git a/src/core/reference/include/openvino/reference/utils/type_util.hpp b/src/core/reference/include/openvino/reference/utils/type_util.hpp index 878a7e9db712d9..e368edd3c83ed1 100644 --- a/src/core/reference/include/openvino/reference/utils/type_util.hpp +++ b/src/core/reference/include/openvino/reference/utils/type_util.hpp @@ -21,6 +21,6 @@ constexpr bool is_floating_point() { using U = typename std::decay::type; return std::is_floating_point::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || - std::is_same::value; + std::is_same::value || std::is_same::value; } } // namespace ov diff --git a/src/core/src/op/constant.cpp b/src/core/src/op/constant.cpp index 3753eef03fefb2..e84cad9341b9c9 100644 --- a/src/core/src/op/constant.cpp +++ b/src/core/src/op/constant.cpp @@ -334,7 +334,8 @@ std::string Constant::convert_value_to_string(size_t index) const { f8e4m3, f8e5m2, string, - f4e2m1>::apply(get_element_type(), get_data_ptr(), index); + f4e2m1, + f8e8m0>::apply(get_element_type(), get_data_ptr(), index); } size_t Constant::get_byte_size() const { @@ -410,7 +411,8 @@ std::vector Constant::get_value_strings() const { u64, nf4, string, - f4e2m1>::apply(get_element_type(), get_data_ptr(), shape_size(m_shape), out); + f4e2m1, + f8e8m0>::apply(get_element_type(), get_data_ptr(), shape_size(m_shape), out); return out; } @@ -777,6 +779,7 @@ CONSTANT_FILL_DATA(u1, long long) CONSTANT_FILL_DATA(u1, unsigned long long) CONSTANT_FILL_DATA(u1, float8_e4m3) CONSTANT_FILL_DATA(u1, float8_e5m2) +CONSTANT_FILL_DATA(u1, float8_e8m0) CONSTANT_FILL_DATA(u1, float16) CONSTANT_FILL_DATA(u1, bfloat16) CONSTANT_FILL_DATA(u1, float) @@ -796,6 +799,7 @@ CONSTANT_FILL_DATA(u2, long long) CONSTANT_FILL_DATA(u2, unsigned long long) CONSTANT_FILL_DATA(u2, float8_e4m3) CONSTANT_FILL_DATA(u2, float8_e5m2) +CONSTANT_FILL_DATA(u2, float8_e8m0) CONSTANT_FILL_DATA(u2, float16) CONSTANT_FILL_DATA(u2, bfloat16) CONSTANT_FILL_DATA(u2, float) @@ -815,6 +819,7 @@ CONSTANT_FILL_DATA(u3, long long) CONSTANT_FILL_DATA(u3, unsigned long long) CONSTANT_FILL_DATA(u3, float8_e4m3) CONSTANT_FILL_DATA(u3, float8_e5m2) +CONSTANT_FILL_DATA(u3, float8_e8m0) CONSTANT_FILL_DATA(u3, float16) CONSTANT_FILL_DATA(u3, bfloat16) CONSTANT_FILL_DATA(u3, float) @@ -834,6 +839,7 @@ CONSTANT_FILL_DATA(u4, long long) CONSTANT_FILL_DATA(u4, unsigned long long) CONSTANT_FILL_DATA(u4, float8_e4m3) CONSTANT_FILL_DATA(u4, float8_e5m2) +CONSTANT_FILL_DATA(u4, float8_e8m0) CONSTANT_FILL_DATA(u4, float16) CONSTANT_FILL_DATA(u4, bfloat16) CONSTANT_FILL_DATA(u4, float) @@ -853,6 +859,7 @@ CONSTANT_FILL_DATA(u6, long long) CONSTANT_FILL_DATA(u6, unsigned long long) CONSTANT_FILL_DATA(u6, float8_e4m3) CONSTANT_FILL_DATA(u6, float8_e5m2) +CONSTANT_FILL_DATA(u6, float8_e8m0) CONSTANT_FILL_DATA(u6, float16) CONSTANT_FILL_DATA(u6, bfloat16) CONSTANT_FILL_DATA(u6, float) @@ -872,6 +879,7 @@ CONSTANT_FILL_DATA(i4, long long) CONSTANT_FILL_DATA(i4, unsigned long long) CONSTANT_FILL_DATA(i4, float8_e4m3) CONSTANT_FILL_DATA(i4, float8_e5m2) +CONSTANT_FILL_DATA(i4, float8_e8m0) CONSTANT_FILL_DATA(i4, float16) CONSTANT_FILL_DATA(i4, bfloat16) CONSTANT_FILL_DATA(i4, float) @@ -891,6 +899,7 @@ CONSTANT_FILL_DATA(nf4, long long) CONSTANT_FILL_DATA(nf4, unsigned long long) CONSTANT_FILL_DATA(nf4, float8_e4m3) CONSTANT_FILL_DATA(nf4, float8_e5m2) +CONSTANT_FILL_DATA(nf4, float8_e8m0) CONSTANT_FILL_DATA(nf4, float16) CONSTANT_FILL_DATA(nf4, bfloat16) CONSTANT_FILL_DATA(nf4, float) @@ -910,6 +919,7 @@ CONSTANT_FILL_DATA(f4e2m1, long long) CONSTANT_FILL_DATA(f4e2m1, unsigned long long) CONSTANT_FILL_DATA(f4e2m1, float8_e4m3) CONSTANT_FILL_DATA(f4e2m1, float8_e5m2) +CONSTANT_FILL_DATA(f4e2m1, float8_e8m0) CONSTANT_FILL_DATA(f4e2m1, float16) CONSTANT_FILL_DATA(f4e2m1, bfloat16) CONSTANT_FILL_DATA(f4e2m1, float) @@ -1065,6 +1075,7 @@ CONSTANT_WRITE_BUFFER(u1, long long) CONSTANT_WRITE_BUFFER(u1, unsigned long long) CONSTANT_WRITE_BUFFER(u1, float8_e4m3) CONSTANT_WRITE_BUFFER(u1, float8_e5m2) +CONSTANT_WRITE_BUFFER(u1, float8_e8m0) CONSTANT_WRITE_BUFFER(u1, float16) CONSTANT_WRITE_BUFFER(u1, bfloat16) CONSTANT_WRITE_BUFFER(u1, float) @@ -1084,6 +1095,7 @@ CONSTANT_WRITE_BUFFER(u2, long long) CONSTANT_WRITE_BUFFER(u2, unsigned long long) CONSTANT_WRITE_BUFFER(u2, float8_e4m3) CONSTANT_WRITE_BUFFER(u2, float8_e5m2) +CONSTANT_WRITE_BUFFER(u2, float8_e8m0) CONSTANT_WRITE_BUFFER(u2, float16) CONSTANT_WRITE_BUFFER(u2, bfloat16) CONSTANT_WRITE_BUFFER(u2, float) @@ -1103,6 +1115,7 @@ CONSTANT_WRITE_BUFFER(u3, long long) CONSTANT_WRITE_BUFFER(u3, unsigned long long) CONSTANT_WRITE_BUFFER(u3, float8_e4m3) CONSTANT_WRITE_BUFFER(u3, float8_e5m2) +CONSTANT_WRITE_BUFFER(u3, float8_e8m0) CONSTANT_WRITE_BUFFER(u3, float16) CONSTANT_WRITE_BUFFER(u3, bfloat16) CONSTANT_WRITE_BUFFER(u3, float) @@ -1122,6 +1135,7 @@ CONSTANT_WRITE_BUFFER(u4, long long) CONSTANT_WRITE_BUFFER(u4, unsigned long long) CONSTANT_WRITE_BUFFER(u4, float8_e4m3) CONSTANT_WRITE_BUFFER(u4, float8_e5m2) +CONSTANT_WRITE_BUFFER(u4, float8_e8m0) CONSTANT_WRITE_BUFFER(u4, float16) CONSTANT_WRITE_BUFFER(u4, bfloat16) CONSTANT_WRITE_BUFFER(u4, float) @@ -1141,6 +1155,7 @@ CONSTANT_WRITE_BUFFER(u6, long long) CONSTANT_WRITE_BUFFER(u6, unsigned long long) CONSTANT_WRITE_BUFFER(u6, float8_e4m3) CONSTANT_WRITE_BUFFER(u6, float8_e5m2) +CONSTANT_WRITE_BUFFER(u6, float8_e8m0) CONSTANT_WRITE_BUFFER(u6, float16) CONSTANT_WRITE_BUFFER(u6, bfloat16) CONSTANT_WRITE_BUFFER(u6, float) @@ -1160,6 +1175,7 @@ CONSTANT_WRITE_BUFFER(i4, long long) CONSTANT_WRITE_BUFFER(i4, unsigned long long) CONSTANT_WRITE_BUFFER(i4, float8_e4m3) CONSTANT_WRITE_BUFFER(i4, float8_e5m2) +CONSTANT_WRITE_BUFFER(i4, float8_e8m0) CONSTANT_WRITE_BUFFER(i4, float16) CONSTANT_WRITE_BUFFER(i4, bfloat16) CONSTANT_WRITE_BUFFER(i4, float) @@ -1179,6 +1195,7 @@ CONSTANT_WRITE_BUFFER(nf4, long long) CONSTANT_WRITE_BUFFER(nf4, unsigned long long) CONSTANT_WRITE_BUFFER(nf4, float8_e4m3) CONSTANT_WRITE_BUFFER(nf4, float8_e5m2) +CONSTANT_WRITE_BUFFER(nf4, float8_e8m0) CONSTANT_WRITE_BUFFER(nf4, float16) CONSTANT_WRITE_BUFFER(nf4, bfloat16) CONSTANT_WRITE_BUFFER(nf4, float) @@ -1198,6 +1215,7 @@ CONSTANT_WRITE_BUFFER(f4e2m1, long long) CONSTANT_WRITE_BUFFER(f4e2m1, unsigned long long) CONSTANT_WRITE_BUFFER(f4e2m1, float8_e4m3) CONSTANT_WRITE_BUFFER(f4e2m1, float8_e5m2) +CONSTANT_WRITE_BUFFER(f4e2m1, float8_e8m0) CONSTANT_WRITE_BUFFER(f4e2m1, float16) CONSTANT_WRITE_BUFFER(f4e2m1, bfloat16) CONSTANT_WRITE_BUFFER(f4e2m1, float) diff --git a/src/core/src/op/convert.cpp b/src/core/src/op/convert.cpp index 3a62dcfed32e7c..70f47d6ee67556 100644 --- a/src/core/src/op/convert.cpp +++ b/src/core/src/op/convert.cpp @@ -18,10 +18,11 @@ namespace convert { #define CONVERT_ET_LIST \ boolean, bf16, f16, f32, f64, i4, i8, i16, i32, i64, u1, u2, u3, u4, u6, u8, u16, u32, u64, nf4, f8e4m3, f8e5m2, \ - f4e2m1 + f4e2m1, f8e8m0 -#define CONVERT_TO_ANY_NO_NF4 \ - boolean, bf16, f16, f32, f64, i4, i8, i16, i32, i64, u1, u2, u3, u4, u6, u8, u16, u32, u64, f8e4m3, f8e5m2, f4e2m1 +#define CONVERT_TO_ANY_NO_NF4 \ + boolean, bf16, f16, f32, f64, i4, i8, i16, i32, i64, u1, u2, u3, u4, u6, u8, u16, u32, u64, f8e4m3, f8e5m2, \ + f4e2m1, f8e8m0 #define CONVERT_TO_ANY_NO_F4 \ boolean, bf16, f16, f32, f64, i4, i8, i16, i32, i64, u1, u2, u3, u4, u6, u8, u16, u32, u64, f8e4m3, f8e5m2 @@ -29,11 +30,12 @@ namespace convert { struct Evaluate : public element::NoAction { using element::NoAction::visit; - // convert from any (except F16, bf16, f32, NF4) to any except NF4 + // convert from any (except F16, bf16, f32, NF4, f8e8m0) to any except NF4 template , typename std::enable_if::type* = nullptr> + ET_IN != element::nf4 && ET_IN != element::f4e2m1 && + ET_IN != element::f8e8m0>::type* = nullptr> static result_type visit(const Tensor& arg, Tensor& out, const size_t count) { using namespace ov::element; return IF_TYPE_OF(Convert_out, @@ -90,7 +92,7 @@ struct Evaluate : public element::NoAction { count); } - // convert to F4E2M1 + // convert from F4E2M1 template , typename std::enable_if::type* = nullptr> @@ -105,6 +107,21 @@ struct Evaluate : public element::NoAction { count); } + // convert from F8E8M0 + template , + typename std::enable_if::type* = nullptr> + static result_type visit(const Tensor& arg, Tensor& out, const size_t count) { + using namespace ov::element; + return IF_TYPE_OF(Convert_out, + OV_PP_ET_LIST(f16, bf16, f32, f8e8m0), + EvalByOutputType, + out.get_element_type(), + iterator(reinterpret_cast(arg.data())), + out, + count); + } + private: struct EvalByOutputType : public element::NoAction { using element::NoAction::visit; @@ -208,8 +225,16 @@ bool Convert::has_evaluate() const { return (from == element::nf4) && (to == element::f16 || to == element::f32 || to == element::nf4); }; - const auto can_convert_f4e2m1 = [](const element::Type& et) { - return et == element::f16 || et == element::bf16 || et == element::f32 || et == element::f4e2m1; + const auto can_convert_f16_bf16_f32 = [](const element::Type& et) { + return et == element::f16 || et == element::bf16 || et == element::f32; + }; + + const auto can_convert_f4e2m1 = [&](const element::Type& et) { + return can_convert_f16_bf16_f32(et) || et == element::f4e2m1; + }; + + const auto can_convert_f8e8m0 = [&](const element::Type& et) { + return can_convert_f16_bf16_f32(et) || et == element::f8e8m0; }; const auto is_valid_type = [](const element::Type& et) -> bool { @@ -242,7 +267,8 @@ bool Convert::has_evaluate() const { const auto& output_et = get_output_element_type(0); return (is_valid_type(input_et) && is_valid_type(output_et)) || is_to_nf4_supported(input_et, output_et) || - (can_convert_f4e2m1(input_et) && can_convert_f4e2m1(output_et)); + (can_convert_f4e2m1(input_et) && can_convert_f4e2m1(output_et)) || + (can_convert_f8e8m0(input_et) && can_convert_f8e8m0(output_et)); } bool Convert::evaluate_lower(TensorVector& output_values) const { diff --git a/src/core/tests/constant.cpp b/src/core/tests/constant.cpp index 9908a4282d8c63..11eb25188ee92c 100644 --- a/src/core/tests/constant.cpp +++ b/src/core/tests/constant.cpp @@ -1860,6 +1860,29 @@ TEST(constant, float8_e5m3_vector) { EXPECT_EQ(data_vec, const_op_from_ptr.get_vector()); } +TEST(constant, float8_e8m0_vector) { + const auto data_vec = std::vector{std::numeric_limits::lowest(), + -std::numeric_limits::min(), + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::min(), + -1.5f, + -1.f, + -0.5f, + 0.f, + 0.5f, + 1.f, + 1.5f}; + Shape data_shape{data_vec.size()}; + EXPECT_EQ(data_vec.size(), shape_size(data_shape)); + + ov::op::v0::Constant const_op_from_vec(ov::element::f8e8m0, data_shape, data_vec); + EXPECT_EQ(data_vec, const_op_from_vec.get_vector()); + + ov::op::v0::Constant const_op_from_ptr(ov::element::f8e8m0, data_shape, data_vec.data()); + EXPECT_EQ(data_vec, const_op_from_ptr.get_vector()); +} + TEST(constant, float16_vector_broadcast) { Shape shape{4}; ov::op::v0::Constant c(element::f16, shape, vector{1}); @@ -2028,10 +2051,97 @@ TEST(constant, f4e2m1_write_then_cast_custom_type) { EXPECT_EQ(v, input); } +// +// f8e8m0 +// +TEST(constant, f8e8m0_string) { + vector input{"1", "0", "4", "0.5"}; + vector output{"1", "5.87747e-39", "4", "0.5"}; + ov::op::v0::Constant c(element::f8e8m0, Shape{4}, input); + auto v = c.cast_vector(); + ASSERT_EQ(v.size(), shape_size(c.get_shape())); + EXPECT_THAT(v, ElementsAre(1.0f, std::numeric_limits::min() / 2, 4.0f, 0.5f)); + + const auto p = c.get_data_ptr(); + EXPECT_EQ(p[0], 0x7f); + EXPECT_EQ(p[1], 0x00); + EXPECT_EQ(p[2], 0x81); + EXPECT_EQ(p[3], 0x7e); + + EXPECT_EQ(output, c.get_value_strings()); + + for (unsigned i = 0; i != input.size(); ++i) { + EXPECT_EQ(output[i], c.convert_value_to_string(i)); + } +} + +TEST(constant, f8e8m0_string_broadcast) { + Shape shape{4}; + op::v0::Constant c(element::f8e8m0, shape, std::vector{"1.5"}); + auto v = c.cast_vector(); + ASSERT_EQ(v.size(), shape_size(shape)); + EXPECT_THAT(v, Each(2.0f)); + + const auto p = c.get_data_ptr(); + EXPECT_EQ(p[0], 0x80); + EXPECT_EQ(p[1], 0x80); + EXPECT_EQ(p[2], 0x80); + EXPECT_EQ(p[3], 0x80); +} + +TEST(constant, f8e8m0_vector) { + op::v0::Constant c(element::f8e8m0, Shape{5}, std::vector{-1.5f, 4.0f, 2.0f, 1.5f, 3.0f}); + auto v = c.cast_vector(); + EXPECT_THAT(v, ElementsAre(std::numeric_limits::min() / 2, 4.0f, 2.0f, 2.0f, 2.0f)); + + const auto p = c.get_data_ptr(); + EXPECT_EQ(p[0], 0x00); + EXPECT_EQ(p[1], 0x81); + EXPECT_EQ(p[2], 0x80); + EXPECT_EQ(p[3], 0x80); + EXPECT_EQ(p[4], 0x80); +} + +TEST(constant, f8e8m0_from_float_vector) { + op::v0::Constant c(element::f8e8m0, Shape{5}, std::vector{-1.5f, 4.0f, 2.0f, 1.5f, 2.0f}); + auto v = c.cast_vector(); + EXPECT_THAT(v, ElementsAre(std::numeric_limits::min() / 2, 4.0f, 2.0f, 2.0f, 2.0f)); + + const auto p = c.get_data_ptr(); + EXPECT_EQ(p[0], 0x00); + EXPECT_EQ(p[1], 0x81); + EXPECT_EQ(p[2], 0x80); + EXPECT_EQ(p[3], 0x80); + EXPECT_EQ(p[4], 0x80); +} + +TEST(constant, f8e8m0_vector_broadcast) { + Shape shape{3}; + op::v0::Constant c(element::f8e8m0, shape, std::vector{1.5f}); + auto v = c.cast_vector(); + ASSERT_EQ(v.size(), shape_size(shape)); + EXPECT_THAT(v, Each(2.0f)); + + const auto p = c.get_data_ptr(); + EXPECT_EQ(0x80, p[0]); + EXPECT_EQ(0x80, p[1]); +} + +TEST(constant, f8e8m0_write_then_cast_custom_type) { + Shape shape{3}; + std::vector input{1.5f, 3.0f, 6.0f}; + std::vector expected{2.0f, 2.0f, 8.0f}; + op::v0::Constant c(element::f8e8m0, shape, input); + + auto v = c.cast_vector(); + + ASSERT_EQ(v.size(), shape_size(shape)); + EXPECT_EQ(v, expected); +} + template -::testing::AssertionResult test_convert() { +::testing::AssertionResult test_convert(vector expected = {1, 2, 3, 4, 6}) { Shape shape{5}; - vector expected{1, 2, 3, 4, 6}; auto c1 = std::make_shared(ov::element::from(), shape, expected); vector actual = c1->template cast_vector(); ::testing::AssertionResult rc = @@ -2063,6 +2173,7 @@ TEST(constant, convert_input) { EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); + EXPECT_TRUE((test_convert({1, 2, 4, 8, 16}))); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); @@ -2077,6 +2188,7 @@ TEST(constant, convert_input) { EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); + EXPECT_TRUE((test_convert({1, 2, 4, 8, 16}))); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); @@ -2091,6 +2203,7 @@ TEST(constant, convert_input) { EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); + EXPECT_TRUE((test_convert({1, 2, 4, 8, 16}))); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); @@ -2105,6 +2218,7 @@ TEST(constant, convert_input) { EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); + EXPECT_TRUE((test_convert({1, 2, 4, 8, 16}))); EXPECT_TRUE((test_convert())); EXPECT_TRUE((test_convert())); @@ -2237,6 +2351,7 @@ TEST(constant, construct_uniform) { EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); + EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); @@ -2251,6 +2366,7 @@ TEST(constant, construct_uniform) { EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); + EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); @@ -2265,6 +2381,7 @@ TEST(constant, construct_uniform) { EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); + EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); @@ -2279,6 +2396,7 @@ TEST(constant, construct_uniform) { EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); + EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); EXPECT_TRUE((test_uniform_ctor())); @@ -2547,10 +2665,10 @@ TEST(constant, lazy_bitwise_identical) { } TEST(constant, cast_vector) { - std::vector types = {element::boolean, element::bf16, element::f16, element::f32, element::f64, - element::i4, element::i8, element::i16, element::i32, element::i64, - element::u1, element::u2, element::u3, element::u4, element::u6, - element::u8, element::u16, element::u32, element::u64, element::f4e2m1}; + std::vector types = { + element::boolean, element::bf16, element::f16, element::f32, element::f64, element::i4, element::i8, + element::i16, element::i32, element::i64, element::u1, element::u2, element::u3, element::u4, + element::u6, element::u8, element::u16, element::u32, element::u64, element::f4e2m1, element::f8e8m0}; std::vector data = {0, 1, 0, 0, 1, 1, 0, 1}; std::vector expected_partial_data = {0, 1, 0, 0, 1, 1}; diff --git a/src/plugins/template/tests/functional/op_reference/base_reference_test.cpp b/src/plugins/template/tests/functional/op_reference/base_reference_test.cpp index a45d19c76175ef..b47b151f7707d6 100644 --- a/src/plugins/template/tests/functional/op_reference/base_reference_test.cpp +++ b/src/plugins/template/tests/functional/op_reference/base_reference_test.cpp @@ -114,6 +114,7 @@ void CommonReferenceTest::ValidateBlobs(const ov::Tensor& refBlob, case ov::element::u64: case ov::element::f8e4m3: case ov::element::f8e5m2: + case ov::element::f8e8m0: ov::test::utils::compare(refBlob, outBlob, abs_threshold, threshold); break; case ov::element::string: @@ -155,6 +156,13 @@ void CommonReferenceTest::ValidateBlobs(const ov::Tensor& refBlob, threshold, abs_threshold); break; + case ov::element::f8e8m0: + ov::test::utils::compare_raw_data(refBlob.data(), + outBlob.data(), + actual_comparision_size, + threshold, + abs_threshold); + break; case ov::element::f32: ov::test::utils::compare_raw_data(refBlob.data(), outBlob.data(), diff --git a/src/plugins/template/tests/functional/op_reference/convert_like.cpp b/src/plugins/template/tests/functional/op_reference/convert_like.cpp index e2798272a043a4..7d2f7f89d03c36 100644 --- a/src/plugins/template/tests/functional/op_reference/convert_like.cpp +++ b/src/plugins/template/tests/functional/op_reference/convert_like.cpp @@ -17,6 +17,10 @@ namespace ConversionOpsRefTestDefinitions { namespace { const auto f4e2m1_values = std::vector{0x0a, 0x2f, 0x49, 0x3b, 0x78, 0x05}; +const auto e8m0_min = std::numeric_limits::min(); // 2^-127 +const auto f32_min = std::numeric_limits::min() / 2; // 2^-127 +const auto f16_min = ov::float16::from_bits(0x1); // smallest greater than zero = 2^-25 +const auto bf16_min = std::numeric_limits::min() / 2; // 2^-127 INSTANTIATE_TEST_SUITE_P( smoke_Conversion_With_Hardcoded_Refs, @@ -118,6 +122,12 @@ INSTANTIATE_TEST_SUITE_P( // Unpacked data only first nibble got value, the other is 0 std::vector{0.0f, -1.0f, -0.5f, 6.0f, -4.0f, 1.5f}, std::vector{0.0f, 0.0f, -1.0f, 0.0f, -0.5f, 0.0f, 6.0f, 0.0f, -4.0f, 0.0f, 1.5f}), + ConvertParams(ConversionTypes::CONVERT_LIKE, + ov::PartialShape{6}, + ov::element::f8e8m0, + ov::element::bf16, + std::vector{0.0f, 1.0f, 0.5f, 6.0f, 4.0f, 1.5f}, + std::vector{bf16_min, 1.0f, 0.5f, 8.0f, 4.0f, 2.f}), // destination f16 ConvertParams(ConversionTypes::CONVERT_LIKE, @@ -165,6 +175,12 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f16, f4e2m1_values, std::vector{-1.0f, 0.0f, -6.0f, 1.0f, -0.5f, 2.0f, -1.5f, 1.5f, -0.0f, 6.0f, 3.0f}), + ConvertParams(ConversionTypes::CONVERT_LIKE, + ov::PartialShape{8}, + ov::element::f8e8m0, + ov::element::f16, + std::vector{-0.0f, -6.0f, 1.0f, 0.5f, 2.0f, 1.5f, 6.0f, 3.0f}, + std::vector{f16_min, f16_min, 1.0f, 0.5f, 2.0f, 2.0f, 8.0f, 2.0f}), // destination f32 ConvertParams(ConversionTypes::CONVERT_LIKE, ov::PartialShape{2, 2}, @@ -334,6 +350,12 @@ INSTANTIATE_TEST_SUITE_P( // only one nibble got value others are be 0 f4e2m1_values, std::vector{-1.0f, 0.0f, -6.0f, 1.0f, -0.5f, 2.0f, -1.5f, 1.5f, -0.0f, 6.0f, 3.0f}), + ConvertParams(ConversionTypes::CONVERT_LIKE, + ov::PartialShape{10}, + ov::element::f8e8m0, + ov::element::f32, + std::vector{-1.0f, -0.0f, 0.6f, 1.0f, 0.5f, 2.0f, 1.5f, 2.5f, 6.0f, 3.0f}, + std::vector{f32_min, f32_min, 0.5f, 1.0f, 0.5f, 2.0f, 2.0f, 2.0f, 8.0f, 2.0f}), ConvertParams(ConversionTypes::CONVERT_LIKE, ov::PartialShape{4}, ov::element::nf4, @@ -1848,7 +1870,29 @@ INSTANTIATE_TEST_SUITE_P( ov::element::f32, ov::element::f4e2m1, std::vector{-1.2f, 0.2f, -6.5f, 1.2f, -0.6f, 2.49f, -1.5f, 1.6f, -0.1f, 6.0f, 2.8f}, - f4e2m1_values)), + f4e2m1_values), + // destination f8e8m0 + ConvertParams( + ConversionTypes::CONVERT_LIKE, + ov::PartialShape{11}, + ov::element::f16, + ov::element::f8e8m0, + std::vector{-1.2f, 0.05f, 0.1f, 0.2f, 0.3f, 0.4f, 0.7f, 1.2f, 2.49f, 1.6f, 6.0f, 2.8f}, + std::vector{e8m0_min, 0.0625, 0.125, 0.25f, 0.25f, 0.5f, 0.5f, 1.0f, 2.0f, 2.0f, 8.0f, 2.0f}), + ConvertParams( + ConversionTypes::CONVERT_LIKE, + ov::PartialShape{12}, + ov::element::bf16, + ov::element::f8e8m0, + std::vector{-1.2f, 0.05f, 0.1f, 0.2f, 0.3f, 0.4f, 0.7f, 1.2f, 2.49f, 1.6f, 6.0f, 2.8f}, + std::vector{e8m0_min, 0.0625, 0.125, 0.25f, 0.25f, 0.5f, 0.5f, 1.0f, 2.0f, 2.0f, 8.0f, 2.0f}), + ConvertParams( + ConversionTypes::CONVERT_LIKE, + ov::PartialShape{12}, + ov::element::f32, + ov::element::f8e8m0, + std::vector{-1.2f, 0.05f, 0.1f, 0.2f, 0.3f, 0.4f, 0.7f, 1.2f, 2.49f, 1.6f, 6.0f, 2.8f}, + std::vector{e8m0_min, 0.0625, 0.125, 0.25f, 0.25f, 0.5f, 0.5f, 1.0f, 2.0f, 2.0f, 8.0f, 2.0f})), ReferenceConversionLayerTest::getTestCaseName); } // namespace } // namespace ConversionOpsRefTestDefinitions diff --git a/src/tests/test_utils/common_test_utils/include/common_test_utils/data_utils.hpp b/src/tests/test_utils/common_test_utils/include/common_test_utils/data_utils.hpp index d4df10444caac8..639c22067826e4 100644 --- a/src/tests/test_utils/common_test_utils/include/common_test_utils/data_utils.hpp +++ b/src/tests/test_utils/common_test_utils/include/common_test_utils/data_utils.hpp @@ -414,6 +414,10 @@ inline ov::float8_e5m2 ie_abs(const ov::float8_e5m2& val) { return ov::float8_e5m2::from_bits(val.to_bits() & 0x7F); } +inline ov::float8_e8m0 ie_abs(const ov::float8_e8m0& val) { + return val; +} + template static void compare_raw_data(const T_EXPECTED* expected, const T_ACTUAL* actual,