Efficient FP32 -> FP16 conversion for convert_precision, save_model, ovc and mo (#18988)

* WIP Postpone fp16 in CompressFloatConstantsImpl

* Apply suggestions from code review

Co-authored-by: Ilya Lavrenov <ilya.lavrenov@intel.com>

* WIP: Compression to FP16 in Serialize

* Prepared for efficient fp32 to fp16 conversion

* Update src/core/reference/src/runtime/reference/convert.cpp

* Called real slow reference implementations in the place where the optimized versions are supposed to be implemented

* Code style

* Fixed 0 values in the fast f64 to f16 compression

* Optimized convert_from_f32_to_f16_with_clamp

* Added optimized f32->f16 instance of change_constant_precision

* compression transformation Python test

* use tmp dir, minor corrections

* Update src/bindings/python/tests/test_transformations/test_compression.py

* Update src/bindings/python/tests/test_transformations/test_compression.py

* style fix

* define rt_info for postponed_fp16_compression

* remove redundant class

* fix temp dir for Win in test_compression.py

* update definitions in convert.hpp

* Update implementation in convert.cpp

* Update serialize.cpp

* Update compress_float_constants.cpp

* added macros for ARM/non_x86 in convert.cpp

* fix macros in convert.cpp

* change fixme placement in serialize.cpp

* style_fix

* Update src/core/reference/src/runtime/reference/convert.cpp

* style_fix

* Optimized count_out_of_f16_range

* Code style

* Revert unused

* Update src/core/src/pass/serialize.cpp

Co-authored-by: Ilya Lavrenov <ilya.lavrenov@intel.com>

* Update src/core/reference/src/runtime/reference/convert.cpp

Co-authored-by: Ilya Lavrenov <ilya.lavrenov@intel.com>

* use optimized convert_from_f32_to_f16_with_clamp for non postponed

* minor corrections

* Update src/common/transformations/src/transformations/common_optimizations/compress_float_constants.cpp

* Update compress_float_constants.cpp

* Switched mo and ovc to save_model instead of serialize to leverage performance improvements in fp32->fp16

* Applied minor code imporvements to address review feedback

* Minor changes in code

* Update tools/ovc/openvino/tools/ovc/main.py

* Apply suggestions from code review

* Fixed failed test in case when both usual xml compression and fp16 compression are applied simultaneously (disabled for now)

* Added description for CompressFloatConstantImpl postponed parameter

* Description of postponed parameter for CompressFloatConstants

* Reverted switching to save_model in mo as the compression can be applied not only via CLI and old code should be kept for Python path (not applicable for ovc)

* Removed remining committed test artefacts and reverted remaining changes in mo

---------

Co-authored-by: Ilya Lavrenov <ilya.lavrenov@intel.com>
Co-authored-by: dmitrygo <dmitry.gorokhov@intel.com>
Co-authored-by: Vladimir Paramuzov <vladimir.paramuzov@intel.com>
Co-authored-by: Pavel Esir <pavel.esir@intel.com>
Co-authored-by: Pavel Esir <pavel.esir@gmail.com>
This commit is contained in:
authored and GitHub committed 2023-08-17 11:08:33 +00:00
1 parent 2394732055
commit f0300a36eb
11 files changed
+666 -44

No files matched your search

@@ -24,7 +24,13 @@ class TRANSFORMATIONS_API CompressFloatConstants;
class ov::pass::CompressFloatConstantsImpl : public ov::pass::MatcherPass {
public:
OPENVINO_RTTI("CompressFloatConstantsImpl", "0");
CompressFloatConstantsImpl();
/// @brief Transformation constructor
/// @param postponed If true then the transformation won't compress the constants
/// keeping them in the original type but still will insert Converts. This is
/// a special mode of operation that requires another transformation to
/// apply a real compression on constants. Constants eligible for
/// postponed compression are marked with a special rt_info tag.
CompressFloatConstantsImpl(bool postponed = false);
};
/**
@@ -44,8 +50,10 @@ public:
class ov::pass::CompressFloatConstants : public ov::pass::GraphRewrite {
public:
OPENVINO_RTTI("CompressFloatConstants", "0");
CompressFloatConstants() {
add_matcher<ov::pass::CompressFloatConstantsImpl>();
/// @brief Transformation constructor
/// @param postponed Postponed compression, see ov::pass::CompressFloatConstantsImpl for details.
CompressFloatConstants(bool postponed = false) {
add_matcher<ov::pass::CompressFloatConstantsImpl>(postponed);
add_matcher<ov::pass::AddOldApiMapToParameters>();
}
};
@@ -16,6 +16,12 @@ TRANSFORMATIONS_API void enable_fp16_compression(const std::shared_ptr<Node>& no
TRANSFORMATIONS_API bool fp16_compression_is_disabled(const std::shared_ptr<const Node>& node);
TRANSFORMATIONS_API void postpone_fp16_compression(RTMap& rt_info);
TRANSFORMATIONS_API bool is_fp16_compression_postponed(const RTMap& rt_info);
TRANSFORMATIONS_API void do_not_postpone_fp16_compression(RTMap& rt_info);
/**
* @ingroup ie_runtime_attr_api
* @brief DisableFP16Compression class represents runtime info attribute that marks operation
@@ -5,6 +5,7 @@
#include "transformations/common_optimizations/compress_float_constants.hpp"
#include "itt.hpp"
#include "ngraph/runtime/reference/convert.hpp"
#include "openvino/core/rt_info.hpp"
#include "openvino/op/constant.hpp"
#include "openvino/op/convert.hpp"
@@ -16,7 +17,8 @@
namespace {
template <ov::element::Type_t PREC_FROM>
std::shared_ptr<ov::Node> change_constant_precision_to_fp16(std::shared_ptr<ov::op::v0::Constant>& constant) {
std::shared_ptr<ov::Node> change_constant_precision_to_fp16(std::shared_ptr<ov::op::v0::Constant>& constant,
bool postponed = false) {
using src_type = typename ov::element_type_traits<PREC_FROM>::value_type;
const auto* src_data = constant->get_data_ptr<src_type>();
@@ -24,9 +26,10 @@ std::shared_ptr<ov::Node> change_constant_precision_to_fp16(std::shared_ptr<ov::
auto new_constant = std::make_shared<ov::op::v0::Constant>(ov::element::f16, constant->get_shape());
auto* dst_data = const_cast<ov::float16*>(reinterpret_cast<const ov::float16*>(new_constant->get_data_ptr()));
if (dst_data == nullptr)
if (!dst_data || !size)
return nullptr;
// slow implementation: is used when optimized ones are not available: f64 or for ARM (both for f64 and f32)
int num_out_of_range = 0;
for (size_t i = 0; i < size; ++i) {
// if abs value is smaller than the smallest positive fp16, but not zero
@@ -44,18 +47,24 @@ std::shared_ptr<ov::Node> change_constant_precision_to_fp16(std::shared_ptr<ov::
}
// if more than 75% of a FP32 constant do not fit into FP16 keep in FP32
float keep_threshold = 0.75f;
float out_of_range_proportion = static_cast<float>(num_out_of_range) / static_cast<float>(size);
const float keep_threshold = 0.75f;
const float out_of_range_proportion = static_cast<float>(num_out_of_range) / static_cast<float>(size);
if (out_of_range_proportion >= keep_threshold) {
return nullptr;
}
return new_constant;
if (postponed) {
// dispose just converted constant to avoid allocation too much memory
// it will be converted again while serialization
return constant;
} else {
return new_constant;
}
}
} // namespace
ov::pass::CompressFloatConstantsImpl::CompressFloatConstantsImpl() {
ov::pass::CompressFloatConstantsImpl::CompressFloatConstantsImpl(bool postponed) {
MATCHER_SCOPE(CompressFloatConstantsImpl);
auto const_pattern = pattern::wrap_type<ov::op::v0::Constant>();
@@ -72,26 +81,68 @@ ov::pass::CompressFloatConstantsImpl::CompressFloatConstantsImpl() {
auto c_type = const_node->get_element_type();
std::shared_ptr<ov::Node> new_const;
#if !defined(OPENVINO_ARCH_X86) && !defined(OPENVINO_ARCH_X86_64)
if (c_type == ov::element::f32) {
new_const = change_constant_precision_to_fp16<ov::element::Type_t::f32>(const_node);
new_const = change_constant_precision_to_fp16<ov::element::Type_t::f32>(const_node, postponed);
} else if (c_type == ov::element::f64) {
new_const = change_constant_precision_to_fp16<ov::element::Type_t::f64>(const_node);
new_const = change_constant_precision_to_fp16<ov::element::Type_t::f64>(const_node, postponed);
}
if (!new_const) // if out of range > threshold -> then new_const == nullptr
return false;
#else
if (c_type == ov::element::f32) {
auto size = shape_size(const_node->get_output_shape(0));
if (size == 0)
return false;
auto num_out_of_range =
ngraph::runtime::reference::count_out_of_f16_range(const_node->get_data_ptr<ov::element::f32>(), size);
// if more than 75% of a FP32 constant do not fit into FP16 keep in FP32
const float keep_threshold = 0.75f;
const float out_of_range_proportion = static_cast<float>(num_out_of_range) / static_cast<float>(size);
if (out_of_range_proportion >= keep_threshold)
return false;
if (postponed) {
new_const = const_node;
} else {
const auto* src_data = const_node->get_data_ptr<float>();
auto compressed_const =
std::make_shared<ov::op::v0::Constant>(ov::element::f16, const_node->get_shape());
auto* dst_data =
const_cast<ov::float16*>(reinterpret_cast<const ov::float16*>(compressed_const->get_data_ptr()));
OPENVINO_ASSERT(dst_data);
ngraph::runtime::reference::convert_from_f32_to_f16_with_clamp(src_data, dst_data, size);
new_const = compressed_const;
}
} else if (c_type == ov::element::f64) {
new_const = change_constant_precision_to_fp16<ov::element::Type_t::f64>(const_node, postponed);
} else {
return false;
}
#endif // !defined(OPENVINO_ARCH_X86) && !defined(OPENVINO_ARCH_X86_64)
if (!new_const) {
return false;
}
auto constant_target_inputs = const_node->get_output_target_inputs(0);
auto convert = std::make_shared<ov::op::v0::Convert>(new_const, const_node->get_element_type());
new_const->set_friendly_name(const_node->get_friendly_name() + "_compressed");
convert->set_friendly_name(const_node->get_friendly_name());
new_const->set_friendly_name(const_node->get_friendly_name() + "_compressed");
ov::copy_runtime_info(const_node, convert);
ov::mark_as_decompression(convert);
if (postponed) {
postpone_fp16_compression(new_const->get_rt_info());
postpone_fp16_compression(new_const->get_output_tensor(0).get_rt_info());
ov::replace_node(const_node, convert);
for (const auto& target_input : constant_target_inputs) {
target_input.replace_source_output(convert);
}
} else {
ov::replace_node(const_node, convert);
}
return true;
};
@@ -895,6 +895,26 @@ std::shared_ptr<ngraph::Node> change_constant_precision(std::shared_ptr<opset4::
return new_constant;
}
template <>
std::shared_ptr<Node> change_constant_precision<ov::element::Type_t::f32, ov::element::Type_t::f16>(
std::shared_ptr<opset4::Constant>& constant) {
using src_type = typename element_type_traits<ov::element::Type_t::f32>::value_type;
using dst_type = typename element_type_traits<ov::element::Type_t::f16>::value_type;
const auto* src_data = constant->get_data_ptr<src_type>();
const auto size = shape_size(constant->get_shape());
auto new_constant = std::make_shared<opset4::Constant>(ov::element::Type_t::f16, constant->get_shape());
new_constant->output(0).set_names(constant->output(0).get_names());
auto* dst_data = const_cast<dst_type*>(reinterpret_cast<const dst_type*>(new_constant->get_data_ptr()));
if (dst_data == nullptr)
OPENVINO_THROW("Can't get destination data pointer");
ngraph::runtime::reference::convert_from_f32_to_f16_with_clamp(src_data, dst_data, size);
return new_constant;
}
template <>
std::shared_ptr<Node> change_constant_precision<ov::element::Type_t::f16, ov::element::Type_t::f32>(
std::shared_ptr<opset4::Constant>& constant) {
@@ -4,6 +4,10 @@
#include "transformations/rt_info/disable_fp16_compression.hpp"
namespace {
const std::string& postponed_fp16_compression_tag = "postponed_fp16_compression";
}
void ov::disable_fp16_compression(const std::shared_ptr<Node>& node) {
auto& rt_info = node->get_rt_info();
rt_info[DisableFP16Compression::get_type_info_static()] = DisableFP16Compression{};
@@ -18,3 +22,15 @@ bool ov::fp16_compression_is_disabled(const std::shared_ptr<const Node>& node) {
const auto& rt_info = node->get_rt_info();
return rt_info.count(DisableFP16Compression::get_type_info_static());
}
void ov::postpone_fp16_compression(ov::RTMap& rt_info) {
rt_info[postponed_fp16_compression_tag] = true;
}
bool ov::is_fp16_compression_postponed(const ov::RTMap& rt_info) {
return rt_info.count(postponed_fp16_compression_tag);
}
void ov::do_not_postpone_fp16_compression(ov::RTMap& rt_info) {
rt_info.erase(postponed_fp16_compression_tag);
}