From db7dcae290e77387e2ef074a94299bf3ce91e397 Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Mon, 4 Aug 2025 13:35:52 +0200 Subject: [PATCH 01/12] Extract xml deserializer as dev API util Signed-off-by: Raasz, Pawel --- src/common/util/src/xml_parse_utils.cpp | 2 +- src/core/CMakeLists.txt | 1 + src/core/xml_util/CMakeLists.txt | 42 ++ .../xml_util/xml_deserialize_util.hpp} | 154 +--- .../xml_util/src/xml_deserialize_util.cpp} | 694 +++++++++++++----- src/frontends/ir/src/CMakeLists.txt | 3 +- src/frontends/ir/src/input_model.cpp | 4 +- 7 files changed, 609 insertions(+), 291 deletions(-) create mode 100644 src/core/xml_util/CMakeLists.txt rename src/{frontends/ir/src/ir_deserializer.hpp => core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp} (57%) rename src/{frontends/ir/src/ir_deserializer.cpp => core/xml_util/src/xml_deserialize_util.cpp} (72%) diff --git a/src/common/util/src/xml_parse_utils.cpp b/src/common/util/src/xml_parse_utils.cpp index 3c347ca79f34e5..6c01ab91921664 100644 --- a/src/common/util/src/xml_parse_utils.cpp +++ b/src/common/util/src/xml_parse_utils.cpp @@ -233,4 +233,4 @@ int pugixml::get_int_child(const pugi::xml_node& node, const char* str, int defV } } // namespace util -} // namespace ov \ No newline at end of file +} // namespace ov diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e594fabc292748..1dadebdf3e66d1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -17,6 +17,7 @@ file(GLOB_RECURSE DEV_HEADERS ${OV_CORE_DEV_API_PATH}/*.hpp) add_subdirectory(reference) add_subdirectory(shape_inference) +add_subdirectory(xml_util) set(MIXED_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/runtime/allocator.cpp" diff --git a/src/core/xml_util/CMakeLists.txt b/src/core/xml_util/CMakeLists.txt new file mode 100644 index 00000000000000..acfdd2036af6e5 --- /dev/null +++ b/src/core/xml_util/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (C) 2018-2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +set(TARGET_NAME "openvino_xml_util") + +set(TARGET_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") + +file(GLOB_RECURSE LIBRARY_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) +file(GLOB_RECURSE PUBLIC_HEADERS ${TARGET_INCLUDE_DIR}/*.hpp) + +# Create named folders for the sources within the .vcproj +# Empty name lists them directly under the .vcproj + +source_group("src" FILES ${LIBRARY_SRC}) +source_group("include" FILES ${PUBLIC_HEADERS}) + +# Create static library +add_library(${TARGET_NAME} STATIC ${LIBRARY_SRC} ${PUBLIC_HEADERS}) + +add_library(openvino::xml_util ALIAS ${TARGET_NAME}) +set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME openvino_xml_util) + +target_include_directories(${TARGET_NAME} PUBLIC + $ + $ + $) + +ov_add_clang_format_target(${TARGET_NAME}_clang FOR_TARGETS ${TARGET_NAME}) + +if(NOT BUILD_SHARED_LIBS) + target_compile_definitions(${TARGET_NAME} PUBLIC OPENVINO_STATIC_LIBRARY) +endif() + +ov_build_target_faster(${TARGET_NAME} PCH) + +# developer package + +ov_install_static_lib(${TARGET_NAME} ${OV_CPACK_COMP_CORE}) + +ov_developer_package_export_targets(TARGET ${TARGET_NAME} + INSTALL_INCLUDE_DIRECTORIES "${TARGET_INCLUDE_DIR}/") diff --git a/src/frontends/ir/src/ir_deserializer.hpp b/src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp similarity index 57% rename from src/frontends/ir/src/ir_deserializer.hpp rename to src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp index 3e085562174b25..3413aa1ea0e793 100644 --- a/src/frontends/ir/src/ir_deserializer.hpp +++ b/src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp @@ -4,141 +4,56 @@ #pragma once -#include -#include -#include +#include +#include #include +#include +#include -#include "input_model.hpp" -#include "openvino/core/attribute_visitor.hpp" #include "openvino/core/op_extension.hpp" +#include "openvino/core/type/element_type.hpp" +#include "openvino/core/visibility.hpp" #include "openvino/op/loop.hpp" -#include "openvino/op/util/sub_graph_base.hpp" +#include "openvino/op/util/multi_subgraph_base.hpp" #include "openvino/opsets/opset.hpp" #include "openvino/runtime/aligned_buffer.hpp" -#include "utils.hpp" -namespace ov { +namespace ov::util { +struct GenericLayerParams; -struct GenericLayerParams { - struct LayerPortData { - size_t portId; - std::vector dims; - ov::element::Type_t precision; - std::unordered_set names; - }; - size_t layerId; - std::string version; - std::string name; - std::string type; - std::vector inputPorts; - std::vector outputPorts; - - size_t get_real_input_port_id(size_t id) const { - size_t real_id = 0; - for (auto& it : inputPorts) { - if (it.portId == id) { - return real_id; - } - ++real_id; - } - OPENVINO_THROW("Can not find input port with id ", id, " in layer ", name); - } - - size_t get_real_output_port_id(size_t id) const { - size_t real_id = 0; - for (auto& it : outputPorts) { - if (it.portId == id) { - return real_id; - } - ++real_id; - } - OPENVINO_THROW("Can not find output port with id ", id, " in layer ", name); - } -}; - -class XmlDeserializer : public ov::AttributeVisitor { +class OPENVINO_API XmlDeserializer : public ov::AttributeVisitor { public: explicit XmlDeserializer(const pugi::xml_node& node, const std::shared_ptr& weights, const std::unordered_map& opsets, const std::unordered_map& extensions, std::unordered_map>& variables, - size_t version) - : m_node(node), - m_weights(weights), - m_opsets(opsets), - m_extensions(extensions), - m_variables(variables), - m_version(version) {} - - void on_adapter(const std::string& name, ov::ValueAccessor& value) override { - std::string val; - if (!getStrAttribute(m_node.child("data"), name, val)) - return; - value.set(val); - } - void on_adapter(const std::string& name, ov::ValueAccessor& value) override { - std::string val; - if (!getStrAttribute(m_node.child("data"), name, val)) - return; - std::transform(val.begin(), val.end(), val.begin(), [](char ch) { - return std::tolower(static_cast(ch)); - }); - std::set true_names{"true", "1"}; - std::set false_names{"false", "0"}; - - bool is_true = true_names.find(val) != true_names.end(); - bool is_false = false_names.find(val) != false_names.end(); - - if (!is_true && !is_false) - return; - value.set(is_true); - } + size_t version); + + void on_adapter(const std::string& name, ov::ValueAccessor& value) override; + + void on_adapter(const std::string& name, ov::ValueAccessor& value) override; void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - std::string val; - if (!getStrAttribute(m_node.child("data"), name, val)) - return; - adapter.set(stringToType(val)); - } - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - std::string val; - if (!getStrAttribute(m_node.child("data"), name, val)) - return; - adapter.set(stringToType(val)); - } + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - std::vector value; - if (!getParameters(m_node.child("data"), name, value)) - return; - adapter.set(value); - } + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - std::vector value; - if (!getParameters(m_node.child("data"), name, value)) - return; - adapter.set(value); - } + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - std::vector value; - if (!getParameters(m_node.child("data"), name, value)) - return; - adapter.set(value); - } + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - std::vector value; - if (!getParameters(m_node.child("data"), name, value)) - return; - adapter.set(value); - } + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; + +protected: + virtual ov::Any parse_weightless_cache_attribute(const pugi::xml_node& node) const; + virtual void set_constant_num_buffer(ov::AttributeAdapter>& adapter); + + const pugi::xml_node& get_node() const; + const std::shared_ptr& get_weights() const; private: struct IoMap { @@ -187,6 +102,16 @@ class XmlDeserializer : public ov::AttributeVisitor { const std::unordered_set& names, const pugi::xml_node& root_section); + virtual std::unique_ptr make_visitor( + const pugi::xml_node& node, + const std::shared_ptr& weights, + const std::unordered_map& opsets, + const std::unordered_map& extensions, + std::unordered_map>& variables, + size_t version) const { + return std::make_unique(node, weights, opsets, extensions, variables, version); + } + // -- DATA -- const pugi::xml_node m_node; const std::shared_ptr& m_weights; @@ -202,4 +127,5 @@ class XmlDeserializer : public ov::AttributeVisitor { int64_t m_version; }; -} // namespace ov + +} // namespace ov::util diff --git a/src/frontends/ir/src/ir_deserializer.cpp b/src/core/xml_util/src/xml_deserialize_util.cpp similarity index 72% rename from src/frontends/ir/src/ir_deserializer.cpp rename to src/core/xml_util/src/xml_deserialize_util.cpp index 0fcc105e8c3eee..1f25e73008c229 100644 --- a/src/frontends/ir/src/ir_deserializer.cpp +++ b/src/core/xml_util/src/xml_deserialize_util.cpp @@ -2,40 +2,119 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "ir_deserializer.hpp" +#include "openvino/xml_util/xml_deserialize_util.hpp" -#include #include -#include #include #include "openvino/core/descriptor_tensor.hpp" -#include "openvino/core/except.hpp" #include "openvino/core/meta_data.hpp" #include "openvino/core/rt_info/weightless_caching_attributes.hpp" #include "openvino/core/type.hpp" -#include "openvino/core/type/element_type.hpp" +#include "openvino/core/type/element_iterator.hpp" +#include "openvino/core/type/element_type_traits.hpp" #include "openvino/op/constant.hpp" -#include "openvino/op/loop.hpp" #include "openvino/op/parameter.hpp" #include "openvino/op/result.hpp" #include "openvino/op/util/assign_base.hpp" #include "openvino/op/util/framework_node.hpp" #include "openvino/op/util/op_types.hpp" #include "openvino/op/util/read_value_base.hpp" -#include "openvino/op/util/sub_graph_base.hpp" #include "openvino/op/util/variable.hpp" -#include "openvino/runtime/aligned_buffer.hpp" #include "openvino/runtime/shared_buffer.hpp" #include "openvino/runtime/string_aligned_buffer.hpp" +#include "openvino/util/common_util.hpp" #include "openvino/util/xml_parse_utils.hpp" -#include "rt_info_deserializer.hpp" #include "transformations/rt_info/attributes.hpp" -#include "utils.hpp" - -using namespace ov::util; +namespace ov::util { namespace { + +bool getStrAttribute(const pugi::xml_node& node, const std::string& name, std::string& value) { + if (!node) + return false; + + auto attr = node.attribute(name.c_str()); + if (attr.empty()) + return false; + value = std::string(attr.value()); + return true; +} + +template +void str_to_container(const std::string& value, T& res) { + std::stringstream ss(value); + std::string field; + while (getline(ss, field, ',')) { + if (field.empty()) + OPENVINO_THROW("Cannot get vector of parameters! \"", value, "\" is incorrect"); + std::stringstream fs(field); + typename T::value_type val; + fs >> val; + res.insert(res.end(), val); + } +} + +template +bool getParameters(const pugi::xml_node& node, const std::string& name, std::vector& value) { + std::string param; + if (!getStrAttribute(node, name, param)) + return false; + str_to_container(param, value); + return true; +} + +template +T stringToType(const std::string& valStr) { + T ret{0}; + std::istringstream ss(valStr); + if (!ss.eof()) { + ss >> ret; + } + return ret; +} + +bool get_partial_shape_from_attribute(const pugi::xml_node& node, const std::string& name, PartialShape& value) { + std::string param; + if (!getStrAttribute(node, name, param)) + return false; + value = PartialShape(param); + return true; +} + +bool get_dimension_from_attribute(const pugi::xml_node& node, const std::string& name, Dimension& value) { + std::string param; + if (!getStrAttribute(node, name, param)) + return false; + value = Dimension(param); + return true; +} + +void str_to_set_of_strings(const std::string& value, std::set& res) { + std::stringstream ss(value); + std::string field; + while (getline(ss, field, ',')) { + // trim leading and trailing whitespaces + auto strBegin = field.find_first_not_of(" "); + if (strBegin == std::string::npos) + OPENVINO_THROW("Cannot get a set of strings from \"", value, "\". Value \"", field, "\" is incorrect"); + auto strRange = field.find_last_not_of(" ") - strBegin + 1; + + res.insert(field.substr(strBegin, strRange)); + } +} + +void str_to_container(const std::string& value, std::vector& res) { + std::stringstream ss(value); + std::string field; + while (getline(ss, field, ',')) { + field = ov::util::trim(field); + if (!field.empty()) { + res.emplace_back(field); + } + } +} + /** * @brief Function deserializing tensor names. * @@ -58,7 +137,7 @@ std::unordered_set deserialize_tensor_names(const std::string_view& *name_inserter = std::regex_replace(std::string(name_view), escaped_delim, delim); } start = pos; - } else if (pos > 0 && tensor_names[pos - 1] == esc_char) { + } else if (auto delim_pos = pos - 1; delim_pos != std::string::npos && tensor_names[delim_pos] == esc_char) { ++pos; } else { if (auto length = pos - start; length > 0) { @@ -73,8 +152,356 @@ std::unordered_set deserialize_tensor_names(const std::string_view& } } // namespace -ov::XmlDeserializer::IoMap ov::XmlDeserializer::updated_io_map(const pugi::xml_node& node, - const pugi::xml_node& body_node) { +struct GenericLayerParams { + struct LayerPortData { + size_t portId; + std::vector dims; + ov::element::Type_t precision; + std::unordered_set names; + }; + size_t layerId; + std::string version; + std::string name; + std::string type; + std::vector inputPorts; + std::vector outputPorts; + + size_t get_real_input_port_id(size_t id) const { + size_t real_id = 0; + for (auto& it : inputPorts) { + if (it.portId == id) { + return real_id; + } + ++real_id; + } + OPENVINO_THROW("Can not find input port with id ", id, " in layer ", name); + } + + size_t get_real_output_port_id(size_t id) const { + size_t real_id = 0; + for (auto& it : outputPorts) { + if (it.portId == id) { + return real_id; + } + ++real_id; + } + OPENVINO_THROW("Can not find output port with id ", id, " in layer ", name); + } +}; + +class MetaDataParser : public ov::MetaDataWithPugixml { +public: + MetaDataParser(const std::string& name, const pugi::xml_node& meta, bool accessible_by_pugixml_node = true) + : m_name(name), + m_accessible_by_pugixml_node(accessible_by_pugixml_node) { + m_meta.append_copy(meta); + if (accessible_by_pugixml_node) { + m_meta_node = m_meta.child(m_name.c_str()); + } else { + m_meta_node = pugi::xml_node(); + } + } + + operator const ov::AnyMap&() const override { + parse(); + return m_parsed_map; + } + + operator ov::AnyMap&() override { + parse(); + return m_parsed_map; + } + + const pugi::xml_node& get_pugi_node() const override { + if (!m_meta_node.empty() && !m_accessible_by_pugixml_node) { + // Meta cannot be accessed by pugixml node. Return empty node + m_meta_node = pugi::xml_node(); + } + return m_meta_node; + }; + +private: + bool has_attr(const pugi::xml_node& node, const std::string& name = "value") const { + auto attr = node.attribute(name.c_str()); + return !attr.empty(); + } + + ov::Any parse_value(const pugi::xml_node& node) const { + if (has_attr(node)) { + return pugixml::get_str_attr(node, "value"); + } else if (std::string(node.name()) == "unset" && has_attr(node, "unset_cli_parameters")) { + return pugixml::get_str_attr(node, "unset_cli_parameters"); + } else { + return parse_node(node); + } + } + + ov::AnyMap parse_node(const pugi::xml_node& node) const { + ov::AnyMap result; + // Old version may produce nodes like , but it may brake xml-naming convention + // Now it should look like . + // Also we keep an option to read an old XMLs where it doesn't have name attribute + const auto name_attr = node.attribute("name"); + const std::string node_name = name_attr.empty() ? node.name() : name_attr.value(); + for (const auto& data : node.children()) { + const auto name_attr = data.attribute("name"); + const std::string data_name = name_attr.empty() ? data.name() : name_attr.value(); + // WA for legacy POT config + if (data_name == "config" && node_name == "quantization_parameters") { + // Read legacy pot config + std::stringstream stream; + data.print(stream); + std::string str_config = stream.str(); + str_config = std::regex_replace(str_config, std::regex(""), ""); + str_config = std::regex_replace(str_config, std::regex(""), ""); + str_config = std::regex_replace(str_config, std::regex("\n"), ""); + str_config = std::regex_replace(str_config, std::regex("( +)"), " "); + result[data_name] = str_config; + } else { + result[data_name] = parse_value(data); + } + } + return result; + } + + void parse() const { + std::call_once(m_oc, [this]() { + m_accessible_by_pugixml_node = false; + const pugi::xml_node& node = m_meta.child(m_name.c_str()); + m_parsed_map = parse_node(node); + }); + } + + pugi::xml_document m_meta; + const std::string m_name; + mutable ov::AnyMap m_parsed_map; + mutable std::once_flag m_oc; + mutable std::atomic_bool m_accessible_by_pugixml_node; + mutable pugi::xml_node m_meta_node; +}; + +class RTInfoDeserializer : public ov::AttributeVisitor { +public: + explicit RTInfoDeserializer(const pugi::xml_node& node) : m_node(node) {} + + void on_adapter(const std::string& name, ov::ValueAccessor& value) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + value.set(val); + } + + void on_adapter(const std::string& name, ov::ValueAccessor& value) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + std::transform(val.begin(), val.end(), val.begin(), [](char ch) { + return std::tolower(static_cast(ch)); + }); + std::set true_names{"true", "1"}; + std::set false_names{"false", "0"}; + + bool is_true = true_names.find(val) != true_names.end(); + bool is_false = false_names.find(val) != false_names.end(); + + if (!is_true && !is_false) + return; + value.set(is_true); + } + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + if (auto a = as_type>>(&adapter)) { + std::set ss; + str_to_set_of_strings(val, ss); + a->set(ss); + } else { + OPENVINO_NOT_IMPLEMENTED; + } + } + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + adapter.set(stringToType(val)); + } + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + adapter.set(stringToType(val)); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + OPENVINO_THROW("Model type is unsupported for rt info deserialization"); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + std::vector value; + str_to_container(val, value); + adapter.set(value); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + std::vector value; + str_to_container(val, value); + adapter.set(value); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + std::vector value; + str_to_container(val, value); + adapter.set(value); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + std::vector value; + str_to_container(val, value); + adapter.set(value); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + std::string val; + if (!getStrAttribute(m_node, name, val)) + return; + std::vector value; + str_to_container(val, value); + adapter.set(value); + } + + void check_attribute_name(const std::string& name) const { + OPENVINO_ASSERT(name != "name" && name != "version", + "Attribute key with name: ", + name, + " is not allowed. Please use another name"); + } + +private: + pugi::xml_node m_node; +}; + +XmlDeserializer::XmlDeserializer(const pugi::xml_node& node, + const std::shared_ptr& weights, + const std::unordered_map& opsets, + const std::unordered_map& extensions, + std::unordered_map>& variables, + size_t version) + : m_node(node), + m_weights(weights), + m_opsets(opsets), + m_extensions(extensions), + m_variables(variables), + m_version(version) {} + +ov::Any XmlDeserializer::parse_weightless_cache_attribute(const pugi::xml_node& node) const { + ov::Any wl_attr; + if (const auto data_node = node.child("data")) { + const auto size = data_node.attribute("size"); + const auto offset = data_node.attribute("offset"); + const auto element_type = data_node.attribute("element_type"); + if (size && offset && element_type) { + wl_attr = ov::WeightlessCacheAttribute(static_cast(pugixml::get_uint64_attr(data_node, "size")), + static_cast(pugixml::get_uint64_attr(data_node, "offset")), + ov::element::Type(pugixml::get_str_attr(data_node, "element_type"))); + } + } + + return wl_attr; +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& value) { + std::string val; + if (!getStrAttribute(m_node.child("data"), name, val)) + return; + value.set(val); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& value) { + std::string val; + if (!getStrAttribute(m_node.child("data"), name, val)) + return; + std::transform(val.begin(), val.end(), val.begin(), [](char ch) { + return std::tolower(static_cast(ch)); + }); + std::set true_names{"true", "1"}; + std::set false_names{"false", "0"}; + + bool is_true = true_names.find(val) != true_names.end(); + bool is_false = false_names.find(val) != false_names.end(); + + if (!is_true && !is_false) + return; + value.set(is_true); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + std::string val; + if (!getStrAttribute(m_node.child("data"), name, val)) + return; + adapter.set(stringToType(val)); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + std::string val; + if (!getStrAttribute(m_node.child("data"), name, val)) + return; + adapter.set(stringToType(val)); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + std::vector value; + if (!getParameters(m_node.child("data"), name, value)) + return; + adapter.set(value); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + std::vector value; + if (!getParameters(m_node.child("data"), name, value)) + return; + adapter.set(value); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + std::vector value; + if (!getParameters(m_node.child("data"), name, value)) + return; + adapter.set(value); +} + +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + std::vector value; + if (!getParameters(m_node.child("data"), name, value)) + return; + adapter.set(value); +} + +XmlDeserializer::IoMap XmlDeserializer::updated_io_map(const pugi::xml_node& node, const pugi::xml_node& body_node) { if (body_node.empty()) { OPENVINO_THROW("Missing body part."); } @@ -96,7 +523,7 @@ ov::XmlDeserializer::IoMap ov::XmlDeserializer::updated_io_map(const pugi::xml_n return extend_io_map; } -std::vector> ov::XmlDeserializer::parse_input_description( +std::vector> XmlDeserializer::parse_input_description( const pugi::xml_node& node, const std::string& body_name, const std::string& port_map_name) { @@ -170,9 +597,9 @@ std::vector> ov::Xml } std::vector> -ov::XmlDeserializer::parse_output_description(const pugi::xml_node& node, - const std::string& body_name, - const std::string& port_map_name) { +XmlDeserializer::parse_output_description(const pugi::xml_node& node, + const std::string& body_name, + const std::string& port_map_name) { std::vector> outputs; auto body_node = node.child(body_name.c_str()); const auto up_io_map = updated_io_map(node, body_node); @@ -226,7 +653,7 @@ ov::XmlDeserializer::parse_output_description(const pugi::xml_node& node, return outputs; } -ov::op::v5::Loop::SpecialBodyPorts ov::XmlDeserializer::parse_purpose_attribute(const pugi::xml_node& node) { +ov::op::v5::Loop::SpecialBodyPorts XmlDeserializer::parse_purpose_attribute(const pugi::xml_node& node) { ov::op::v5::Loop::SpecialBodyPorts result = {-1, -1}; auto body_node = node.child("body"); const auto up_io_map = updated_io_map(node, body_node); @@ -268,7 +695,7 @@ ov::op::v5::Loop::SpecialBodyPorts ov::XmlDeserializer::parse_purpose_attribute( return result; } -void ov::XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { static const std::unordered_set skip_names = {"input_descriptions", "output_descriptions", "special_body_ports", @@ -380,36 +807,7 @@ void ov::XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor< value.copy(data, value.size()); a->set(buffer); } else if (name == "value" && type == "Const") { - std::vector shape; - std::string el_type_str; - - size_t offset = static_cast(pugixml::get_uint64_attr(dn, "offset")); - size_t size = static_cast(pugixml::get_uint64_attr(dn, "size")); - if (!getStrAttribute(dn, "element_type", el_type_str)) - return; - if (!getParameters(dn, "shape", shape)) - return; - - ov::element::Type el_type = ov::element::Type(el_type_str); - - if (!m_weights) - OPENVINO_THROW("Empty weights data in bin file or bin file cannot be found!"); - if (m_weights->size() < offset + size) - OPENVINO_THROW("Incorrect weights in bin file!"); - char* data = m_weights->get_ptr() + offset; - - if (el_type == element::string) { - auto buffer = - ov::AttributeAdapter>::unpack_string_tensor(data, size); - a->set(buffer); - } else { - if (size < ((ov::shape_size(shape) * el_type.bitwidth() + 7) >> 3)) - OPENVINO_THROW("Attribute and shape size are inconsistent for ", type, " op!"); - - auto buffer = - std::make_shared>>(data, size, m_weights); - a->set(buffer); - } + set_constant_num_buffer(*a); } } else if (auto a = ov::as_type>>(&adapter)) { pugi::xml_node dn = m_node.child("data"); @@ -461,7 +859,7 @@ void ov::XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor< } } -void ov::XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { +void XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { std::shared_ptr model; io_map = {}; @@ -479,8 +877,49 @@ void ov::XmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor< adapter.set(model); } -std::shared_ptr ov::XmlDeserializer::parse_function(const pugi::xml_node& root, - const std::shared_ptr& weights) { +void XmlDeserializer::set_constant_num_buffer(ov::AttributeAdapter>& adapter) { + OPENVINO_ASSERT(m_weights, "Empty weights data in bin file or bin file cannot be found!"); + std::vector shape; + std::string el_type_str; + const auto& dn = m_node.child("data"); + + if (!getStrAttribute(dn, "element_type", el_type_str)) + return; + + if (!getParameters(dn, "shape", shape)) { + return; + } + + const auto size = static_cast(pugixml::get_uint64_attr(dn, "size")); + const auto offset = static_cast(pugixml::get_uint64_attr(dn, "offset")); + OPENVINO_ASSERT(m_weights->size() >= offset + size, "Incorrect weights in bin file!"); + + char* data = m_weights->get_ptr() + offset; + + const auto el_type = ov::element::Type(el_type_str); + if (el_type == element::string) { + auto buffer = ov::AttributeAdapter>::unpack_string_tensor(data, size); + adapter.set(buffer); + } else { + if (size < ((ov::shape_size(shape) * el_type.bitwidth() + 7) >> 3)) { + const auto type = pugixml::get_str_attr(m_node, "type"); + OPENVINO_THROW("Attribute and shape size are inconsistent for ", + type, + " op!", + size, + ", ", + ((ov::shape_size(shape) * el_type.bitwidth() + 7) >> 3), + ", ", + ov::element::get_memory_size(el_type, ov::shape_size(shape))); + } + + auto buffer = std::make_shared>>(data, size, m_weights); + adapter.set(buffer); + } +} + +std::shared_ptr XmlDeserializer::parse_function(const pugi::xml_node& root, + const std::shared_ptr& weights) { // OV_ITT_SCOPE_CHAIN(FIRST_INFERENCE, taskChain, itt::domains::V10Reader_RT, "V10Parser", "Parse"); struct FunctionNodes { @@ -530,7 +969,7 @@ std::shared_ptr ov::XmlDeserializer::parse_function(const pugi::xml_n edges[toLayer].push_back({fromLayer, fromPort, toPort}); } - // Run DFS starting from outputs to get nodes topological order without recursion + // Run DFS starting from outputs to get nodes topological order std::function dfs = [&edges, &order, &dfs_used_nodes](const size_t start_id) { std::stack stack; std::unordered_set visited; @@ -645,98 +1084,7 @@ std::shared_ptr ov::XmlDeserializer::parse_function(const pugi::xml_n return function; } -class MetaDataParser : public ov::MetaDataWithPugixml { -public: - MetaDataParser(const std::string& name, const pugi::xml_node& meta, bool accessible_by_pugixml_node = true) - : m_name(name), - m_accessible_by_pugixml_node(accessible_by_pugixml_node) { - m_meta.append_copy(meta); - if (accessible_by_pugixml_node) { - m_meta_node = m_meta.child(m_name.c_str()); - } else { - m_meta_node = pugi::xml_node(); - } - } - - operator const ov::AnyMap&() const override { - parse(); - return m_parsed_map; - } - - operator ov::AnyMap&() override { - parse(); - return m_parsed_map; - } - - const pugi::xml_node& get_pugi_node() const override { - if (!m_meta_node.empty() && !m_accessible_by_pugixml_node) { - // Meta cannot be accessed by pugixml node. Return empty node - m_meta_node = pugi::xml_node(); - } - return m_meta_node; - }; - -private: - bool has_attr(const pugi::xml_node& node, const std::string& name = "value") const { - auto attr = node.attribute(name.c_str()); - return !attr.empty(); - } - - ov::Any parse_value(const pugi::xml_node& node) const { - if (has_attr(node)) { - return pugixml::get_str_attr(node, "value"); - } else if (std::string(node.name()) == "unset" && has_attr(node, "unset_cli_parameters")) { - return pugixml::get_str_attr(node, "unset_cli_parameters"); - } else { - return parse_node(node); - } - } - - ov::AnyMap parse_node(const pugi::xml_node& node) const { - ov::AnyMap result; - // Old version may produce nodes like , but it may brake xml-naming convention - // Now it should look like . - // Also we keep an option to read an old XMLs where it doesn't have name attribute - const auto name_attr = node.attribute("name"); - const std::string node_name = name_attr.empty() ? node.name() : name_attr.value(); - for (const auto& data : node.children()) { - const auto name_attr = data.attribute("name"); - const std::string data_name = name_attr.empty() ? data.name() : name_attr.value(); - // WA for legacy POT config - if (data_name == "config" && node_name == "quantization_parameters") { - // Read legacy pot config - std::stringstream stream; - data.print(stream); - std::string str_config = stream.str(); - str_config = std::regex_replace(str_config, std::regex(""), ""); - str_config = std::regex_replace(str_config, std::regex(""), ""); - str_config = std::regex_replace(str_config, std::regex("\n"), ""); - str_config = std::regex_replace(str_config, std::regex("( +)"), " "); - result[data_name] = str_config; - } else { - result[data_name] = parse_value(data); - } - } - return result; - } - - void parse() const { - std::call_once(m_oc, [this]() { - m_accessible_by_pugixml_node = false; - const pugi::xml_node& node = m_meta.child(m_name.c_str()); - m_parsed_map = parse_node(node); - }); - } - - pugi::xml_document m_meta; - const std::string m_name; - mutable ov::AnyMap m_parsed_map; - mutable std::once_flag m_oc; - mutable std::atomic_bool m_accessible_by_pugixml_node; - mutable pugi::xml_node m_meta_node; -}; - -void ov::XmlDeserializer::read_meta_data(const std::shared_ptr& model, const pugi::xml_node& meta_section) { +void XmlDeserializer::read_meta_data(const std::shared_ptr& model, const pugi::xml_node& meta_section) { if (meta_section.empty()) return; auto& rt_info = model->get_rt_info(); @@ -758,9 +1106,9 @@ void ov::XmlDeserializer::read_meta_data(const std::shared_ptr& model } } -void ov::XmlDeserializer::read_legacy_meta_data(const std::shared_ptr& model, - const std::unordered_set& names, - const pugi::xml_node& root_section) { +void XmlDeserializer::read_legacy_meta_data(const std::shared_ptr& model, + const std::unordered_set& names, + const pugi::xml_node& root_section) { const auto& read_meta = [](const std::shared_ptr& model, const std::string& name, const pugi::xml_node& meta_section) { auto& rt_info = model->get_rt_info(); @@ -790,7 +1138,7 @@ void ov::XmlDeserializer::read_legacy_meta_data(const std::shared_ptr read_meta(model, it, root_section.child(it.c_str())); } -ov::GenericLayerParams ov::XmlDeserializer::parse_generic_params(const pugi::xml_node& node) { +GenericLayerParams XmlDeserializer::parse_generic_params(const pugi::xml_node& node) { const auto parsePort = [](const pugi::xml_node& parentNode, const GenericLayerParams& params, bool input) -> GenericLayerParams::LayerPortData { @@ -863,10 +1211,10 @@ static const std::string& translate_type_name(const std::string& name) { return name; } -std::shared_ptr ov::XmlDeserializer::create_node(const std::vector>& inputs, - const pugi::xml_node& node, - const std::shared_ptr& weights, - const GenericLayerParams& params) { +std::shared_ptr XmlDeserializer::create_node(const std::vector>& inputs, + const pugi::xml_node& node, + const std::shared_ptr& weights, + const GenericLayerParams& params) { // Check that inputs are correctly defined for (size_t i = 0; i < inputs.size(); i++) { if (!inputs[i].get_node()) @@ -879,16 +1227,15 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vector ovNode; ov::DiscreteTypeInfo type(type_name.c_str(), params.version.c_str()); - auto extensionIt = m_extensions.find(type); + const auto extensionIt = m_extensions.find(type); if (extensionIt != m_extensions.end()) { - XmlDeserializer visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); - ovNode = (*extensionIt->second).create(inputs, visitor).at(0).get_node_shared_ptr(); + auto visitor = make_visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); + ovNode = (*extensionIt->second).create(inputs, *visitor).at(0).get_node_shared_ptr(); } // Find registered opset @@ -929,7 +1276,6 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vectorsecond; - ovNode = std::shared_ptr(opset.create_insensitive(type_name)); if (!ovNode) { OPENVINO_THROW("Opset ", params.version, " doesn't contain the operation with type: ", type_name); @@ -939,9 +1285,8 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vectoralloc_buffer_on_visit_attributes(false); } ovNode->set_arguments(inputs); - XmlDeserializer visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); - - if (ovNode->visit_attributes(visitor)) { + auto visitor = make_visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); + if (ovNode->visit_attributes(*visitor)) { ovNode->constructor_validate_and_infer_types(); } @@ -950,8 +1295,10 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vector(inputs); - XmlDeserializer visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); - ovNode->visit_attributes(visitor); + // XmlDeserializer visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); + // ovNode->visit_attributes(visitor); + auto visitor = make_visitor(node, weights, m_opsets, m_extensions, m_variables, m_version); + ovNode->visit_attributes(*visitor); size_t index{0}; for (const auto& output_params : params.outputPorts) { @@ -983,14 +1330,10 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vector(pugixml::get_uint64_attr(dn, "size")), - static_cast(pugixml::get_uint64_attr(dn, "offset")), - ov::element::Type(pugixml::get_str_attr(dn, "element_type"))); + + if (auto wl_attribute = parse_weightless_cache_attribute(node); !wl_attribute.empty()) { + ovNode->get_rt_info().emplace(ov::WeightlessCacheAttribute::get_type_info_static(), + std::move(wl_attribute)); } } @@ -1072,3 +1415,8 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vector -#include "ir_deserializer.hpp" #include "openvino/core/except.hpp" #include "openvino/core/validation_util.hpp" #include "openvino/op/concat.hpp" @@ -17,6 +16,7 @@ #include "openvino/opsets/opset.hpp" #include "openvino/util/common_util.hpp" #include "openvino/util/xml_parse_utils.hpp" +#include "openvino/xml_util/xml_deserialize_util.hpp" #include "utils.hpp" namespace { @@ -266,7 +266,7 @@ std::shared_ptr InputModel::InputModelIRImpl::convert() { // Load default opsets size_t version = static_cast(ov::util::pugixml::get_uint64_attr(m_root, "version", 0)); - ov::XmlDeserializer visitor(m_root, m_weights, m_opsets, m_extensions, variables, version); + ov::util::XmlDeserializer visitor(m_root, m_weights, m_opsets, m_extensions, variables, version); std::shared_ptr model; visitor.on_attribute("net", model); model->get_rt_info()["version"] = int64_t(version); From 58c0de4e955b95596c4a573dd951c56b20c9e2b8 Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Mon, 4 Aug 2025 13:36:11 +0200 Subject: [PATCH 02/12] Remove not used code from IR FE Signed-off-by: Raasz, Pawel --- src/frontends/ir/src/rt_info_deserializer.cpp | 25 ---- src/frontends/ir/src/rt_info_deserializer.hpp | 127 ------------------ src/frontends/ir/src/utils.cpp | 52 ------- src/frontends/ir/src/utils.hpp | 19 --- 4 files changed, 223 deletions(-) delete mode 100644 src/frontends/ir/src/rt_info_deserializer.cpp delete mode 100644 src/frontends/ir/src/rt_info_deserializer.hpp diff --git a/src/frontends/ir/src/rt_info_deserializer.cpp b/src/frontends/ir/src/rt_info_deserializer.cpp deleted file mode 100644 index b052ec844155c5..00000000000000 --- a/src/frontends/ir/src/rt_info_deserializer.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2018-2025 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#include "rt_info_deserializer.hpp" - -#include - -#include "openvino/frontend/exception.hpp" - -using namespace ov; - -void RTInfoDeserializer::on_adapter(const std::string& name, ValueAccessor& adapter) { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - if (auto a = as_type>>(&adapter)) { - std::set ss; - str_to_set_of_strings(val, ss); - a->set(ss); - } else { - OPENVINO_NOT_IMPLEMENTED; - } -} diff --git a/src/frontends/ir/src/rt_info_deserializer.hpp b/src/frontends/ir/src/rt_info_deserializer.hpp deleted file mode 100644 index 6d981d567392ed..00000000000000 --- a/src/frontends/ir/src/rt_info_deserializer.hpp +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (C) 2018-2025 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#pragma once - -#include -#include -#include - -#include "openvino/core/attribute_visitor.hpp" -#include "utils.hpp" - -namespace ov { -class RTInfoDeserializer : public ov::AttributeVisitor { -public: - explicit RTInfoDeserializer(const pugi::xml_node& node) : m_node(node) {} - - void on_adapter(const std::string& name, ov::ValueAccessor& value) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - value.set(val); - } - - void on_adapter(const std::string& name, ov::ValueAccessor& value) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - std::transform(val.begin(), val.end(), val.begin(), [](char ch) { - return std::tolower(static_cast(ch)); - }); - std::set true_names{"true", "1"}; - std::set false_names{"false", "0"}; - - bool is_true = true_names.find(val) != true_names.end(); - bool is_false = false_names.find(val) != false_names.end(); - - if (!is_true && !is_false) - return; - value.set(is_true); - } - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - adapter.set(stringToType(val)); - } - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - adapter.set(stringToType(val)); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - OPENVINO_THROW("Model type is unsupported for rt info deserialization"); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - std::vector value; - str_to_container(val, value); - adapter.set(value); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - std::vector value; - str_to_container(val, value); - adapter.set(value); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - std::vector value; - str_to_container(val, value); - adapter.set(value); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - std::vector value; - str_to_container(val, value); - adapter.set(value); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - std::string val; - if (!getStrAttribute(m_node, name, val)) - return; - std::vector value; - str_to_container(val, value); - adapter.set(value); - } - - void check_attribute_name(const std::string& name) const { - OPENVINO_ASSERT(name != "name" && name != "version", - "Attribute key with name: ", - name, - " is not allowed. Please use another name"); - } - -private: - pugi::xml_node m_node; -}; -} // namespace ov diff --git a/src/frontends/ir/src/utils.cpp b/src/frontends/ir/src/utils.cpp index 030a10c05c3a43..9fefc479f36456 100644 --- a/src/frontends/ir/src/utils.cpp +++ b/src/frontends/ir/src/utils.cpp @@ -13,56 +13,4 @@ void operator>>(const std::stringstream& in, ov::element::Type& type) { type = ov::element::Type(ov::util::trim(in.str())); } -bool getStrAttribute(const pugi::xml_node& node, const std::string& name, std::string& value) { - if (!node) - return false; - - auto attr = node.attribute(name.c_str()); - if (attr.empty()) - return false; - value = std::string(attr.value()); - return true; -} - -bool get_partial_shape_from_attribute(const pugi::xml_node& node, const std::string& name, PartialShape& value) { - std::string param; - if (!getStrAttribute(node, name, param)) - return false; - value = PartialShape(param); - return true; -} - -bool get_dimension_from_attribute(const pugi::xml_node& node, const std::string& name, Dimension& value) { - std::string param; - if (!getStrAttribute(node, name, param)) - return false; - value = Dimension(param); - return true; -} - -void str_to_set_of_strings(const std::string& value, std::set& res) { - std::stringstream ss(value); - std::string field; - while (getline(ss, field, ',')) { - // trim leading and trailing whitespaces - auto strBegin = field.find_first_not_of(" "); - if (strBegin == std::string::npos) - OPENVINO_THROW("Cannot get a set of strings from \"", value, "\". Value \"", field, "\" is incorrect"); - auto strRange = field.find_last_not_of(" ") - strBegin + 1; - - res.insert(field.substr(strBegin, strRange)); - } -} - -void str_to_container(const std::string& value, std::vector& res) { - std::stringstream ss(value); - std::string field; - while (getline(ss, field, ',')) { - field = ov::util::trim(field); - if (!field.empty()) { - res.emplace_back(field); - } - } -} - } // namespace ov diff --git a/src/frontends/ir/src/utils.hpp b/src/frontends/ir/src/utils.hpp index 8f3336bad1a517..ec0bc1e28d6d39 100644 --- a/src/frontends/ir/src/utils.hpp +++ b/src/frontends/ir/src/utils.hpp @@ -13,12 +13,6 @@ namespace ov { void operator>>(const std::stringstream& in, ov::element::Type& type); -bool getStrAttribute(const pugi::xml_node& node, const std::string& name, std::string& value); -bool get_dimension_from_attribute(const pugi::xml_node& node, const std::string& name, Dimension& value); -bool get_partial_shape_from_attribute(const pugi::xml_node& node, const std::string& name, PartialShape& value); - -void str_to_container(const std::string& value, std::vector& res); - template void str_to_container(const std::string& value, T& res) { std::stringstream ss(value); @@ -33,19 +27,6 @@ void str_to_container(const std::string& value, T& res) { } } -// separated function for set to keep whitespaces in values -// because stringstream splits its values with whitespace delimiter -void str_to_set_of_strings(const std::string& value, std::set& res); - -template -bool getParameters(const pugi::xml_node& node, const std::string& name, std::vector& value) { - std::string param; - if (!getStrAttribute(node, name, param)) - return false; - str_to_container(param, value); - return true; -} - template T stringToType(const std::string& valStr) { T ret{0}; From c75794db510e6262f1238c10f7dd7fbec5deade4 Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Mon, 4 Aug 2025 14:21:48 +0200 Subject: [PATCH 03/12] Fix includes Signed-off-by: Raasz, Pawel --- src/core/xml_util/src/xml_deserialize_util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/xml_util/src/xml_deserialize_util.cpp b/src/core/xml_util/src/xml_deserialize_util.cpp index 1f25e73008c229..f1b7db883c885a 100644 --- a/src/core/xml_util/src/xml_deserialize_util.cpp +++ b/src/core/xml_util/src/xml_deserialize_util.cpp @@ -2,9 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // -#include "openvino/xml_util/xml_deserialize_util.hpp" - #include +#include #include #include "openvino/core/descriptor_tensor.hpp" @@ -25,6 +24,7 @@ #include "openvino/runtime/string_aligned_buffer.hpp" #include "openvino/util/common_util.hpp" #include "openvino/util/xml_parse_utils.hpp" +#include "openvino/xml_util/xml_deserialize_util.hpp" #include "transformations/rt_info/attributes.hpp" namespace ov::util { From ef3d16c8fbfdb85fd8729fdb51abbcb96f122e48 Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Mon, 4 Aug 2025 15:21:18 +0200 Subject: [PATCH 04/12] Fix build issues Signed-off-by: Raasz, Pawel --- .../include/openvino/xml_util/xml_deserialize_util.hpp | 2 +- src/core/xml_util/src/xml_deserialize_util.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp b/src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp index 3413aa1ea0e793..1ec352d53fe00b 100644 --- a/src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp +++ b/src/core/xml_util/include/openvino/xml_util/xml_deserialize_util.hpp @@ -21,7 +21,7 @@ namespace ov::util { struct GenericLayerParams; -class OPENVINO_API XmlDeserializer : public ov::AttributeVisitor { +class XmlDeserializer : public ov::AttributeVisitor { public: explicit XmlDeserializer(const pugi::xml_node& node, const std::shared_ptr& weights, diff --git a/src/core/xml_util/src/xml_deserialize_util.cpp b/src/core/xml_util/src/xml_deserialize_util.cpp index f1b7db883c885a..abd0ba2a850ad7 100644 --- a/src/core/xml_util/src/xml_deserialize_util.cpp +++ b/src/core/xml_util/src/xml_deserialize_util.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 // +#include "openvino/xml_util/xml_deserialize_util.hpp" + #include #include #include @@ -24,7 +26,6 @@ #include "openvino/runtime/string_aligned_buffer.hpp" #include "openvino/util/common_util.hpp" #include "openvino/util/xml_parse_utils.hpp" -#include "openvino/xml_util/xml_deserialize_util.hpp" #include "transformations/rt_info/attributes.hpp" namespace ov::util { From 6cc4ea5ca19ca44eda434dcb0532c572a235bada Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Tue, 5 Aug 2025 08:22:36 +0200 Subject: [PATCH 05/12] Fix linking of xml util Signed-off-by: Raasz, Pawel --- src/core/xml_util/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/xml_util/CMakeLists.txt b/src/core/xml_util/CMakeLists.txt index acfdd2036af6e5..68813e4081b7cf 100644 --- a/src/core/xml_util/CMakeLists.txt +++ b/src/core/xml_util/CMakeLists.txt @@ -16,7 +16,8 @@ source_group("src" FILES ${LIBRARY_SRC}) source_group("include" FILES ${PUBLIC_HEADERS}) # Create static library -add_library(${TARGET_NAME} STATIC ${LIBRARY_SRC} ${PUBLIC_HEADERS}) +add_library(${TARGET_NAME} STATIC ${LIBRARY_SRC} ${PUBLIC_HEADERS} + $) add_library(openvino::xml_util ALIAS ${TARGET_NAME}) set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME openvino_xml_util) From b9201404ebd83947602d3b93b4596f79149721ff Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Tue, 5 Aug 2025 14:20:36 +0200 Subject: [PATCH 06/12] fix linking for xml util Signed-off-by: Raasz, Pawel --- src/core/xml_util/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/core/xml_util/CMakeLists.txt b/src/core/xml_util/CMakeLists.txt index 68813e4081b7cf..f1bbbe6eefc776 100644 --- a/src/core/xml_util/CMakeLists.txt +++ b/src/core/xml_util/CMakeLists.txt @@ -16,18 +16,17 @@ source_group("src" FILES ${LIBRARY_SRC}) source_group("include" FILES ${PUBLIC_HEADERS}) # Create static library -add_library(${TARGET_NAME} STATIC ${LIBRARY_SRC} ${PUBLIC_HEADERS} - $) +add_library(${TARGET_NAME} STATIC ${LIBRARY_SRC} ${PUBLIC_HEADERS}) add_library(openvino::xml_util ALIAS ${TARGET_NAME}) set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME openvino_xml_util) target_include_directories(${TARGET_NAME} PUBLIC $ - $ $) ov_add_clang_format_target(${TARGET_NAME}_clang FOR_TARGETS ${TARGET_NAME}) +target_link_libraries(${TARGET_NAME} PRIVATE openvino::runtime) if(NOT BUILD_SHARED_LIBS) target_compile_definitions(${TARGET_NAME} PUBLIC OPENVINO_STATIC_LIBRARY) From 2073aa74db45e12f89c804b678138f0feb9725ea Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Fri, 1 Aug 2025 14:21:59 +0200 Subject: [PATCH 07/12] Make StreamSerializer and XmlSerializer possible to customize by other components e.g. plugin - The XmlSerializer is part of dev API for internal modifications Signed-off-by: Raasz, Pawel --- .../include/openvino/util/common_util.hpp | 15 + .../openvino/xml_util/constant_writer.hpp | 54 + .../openvino/xml_util/xml_serialize_util.hpp | 105 ++ src/core/include/openvino/pass/serialize.hpp | 25 +- src/core/src/pass/serialize.cpp | 1287 +---------------- src/core/src/xml_util/constant_writer.cpp | 124 ++ src/core/src/xml_util/xml_serialize_util.cpp | 1088 ++++++++++++++ 7 files changed, 1454 insertions(+), 1244 deletions(-) create mode 100644 src/core/dev_api/openvino/xml_util/constant_writer.hpp create mode 100644 src/core/dev_api/openvino/xml_util/xml_serialize_util.hpp create mode 100644 src/core/src/xml_util/constant_writer.cpp create mode 100644 src/core/src/xml_util/xml_serialize_util.cpp diff --git a/src/common/util/include/openvino/util/common_util.hpp b/src/common/util/include/openvino/util/common_util.hpp index 39d9dac06eda3c..f7223b62613e55 100644 --- a/src/common/util/include/openvino/util/common_util.hpp +++ b/src/common/util/include/openvino/util/common_util.hpp @@ -78,6 +78,21 @@ inline size_t hash_combine(std::initializer_list&& list) { return seed; } +constexpr uint64_t u64_hash_combine(uint64_t h, uint64_t k) { + // Hash combine formula from boost for uint64_t. + constexpr uint64_t m = 0xc6a4a7935bd1e995; + constexpr int r = 47; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + + return h + 0xe6546b64; +} + /** * @brief trim from start (in place) * @param s - string to trim diff --git a/src/core/dev_api/openvino/xml_util/constant_writer.hpp b/src/core/dev_api/openvino/xml_util/constant_writer.hpp new file mode 100644 index 00000000000000..3c6ca059939f8d --- /dev/null +++ b/src/core/dev_api/openvino/xml_util/constant_writer.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include + +#include "openvino/core/attribute_visitor.hpp" +#include "openvino/core/type/element_type.hpp" +#include "openvino/core/visibility.hpp" + +namespace ov::util { + +class OPENVINO_API OstreamHashWrapperBin final : public std::streambuf { + uint64_t m_res = 0lu; + +public: + uint64_t get_result() const { + return m_res; + } + std::streamsize xsputn(const char* s, std::streamsize n) override; +}; + +class OPENVINO_API ConstantWriter { +public: + using FilePosition = int64_t; + using HashValue = size_t; + using ConstWritePositions = std::multimap>; + + ConstantWriter(std::ostream& bin_data, bool enable_compression = true); + virtual ~ConstantWriter(); + + virtual FilePosition write(const char* ptr, + size_t size, + size_t& new_size, + bool compress_to_fp16 = false, + ov::element::Type src_type = ov::element::dynamic, + bool ptr_is_temporary = false); + +private: + static std::unique_ptr compress_data_to_fp16(const char* ptr, + size_t size, + ov::element::Type src_type, + size_t& compressed_size); + + ConstWritePositions m_hash_to_file_positions; + std::reference_wrapper m_binary_output; + bool m_enable_compression; + bool m_write_hash_value; + FilePosition m_blob_offset; // blob offset inside output stream +}; +} // namespace ov::util diff --git a/src/core/dev_api/openvino/xml_util/xml_serialize_util.hpp b/src/core/dev_api/openvino/xml_util/xml_serialize_util.hpp new file mode 100644 index 00000000000000..c847b61bd3a96d --- /dev/null +++ b/src/core/dev_api/openvino/xml_util/xml_serialize_util.hpp @@ -0,0 +1,105 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include +#include +#include +#include +#include + +#include "openvino/core/type/element_type.hpp" +#include "openvino/core/visibility.hpp" +#include "openvino/op/loop.hpp" +#include "openvino/op/util/multi_subgraph_base.hpp" +#include "openvino/util/common_util.hpp" +#include "openvino/xml_util/constant_writer.hpp" + +namespace ov::util { + +OPENVINO_API std::string get_ir_precision_name(const element::Type& precision); + +class OPENVINO_API XmlSerializer : public ov::AttributeVisitor { + pugi::xml_node& m_xml_node; + const std::string& m_node_type_name; + std::reference_wrapper m_constant_node_write_handler; + int64_t m_version; + bool m_deterministic; + bool m_compress_to_fp16; + ov::element::Type m_output_element_type; + bool m_data_is_temporary; + std::function m_custom_rt_info_append; + + template + std::string create_attribute_list(ov::ValueAccessor>& adapter) { + return util::join(adapter.get()); + } + + std::vector map_type_from_body(const pugi::xml_node& xml_node, + const std::string& map_type, + int64_t ir_version, + const std::string& body_name = "body"); + + void input_descriptions_on_adapter( + const std::vector>& input_descriptions, + const std::vector& parameter_mapping, + const std::vector& result_mapping, + pugi::xml_node& port_map, + const std::string& portmap_name); + + void output_descriptions_on_adapter( + const std::vector>& output_descriptions, + const uint32_t& input_count, + const std::vector& result_mapping, + pugi::xml_node& port_map, + const std::string& portmap_name); + + void special_body_ports_on_adapter(const ov::op::v5::Loop::SpecialBodyPorts& special_body_ports, + const std::vector& parameter_mapping, + const std::vector& result_mapping, + pugi::xml_node& port_map); + + virtual std::unique_ptr make_visitor(pugi::xml_node& data, + const std::string& node_type_name, + ov::util::ConstantWriter& constant_write_handler, + int64_t version, + bool deterministic = false, + bool compress_to_fp16 = false, + ov::element::Type output_element_type = ov::element::dynamic, + bool data_is_temporary = false) const; + + void serialize(pugi::xml_node& net_xml, const ov::Model& model); + +protected: + virtual void append_rt_info(pugi::xml_node& node, ov::RTMap& attributes); + virtual bool append_rt_attribute(pugi::xml_node& node, const ov::RuntimeAttribute& attribute); + virtual bool append_node_attributes(ov::Node& node); + virtual util::ConstantWriter& get_constant_write_handler() const { + return m_constant_node_write_handler; + } + +public: + XmlSerializer(pugi::xml_node& data, + const std::string& node_type_name, + ov::util::ConstantWriter& constant_write_handler, + int64_t version, + bool deterministic = false, + bool compress_to_fp16 = false, + ov::element::Type output_element_type = ov::element::dynamic, + bool data_is_temporary = false); + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override; +}; // class XmlSerializer +} // namespace ov::util diff --git a/src/core/include/openvino/pass/serialize.hpp b/src/core/include/openvino/pass/serialize.hpp index d0bb853352a25c..621c21dd7201f2 100644 --- a/src/core/include/openvino/pass/serialize.hpp +++ b/src/core/include/openvino/pass/serialize.hpp @@ -12,8 +12,16 @@ #include "openvino/opsets/opset.hpp" #include "openvino/pass/pass.hpp" -namespace ov { -namespace pass { +namespace pugi { +class xml_node; +} + +namespace ov::util { +class XmlSerializer; +class ConstantWriter; +} // namespace ov::util + +namespace ov::pass { /** * @brief Serialize transformation converts ov::Model into IR files @@ -76,10 +84,19 @@ class OPENVINO_API StreamSerialize : public ov::pass::ModelPass { private: virtual bool use_absolute_offset(); + virtual std::unique_ptr make_serializer( + pugi::xml_node& data, + const std::string& node_type_name, + util::ConstantWriter& constant_write_handler, + int64_t version, + bool deterministic = false, + bool compress_to_fp16 = false, + ov::element::Type output_element_type = ov::element::dynamic, + bool data_is_temporary = false) const; + std::ostream& m_stream; std::function m_custom_data_serializer; std::function m_cache_encrypt; const Serialize::Version m_version; }; -} // namespace pass -} // namespace ov +} // namespace ov::pass diff --git a/src/core/src/pass/serialize.cpp b/src/core/src/pass/serialize.cpp index 00a13f7905f952..3c1ce93f826954 100644 --- a/src/core/src/pass/serialize.cpp +++ b/src/core/src/pass/serialize.cpp @@ -5,1225 +5,31 @@ #include "openvino/pass/serialize.hpp" #include -#include #include #include -#include #include #include +#include "openvino/cc/pass/itt.hpp" #include "openvino/core/coordinate_diff.hpp" -#include "openvino/core/descriptor_tensor.hpp" #include "openvino/core/except.hpp" -#include "openvino/core/meta_data.hpp" #include "openvino/core/model.hpp" #include "openvino/core/parallel.hpp" #include "openvino/core/type/float16.hpp" -#include "openvino/op/binary_convolution.hpp" -#include "openvino/op/constant.hpp" -#include "openvino/op/convolution.hpp" -#include "openvino/op/group_conv.hpp" -#include "openvino/op/loop.hpp" -#include "openvino/op/lstm_cell.hpp" -#include "openvino/op/parameter.hpp" -#include "openvino/op/util/avg_pool_base.hpp" -#include "openvino/op/util/deformable_convolution_base.hpp" -#include "openvino/op/util/framework_node.hpp" -#include "openvino/op/util/max_pool_base.hpp" -#include "openvino/op/util/op_types.hpp" -#include "openvino/op/util/sub_graph_base.hpp" #include "openvino/pass/constant_folding.hpp" -#include "openvino/reference/convert.hpp" #include "openvino/runtime/aligned_buffer.hpp" #include "openvino/runtime/compute_hash.hpp" #include "openvino/runtime/string_aligned_buffer.hpp" #include "openvino/util/common_util.hpp" #include "openvino/util/file_util.hpp" +#include "openvino/xml_util/constant_writer.hpp" +#include "openvino/xml_util/xml_serialize_util.hpp" #include "pugixml.hpp" #include "transformations/hash.hpp" #include "transformations/rt_info/disable_fp16_compression.hpp" #include "transformations/rt_info/primitives_priority_attribute.hpp" -namespace ov { -class OstreamHashWrapperBin final : public std::streambuf { - uint64_t m_res = 0lu; - -public: - uint64_t getResult() const { - return m_res; - } - std::streamsize xsputn(const char* s, std::streamsize n) override; -}; -} // namespace ov - -namespace { // helpers -template -std::string join(const Container& c, const char* glue = ", ") { - std::stringstream oss; - const char* s = ""; - for (const auto& v : c) { - oss << s << v; - s = glue; - } - return oss.str(); -} - -struct Edge { - int from_layer = 0; - int from_port = 0; - int to_layer = 0; - int to_port = 0; -}; - -// Here operation type names are translated from OpenVINO Model convention to IR -// convention. Most of them are the same, but there are exceptions, e.g -// Constant (OpenVINO Model name) and Const (IR name). If there will be more -// discrepancies discovered, translations needs to be added here. -const std::unordered_map& get_translate_type_name_translator() { - static const std::unordered_map translate_type_name_translator = {{"Constant", "Const"}, - {"PRelu", "PReLU"}, - {"Relu", "ReLU"}, - {"Softmax", "SoftMax"}}; - return translate_type_name_translator; -} - -std::string translate_type_name(const std::string& name) { - auto found = get_translate_type_name_translator().find(name); - if (found != end(get_translate_type_name_translator())) { - return found->second; - } - return name; -} - -class ConstantWriter { -public: - using FilePosition = int64_t; - using HashValue = size_t; - using ConstWritePositions = std::multimap>; - - ConstantWriter(std::ostream& bin_data, bool enable_compression = true) - : m_binary_output(bin_data), - m_enable_compression(enable_compression), - m_blob_offset(bin_data.tellp()) { - m_write_hash_value = (dynamic_cast(bin_data.rdbuf())) ? true : false; - } - - FilePosition write(const char* ptr, - size_t size, - size_t& new_size, - bool compress_to_fp16 = false, - ov::element::Type src_type = ov::element::dynamic, - bool ptr_is_temporary = false) { // when true, do not rely on ptr after this function call, data - // is temporary allocated - const FilePosition write_pos = m_binary_output.tellp(); - const auto offset = write_pos - m_blob_offset; - new_size = size; - - if (!m_enable_compression) { - if (!compress_to_fp16) { - m_binary_output.write(ptr, size); - } else { - OPENVINO_ASSERT(size % src_type.size() == 0); - auto fp16_buffer = compress_data_to_fp16(ptr, size, src_type, new_size); - m_binary_output.write(fp16_buffer.get(), new_size); - } - return offset; - } else { - std::unique_ptr fp16_buffer = nullptr; - if (compress_to_fp16) { - OPENVINO_ASSERT(size % src_type.size() == 0); - fp16_buffer = compress_data_to_fp16(ptr, size, src_type, new_size); - } - const char* ptr_to_write; - if (fp16_buffer) { - ptr_to_write = fp16_buffer.get(); - } else { - ptr_to_write = ptr; - } - - // This hash is weak (but efficient). For example current hash algorithms gives - // the same hash for {2, 2} and {0, 128} arrays. - // But even strong hashing algorithms sometimes give collisions. - // Therefore we always have to compare values when finding a match in the hash multimap. - const HashValue hash = ov::runtime::compute_hash(ptr_to_write, new_size); - - auto found = m_hash_to_file_positions.equal_range(hash); - // iterate over all matches of the key in the multimap - for (auto it = found.first; it != found.second; ++it) { - if (memcmp(ptr, it->second.second, size) == 0) { - return it->second.first; - } - } - if (!ptr_is_temporary) { - // Since fp16_compressed data will be disposed at exit point and since we cannot reread it from the - // ostream, we store pointer to the original uncompressed blob. - m_hash_to_file_positions.insert({hash, {offset, static_cast(ptr)}}); - } - if (m_write_hash_value) { - m_binary_output.write(reinterpret_cast(&hash), sizeof(uint64_t)); - } else { - m_binary_output.write(ptr_to_write, new_size); - } - } - return offset; - } - -private: - static std::unique_ptr compress_data_to_fp16(const char* ptr, - size_t size, - ov::element::Type src_type, - size_t& compressed_size) { - auto num_src_elements = size / src_type.size(); - compressed_size = num_src_elements * ov::element::f16.size(); - if (src_type == ov::element::f32) { - auto new_ptr = std::unique_ptr(new char[compressed_size]); - auto dst_data = reinterpret_cast(new_ptr.get()); - auto src_data = reinterpret_cast(ptr); - ov::reference::convert_from_f32_to_f16_with_clamp(src_data, dst_data, num_src_elements); - return new_ptr; - } else if (src_type == ov::element::f64) { - auto new_ptr = std::unique_ptr(new char[compressed_size]); - auto dst_data = reinterpret_cast(new_ptr.get()); - auto src_data = reinterpret_cast(ptr); - - // Reference implementation for fp64 to fp16 conversoin - for (size_t i = 0; i < num_src_elements; ++i) { - // if abs value is smaller than the smallest positive fp16, but not zero - if (std::abs(src_data[i]) < ov::float16::from_bits(0x0001) && src_data[i] != 0.0f) { - dst_data[i] = 0; - } else if (src_data[i] > std::numeric_limits::max()) { - dst_data[i] = std::numeric_limits::max(); - } else if (src_data[i] < std::numeric_limits::lowest()) { - dst_data[i] = std::numeric_limits::lowest(); - } else { - dst_data[i] = static_cast(src_data[i]); - } - } - return new_ptr; - } else { - OPENVINO_THROW("[ INTERNAL ERROR ] Not supported source type for weights compression: ", src_type); - } - } - - ConstWritePositions m_hash_to_file_positions; - std::ostream& m_binary_output; - bool m_enable_compression; - bool m_write_hash_value = false; - FilePosition m_blob_offset; // blob offset inside output stream -}; - -void ngfunction_2_ir(pugi::xml_node& node, - const ov::Model& model, - ConstantWriter& constant_write_handler, - int64_t version, - bool deterministic); - -namespace rt_info { -static const std::vector list_of_names{ - "PrimitivesPriority", - "alt_width", -}; - -class XmlSerializer { -public: - explicit XmlSerializer(pugi::xml_node& xml_node) : m_xml_node(xml_node) {} - - void serialize(const ov::Node::RTMap& rt_info) { - for (const auto& rt_info_name : list_of_names) { - const auto& found_rt_info = rt_info.find(rt_info_name); - if (found_rt_info != rt_info.end()) { - std::stringstream strm; - found_rt_info->second.print(strm); - m_xml_node.append_attribute(rt_info_name.c_str()).set_value(strm.str().c_str()); - } - } - } - -private: - pugi::xml_node& m_xml_node; -}; - -class RTInfoSerializer : public ov::AttributeVisitor { - pugi::xml_node m_node; - -public: - RTInfoSerializer(const pugi::xml_node node) : m_node(node) {} - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - if (auto a = ov::as_type>>(&adapter)) { - const auto& value = join(a->get()); - m_node.append_attribute(name.c_str()).set_value(value.c_str()); - } else { - OPENVINO_THROW("Unsupported attribute type for serialization: ", name); - } - } - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - m_node.append_attribute(name.c_str()).set_value(adapter.get()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - m_node.append_attribute(name.c_str()).set_value(adapter.get().c_str()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - m_node.append_attribute(name.c_str()).set_value(static_cast(adapter.get())); - } - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - check_attribute_name(name); - m_node.append_attribute(name.c_str()).set_value(adapter.get()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - const auto& value = join(adapter.get()); - m_node.append_attribute(name.c_str()).set_value(value.c_str()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - const auto& value = join(adapter.get()); - m_node.append_attribute(name.c_str()).set_value(value.c_str()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - const auto& value = join(adapter.get()); - m_node.append_attribute(name.c_str()).set_value(value.c_str()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - const auto& value = join(adapter.get()); - m_node.append_attribute(name.c_str()).set_value(value.c_str()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - check_attribute_name(name); - const auto& value = join(adapter.get()); - m_node.append_attribute(name.c_str()).set_value(value.c_str()); - } - - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - OPENVINO_THROW("Model type is unsupported for rt info serialization"); - } - - void check_attribute_name(const std::string& name) const { - if (name == "name" || name == "version") { - OPENVINO_THROW("Attribute key with name: ", name, " is not allowed. Please use another name"); - } - } -}; -} // namespace rt_info - -class XmlSerializer : public ov::AttributeVisitor { - pugi::xml_node& m_xml_node; - const std::string& m_node_type_name; - ConstantWriter& m_constant_write_handler; - int64_t m_version; - bool m_deterministic; - bool m_compress_to_fp16; - ov::element::Type m_output_element_type; - bool m_data_is_temporary; - - template - std::string create_atribute_list(ov::ValueAccessor>& adapter) { - return join(adapter.get()); - } - - std::vector map_type_from_body(const pugi::xml_node& xml_node, - const std::string& map_type, - int64_t ir_version, - const std::string& body_name = "body") { - std::vector output; - for (pugi::xml_node node : xml_node.child(body_name.c_str()).child("layers")) { - if (map_type == node.attribute("type").value()) { - output.emplace_back(node.attribute("id").value()); - } - } - - return output; - } - - void input_descriptions_on_adapter( - const std::vector>& input_descriptions, - const std::vector& parameter_mapping, - const std::vector& result_mapping, - pugi::xml_node& port_map, - const std::string& portmap_name) { - if (!m_xml_node.parent().child(portmap_name.c_str())) { - port_map = m_xml_node.parent().insert_child_before(portmap_name.c_str(), m_xml_node.parent().first_child()); - } - - for (const auto& input_description : input_descriptions) { - pugi::xml_node input = port_map.append_child("input"); - input.append_attribute("external_port_id") - .set_value(static_cast(input_description->m_input_index)); - input.append_attribute("internal_layer_id") - .set_value(parameter_mapping[input_description->m_body_parameter_index].c_str()); - - if (auto slice_input = - ov::as_type_ptr(input_description)) { - input.prepend_attribute("axis").set_value(static_cast(slice_input->m_axis)); - input.append_attribute("start").set_value(static_cast(slice_input->m_start)); - input.append_attribute("end").set_value(static_cast(slice_input->m_end)); - input.append_attribute("stride").set_value(static_cast(slice_input->m_stride)); - input.append_attribute("part_size").set_value(static_cast(slice_input->m_part_size)); - } else if (auto merged_input = - ov::as_type_ptr(input_description)) { - pugi::xml_node back_edges = m_xml_node.parent().child("back_edges"); - if (!back_edges) { - back_edges = m_xml_node.parent().insert_child_after("back_edges", port_map); - } - pugi::xml_node edge = back_edges.append_child("edge"); - edge.append_attribute("from-layer").set_value(result_mapping[merged_input->m_body_value_index].c_str()); - edge.append_attribute("to-layer") - .set_value(parameter_mapping[merged_input->m_body_parameter_index].c_str()); - } - } - } - - void output_descriptions_on_adapter( - const std::vector>& output_descriptions, - const uint32_t& input_count, - const std::vector& result_mapping, - pugi::xml_node& port_map, - const std::string& portmap_name) { - OPENVINO_ASSERT(!result_mapping.empty(), "No results found in body Model."); - - if (!port_map) { - port_map = m_xml_node.parent().insert_child_before(portmap_name.c_str(), m_xml_node.parent().first_child()); - } - - for (const auto& output_description : output_descriptions) { - pugi::xml_node output = port_map.append_child("output"); - output.append_attribute("external_port_id") - .set_value(static_cast(input_count + output_description->m_output_index)); - output.append_attribute("internal_layer_id") - .set_value(result_mapping[output_description->m_body_value_index].c_str()); - - if (auto concat_output = - ov::as_type_ptr(output_description)) { - output.prepend_attribute("axis").set_value(static_cast(concat_output->m_axis)); - output.append_attribute("start").set_value(static_cast(concat_output->m_start)); - output.append_attribute("end").set_value(static_cast(concat_output->m_end)); - output.append_attribute("stride").set_value(static_cast(concat_output->m_stride)); - output.append_attribute("part_size").set_value(static_cast(concat_output->m_part_size)); - } - } - } - - void special_body_ports_on_adapter(const ov::op::v5::Loop::SpecialBodyPorts& special_body_ports, - const std::vector& parameter_mapping, - const std::vector& result_mapping, - pugi::xml_node& port_map) { - OPENVINO_ASSERT(port_map, "port_map section not found, purpose attribute cannot be added."); - - if (special_body_ports.current_iteration_input_idx != -1) { - pugi::xml_node iter_input = port_map.append_child("input"); - iter_input.append_attribute("external_port_id").set_value("-1"); - iter_input.append_attribute("internal_layer_id") - .set_value(parameter_mapping[special_body_ports.current_iteration_input_idx].c_str()); - iter_input.append_attribute("purpose").set_value("current_iteration"); - } - - if (special_body_ports.body_condition_output_idx != -1) { - pugi::xml_node exec_output = port_map.append_child("output"); - exec_output.append_attribute("external_port_id").set_value("-1"); - exec_output.append_attribute("internal_layer_id") - .set_value(result_mapping[special_body_ports.body_condition_output_idx].c_str()); - exec_output.append_attribute("purpose").set_value("execution_condition"); - } - } - -public: - XmlSerializer(pugi::xml_node& data, - const std::string& node_type_name, - ConstantWriter& constant_write_handler, - int64_t version, - bool deterministic = false, - bool compress_to_fp16 = false, - ov::element::Type output_element_type = ov::element::dynamic, - bool data_is_temporary = false) - : m_xml_node(data), - m_node_type_name(node_type_name), - m_constant_write_handler(constant_write_handler), - m_version(version), - m_deterministic(deterministic), - m_compress_to_fp16(compress_to_fp16), - m_output_element_type(output_element_type), - m_data_is_temporary(data_is_temporary) {} - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - using BodyTargetNames = std::tuple>; - - const std::vector body_names = { - BodyTargetNames{"body", "port_map", {"input_descriptions", "output_descriptions", "special_body_ports"}}, - BodyTargetNames{"then_body", "then_port_map", {"then_inputs", "then_outputs"}}, - BodyTargetNames{"else_body", "else_port_map", {"else_inputs", "else_outputs"}}}; - BodyTargetNames bnames; - bool is_body_target = false; - for (const auto& _body_target : body_names) { - if (m_xml_node.parent().child(std::get<0>(_body_target).c_str())) { - auto vec_names = std::get<2>(_body_target); - - if (std::find(vec_names.begin(), vec_names.end(), name) != vec_names.end()) { - is_body_target = true; - bnames = _body_target; - break; - } - } - } - if (!is_body_target) { - std::string id = "input_descriptions"; - std::string od = "output_descriptions"; - const auto& id_pos = name.find("input_descriptions"); - const auto& od_pos = name.find("output_descriptions"); - auto id_str = name; - size_t body_id; - if (id_pos != std::string::npos) { - id_str.erase(id_pos, id.length()); - (void)std::stoi(id_str, &body_id); - is_body_target = true; - } else if (od_pos != std::string::npos) { - id_str.erase(od_pos, od.length()); - (void)std::stoi(id_str, &body_id); - is_body_target = true; - } - if (is_body_target) { - auto body_name = "body" + id_str; - if (m_xml_node.parent().child(body_name.c_str())) { - bnames = BodyTargetNames{body_name, - "port_map" + id_str, - {"input_descriptions" + id_str, "output_descriptions" + id_str}}; - } else { - is_body_target = false; - } - } - } - if (is_body_target) { - const auto& body_name = std::get<0>(bnames); - const auto& portmap_name = std::get<1>(bnames); - std::vector result_mapping = - map_type_from_body(m_xml_node.parent(), "Result", m_version, body_name); - std::vector parameter_mapping = - map_type_from_body(m_xml_node.parent(), "Parameter", m_version, body_name); - - pugi::xml_node port_map = m_xml_node.parent().child(portmap_name.c_str()); - // Bodies can be without parameters(dependig on constants), but can not be without results - OPENVINO_ASSERT(!result_mapping.empty(), "No results found in body Model."); - // TI, Loop do not have attributtes as regular ops, it is necessary to append "port_map" and - // "back_edges" to layer above (m_xml_node.parent()) as in ngfunction_2_ir() layer (here "m_xml_node") - // with empty attributes is removed. - if (const auto& a = ov::as_type>>>(&adapter)) { - input_descriptions_on_adapter(a->get(), parameter_mapping, result_mapping, port_map, portmap_name); - } else if (const auto& a = ov::as_type>>>(&adapter)) { - uint32_t op_input_count = 0; - for (auto c = m_xml_node.parent().child("input").first_child(); !c.empty(); c = c.next_sibling()) { - op_input_count++; - } - output_descriptions_on_adapter(a->get(), op_input_count, result_mapping, port_map, portmap_name); - } else if (const auto& a = - ov::as_type>(&adapter)) { - special_body_ports_on_adapter(a->get(), parameter_mapping, result_mapping, port_map); - } - } else if (const auto& a = - ov::as_type>>(&adapter)) { - m_xml_node.append_attribute(name.c_str()).set_value(a->get()->get_info().variable_id.c_str()); - } else if (ov::is_type>>(&adapter) || - ov::is_type>>(&adapter)) { - if (name == "value" && translate_type_name(m_node_type_name) == "Const") { - auto a1 = ov::as_type>>(&adapter); - auto a2 = ov::as_type>>(&adapter); - size_t new_size = 0; - size_t inter_size = 0; - // write a header of packed string tensor - std::shared_ptr header_ptr = nullptr; - size_t header_size = 0; - if (a1) { - a1->get_header(header_ptr, header_size); - } else { - a2->get_header(header_ptr, header_size); - } - - int64_t offset = m_constant_write_handler.write( - reinterpret_cast(header_ptr.get()), - header_size, - inter_size, - m_compress_to_fp16, - m_output_element_type, - true); // header_ptr is allocated in AttributeAdapter that has limited life time - new_size += inter_size; - - // write raw strings part - size_t num_elements = 0; - if (a1) { - num_elements = a1->get()->get_num_elements(); - } else { - num_elements = a2->get()->get_num_elements(); - } - for (size_t ind = 0; ind < num_elements; ++ind) { - const char* raw_string_ptr; - size_t raw_string_size; - if (a1) { - a1->get_raw_string_by_index(raw_string_ptr, raw_string_size, ind); - } else { - a2->get_raw_string_by_index(raw_string_ptr, raw_string_size, ind); - } - - m_constant_write_handler.write(raw_string_ptr, - raw_string_size, - inter_size, - m_compress_to_fp16, - m_output_element_type, - m_data_is_temporary); - - new_size += inter_size; - } - m_xml_node.append_attribute("offset").set_value(static_cast(offset)); - m_xml_node.append_attribute("size").set_value(static_cast(new_size)); - } - } else if (const auto& a = ov::as_type>>(&adapter)) { - if (name == "value" && translate_type_name(m_node_type_name) == "Const") { - const int64_t size = a->get()->size(); - size_t new_size; - int64_t offset = m_constant_write_handler.write(static_cast(a->get()->get_ptr()), - size, - new_size, - m_compress_to_fp16, - m_output_element_type, - m_data_is_temporary); - - m_xml_node.append_attribute("offset").set_value(static_cast(offset)); - m_xml_node.append_attribute("size").set_value(static_cast(new_size)); - } - } else if (const auto& a = ov::as_type>(&adapter)) { - const auto& attrs = a->get(); - - // Update type and version attributes - pugi::xml_node layer = m_xml_node.parent(); - - auto type_attr = layer.attribute("type"); - auto version_attr = layer.attribute("version"); - - type_attr.set_value(attrs.get_type_name().c_str()); - - if (!attrs.get_opset_name().empty()) { - version_attr.set_value(attrs.get_opset_name().c_str()); - } else { - layer.remove_attribute("version"); - } - - // Update node attributes in data field - for (const auto& attr : attrs) { - m_xml_node.append_attribute(attr.first.c_str()).set_value(attr.second.c_str()); - } - } else if (const auto& a = ov::as_type>(&adapter)) { - const auto& attrs = a->get(); - m_xml_node.append_attribute(name.c_str()).set_value(join(attrs).c_str()); - } else if (const auto& a = ov::as_type>(&adapter)) { - const auto& attrs = a->get(); - auto shape_str = attrs.to_string(); - if (shape_str[0] == '[' && shape_str[shape_str.size() - 1] == ']') - shape_str = shape_str.substr(1, shape_str.size() - 2); - m_xml_node.append_attribute(name.c_str()).set_value(shape_str.c_str()); - } else if (const auto& a = ov::as_type>(&adapter)) { - const auto& attrs = a->get(); - std::stringstream dim_str_stream; - dim_str_stream << attrs; - auto dim_str = dim_str_stream.str(); - if (dim_str[0] == '{' && dim_str[dim_str.size() - 1] == '}') - dim_str = dim_str.substr(1, dim_str.size() - 2); - m_xml_node.append_attribute(name.c_str()).set_value(dim_str.c_str()); - } else { - OPENVINO_THROW("Unsupported attribute type for serialization: ", name); - } - } - - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(adapter.get()); - } - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - std::string value; - if (m_compress_to_fp16 && name == "element_type") { - value = ov::as_string(static_cast(ov::element::f16)); - } else { - value = adapter.get(); - } - m_xml_node.append_attribute(name.c_str()).set_value(value.c_str()); - } - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(static_cast(adapter.get())); - } - void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(adapter.get()); - } - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(create_atribute_list(adapter).c_str()); - } - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(create_atribute_list(adapter).c_str()); - } - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(create_atribute_list(adapter).c_str()); - } - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(create_atribute_list(adapter).c_str()); - } - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - m_xml_node.append_attribute(name.c_str()).set_value(create_atribute_list(adapter).c_str()); - } - void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { - if (name.find("body") != std::string::npos) { - // name that contains subgraphs: body{n}, then_body, else_body - // TI, Loop do not have attributtes as regular ops, it is necessary to append "body" - // to layer above (m_xml_node.parent()) as in ngfunction_2_ir() layer (m_xml_node) with empty attributes - // is removed. - pugi::xml_node xml_body = m_xml_node.parent().append_child(name.c_str()); - ngfunction_2_ir(xml_body, *adapter.get(), m_constant_write_handler, m_version, m_deterministic); - xml_body.remove_attribute("name"); - xml_body.remove_attribute("version"); - } else if (name == "net") { - ngfunction_2_ir(m_xml_node, *adapter.get(), m_constant_write_handler, m_version, m_deterministic); - } else { - OPENVINO_THROW("Unsupported Model name."); - } - } -}; - -const std::unordered_map create_layer_ids(const ov::Model& model) { - std::unordered_map layer_ids; - int id = 0; - for (const auto& node : model.get_ordered_ops()) { - layer_ids[node.get()] = id++; - } - return layer_ids; -} - -const std::vector create_edge_mapping(const std::unordered_map& layer_ids, - const ov::Model& model) { - std::vector edges; - for (const auto& node : model.get_ordered_ops()) { - if (ov::op::util::is_parameter(node)) { - continue; - } - - for (const auto& i : node->inputs()) { - auto source_output = i.get_source_output(); - auto source_node = source_output.get_node(); - auto current_node = i.get_node(); - - OPENVINO_ASSERT(layer_ids.find(source_node) != layer_ids.end(), "Internal error"); - OPENVINO_ASSERT(layer_ids.find(current_node) != layer_ids.end(), "Internal error"); - - Edge e{}; - e.from_layer = layer_ids.find(source_node)->second; - e.from_port = static_cast(source_node->get_input_size() + source_output.get_index()); - e.to_layer = layer_ids.find(current_node)->second; - e.to_port = static_cast(i.get_index()); - edges.push_back(e); - } - } - std::sort(begin(edges), end(edges), [](const Edge& a, const Edge& b) -> bool { - return a.from_layer < b.from_layer; - }); - return edges; -} - -std::string get_opset_name(const ov::Node* n) { - OPENVINO_ASSERT(n != nullptr); - - // CVS-169882: Try to find opset name from RT info. Below (not recommended) solution affects TypeRelaxed and similar - // template internal operations. - auto opset_it = n->get_rt_info().find("opset"); - if (opset_it != n->get_rt_info().end()) { - if (opset_it->second.is()) { - return opset_it->second.as(); - } - } - - return n->get_type_info().version_id == nullptr ? "experimental" : n->get_type_info().version_id; -} - -std::string get_precision_name(const ov::element::Type& elem_type) { - switch (elem_type) { - case ov::element::dynamic: - return "UNSPECIFIED"; - case ov::element::boolean: - return "BOOL"; - case ov::element::u1: - return "BIN"; - case ov::element::f16: - return "FP16"; - case ov::element::f32: - return "FP32"; - case ov::element::bf16: - return "BF16"; - case ov::element::f64: - return "FP64"; - default: - return ov::util::to_upper(elem_type.get_type_name()); - } -} - -std::string escape_delim(const std::string& name, const char delim = ',') { - std::string result_name = name; - const std::string escaped_delim = std::string("\\") + delim; - size_t index = result_name.find(delim, 0); - while (index != std::string::npos) { - result_name.replace(index, 1, escaped_delim); - index = result_name.find(delim, index + 2); - } - return result_name; -} - -void visit_exec_graph_node(pugi::xml_node& layer, const ov::Node* n) { - auto data = layer.child("data"); - for (const auto& param : n->get_rt_info()) { - if (param.second.is()) { - const std::string& name = param.first; - const std::string& value = param.second.as(); - - if (name == "layerType") { - layer.attribute("type").set_value(value.c_str()); - continue; - } - - data.append_attribute(name.c_str()).set_value(value.c_str()); - } - } -} - -bool is_exec_graph(const ov::Model& model) { - // go over all operations and check whether performance stat is set - for (const auto& op : model.get_ops()) { - const auto& rtInfo = op->get_rt_info(); - if (rtInfo.find("execTimeMcs") != rtInfo.end()) { - return true; - } - } - return false; -} - -class PaddingsFixer { -private: - ov::Node* m_node; - - ov::OutputVector m_parameters; - std::shared_ptr m_cloned_node; - - const std::set pad_agnostic_types = { - ov::op::PadType::SAME_LOWER, - ov::op::PadType::SAME_UPPER, - ov::op::PadType::VALID, - ov::op::PadType::AUTO, - }; - - template - void clone_op_and_fix_paddings(const T* op) { - for (const auto& input : op->inputs()) { - m_parameters.emplace_back( - std::make_shared(input.get_element_type(), input.get_partial_shape())); - } - m_cloned_node = op->clone_with_new_inputs(m_parameters); - auto typed_cloned_node = ov::as_type_ptr(m_cloned_node); - OPENVINO_ASSERT(typed_cloned_node); - typed_cloned_node->set_pads_begin(P(op->get_pads_begin().size(), 0)); - typed_cloned_node->set_pads_end(P(op->get_pads_end().size(), 0)); - m_node = m_cloned_node.get(); - } - -public: - ov::Node* get_node() { - return m_node; - } - - explicit PaddingsFixer(ov::Node* node) : m_node(node) { - if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } else if (auto op = ov::as_type(node)) { - if (pad_agnostic_types.count(op->get_auto_pad())) { - clone_op_and_fix_paddings(op); - } - } - } -}; - -// Substitute a Constant node instead of a node by calling node->constant_fold if 'postponed_constant' rt_info attribute -// is present in the node -class PostponedConstantReplacer { -private: - ov::Node* m_node; - std::shared_ptr m_constant; - -public: - ov::Node* get_node() { - return m_node; - } - - bool data_is_temporary() const { - return m_constant != nullptr; - } - - PostponedConstantReplacer(ov::Node* node) : m_node(node), m_constant() { - if (node->get_rt_info().count("postponed_constant")) { - OPENVINO_ASSERT(node->get_output_size() == 1); - ov::OutputVector outputs(1); - OPENVINO_ASSERT( - node->constant_fold(outputs, node->input_values()), - "Node with set `postponed_constant` attribute cannot be fold to constant when saving model to IR file"); - m_constant = outputs[0].get_node_shared_ptr(); - m_node = m_constant.get(); - m_node->set_friendly_name(node->get_friendly_name()); - } - } -}; - -bool is_correct_tag_name(const std::string& name) { - if (name.length() == 0) { - return false; - } - if (!std::all_of(name.begin(), name.end(), [](const int c) { - return std::isalnum(c) || (c == '_') || (c == '-') || (c == '.'); - })) { - return false; - } - if (std::isalpha(name[0]) == false && name[0] != '_') { - return false; - } - if (name.length() >= 3 && (name[0] == 'X' || name[0] == 'x') && (name[1] == 'M' || name[1] == 'm') && - (name[2] == 'l' || name[2] == 'L')) { - return false; - } - return true; -} - -void serialize_rt_info(pugi::xml_node& root, const std::string& name, const ov::Any& data) { - pugi::xml_node child; - if (is_correct_tag_name(name)) { - child = root.append_child(name.c_str()); - } else { - // Name may brake XML-naming specification, so better to store it as an attribute of typical - // node - child = root.append_child("info"); - child.append_attribute("name").set_value(name.c_str()); - } - if (data.is>()) { - auto meta = data.as>(); - do { - if (auto meta_with_pugixml_node = std::dynamic_pointer_cast(meta)) { - if (auto pugi_node = meta_with_pugixml_node->get_pugi_node()) { - root.remove_child(child); - auto added_node = root.append_copy(pugi_node); - OPENVINO_ASSERT(added_node, "Cannot add pugixml node with name: ", name); - added_node.set_name(name.c_str()); - break; - } - } - // Meta in ov::Meta cannot be accessed by MetaDataWithPugixml::get_pugi_node. Read it as ov::AnyMap - ov::AnyMap& map = *meta; - for (const auto& it : map) { - serialize_rt_info(child, it.first, it.second); - } - } while (false); - } else if (data.is()) { - const ov::AnyMap& any_map = data.as(); - for (const auto& it : any_map) { - serialize_rt_info(child, it.first, it.second); - } - } else { - std::string value = data.as(); - child.append_attribute("value").set_value(value.c_str()); - } -} - -void ngfunction_2_ir(pugi::xml_node& netXml, - const ov::Model& model, - ConstantWriter& constant_node_write_handler, - int64_t version, - bool deterministic) { - // If determinism is not required, include auto-generated names into xml - // model name is not critical for hash computing - if (!deterministic) { - netXml.append_attribute("name").set_value(model.get_friendly_name().c_str()); - } - netXml.append_attribute("version").set_value(static_cast(version)); - pugi::xml_node layers = netXml.append_child("layers"); - - const std::unordered_map layer_ids = create_layer_ids(model); - - const bool exec_graph = is_exec_graph(model); - - auto sorted_ops = model.get_ordered_ops(); - - // get_ordered_ops() returns operations after a topological sort. The topological sort reverses order of Parameters - // and Results. So we need to put them into sorted_ops separately to ensure correct order of inputs and outputs. - { - std::vector> result; - result.reserve(sorted_ops.size()); - for (const auto& param : model.get_parameters()) { - result.emplace_back(param); - } - auto model_sinks = model.get_sinks(); - for (auto&& node : sorted_ops) { - if (!ov::op::util::is_parameter(node) && !ov::op::util::is_output(node) && - std::find(model_sinks.begin(), model_sinks.end(), node) == model_sinks.end()) - result.emplace_back(node); - } - for (const auto& sink : model.get_sinks()) { - result.emplace_back(sink); - } - for (const auto& res : model.get_results()) { - result.emplace_back(res); - } - sorted_ops = std::move(result); - } - - for (const auto& n : sorted_ops) { - ov::Node* node = n.get(); - int node_id{}; - { - auto it = layer_ids.find(node); - OPENVINO_ASSERT(it != layer_ids.end(), "Internal error"); - node_id = it->second; - } - PostponedConstantReplacer modified_node(node); - node = modified_node.get_node(); - - const std::string& node_type_name{node->get_type_name()}; - - // - pugi::xml_node layer = layers.append_child("layer"); - layer.append_attribute("id").set_value(node_id); - // If determinism is not required, include auto-generated names into xml - // layer name is not critical for hash computing - if (!deterministic) { - layer.append_attribute("name").set_value(node->get_friendly_name().c_str()); - } - layer.append_attribute("type").set_value(translate_type_name(node_type_name).c_str()); - if (!exec_graph) { - layer.append_attribute("version").set_value(get_opset_name(node).c_str()); - } - - // general attributes - pugi::xml_node data = layer.append_child("data"); - - auto append_runtime_info = [&deterministic](pugi::xml_node& node, ov::RTMap& attributes) { - pugi::xml_node rt_node = node.append_child("rt_info"); - bool has_attrs = false; - for (auto& item : attributes) { - if (item.second.is()) { - auto& rt_attribute = item.second.as(); - if (!deterministic || rt_attribute.is_deterministic()) { - auto attribute_node = rt_node.append_child("attribute"); - const auto& type_info = rt_attribute.get_type_info(); - attribute_node.append_attribute("name").set_value(type_info.name); - attribute_node.append_attribute("version").set_value(type_info.get_version().c_str()); - rt_info::RTInfoSerializer serializer(attribute_node); - if (!rt_attribute.visit_attributes(serializer)) { - rt_node.remove_child(attribute_node); - } else { - has_attrs = true; - } - } - } - } - if (!has_attrs) { - node.remove_child(rt_node); - } - }; - - if (version >= 11) { - append_runtime_info(layer, node->get_rt_info()); - } - - int port_id = 0; - // - if (node->get_input_size() > 0) { - pugi::xml_node input = layer.append_child("input"); - for (auto& i : node->inputs()) { - // v0::LSTMCell peephole input shall not be serialized - if (i.get_index() == 6 && ov::as_type(node)) { - port_id++; - continue; - } - - pugi::xml_node port = input.append_child("port"); - port.append_attribute("id").set_value(port_id++); - - const auto& rt_info = i.get_tensor().get_rt_info(); - auto port_element_type = - is_fp16_compression_postponed(rt_info) ? ov::element::f16 : i.get_element_type(); - - port.append_attribute("precision").set_value(get_precision_name(port_element_type).c_str()); - for (const auto& d : i.get_partial_shape()) { - pugi::xml_node dim = port.append_child("dim"); - if (d.is_dynamic()) { - dim.append_child(pugi::xml_node_type::node_pcdata).set_value("-1"); - } else { - dim.append_child(pugi::xml_node_type::node_pcdata) - .set_value(std::to_string(d.get_length()).c_str()); - } - } - if (version >= 11) - append_runtime_info(port, i.get_rt_info()); - } - - if (node_type_name == "TensorIterator" || node_type_name == "Loop") { - layer.prepend_move(input); - } - } - // - if (node->get_output_size() > 0) { - auto serialize_tensor_names = [](const std::unordered_set& names) -> std::string { - auto sorted_names = std::vector(names.begin(), names.end()); - std::sort(sorted_names.begin(), sorted_names.end()); - - std::string serialized_names; - for (const auto& name : sorted_names) { - if (!serialized_names.empty()) - serialized_names += ","; - serialized_names += escape_delim(name); - } - return serialized_names; - }; - - if (ov::op::util::is_output(node)) { - if (version > 10 && !deterministic) { - // Not serialize output names for deterministic mode (hash) computation as it is optional - // attribute for v11 and not affect on model structure or how it works - if (const auto& names = ov::descriptor::get_assigned_names(node->get_output_tensor(0)); - !names.empty()) { - layer.append_attribute("output_names").set_value(serialize_tensor_names(names).c_str()); - } - } - } else { - pugi::xml_node output = layer.append_child("output"); - for (auto& o : node->outputs()) { - pugi::xml_node port = output.append_child("port"); - port.append_attribute("id").set_value(port_id++); - - const auto& rt_info = o.get_tensor().get_rt_info(); - auto port_element_type = - is_fp16_compression_postponed(rt_info) ? ov::element::f16 : o.get_element_type(); - - port.append_attribute("precision").set_value(get_precision_name(port_element_type).c_str()); - - if (const auto& tensor_names = o.get_tensor().get_names(); !tensor_names.empty()) { - port.append_attribute("names").set_value(serialize_tensor_names(tensor_names).c_str()); - } - - for (const auto& d : o.get_partial_shape()) { - pugi::xml_node dim = port.append_child("dim"); - if (d.is_dynamic()) { - dim.append_child(pugi::xml_node_type::node_pcdata).set_value("-1"); - } else { - dim.append_child(pugi::xml_node_type::node_pcdata) - .set_value(std::to_string(d.get_length()).c_str()); - } - } - if (version >= 11) - append_runtime_info(port, o.get_rt_info()); - } - if (node_type_name == "TensorIterator" || node_type_name == "Loop") { - layer.insert_move_after(output, layer.first_child()); - } - } - } - - // fill general attributes - { - bool compress_to_fp16 = false; - ov::element::Type output_element_type = ov::element::dynamic; - if (is_fp16_compression_postponed(node->get_rt_info())) { - compress_to_fp16 = true; - output_element_type = node->get_output_element_type(0); - } - // Backward compatibility: clear padding values for nodes with auto_pad - PaddingsFixer fixed_node(node); - XmlSerializer visitor(data, - node_type_name, - constant_node_write_handler, - version, - deterministic, - compress_to_fp16, - output_element_type, - modified_node.data_is_temporary()); - OPENVINO_ASSERT(fixed_node.get_node()->visit_attributes(visitor), "Visitor API is not supported in ", node); - } - rt_info::XmlSerializer{data}.serialize(node->get_rt_info()); - - if (exec_graph) { - visit_exec_graph_node(layer, node); - } - - const bool data_attr_size = data.attributes().begin() == data.attributes().end(); - if (data_attr_size) { - layer.remove_child(data); - } - } - // - const std::vector edge_mapping = create_edge_mapping(layer_ids, model); - pugi::xml_node edges = netXml.append_child("edges"); - auto ordered_ops = model.get_ordered_ops(); - for (auto e : edge_mapping) { - // v0::LSTMCell peephole input shall not be serialized - if (e.to_port == 6) { - const auto& type_info = ordered_ops[e.to_layer]->get_type_info(); - if (!strcmp(type_info.name, "LSTMCell")) { - continue; - } - } - pugi::xml_node edge = edges.append_child("edge"); - edge.append_attribute("from-layer").set_value(e.from_layer); - edge.append_attribute("from-port").set_value(e.from_port); - edge.append_attribute("to-layer").set_value(e.to_layer); - edge.append_attribute("to-port").set_value(e.to_port); - } - - // Serialize rt info - pugi::xml_node rt_info_node = netXml.append_child("rt_info"); - for (const auto& it : model.get_rt_info()) { - // Skip IR version - if (it.first == "version" || it.first == "__weights_path") - continue; - serialize_rt_info(rt_info_node, it.first, it.second); - } -} - +namespace { const std::filesystem::path valid_xml_path(const std::filesystem::path& path) { OPENVINO_ASSERT(path.extension() == ".xml", "Path for xml file doesn't contains file name with 'xml' extension: \"", @@ -1242,11 +48,11 @@ std::filesystem::path provide_bin_path(const std::filesystem::path& xml_path, co } } -void serializeFunc(std::ostream& xml_file, - std::ostream& bin_file, - std::shared_ptr model, - ov::pass::Serialize::Version ver, - bool deterministic = false) { +void serialize_func(std::ostream& xml_file, + std::ostream& bin_file, + std::shared_ptr model, + ov::pass::Serialize::Version ver, + bool deterministic = false) { auto version = static_cast(ver); auto& rt_info = model->get_rt_info(); @@ -1267,14 +73,15 @@ void serializeFunc(std::ostream& xml_file, std::string name = "net"; pugi::xml_document xml_doc; pugi::xml_node net_node = xml_doc.append_child(name.c_str()); - ConstantWriter constant_write_handler(bin_file); - XmlSerializer visitor(net_node, name, constant_write_handler, version, deterministic); + ov::util::ConstantWriter constant_write_handler(bin_file); + ov::util::XmlSerializer + visitor(net_node, name, constant_write_handler, version, deterministic, false, ov::element::dynamic, false); visitor.on_attribute(name, model); xml_doc.save(xml_file); xml_file.flush(); bin_file.flush(); -}; +} } // namespace @@ -1292,7 +99,7 @@ bool pass::Serialize::run_on_model(const std::shared_ptr& model) { disable_fp16_compression(node); if (m_xmlFile && m_binFile) { - serializeFunc(*m_xmlFile, *m_binFile, model, m_version); + serialize_func(*m_xmlFile, *m_binFile, model, m_version); } else { ov::util::create_directory_recursive(m_xmlPath); @@ -1304,7 +111,7 @@ bool pass::Serialize::run_on_model(const std::shared_ptr& model) { OPENVINO_ASSERT(xml_file, "Can't open xml file: \"", m_xmlPath, "\""); try { - serializeFunc(xml_file, bin_file, model, m_version); + serialize_func(xml_file, bin_file, model, m_version); } catch (const ov::AssertFailure&) { // optimization decision was made to create .bin file upfront and // write to it directly instead of buffering its content in memory, @@ -1381,8 +188,8 @@ bool pass::StreamSerialize::run_on_model(const std::shared_ptr& model } // Header - const size_t header_offset = use_absolute_offset() ? 0 : static_cast(m_stream.tellp()); - const size_t absolute_header_offset = static_cast(m_stream.tellp()); + const auto absolute_header_offset = static_cast(m_stream.tellp()); + const auto header_offset = use_absolute_offset() ? size_t{0} : absolute_header_offset; writeHeader(hdr); // Custom data @@ -1393,13 +200,13 @@ bool pass::StreamSerialize::run_on_model(const std::shared_ptr& model // Blobs hdr.consts_offset = static_cast(m_stream.tellp()) - header_offset; - std::string name = "net"; + const std::string name = "net"; pugi::xml_document xml_doc; pugi::xml_node net_node = xml_doc.append_child(name.c_str()); - ConstantWriter constant_write_handler(m_stream); - XmlSerializer visitor(net_node, name, constant_write_handler, version); + auto constant_write_handler = util::ConstantWriter(m_stream); + const auto visitor = make_serializer(net_node, name, constant_write_handler, version); std::shared_ptr fun = model; - visitor.on_attribute(name, fun); + visitor->on_attribute(name, fun); // IR hdr.model_offset = static_cast(m_stream.tellp()) - header_offset; @@ -1407,13 +214,13 @@ bool pass::StreamSerialize::run_on_model(const std::shared_ptr& model std::stringstream ss; xml_doc.save(ss); auto str_encode = m_cache_encrypt(ss.str()); - m_stream.write((char*)str_encode.c_str(), str_encode.length()); + m_stream.write(str_encode.c_str(), str_encode.length()); } else { xml_doc.save(m_stream); } m_stream.flush(); - const size_t file_size = static_cast(m_stream.tellp()) - header_offset; + const auto file_size = static_cast(m_stream.tellp()) - header_offset; hdr.custom_data_size = hdr.consts_offset - hdr.custom_data_offset; hdr.consts_size = hdr.model_offset - hdr.consts_offset; @@ -1428,23 +235,28 @@ bool pass::StreamSerialize::run_on_model(const std::shared_ptr& model return false; } +std::unique_ptr pass::StreamSerialize::make_serializer( + pugi::xml_node& data, + const std::string& node_type_name, + util::ConstantWriter& constant_write_handler, + int64_t version, + bool deterministic, + bool compress_to_fp16, + ov::element::Type output_element_type, + bool data_is_temporary) const { + return std::make_unique(data, + node_type_name, + constant_write_handler, + version, + deterministic, + compress_to_fp16, + output_element_type, + data_is_temporary); +} + /// -------- Hash calculation pass ------------- namespace { -// Hash combine formula from boost for uint64_t. -inline uint64_t hash_combine(uint64_t h, uint64_t k) { - constexpr uint64_t m = 0xc6a4a7935bd1e995; - constexpr int r = 47; - - k *= m; - k ^= k >> r; - k *= m; - - h ^= k; - h *= m; - - return h + 0xe6546b64; -} class OstreamHashWrapper final : public std::streambuf { uint64_t m_res = 0lu; @@ -1456,31 +268,26 @@ class OstreamHashWrapper final : public std::streambuf { std::streamsize xsputn(const char* s, std::streamsize n) override { uint64_t h = ov::runtime::compute_hash(s, n); - m_res = hash_combine(m_res, h); + m_res = util::u64_hash_combine(m_res, h); return n; } }; } // namespace -std::streamsize OstreamHashWrapperBin::xsputn(const char* s, std::streamsize n) { - m_res = hash_combine(m_res, *reinterpret_cast(s)); - return n; -} - bool pass::Hash::run_on_model(const std::shared_ptr& model) { RUN_ON_MODEL_SCOPE(Hash); OstreamHashWrapper xmlHash; - OstreamHashWrapperBin binHash; + util::OstreamHashWrapperBin binHash; std::ostream xml(&xmlHash); std::ostream bin(&binHash); // Determinism is important for hash calculation - serializeFunc(xml, bin, model, Serialize::Version::UNSPECIFIED, true); + serialize_func(xml, bin, model, Serialize::Version::UNSPECIFIED, true); uint64_t seed = 0; - seed = hash_combine(seed, xmlHash.getResult()); - seed = hash_combine(seed, binHash.getResult()); + seed = util::u64_hash_combine(seed, xmlHash.getResult()); + seed = util::u64_hash_combine(seed, binHash.get_result()); m_hash = seed; // Return false because we didn't change OpenVINO Model diff --git a/src/core/src/xml_util/constant_writer.cpp b/src/core/src/xml_util/constant_writer.cpp new file mode 100644 index 00000000000000..c2bb367f0fb155 --- /dev/null +++ b/src/core/src/xml_util/constant_writer.cpp @@ -0,0 +1,124 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/xml_util/constant_writer.hpp" + +#include "openvino/core/except.hpp" +#include "openvino/reference/convert.hpp" +#include "openvino/runtime/compute_hash.hpp" +#include "openvino/util/common_util.hpp" + +namespace ov::util { + +std::streamsize OstreamHashWrapperBin::xsputn(const char* s, std::streamsize n) { + m_res = u64_hash_combine(m_res, *reinterpret_cast(s)); + return n; +} + +ConstantWriter::ConstantWriter(std::ostream& bin_data, bool enable_compression) + : m_binary_output(bin_data), + m_enable_compression(enable_compression), + m_write_hash_value(static_cast(dynamic_cast(bin_data.rdbuf()))), + m_blob_offset(bin_data.tellp()) {} + +ConstantWriter::~ConstantWriter() = default; + +ConstantWriter::FilePosition ConstantWriter::write(const char* ptr, + size_t size, + size_t& new_size, + bool compress_to_fp16, + ov::element::Type src_type, + bool ptr_is_temporary) { + // when true, do not rely on ptr after this function call, data + // is temporary allocated + const FilePosition write_pos = m_binary_output.get().tellp(); + const auto offset = write_pos - m_blob_offset; + new_size = size; + + if (!m_enable_compression) { + if (!compress_to_fp16) { + m_binary_output.get().write(ptr, size); + } else { + OPENVINO_ASSERT(size % src_type.size() == 0); + auto fp16_buffer = compress_data_to_fp16(ptr, size, src_type, new_size); + m_binary_output.get().write(fp16_buffer.get(), new_size); + } + return offset; + } else { + std::unique_ptr fp16_buffer = nullptr; + if (compress_to_fp16) { + OPENVINO_ASSERT(size % src_type.size() == 0); + fp16_buffer = compress_data_to_fp16(ptr, size, src_type, new_size); + } + const char* ptr_to_write; + if (fp16_buffer) { + ptr_to_write = fp16_buffer.get(); + } else { + ptr_to_write = ptr; + } + + // This hash is weak (but efficient). For example current hash algorithms gives + // the same hash for {2, 2} and {0, 128} arrays. + // But even strong hashing algorithms sometimes give collisions. + // Therefore we always have to compare values when finding a match in the hash multimap. + const HashValue hash = ov::runtime::compute_hash(ptr_to_write, new_size); + + auto found = m_hash_to_file_positions.equal_range(hash); + // iterate over all matches of the key in the multimap + for (auto it = found.first; it != found.second; ++it) { + if (memcmp(ptr, it->second.second, size) == 0) { + return it->second.first; + } + } + if (!ptr_is_temporary) { + // Since fp16_compressed data will be disposed at exit point and since we cannot reread it from the + // ostream, we store pointer to the original uncompressed blob. + m_hash_to_file_positions.insert({hash, {offset, static_cast(ptr)}}); + } + if (m_write_hash_value) { + m_binary_output.get().write(reinterpret_cast(&hash), sizeof(uint64_t)); + } else { + m_binary_output.get().write(ptr_to_write, new_size); + } + } + return offset; +} + +std::unique_ptr ConstantWriter::compress_data_to_fp16(const char* ptr, + size_t size, + ov::element::Type src_type, + size_t& compressed_size) { + auto num_src_elements = size / src_type.size(); + compressed_size = num_src_elements * ov::element::f16.size(); + if (src_type == ov::element::f32) { + auto new_ptr = std::unique_ptr(new char[compressed_size]); + auto dst_data = reinterpret_cast(new_ptr.get()); + auto src_data = reinterpret_cast(ptr); + ov::reference::convert_from_f32_to_f16_with_clamp(src_data, dst_data, num_src_elements); + return new_ptr; + } else if (src_type == ov::element::f64) { + auto new_ptr = std::unique_ptr(new char[compressed_size]); + auto dst_data = reinterpret_cast(new_ptr.get()); + auto src_data = reinterpret_cast(ptr); + + // Reference implementation for fp64 to fp16 conversion + for (size_t i = 0; i < num_src_elements; ++i) { + // if abs value is smaller than the smallest positive fp16, but not zero + if (std::abs(src_data[i]) < ov::float16::from_bits(0x0001) && src_data[i] != 0.0f) { + dst_data[i] = 0; + } else if (src_data[i] > std::numeric_limits::max()) { + dst_data[i] = std::numeric_limits::max(); + } else if (src_data[i] < std::numeric_limits::lowest()) { + dst_data[i] = std::numeric_limits::lowest(); + } else { + dst_data[i] = static_cast(src_data[i]); + } + } + return new_ptr; + } else { + OPENVINO_THROW("[ INTERNAL ERROR ] Not supported source type for weights compression: ", src_type); + } +} + +} // namespace ov::util diff --git a/src/core/src/xml_util/xml_serialize_util.cpp b/src/core/src/xml_util/xml_serialize_util.cpp new file mode 100644 index 00000000000000..96a8c1772344a0 --- /dev/null +++ b/src/core/src/xml_util/xml_serialize_util.cpp @@ -0,0 +1,1088 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "openvino/xml_util/xml_serialize_util.hpp" + +#include + +#include "openvino/core/descriptor_tensor.hpp" +#include "openvino/core/except.hpp" +#include "openvino/core/meta_data.hpp" +#include "openvino/core/model.hpp" +#include "openvino/core/runtime_attribute.hpp" +#include "openvino/op/binary_convolution.hpp" +#include "openvino/op/constant.hpp" +#include "openvino/op/convolution.hpp" +#include "openvino/op/group_conv.hpp" +#include "openvino/op/loop.hpp" +#include "openvino/op/lstm_cell.hpp" +#include "openvino/op/parameter.hpp" +#include "openvino/op/util/avg_pool_base.hpp" +#include "openvino/op/util/deformable_convolution_base.hpp" +#include "openvino/op/util/framework_node.hpp" +#include "openvino/op/util/max_pool_base.hpp" +#include "openvino/op/util/op_types.hpp" +#include "openvino/op/util/sub_graph_base.hpp" +#include "openvino/runtime/string_aligned_buffer.hpp" +#include "openvino/xml_util/constant_writer.hpp" +#include "transformations/rt_info/disable_fp16_compression.hpp" + +namespace ov::util { + +namespace { +// Substitute a Constant node instead of a node by calling node->constant_fold if 'postponed_constant' rt_info attribute +// is present in the node +class PostponedConstantReplacer { +private: + ov::Node* m_node; + std::shared_ptr m_constant; + +public: + ov::Node* get_node() { + return m_node; + } + + bool data_is_temporary() const { + return m_constant != nullptr; + } + + PostponedConstantReplacer(ov::Node* node) : m_node(node), m_constant() { + if (node->get_rt_info().count("postponed_constant")) { + OPENVINO_ASSERT(node->get_output_size() == 1); + ov::OutputVector outputs(1); + OPENVINO_ASSERT( + node->constant_fold(outputs, node->input_values()), + "Node with set `postponed_constant` attribute cannot be fold to constant when saving model to IR file"); + m_constant = outputs[0].get_node_shared_ptr(); + m_node = m_constant.get(); + m_node->set_friendly_name(node->get_friendly_name()); + } + } +}; + +class PaddingsFixer { +private: + ov::Node* m_node; + + ov::OutputVector m_parameters; + std::shared_ptr m_cloned_node; + + const std::set pad_agnostic_types = { + ov::op::PadType::SAME_LOWER, + ov::op::PadType::SAME_UPPER, + ov::op::PadType::VALID, + ov::op::PadType::AUTO, + }; + + template + void clone_op_and_fix_paddings(const T* op) { + for (const auto& input : op->inputs()) { + m_parameters.emplace_back( + std::make_shared(input.get_element_type(), input.get_partial_shape())); + } + m_cloned_node = op->clone_with_new_inputs(m_parameters); + auto typed_cloned_node = ov::as_type_ptr(m_cloned_node); + OPENVINO_ASSERT(typed_cloned_node); + typed_cloned_node->set_pads_begin(P(op->get_pads_begin().size(), 0)); + typed_cloned_node->set_pads_end(P(op->get_pads_end().size(), 0)); + m_node = m_cloned_node.get(); + } + +public: + ov::Node* get_node() { + return m_node; + } + + explicit PaddingsFixer(ov::Node* node) : m_node(node) { + if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } else if (auto op = ov::as_type(node)) { + if (pad_agnostic_types.count(op->get_auto_pad())) { + clone_op_and_fix_paddings(op); + } + } + } +}; + +struct Edge { + int from_layer = 0; + int from_port = 0; + int to_layer = 0; + int to_port = 0; +}; + +const std::vector create_edge_mapping(const std::unordered_map& layer_ids, + const ov::Model& model) { + std::vector edges; + for (const auto& node : model.get_ordered_ops()) { + if (ov::op::util::is_parameter(node)) { + continue; + } + + for (const auto& i : node->inputs()) { + auto source_output = i.get_source_output(); + auto source_node = source_output.get_node(); + auto current_node = i.get_node(); + + OPENVINO_ASSERT(layer_ids.find(source_node) != layer_ids.end(), "Internal error"); + OPENVINO_ASSERT(layer_ids.find(current_node) != layer_ids.end(), "Internal error"); + + Edge e{}; + e.from_layer = layer_ids.find(source_node)->second; + e.from_port = static_cast(source_node->get_input_size() + source_output.get_index()); + e.to_layer = layer_ids.find(current_node)->second; + e.to_port = static_cast(i.get_index()); + edges.push_back(e); + } + } + std::sort(begin(edges), end(edges), [](const Edge& a, const Edge& b) -> bool { + return a.from_layer < b.from_layer; + }); + return edges; +} + +std::string get_opset_name(const ov::Node* n) { + OPENVINO_ASSERT(n != nullptr); + + // CVS-169882: Try to find opset name from RT info. Below (not recommended) solution affects TypeRelaxed and similar + // template internal operations. + auto opset_it = n->get_rt_info().find("opset"); + if (opset_it != n->get_rt_info().end()) { + if (opset_it->second.is()) { + return opset_it->second.as(); + } + } + + return n->get_type_info().version_id == nullptr ? "experimental" : n->get_type_info().version_id; +} + +std::string escape_delim(const std::string& name, const char delim = ',') { + std::string result_name = name; + const std::string escaped_delim = std::string("\\") + delim; + size_t index = result_name.find(delim, 0); + while (index != std::string::npos) { + result_name.replace(index, 1, escaped_delim); + index = result_name.find(delim, index + 2); + } + return result_name; +} + +void visit_exec_graph_node(pugi::xml_node& layer, const ov::Node* n) { + auto data = layer.child("data"); + for (const auto& param : n->get_rt_info()) { + if (param.second.is()) { + const std::string& name = param.first; + const std::string& value = param.second.as(); + + if (name == "layerType") { + layer.attribute("type").set_value(value.c_str()); + continue; + } + + data.append_attribute(name.c_str()).set_value(value.c_str()); + } + } +} + +bool is_correct_tag_name(const std::string& name) { + if (name.length() == 0) { + return false; + } + if (!std::all_of(name.begin(), name.end(), [](const int c) { + return std::isalnum(c) || (c == '_') || (c == '-') || (c == '.'); + })) { + return false; + } + if (std::isalpha(name[0]) == false && name[0] != '_') { + return false; + } + if (name.length() >= 3 && (name[0] == 'X' || name[0] == 'x') && (name[1] == 'M' || name[1] == 'm') && + (name[2] == 'l' || name[2] == 'L')) { + return false; + } + return true; +} + +void serialize_rt_info(pugi::xml_node& root, const std::string& name, const ov::Any& data) { + pugi::xml_node child; + if (is_correct_tag_name(name)) { + child = root.append_child(name.c_str()); + } else { + // Name may brake XML-naming specification, so better to store it as an attribute of typical + // node + child = root.append_child("info"); + child.append_attribute("name").set_value(name.c_str()); + } + if (data.is>()) { + auto meta = data.as>(); + do { + if (auto meta_with_pugixml_node = std::dynamic_pointer_cast(meta)) { + if (auto pugi_node = meta_with_pugixml_node->get_pugi_node()) { + root.remove_child(child); + auto added_node = root.append_copy(pugi_node); + OPENVINO_ASSERT(added_node, "Cannot add pugixml node with name: ", name); + added_node.set_name(name.c_str()); + break; + } + } + // Meta in ov::Meta cannot be accessed by MetaDataWithPugixml::get_pugi_node. Read it as ov::AnyMap + ov::AnyMap& map = *meta; + for (const auto& it : map) { + serialize_rt_info(child, it.first, it.second); + } + } while (false); + } else if (data.is()) { + const ov::AnyMap& any_map = data.as(); + for (const auto& it : any_map) { + serialize_rt_info(child, it.first, it.second); + } + } else { + std::string value = data.as(); + child.append_attribute("value").set_value(value.c_str()); + } +} + +// Here operation type names are translated from OpenVINO Model convention to IR +// convention. Most of them are the same, but there are exceptions, e.g +// Constant (OpenVINO Model name) and Const (IR name). If there will be more +// discrepancies discovered, translations needs to be added here. +const std::unordered_map& get_translate_type_name_translator() { + static const std::unordered_map translate_type_name_translator = {{"Constant", "Const"}, + {"PRelu", "PReLU"}, + {"Relu", "ReLU"}, + {"Softmax", "SoftMax"}}; + return translate_type_name_translator; +} + +std::string translate_type_name(const std::string& name) { + auto found = get_translate_type_name_translator().find(name); + if (found != end(get_translate_type_name_translator())) { + return found->second; + } + return name; +} + +const std::unordered_map create_layer_ids(const ov::Model& model) { + std::unordered_map layer_ids; + int id = 0; + for (const auto& node : model.get_ordered_ops()) { + layer_ids[node.get()] = id++; + } + return layer_ids; +} + +bool is_exec_graph(const ov::Model& model) { + // go over all operations and check whether performance stat is set + for (const auto& op : model.get_ops()) { + const auto& rtInfo = op->get_rt_info(); + if (rtInfo.find("execTimeMcs") != rtInfo.end()) { + return true; + } + } + return false; +} + +} // namespace + +namespace rt_info { +static const std::vector list_of_names{ + "PrimitivesPriority", + "alt_width", +}; + +class XmlSerializer { +public: + explicit XmlSerializer(pugi::xml_node& xml_node) : m_xml_node(xml_node) {} + + void serialize(const ov::Node::RTMap& rt_info) { + for (const auto& rt_info_name : list_of_names) { + const auto& found_rt_info = rt_info.find(rt_info_name); + if (found_rt_info != rt_info.end()) { + std::stringstream strm; + found_rt_info->second.print(strm); + m_xml_node.append_attribute(rt_info_name.c_str()).set_value(strm.str().c_str()); + } + } + } + +private: + pugi::xml_node& m_xml_node; +}; + +class RTInfoSerializer : public ov::AttributeVisitor { + pugi::xml_node m_node; + +public: + RTInfoSerializer(const pugi::xml_node node) : m_node(node) {} + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + if (auto a = ov::as_type>>(&adapter)) { + const auto& value = join(a->get()); + m_node.append_attribute(name.c_str()).set_value(value.c_str()); + } else { + OPENVINO_THROW("Unsupported attribute type for serialization: ", name); + } + } + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + m_node.append_attribute(name.c_str()).set_value(adapter.get()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + m_node.append_attribute(name.c_str()).set_value(adapter.get().c_str()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + m_node.append_attribute(name.c_str()).set_value(static_cast(adapter.get())); + } + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override { + check_attribute_name(name); + m_node.append_attribute(name.c_str()).set_value(adapter.get()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + const auto& value = join(adapter.get()); + m_node.append_attribute(name.c_str()).set_value(value.c_str()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + const auto& value = join(adapter.get()); + m_node.append_attribute(name.c_str()).set_value(value.c_str()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + const auto& value = join(adapter.get()); + m_node.append_attribute(name.c_str()).set_value(value.c_str()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + const auto& value = join(adapter.get()); + m_node.append_attribute(name.c_str()).set_value(value.c_str()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + check_attribute_name(name); + const auto& value = join(adapter.get()); + m_node.append_attribute(name.c_str()).set_value(value.c_str()); + } + + void on_adapter(const std::string& name, ov::ValueAccessor>& adapter) override { + OPENVINO_THROW("Model type is unsupported for rt info serialization"); + } + + void check_attribute_name(const std::string& name) const { + if (name == "name" || name == "version") { + OPENVINO_THROW("Attribute key with name: ", name, " is not allowed. Please use another name"); + } + } +}; +} // namespace rt_info + +std::vector XmlSerializer::map_type_from_body(const pugi::xml_node& xml_node, + const std::string& map_type, + int64_t ir_version, + const std::string& body_name) { + std::vector output; + for (pugi::xml_node node : xml_node.child(body_name.c_str()).child("layers")) { + if (map_type == node.attribute("type").value()) { + output.emplace_back(node.attribute("id").value()); + } + } + + return output; +} + +void XmlSerializer::input_descriptions_on_adapter( + const std::vector>& input_descriptions, + const std::vector& parameter_mapping, + const std::vector& result_mapping, + pugi::xml_node& port_map, + const std::string& portmap_name) { + if (!m_xml_node.parent().child(portmap_name.c_str())) { + port_map = m_xml_node.parent().insert_child_before(portmap_name.c_str(), m_xml_node.parent().first_child()); + } + + for (const auto& input_description : input_descriptions) { + pugi::xml_node input = port_map.append_child("input"); + input.append_attribute("external_port_id") + .set_value(static_cast(input_description->m_input_index)); + input.append_attribute("internal_layer_id") + .set_value(parameter_mapping[input_description->m_body_parameter_index].c_str()); + + if (auto slice_input = ov::as_type_ptr(input_description)) { + input.prepend_attribute("axis").set_value(static_cast(slice_input->m_axis)); + input.append_attribute("start").set_value(static_cast(slice_input->m_start)); + input.append_attribute("end").set_value(static_cast(slice_input->m_end)); + input.append_attribute("stride").set_value(static_cast(slice_input->m_stride)); + input.append_attribute("part_size").set_value(static_cast(slice_input->m_part_size)); + } else if (auto merged_input = + ov::as_type_ptr(input_description)) { + pugi::xml_node back_edges = m_xml_node.parent().child("back_edges"); + if (!back_edges) { + back_edges = m_xml_node.parent().insert_child_after("back_edges", port_map); + } + pugi::xml_node edge = back_edges.append_child("edge"); + edge.append_attribute("from-layer").set_value(result_mapping[merged_input->m_body_value_index].c_str()); + edge.append_attribute("to-layer") + .set_value(parameter_mapping[merged_input->m_body_parameter_index].c_str()); + } + } +} + +void XmlSerializer::output_descriptions_on_adapter( + const std::vector>& output_descriptions, + const uint32_t& input_count, + const std::vector& result_mapping, + pugi::xml_node& port_map, + const std::string& portmap_name) { + OPENVINO_ASSERT(!result_mapping.empty(), "No results found in body Model."); + + if (!port_map) { + port_map = m_xml_node.parent().insert_child_before(portmap_name.c_str(), m_xml_node.parent().first_child()); + } + + for (const auto& output_description : output_descriptions) { + pugi::xml_node output = port_map.append_child("output"); + output.append_attribute("external_port_id") + .set_value(static_cast(input_count + output_description->m_output_index)); + output.append_attribute("internal_layer_id") + .set_value(result_mapping[output_description->m_body_value_index].c_str()); + + if (auto concat_output = + ov::as_type_ptr(output_description)) { + output.prepend_attribute("axis").set_value(static_cast(concat_output->m_axis)); + output.append_attribute("start").set_value(static_cast(concat_output->m_start)); + output.append_attribute("end").set_value(static_cast(concat_output->m_end)); + output.append_attribute("stride").set_value(static_cast(concat_output->m_stride)); + output.append_attribute("part_size").set_value(static_cast(concat_output->m_part_size)); + } + } +} + +void XmlSerializer::special_body_ports_on_adapter(const ov::op::v5::Loop::SpecialBodyPorts& special_body_ports, + const std::vector& parameter_mapping, + const std::vector& result_mapping, + pugi::xml_node& port_map) { + OPENVINO_ASSERT(port_map, "port_map section not found, purpose attribute cannot be added."); + + if (special_body_ports.current_iteration_input_idx != -1) { + pugi::xml_node iter_input = port_map.append_child("input"); + iter_input.append_attribute("external_port_id").set_value("-1"); + iter_input.append_attribute("internal_layer_id") + .set_value(parameter_mapping[special_body_ports.current_iteration_input_idx].c_str()); + iter_input.append_attribute("purpose").set_value("current_iteration"); + } + + if (special_body_ports.body_condition_output_idx != -1) { + pugi::xml_node exec_output = port_map.append_child("output"); + exec_output.append_attribute("external_port_id").set_value("-1"); + exec_output.append_attribute("internal_layer_id") + .set_value(result_mapping[special_body_ports.body_condition_output_idx].c_str()); + exec_output.append_attribute("purpose").set_value("execution_condition"); + } +} + +bool XmlSerializer::append_rt_attribute(pugi::xml_node& node, const ov::RuntimeAttribute& attribute) { + const auto& type_info = attribute.get_type_info(); + node.append_attribute("name").set_value(type_info.name); + node.append_attribute("version").set_value(type_info.get_version().c_str()); + rt_info::RTInfoSerializer serializer(node); + return attribute.visit_attributes(serializer); +} + +void XmlSerializer::append_rt_info(pugi::xml_node& node, ov::RTMap& attributes) { + pugi::xml_node rt_node = node.append_child("rt_info"); + bool has_attrs = false; + for (auto& item : attributes) { + if (item.second.is()) { + auto& rt_attribute = item.second.as(); + if (!m_deterministic || rt_attribute.is_deterministic()) { + auto attribute_node = rt_node.append_child("attribute"); + if (append_rt_attribute(attribute_node, rt_attribute)) { + has_attrs = true; + } else { + rt_node.remove_child(attribute_node); + } + } + } + } + if (!has_attrs) { + node.remove_child(rt_node); + } +} + +XmlSerializer::XmlSerializer(pugi::xml_node& data, + const std::string& node_type_name, + ov::util::ConstantWriter& constant_write_handler, + int64_t version, + bool deterministic, + bool compress_to_fp16, + ov::element::Type output_element_type, + bool data_is_temporary) + : m_xml_node(data), + m_node_type_name(node_type_name), + m_constant_node_write_handler(std::ref(constant_write_handler)), + m_version(version), + m_deterministic(deterministic), + m_compress_to_fp16(compress_to_fp16), + m_output_element_type(output_element_type), + m_data_is_temporary(data_is_temporary), + m_custom_rt_info_append{} {} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + using BodyTargetNames = std::tuple>; + + const std::vector body_names = { + BodyTargetNames{"body", "port_map", {"input_descriptions", "output_descriptions", "special_body_ports"}}, + BodyTargetNames{"then_body", "then_port_map", {"then_inputs", "then_outputs"}}, + BodyTargetNames{"else_body", "else_port_map", {"else_inputs", "else_outputs"}}}; + BodyTargetNames bnames; + bool is_body_target = false; + for (const auto& _body_target : body_names) { + if (m_xml_node.parent().child(std::get<0>(_body_target).c_str())) { + auto vec_names = std::get<2>(_body_target); + + if (std::find(vec_names.begin(), vec_names.end(), name) != vec_names.end()) { + is_body_target = true; + bnames = _body_target; + break; + } + } + } + if (!is_body_target) { + std::string id = "input_descriptions"; + std::string od = "output_descriptions"; + const auto& id_pos = name.find("input_descriptions"); + const auto& od_pos = name.find("output_descriptions"); + auto id_str = name; + size_t body_id; + if (id_pos != std::string::npos) { + id_str.erase(id_pos, id.length()); + (void)std::stoi(id_str, &body_id); + is_body_target = true; + } else if (od_pos != std::string::npos) { + id_str.erase(od_pos, od.length()); + (void)std::stoi(id_str, &body_id); + is_body_target = true; + } + if (is_body_target) { + auto body_name = "body" + id_str; + if (m_xml_node.parent().child(body_name.c_str())) { + bnames = BodyTargetNames{body_name, + "port_map" + id_str, + {"input_descriptions" + id_str, "output_descriptions" + id_str}}; + } else { + is_body_target = false; + } + } + } + + if (is_body_target) { + const auto& body_name = std::get<0>(bnames); + const auto& portmap_name = std::get<1>(bnames); + std::vector result_mapping = + map_type_from_body(m_xml_node.parent(), "Result", m_version, body_name); + std::vector parameter_mapping = + map_type_from_body(m_xml_node.parent(), "Parameter", m_version, body_name); + + pugi::xml_node port_map = m_xml_node.parent().child(portmap_name.c_str()); + // Bodies can be without parameters(depending on constants), but can not be without results + OPENVINO_ASSERT(!result_mapping.empty(), "No results found in body Model."); + // TI, Loop do not have attributes as regular ops, it is necessary to append "port_map" and + // "back_edges" to layer above (m_xml_node.parent()) as in serialize() layer (here "m_xml_node") + // with empty attributes is removed. + if (const auto& a = ov::as_type< + ov::AttributeAdapter>>>( + &adapter)) { + input_descriptions_on_adapter(a->get(), parameter_mapping, result_mapping, port_map, portmap_name); + } else if (const auto& a = ov::as_type>>>(&adapter)) { + uint32_t op_input_count = 0; + for (auto c = m_xml_node.parent().child("input").first_child(); !c.empty(); c = c.next_sibling()) { + op_input_count++; + } + output_descriptions_on_adapter(a->get(), op_input_count, result_mapping, port_map, portmap_name); + } else if (const auto& a = ov::as_type>(&adapter)) { + special_body_ports_on_adapter(a->get(), parameter_mapping, result_mapping, port_map); + } + } else if (const auto& a = ov::as_type>>(&adapter)) { + m_xml_node.append_attribute(name.c_str()).set_value(a->get()->get_info().variable_id.c_str()); + } else if (ov::is_type>>(&adapter) || + ov::is_type>>(&adapter)) { + if (name == "value" && translate_type_name(m_node_type_name) == "Const") { + auto a1 = ov::as_type>>(&adapter); + auto a2 = ov::as_type>>(&adapter); + size_t new_size = 0; + size_t inter_size = 0; + // write a header of packed string tensor + std::shared_ptr header_ptr = nullptr; + size_t header_size = 0; + if (a1) { + a1->get_header(header_ptr, header_size); + } else { + a2->get_header(header_ptr, header_size); + } + + int64_t offset = get_constant_write_handler().write( + reinterpret_cast(header_ptr.get()), + header_size, + inter_size, + m_compress_to_fp16, + m_output_element_type, + true); // header_ptr is allocated in AttributeAdapter that has limited life time + new_size += inter_size; + + // write raw strings part + size_t num_elements = 0; + if (a1) { + num_elements = a1->get()->get_num_elements(); + } else { + num_elements = a2->get()->get_num_elements(); + } + for (size_t ind = 0; ind < num_elements; ++ind) { + const char* raw_string_ptr; + size_t raw_string_size; + if (a1) { + a1->get_raw_string_by_index(raw_string_ptr, raw_string_size, ind); + } else { + a2->get_raw_string_by_index(raw_string_ptr, raw_string_size, ind); + } + + get_constant_write_handler().write(raw_string_ptr, + raw_string_size, + inter_size, + m_compress_to_fp16, + m_output_element_type, + m_data_is_temporary); + + new_size += inter_size; + } + m_xml_node.append_attribute("offset").set_value(static_cast(offset)); + m_xml_node.append_attribute("size").set_value(static_cast(new_size)); + } + } else if (const auto& a = ov::as_type>>(&adapter)) { + if (name == "value" && translate_type_name(m_node_type_name) == "Const") { + const auto size = a->get()->size(); + size_t new_size = 0lu; + int64_t offset = get_constant_write_handler().write(static_cast(a->get()->get_ptr()), + size, + new_size, + m_compress_to_fp16, + m_output_element_type, + m_data_is_temporary); + + m_xml_node.append_attribute("offset").set_value(static_cast(offset)); + m_xml_node.append_attribute("size").set_value(static_cast(new_size)); + } + } else if (const auto& a = ov::as_type>(&adapter)) { + const auto& attrs = a->get(); + + // Update type and version attributes + pugi::xml_node layer = m_xml_node.parent(); + + auto type_attr = layer.attribute("type"); + auto version_attr = layer.attribute("version"); + + type_attr.set_value(attrs.get_type_name().c_str()); + + if (!attrs.get_opset_name().empty()) { + version_attr.set_value(attrs.get_opset_name().c_str()); + } else { + layer.remove_attribute("version"); + } + + // Update node attributes in data field + for (const auto& attr : attrs) { + m_xml_node.append_attribute(attr.first.c_str()).set_value(attr.second.c_str()); + } + } else if (const auto& a = ov::as_type>(&adapter)) { + const auto& attrs = a->get(); + m_xml_node.append_attribute(name.c_str()).set_value(util::join(attrs).c_str()); + } else if (const auto& a = ov::as_type>(&adapter)) { + const auto& attrs = a->get(); + auto shape_str = attrs.to_string(); + if (shape_str[0] == '[' && shape_str[shape_str.size() - 1] == ']') + shape_str = shape_str.substr(1, shape_str.size() - 2); + m_xml_node.append_attribute(name.c_str()).set_value(shape_str.c_str()); + } else if (const auto& a = ov::as_type>(&adapter)) { + const auto& attrs = a->get(); + std::stringstream dim_str_stream; + dim_str_stream << attrs; + auto dim_str = dim_str_stream.str(); + if (dim_str[0] == '{' && dim_str[dim_str.size() - 1] == '}') + dim_str = dim_str.substr(1, dim_str.size() - 2); + m_xml_node.append_attribute(name.c_str()).set_value(dim_str.c_str()); + } else { + OPENVINO_THROW("Unsupported attribute type for serialization: ", name); + } +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(adapter.get()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + std::string value; + if (m_compress_to_fp16 && name == "element_type") { + value = ov::as_string(static_cast(ov::element::f16)); + } else { + value = adapter.get(); + } + m_xml_node.append_attribute(name.c_str()).set_value(value.c_str()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(static_cast(adapter.get())); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(adapter.get()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(create_attribute_list(adapter).c_str()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(create_attribute_list(adapter).c_str()); +} +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(create_attribute_list(adapter).c_str()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(create_attribute_list(adapter).c_str()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + m_xml_node.append_attribute(name.c_str()).set_value(create_attribute_list(adapter).c_str()); +} + +void XmlSerializer::on_adapter(const std::string& name, ov::ValueAccessor>& adapter) { + if (name.find("body") != std::string::npos) { + // name that contains subgraphs: body{n}, then_body, else_body + // TI, Loop do not have attributes as regular ops, it is necessary to append "body" + // to layer above (m_xml_node.parent()) as in serialize() layer (m_xml_node) with empty attributes + // is removed. + pugi::xml_node xml_body = m_xml_node.parent().append_child(name.c_str()); + serialize(xml_body, *adapter.get()); + xml_body.remove_attribute("name"); + xml_body.remove_attribute("version"); + } else if (name == "net") { + serialize(m_xml_node, *adapter.get()); + } else { + OPENVINO_THROW("Unsupported Model name."); + } +} + +std::unique_ptr XmlSerializer::make_visitor(pugi::xml_node& data, + const std::string& node_type_name, + ov::util::ConstantWriter& constant_write_handler, + int64_t version, + bool deterministic, + bool compress_to_fp16, + ov::element::Type output_element_type, + bool data_is_temporary) const { + return std::make_unique(data, + node_type_name, + constant_write_handler, + version, + deterministic, + compress_to_fp16, + output_element_type, + data_is_temporary); +} + +void XmlSerializer::serialize(pugi::xml_node& net_xml, const ov::Model& model) { + // If determinism is not required, include auto-generated names into xml + // model name is not critical for hash computing + if (!m_deterministic) { + net_xml.append_attribute("name").set_value(model.get_friendly_name().c_str()); + } + net_xml.append_attribute("version").set_value(static_cast(m_version)); + pugi::xml_node layers = net_xml.append_child("layers"); + + const std::unordered_map layer_ids = create_layer_ids(model); + + const bool exec_graph = is_exec_graph(model); + + auto sorted_ops = model.get_ordered_ops(); + + // get_ordered_ops() returns operations after a topological sort. The topological sort reverses order of Parameters + // and Results. So we need to put them into sorted_ops separately to ensure correct order of inputs and outputs. + { + std::vector> result; + result.reserve(sorted_ops.size()); + for (const auto& param : model.get_parameters()) { + result.emplace_back(param); + } + auto model_sinks = model.get_sinks(); + for (auto&& node : sorted_ops) { + if (!ov::op::util::is_parameter(node) && !ov::op::util::is_output(node) && + std::find(model_sinks.begin(), model_sinks.end(), node) == model_sinks.end()) + result.emplace_back(node); + } + for (const auto& sink : model.get_sinks()) { + result.emplace_back(sink); + } + for (const auto& res : model.get_results()) { + result.emplace_back(res); + } + sorted_ops = std::move(result); + } + + for (const auto& n : sorted_ops) { + ov::Node* node = n.get(); + int node_id{}; + { + auto it = layer_ids.find(node); + OPENVINO_ASSERT(it != layer_ids.end(), "Internal error"); + node_id = it->second; + } + PostponedConstantReplacer modified_node(node); + node = modified_node.get_node(); + + const std::string& node_type_name{node->get_type_name()}; + + // + pugi::xml_node layer = layers.append_child("layer"); + layer.append_attribute("id").set_value(node_id); + // If determinism is not required, include auto-generated names into xml + // layer name is not critical for hash computing + if (!m_deterministic) { + layer.append_attribute("name").set_value(node->get_friendly_name().c_str()); + } + layer.append_attribute("type").set_value(translate_type_name(node_type_name).c_str()); + if (!exec_graph) { + layer.append_attribute("version").set_value(get_opset_name(node).c_str()); + } + + pugi::xml_node data = layer.append_child("data"); + + if (m_version >= 11) { + append_rt_info(layer, node->get_rt_info()); + } + + int port_id = 0; + // + if (node->get_input_size() > 0) { + pugi::xml_node input = layer.append_child("input"); + for (auto& i : node->inputs()) { + // v0::LSTMCell peephole input shall not be serialized + if (i.get_index() == 6 && ov::as_type(node)) { + port_id++; + continue; + } + + pugi::xml_node port = input.append_child("port"); + port.append_attribute("id").set_value(port_id++); + + const auto& rt_info = i.get_tensor().get_rt_info(); + auto port_element_type = + is_fp16_compression_postponed(rt_info) ? ov::element::f16 : i.get_element_type(); + + port.append_attribute("precision").set_value(get_ir_precision_name(port_element_type).c_str()); + for (const auto& d : i.get_partial_shape()) { + pugi::xml_node dim = port.append_child("dim"); + if (d.is_dynamic()) { + dim.append_child(pugi::xml_node_type::node_pcdata).set_value("-1"); + } else { + dim.append_child(pugi::xml_node_type::node_pcdata) + .set_value(std::to_string(d.get_length()).c_str()); + } + } + if (m_version >= 11) { + append_rt_info(port, i.get_rt_info()); + } + } + + if (node_type_name == "TensorIterator" || node_type_name == "Loop") { + layer.prepend_move(input); + } + } + // + if (node->get_output_size() > 0) { + auto serialize_tensor_names = [](const std::unordered_set& names) -> std::string { + auto sorted_names = std::vector(names.begin(), names.end()); + std::sort(sorted_names.begin(), sorted_names.end()); + + std::string serialized_names; + for (const auto& name : sorted_names) { + if (!serialized_names.empty()) + serialized_names += ","; + serialized_names += escape_delim(name); + } + return serialized_names; + }; + + if (ov::op::util::is_output(node)) { + if (m_version > 10 && !m_deterministic) { + // Not serialize output names for deterministic mode (hash) computation as it is optional + // attribute for v11 and not affect on model structure or how it works + if (const auto& names = ov::descriptor::get_assigned_names(node->get_output_tensor(0)); + !names.empty()) { + layer.append_attribute("output_names").set_value(serialize_tensor_names(names).c_str()); + } + } + } else { + pugi::xml_node output = layer.append_child("output"); + for (auto& o : node->outputs()) { + pugi::xml_node port = output.append_child("port"); + port.append_attribute("id").set_value(port_id++); + + const auto& rt_info = o.get_tensor().get_rt_info(); + auto port_element_type = + is_fp16_compression_postponed(rt_info) ? ov::element::f16 : o.get_element_type(); + + port.append_attribute("precision").set_value(get_ir_precision_name(port_element_type).c_str()); + + if (const auto& tensor_names = o.get_tensor().get_names(); !tensor_names.empty()) { + port.append_attribute("names").set_value(serialize_tensor_names(tensor_names).c_str()); + } + + for (const auto& d : o.get_partial_shape()) { + pugi::xml_node dim = port.append_child("dim"); + if (d.is_dynamic()) { + dim.append_child(pugi::xml_node_type::node_pcdata).set_value("-1"); + } else { + dim.append_child(pugi::xml_node_type::node_pcdata) + .set_value(std::to_string(d.get_length()).c_str()); + } + } + if (m_version >= 11) { + append_rt_info(port, o.get_rt_info()); + } + } + if (node_type_name == "TensorIterator" || node_type_name == "Loop") { + layer.insert_move_after(output, layer.first_child()); + } + } + } + + // general attributes + { + bool compress_to_fp16 = false; + ov::element::Type output_element_type = ov::element::dynamic; + if (is_fp16_compression_postponed(node->get_rt_info())) { + compress_to_fp16 = true; + output_element_type = node->get_output_element_type(0); + } + // Backward compatibility: clear padding values for nodes with auto_pad + PaddingsFixer fixed_node(node); + auto visitor = make_visitor(data, + node_type_name, + get_constant_write_handler(), + m_version, + m_deterministic, + compress_to_fp16, + output_element_type, + modified_node.data_is_temporary()); + OPENVINO_ASSERT(visitor->append_node_attributes(*fixed_node.get_node()), + "Visitor API is not supported in ", + node); + rt_info::XmlSerializer{data}.serialize(node->get_rt_info()); + + if (exec_graph) { + visit_exec_graph_node(layer, node); + } + + const bool data_attr_size = data.attributes().begin() == data.attributes().end(); + if (data_attr_size) { + layer.remove_child(data); + } + } + } + // + const std::vector edge_mapping = create_edge_mapping(layer_ids, model); + pugi::xml_node edges = net_xml.append_child("edges"); + auto ordered_ops = model.get_ordered_ops(); + for (auto e : edge_mapping) { + // v0::LSTMCell peephole input shall not be serialized + if (e.to_port == 6) { + const auto& type_info = ordered_ops[e.to_layer]->get_type_info(); + if (!strcmp(type_info.name, "LSTMCell")) { + continue; + } + } + pugi::xml_node edge = edges.append_child("edge"); + edge.append_attribute("from-layer").set_value(e.from_layer); + edge.append_attribute("from-port").set_value(e.from_port); + edge.append_attribute("to-layer").set_value(e.to_layer); + edge.append_attribute("to-port").set_value(e.to_port); + } + + // Serialize rt info + pugi::xml_node rt_info_node = net_xml.append_child("rt_info"); + for (const auto& it : model.get_rt_info()) { + // Skip IR version and Weights path. + if (it.first == "version" || it.first == "__weights_path") + continue; + serialize_rt_info(rt_info_node, it.first, it.second); + } +} + +bool XmlSerializer::append_node_attributes(ov::Node& node) { + return node.visit_attributes(*this); +} + +// util::ConstantWriter& XmlSerializer::get_constant_write_handler() { +// return m_constant_node_write_handler.get(); +// } + +std::string get_ir_precision_name(const element::Type& precision) { + switch (precision) { + case ov::element::dynamic: + return "UNSPECIFIED"; + case ov::element::boolean: + return "BOOL"; + case ov::element::u1: + return "BIN"; + case ov::element::f16: + return "FP16"; + case ov::element::f32: + return "FP32"; + case ov::element::bf16: + return "BF16"; + case ov::element::f64: + return "FP64"; + default: + return ov::util::to_upper(precision.get_type_name()); + } +} +} // namespace ov::util From 2d9253db5b07b1c0251c5a447a07d1d9b9a1ac3b Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Thu, 7 Aug 2025 14:04:25 +0200 Subject: [PATCH 08/12] Add missing include Signed-off-by: Raasz, Pawel --- src/common/util/include/openvino/util/common_util.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/util/include/openvino/util/common_util.hpp b/src/common/util/include/openvino/util/common_util.hpp index f7223b62613e55..69213bec89e5eb 100644 --- a/src/common/util/include/openvino/util/common_util.hpp +++ b/src/common/util/include/openvino/util/common_util.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include From 254e682ee8bb85ccc818ef66c9eca1b8d05f3728 Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Thu, 7 Aug 2025 15:08:15 +0200 Subject: [PATCH 09/12] Add pugi class to NCC exclude pattern, replace not used name Signed-off-by: Raasz, Pawel --- cmake/developer_package/ncc_naming_style/openvino.style | 2 +- src/core/include/openvino/pass/serialize.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/developer_package/ncc_naming_style/openvino.style b/cmake/developer_package/ncc_naming_style/openvino.style index af5baeab7c1f81..db5da84c45e301 100644 --- a/cmake/developer_package/ncc_naming_style/openvino.style +++ b/cmake/developer_package/ncc_naming_style/openvino.style @@ -1,6 +1,6 @@ # custom OpenVINO values CppMethod: '^(operator\W+|[a-z_\d]+|signaling_NaN|quiet_NaN|OPENVINO_OP|OPENVINO_RTTI)$' -ClassName: '^([A-Z][\w]+|b?float16|float8_e4m3|float8_e5m2|float4_e2m1|float8_e8m0|numeric_limits|ngraph_error|stopwatch|unsupported_op)$' +ClassName: '^([A-Z][\w]+|b?float16|float8_e4m3|float8_e5m2|float4_e2m1|float8_e8m0|numeric_limits|xml_node|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_]*)$' diff --git a/src/core/include/openvino/pass/serialize.hpp b/src/core/include/openvino/pass/serialize.hpp index 621c21dd7201f2..17fafb58b648c8 100644 --- a/src/core/include/openvino/pass/serialize.hpp +++ b/src/core/include/openvino/pass/serialize.hpp @@ -13,9 +13,10 @@ #include "openvino/pass/pass.hpp" namespace pugi { -class xml_node; +class xml_node; // NCC } + namespace ov::util { class XmlSerializer; class ConstantWriter; From 8e0198d32b6e6131d40dfc99530c04006cb192ea Mon Sep 17 00:00:00 2001 From: "Raasz, Pawel" Date: Fri, 8 Aug 2025 06:43:12 +0200 Subject: [PATCH 10/12] Apply code style Signed-off-by: Raasz, Pawel --- src/core/include/openvino/pass/serialize.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/include/openvino/pass/serialize.hpp b/src/core/include/openvino/pass/serialize.hpp index 17fafb58b648c8..9adb2be13f6a04 100644 --- a/src/core/include/openvino/pass/serialize.hpp +++ b/src/core/include/openvino/pass/serialize.hpp @@ -16,7 +16,6 @@ namespace pugi { class xml_node; // NCC } - namespace ov::util { class XmlSerializer; class ConstantWriter; From 9dec54cd6819c6072312bf027e7ca4799d0acb13 Mon Sep 17 00:00:00 2001 From: Razvan Apetroaie Date: Tue, 2 Sep 2025 15:01:20 +0000 Subject: [PATCH 11/12] Implementing the NPU plugin deserializer, it doesn't do anything special yet --- .../xml_util/src/xml_deserialize_util.cpp | 3 +- src/plugins/intel_npu/src/al/CMakeLists.txt | 1 + .../al/include/intel_npu/xml_deserializer.hpp | 22 ++++++++ .../intel_npu/src/al/src/xml_deserializer.cpp | 54 +++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp create mode 100644 src/plugins/intel_npu/src/al/src/xml_deserializer.cpp diff --git a/src/core/xml_util/src/xml_deserialize_util.cpp b/src/core/xml_util/src/xml_deserialize_util.cpp index abd0ba2a850ad7..9b0aa20cfa2b1c 100644 --- a/src/core/xml_util/src/xml_deserialize_util.cpp +++ b/src/core/xml_util/src/xml_deserialize_util.cpp @@ -9,6 +9,7 @@ #include #include "openvino/core/descriptor_tensor.hpp" +#include "openvino/core/memory_util.hpp" #include "openvino/core/meta_data.hpp" #include "openvino/core/rt_info/weightless_caching_attributes.hpp" #include "openvino/core/type.hpp" @@ -911,7 +912,7 @@ void XmlDeserializer::set_constant_num_buffer(ov::AttributeAdapter> 3), ", ", - ov::element::get_memory_size(el_type, ov::shape_size(shape))); + ov::util::get_memory_size(el_type, ov::shape_size(shape))); } auto buffer = std::make_shared>>(data, size, m_weights); diff --git a/src/plugins/intel_npu/src/al/CMakeLists.txt b/src/plugins/intel_npu/src/al/CMakeLists.txt index 7bdb9ccd7a1b6b..8e5383728338d7 100644 --- a/src/plugins/intel_npu/src/al/CMakeLists.txt +++ b/src/plugins/intel_npu/src/al/CMakeLists.txt @@ -24,6 +24,7 @@ target_link_libraries(${TARGET_NAME} PUBLIC openvino::npu_logger_utils openvino::runtime::dev + openvino_xml_util ) set_target_properties(${TARGET_NAME} PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE ${ENABLE_LTO}) diff --git a/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp b/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp new file mode 100644 index 00000000000000..df83ae1b2e1958 --- /dev/null +++ b/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp @@ -0,0 +1,22 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +#include "openvino/xml_util/xml_deserialize_util.hpp" + +namespace intel_npu { + +class NPUXmlDeserializer : public ov::util::XmlDeserializer { +public: + explicit NPUXmlDeserializer(const pugi::xml_node& node, + const std::shared_ptr& weights, + const std::unordered_map& opsets, + std::unordered_map>& variables, + size_t version); +}; + +std::shared_ptr deserialize_ir_model(std::string_view serialized_graph, const ov::Tensor& weights); + +} // namespace intel_npu diff --git a/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp b/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp new file mode 100644 index 00000000000000..bee1458ae62902 --- /dev/null +++ b/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp @@ -0,0 +1,54 @@ +// Copyright (C) 2018-2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include "intel_npu/xml_deserializer.hpp" + +#include "openvino/runtime/shared_buffer.hpp" +#include "openvino/util/common_util.hpp" +#include "openvino/util/xml_parse_utils.hpp" + +namespace intel_npu { + +NPUXmlDeserializer::NPUXmlDeserializer( + const pugi::xml_node& node, + const std::shared_ptr& weights, + const std::unordered_map& opsets, + std::unordered_map>& variables, + size_t version) + : ov::util::XmlDeserializer(node, + weights, + opsets, + std::unordered_map(), + variables, + version) {} + +std::shared_ptr deserialize_ir_model(std::string_view serialized_graph, const ov::Tensor& weights) { + ov::util::StringViewStreamBuf mb(serialized_graph); + std::istream modelStream(&mb); + pugi::xml_document m_xml_doc; + pugi::xml_parse_result res = m_xml_doc.load(modelStream); + OPENVINO_ASSERT(res.status == pugi::status_ok, res.description(), " at offset ", res.offset); + pugi::xml_node root = m_xml_doc.document_element(); + + std::shared_ptr weights_buffer = + std::make_shared>(reinterpret_cast(const_cast(weights.data())), + weights.get_byte_size(), + weights); + + std::unordered_map opsets; + for (const auto& it : ov::get_available_opsets()) { + opsets[it.first] = it.second(); + } + std::unordered_map> variables; + size_t version = static_cast(ov::util::pugixml::get_uint64_attr(root, "version", 0)); + + NPUXmlDeserializer visitor(root, weights_buffer, opsets, variables, version); + std::shared_ptr model; + visitor.on_attribute("net", model); + model->get_rt_info()["version"] = int64_t(version); + + return model; +} + +} // namespace intel_npu From 5cf161e8b52cb92ba7f3ca42d188c147b373c746 Mon Sep 17 00:00:00 2001 From: Razvan Apetroaie Date: Tue, 2 Sep 2025 15:27:59 +0000 Subject: [PATCH 12/12] Starting to override one method --- .../al/include/intel_npu/xml_deserializer.hpp | 21 + .../intel_npu/src/al/src/xml_deserializer.cpp | 482 ++++++++++++++++++ 2 files changed, 503 insertions(+) diff --git a/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp b/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp index df83ae1b2e1958..1cc6dcc9ef406d 100644 --- a/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp +++ b/src/plugins/intel_npu/src/al/include/intel_npu/xml_deserializer.hpp @@ -15,6 +15,27 @@ class NPUXmlDeserializer : public ov::util::XmlDeserializer { const std::unordered_map& opsets, std::unordered_map>& variables, size_t version); + + void on_adapter(const std::string& name, ov::ValueAccessor& adapter) override; + +private: + struct IoMap { + using NodeIdToIoIndex = std::unordered_map; + NodeIdToIoIndex inputs; + NodeIdToIoIndex outputs; + }; + + std::vector> + parse_input_description(const pugi::xml_node& node, const std::string& body_name, const std::string& port_map_name); + + std::vector> parse_output_description( + const pugi::xml_node& node, + const std::string& body_name, + const std::string& port_map_name); + + ov::op::v5::Loop::SpecialBodyPorts parse_purpose_attribute(const pugi::xml_node& node); + + IoMap updated_io_map(const pugi::xml_node& node, const pugi::xml_node& body_node); }; std::shared_ptr deserialize_ir_model(std::string_view serialized_graph, const ov::Tensor& weights); diff --git a/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp b/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp index bee1458ae62902..75cc2ee0ca00e0 100644 --- a/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp +++ b/src/plugins/intel_npu/src/al/src/xml_deserializer.cpp @@ -23,6 +23,488 @@ NPUXmlDeserializer::NPUXmlDeserializer( variables, version) {} +std::vector> NPUXmlDeserializer::parse_input_description( + const pugi::xml_node& node, + const std::string& body_name, + const std::string& port_map_name) { + std::vector> inputs; + auto body_node = node.child(body_name.c_str()); + + const auto up_io_map = updated_io_map(node, body_node); + + // Parse PortMap: external_port_id for inputs does not always appear in consecutive order + std::map input_map; + FOREACH_CHILD (input, node.child(port_map_name.c_str()), "input") { + int64_t ext_port_id = ov::util::pugixml::get_int64_attr(input, "external_port_id"); + input_map.emplace(ext_port_id, input); + } + + for (const auto& input : input_map) { + auto& xml_input = input.second; + auto axis_attr = xml_input.attribute("axis"); + int64_t ti_input_index = ov::util::pugixml::get_int64_attr(xml_input, "external_port_id"); + size_t body_parameter_index = + static_cast(ov::util::pugixml::get_uint64_attr(xml_input, "internal_layer_id")); + + // if axis is set, then slicing is enabled. Create ov::TensorIterator::SlicedInput. + if (!axis_attr.empty()) { + size_t axis = static_cast(ov::util::pugixml::get_uint64_attr(xml_input, "axis")); + int64_t start = ov::util::pugixml::get_int64_attr(xml_input, "start", 0); + int64_t stride = ov::util::pugixml::get_int64_attr(xml_input, "stride", 1); + int64_t end = ov::util::pugixml::get_int64_attr(xml_input, "end", -1); + int64_t part_size = ov::util::pugixml::get_int64_attr(xml_input, "part_size", 1); + + const auto input_index = up_io_map.inputs.at(body_parameter_index); + + inputs.push_back(std::make_shared(ti_input_index, + input_index, + start, + stride, + part_size, + end, + axis)); + } else { + // otherwise find corresponding back edge and create ov::TensorIterator::MergedInput + bool is_back_edge_exist = false; + FOREACH_CHILD (xml_edge, node.child("back_edges"), "edge") { + size_t to_layer = static_cast(ov::util::pugixml::get_uint64_attr(xml_edge, "to-layer")); + + if (to_layer == body_parameter_index) { + size_t from_layer = static_cast(ov::util::pugixml::get_uint64_attr(xml_edge, "from-layer")); + + const auto input_index = up_io_map.inputs.at(body_parameter_index); + const auto output_index = up_io_map.outputs.at(from_layer); + + inputs.push_back(std::make_shared(ti_input_index, + input_index, + output_index)); + + is_back_edge_exist = true; + break; + } + } + + // ti_input_index = -1 means that Parameter of the body is not connected to inputs of + // TensorIterator and is used only for internal needs. + if (!is_back_edge_exist && ti_input_index >= 0) { + const auto input_index = up_io_map.inputs.at(body_parameter_index); + + inputs.push_back( + std::make_shared(ti_input_index, input_index)); + } + } + } + return inputs; +} + +std::vector> +NPUXmlDeserializer::parse_output_description(const pugi::xml_node& node, + const std::string& body_name, + const std::string& port_map_name) { + std::vector> outputs; + auto body_node = node.child(body_name.c_str()); + const auto up_io_map = updated_io_map(node, body_node); + + // Parse PortMap: outputs + std::map output_map; + FOREACH_CHILD (output, node.child(port_map_name.c_str()), "output") { + int64_t ext_port_id = ov::util::pugixml::get_int64_attr(output, "external_port_id"); + output_map.emplace(ext_port_id, output); + } + + uint64_t output_number = 0; + for (const auto& output : output_map) { + auto& xml_output = output.second; + auto axis_attr = xml_output.attribute("axis"); + size_t body_result_index = + static_cast(ov::util::pugixml::get_uint64_attr(xml_output, "internal_layer_id")); + + // if external_port_id < 0 it means that this body result isn't connected to the Loop output + // and is used only for internal needs. For TensorIterator external_port_id is always > 0. + if (ov::util::pugixml::get_int64_attr(xml_output, "external_port_id") >= 0) { + // if axis is set, then concatenation is enabled. Create + // ov::TensorIterator::ConcatOutput. + if (!axis_attr.empty()) { + int64_t axis = ov::util::pugixml::get_int64_attr(xml_output, "axis"); + int64_t start = ov::util::pugixml::get_int64_attr(xml_output, "start", 0); + int64_t stride = ov::util::pugixml::get_int64_attr(xml_output, "stride", 1); + int64_t end = ov::util::pugixml::get_int64_attr(xml_output, "end", -1); + int64_t part_size = ov::util::pugixml::get_int64_attr(xml_output, "part_size", 1); + + const auto output_index = up_io_map.outputs.at(body_result_index); + + outputs.push_back( + std::make_shared(output_index, + output_number, + start, + stride, + part_size, + end, + axis)); + } else { + // otherwise create ov::TensorIterator::BodyOutput. -1 means last iteration. + const auto output_index = up_io_map.outputs.at(body_result_index); + + outputs.push_back(std::make_shared(output_index, + output_number, + -1)); + } + output_number++; + } + } + return outputs; +} + +ov::op::v5::Loop::SpecialBodyPorts NPUXmlDeserializer::parse_purpose_attribute(const pugi::xml_node& node) { + ov::op::v5::Loop::SpecialBodyPorts result = {-1, -1}; + auto body_node = node.child("body"); + const auto up_io_map = updated_io_map(node, body_node); + + OPENVINO_ASSERT(!up_io_map.inputs.empty() || !up_io_map.outputs.empty(), + "No parameters or results found in body Model."); + + // Parse PortMap: external_port_id for inputs/outputs does not always appear in consecutive + // order + std::map input_map; + FOREACH_CHILD (input, node.child("port_map"), "input") { + int64_t ext_port_id = ov::util::pugixml::get_int64_attr(input, "external_port_id"); + input_map.emplace(ext_port_id, input); + } + std::map output_map; + FOREACH_CHILD (output, node.child("port_map"), "output") { + int64_t ext_port_id = ov::util::pugixml::get_int64_attr(output, "external_port_id"); + output_map.emplace(ext_port_id, output); + } + + for (const auto& input : input_map) { + auto& xml_input = input.second; + auto purpose = ov::util::pugixml::get_str_attr(xml_input, "purpose", ""); + size_t body_parameter_index = + static_cast(ov::util::pugixml::get_uint64_attr(xml_input, "internal_layer_id")); + if (purpose == "current_iteration") { + result.current_iteration_input_idx = up_io_map.inputs.at(body_parameter_index); + } + } + + for (const auto& output : output_map) { + auto& xml_output = output.second; + auto purpose = ov::util::pugixml::get_str_attr(xml_output, "purpose", ""); + size_t body_parameter_index = + static_cast(ov::util::pugixml::get_uint64_attr(xml_output, "internal_layer_id")); + if (purpose == "execution_condition") { + result.body_condition_output_idx = up_io_map.outputs.at(body_parameter_index); + } + } + + return result; +} + +NPUXmlDeserializer::IoMap NPUXmlDeserializer::updated_io_map(const pugi::xml_node& node, + const pugi::xml_node& body_node) { + if (body_node.empty()) { + OPENVINO_THROW("Missing body part."); + } + // Fill map: parameter/result id to parameter/result number in Function + + auto extend_io_map = io_map; + + FOREACH_CHILD (layer, body_node.child("layers"), "layer") { + auto type = ov::util::pugixml::get_str_attr(layer, "type"); + + if (type == "Parameter") { + auto id = static_cast(ov::util::pugixml::get_uint64_attr(layer, "id")); + extend_io_map.inputs.insert({id, -1}); // try add as unconnected + } else if (type == "Result") { + auto id = static_cast(ov::util::pugixml::get_uint64_attr(layer, "id")); + extend_io_map.outputs.insert({id, -1}); // try add as unconnected + } + } + return extend_io_map; +} + +void NPUXmlDeserializer::on_adapter(const std::string& name, ov::ValueAccessor& adapter) { + static const std::unordered_set skip_names = {"input_descriptions", + "output_descriptions", + "special_body_ports", + "then_inputs", + "else_inputs", + "then_outputs", + "else_outputs"}; + std::string val; + const pugi::xml_node& m_node = get_node(); + const std::shared_ptr& m_weights = get_weights(); + + // for TensorIterator look for 'port_map' as 'data' does not exist + if (m_node.child("port_map") || m_node.child("then_port_map") || m_node.child("else_port_map")) { + std::string body_name = "body"; + std::string port_map_name = "port_map"; + if (name == "then_inputs" || name == "then_outputs") { + body_name = "then_body"; + port_map_name = "then_port_map"; + } else if (name == "else_inputs" || name == "else_outputs") { + body_name = "else_body"; + port_map_name = "else_port_map"; + } + if (auto a = ov::as_type< + ov::AttributeAdapter>>>( + &adapter)) { + a->set(parse_input_description(m_node, body_name, port_map_name)); + } else if (auto a = ov::as_type>>>(&adapter)) { + a->set(parse_output_description(m_node, body_name, port_map_name)); + } else if (auto a = ov::as_type>(&adapter)) { + a->set(parse_purpose_attribute(m_node)); + } + } + + if (skip_names.count(name) && !getStrAttribute(m_node.child("data"), name, val)) + return; + if (auto a = ov::as_type>(&adapter)) { + static_cast(*a) = ov::element::Type(val); + } else if (auto a = ov::as_type>(&adapter)) { + ov::PartialShape shape; + if (!get_partial_shape_from_attribute(m_node.child("data"), name, shape)) + return; + a->set(shape); + } else if (auto a = ov::as_type>(&adapter)) { + ov::Dimension dim; + if (!get_dimension_from_attribute(m_node.child("data"), name, dim)) + return; + a->set(dim); + } else if (auto a = ov::as_type>(&adapter)) { + std::vector shape; + if (!getParameters(m_node.child("data"), name, shape)) + return; + static_cast(*a) = ov::Shape(shape); + } else if (auto a = ov::as_type>(&adapter)) { + std::vector shape; + if (!getParameters(m_node.child("data"), name, shape)) + return; + static_cast(*a) = ov::Strides(shape); +#if defined(__APPLE__) || defined(__EMSCRIPTEN__) + } else if (auto a = ov::as_type>>(&adapter)) { + std::vector result; + if (!getParameters(m_node.child("data"), name, result)) + return; + static_cast&>(*a) = result; +#else + } else if (auto a = ov::as_type>>(&adapter)) { + std::vector result; + if (!getParameters(m_node.child("data"), name, result)) + return; + a->set(result); +#endif + } else if (auto a = ov::as_type>(&adapter)) { + std::vector axes; + if (!getParameters(m_node.child("data"), name, axes)) + return; + static_cast(*a) = ov::AxisSet(axes); + } else if (auto a = ov::as_type>(&adapter)) { + if (!getStrAttribute(m_node.child("data"), name, val)) + return; + static_cast(*a) = ov::as_enum(val); + } else if (auto a = ov::as_type>(&adapter)) { + if (!getStrAttribute(m_node.child("data"), name, val)) + return; + static_cast(*a) = ov::as_enum(val); + } else if (auto a = ov::as_type>(&adapter)) { + std::vector shape; + if (!getParameters(m_node.child("data"), name, shape)) + return; + std::vector coord_diff(shape.begin(), shape.end()); + static_cast(*a) = ov::CoordinateDiff(coord_diff); + } else if (auto a = ov::as_type>>(&adapter)) { + std::string variable_id; + if (!getStrAttribute(m_node.child("data"), name, variable_id)) + return; + if (!m_variables.count(variable_id)) { + m_variables[variable_id] = std::make_shared( + ov::op::util::VariableInfo{ov::ov::PartialShape::dynamic(), ov::element::dynamic, variable_id}); + } + a->set(m_variables[variable_id]); + } else if (auto a = ov::as_type>>(&adapter)) { + std::string value; + pugi::xml_node dn = m_node.child("data"); + auto type = ov::util::pugixml::get_str_attr(m_node, "type"); + + if (dn.empty()) + OPENVINO_THROW("No attrtibutes defined for ", type, " op!"); + + if (getStrAttribute(dn, name, value)) { + auto buffer = std::make_shared(value.size()); + auto data = static_cast(buffer->get_ptr()); + value.copy(data, value.size()); + a->set(buffer); + } else if (name == "value" && type == "Const") { + std::vector shape; + std::string el_type_str; + if (dn.attribute("offset") && dn.attribute("size")) { + size_t offset = static_cast(ov::util::pugixml::get_uint64_attr(dn, "offset")); + size_t size = static_cast(ov::util::pugixml::get_uint64_attr(dn, "size")); + if (!getStrAttribute(dn, "element_type", el_type_str)) + return; + if (!getParameters(dn, "shape", shape)) + return; + + ov::element::Type el_type = ov::element::Type(el_type_str); + + if (!m_weights) + OPENVINO_THROW("Empty weights data in bin file or bin file cannot be found!"); + if (m_weights->size() < offset + size) + OPENVINO_THROW("Incorrect weights in bin file!"); + char* data = m_weights->get_ptr() + offset; + + if (el_type == element::string) { + auto buffer = + ov::AttributeAdapter>::unpack_string_tensor(data, + size); + a->set(buffer); + } else { + if (size < ((ov::shape_size(shape) * el_type.bitwidth() + 7) >> 3)) + OPENVINO_THROW("Attribute and shape size are inconsistent for ", type, " op!"); + + auto buffer = + std::make_shared>>(data, size, m_weights); + a->set(buffer); + } + } else { + uintptr_t ptr = static_cast(dn.attribute("ptr").as_ullong()); + int type = dn.attribute("type").as_int(); + size_t size = static_cast(ov::util::pugixml::get_uint64_attr(dn, "size")); + + if (!getStrAttribute(dn, "element_type", el_type_str)) + return; + if (!getParameters(dn, "shape", shape)) + return; + ov::element::Type el_type = ov::element::Type(el_type_str); + + std::shared_ptr buffer; + if (type == 0) { + buffer = std::make_shared((char*)ptr, size); + // buffer = *(reinterpret_cast*>(ptr)); + + if (!buffer) + OPENVINO_THROW("Incorrect weights in map!"); + // std::cout << "using StringAlignedBuffer" << std::endl; + } else if (type == 1) { + // buffer = *(reinterpret_cast*>(ptr)); + buffer = std::make_shared((char*)ptr, size); + if (!buffer) + OPENVINO_THROW("Incorrect weights in map!"); + // std::cout << "using SharedStringAlignedBuffer" << std::endl; + } else if (type == 2) { + // buffer = *(reinterpret_cast*>(ptr)); + std::shared_ptr placeholder; + buffer = std::make_shared>>((char*)ptr, + size, + placeholder); + if (!buffer) + OPENVINO_THROW("Incorrect weights in map!"); + // std::cout << "using AlignedBuffer" << std::endl; + } else { + OPENVINO_THROW("Unknown weights type in map!"); + } + // char* data = m_weights->get_ptr() + offset; + + if (el_type == element::string) { + // auto buffer = + // ov::AttributeAdapter>::unpack_string_tensor(data, + // size); + // std::cout << "Warning: For StringAlignedBuffer!" << std::endl; + a->set(buffer); + } else { + if (buffer->size() < ((ov::shape_size(shape) * el_type.bitwidth() + 7) >> 3)) + OPENVINO_THROW("Attribute and shape size are inconsistent for ", type, " op!"); + + // auto buffer = + // std::make_shared>>(data, size, + // m_weights); + // std::cout << "Warning: for AlignedBuffer" << std::endl; + a->set(buffer); + } + } + } + } else if (auto a = ov::as_type>>(&adapter)) { + pugi::xml_node dn = m_node.child("data"); + const auto& type = ov::util::pugixml::get_str_attr(m_node, "type"); + if (name == "value" && type == "Const") { + std::vector shape; + std::string el_type_str; + if (dn.attribute("offset") && dn.attribute("size")) { + size_t offset = static_cast(ov::util::pugixml::get_uint64_attr(dn, "offset")); + size_t size = static_cast(ov::util::pugixml::get_uint64_attr(dn, "size")); + if (!getStrAttribute(dn, "element_type", el_type_str)) + return; + if (!getParameters(dn, "shape", shape)) + return; + + if (!m_weights) + OPENVINO_THROW("Empty weights data in bin file or bin file cannot be found!"); + if (m_weights->size() < offset + size) + OPENVINO_THROW("Incorrect weights in bin file!"); + char* data = m_weights->get_ptr() + offset; + auto buffer = + ov::AttributeAdapter>::unpack_string_tensor(data, size); + + a->set(buffer); + } else { + uintptr_t ptr = static_cast(dn.attribute("ptr").as_ullong()); + int type = dn.attribute("type").as_int(); + size_t size = static_cast(ov::util::pugixml::get_uint64_attr(dn, "size")); + // std::cout << "key : " << key << ", size: " << size << ", type: " << type << std::endl; + if (!getStrAttribute(dn, "element_type", el_type_str)) + return; + if (!getParameters(dn, "shape", shape)) + return; + + std::shared_ptr buffer; + if (type == 0) { + // buffer = *(reinterpret_cast*>(ptr)); + buffer = std::make_shared((char*)ptr, size); + if (!buffer) + OPENVINO_THROW("Incorrect weights in map!"); + // std::cout << "using StringAlignedBuffer" << std::endl; + } else if (type == 1) { + // buffer = *(reinterpret_cast*>(ptr)); + buffer = std::make_shared((char*)ptr, size); + if (!buffer) + OPENVINO_THROW("Incorrect weights in map!"); + // std::cout << "using SharedStringAlignedBuffer" << std::endl; + } else { + OPENVINO_THROW("Unknown weights type in map!"); + } + + // std::cout << "Warning: For StringAlignedBuffer!" << std::endl; + a->set(buffer); + } + } + } else if (auto a = ov::as_type>(&adapter)) { + const auto& type = ov::util::pugixml::get_str_attr(m_node, "type"); + const auto& version = ov::util::pugixml::get_str_attr(m_node, "version"); + + ov::op::util::FrameworkNodeAttrs node_attrs; + node_attrs.set_opset_name(version); + node_attrs.set_type_name(type); + + pugi::xml_node dn = m_node.child("data"); + + if (!dn.empty()) { + for (const auto& data_attr : dn.attributes()) { + node_attrs[data_attr.name()] = data_attr.as_string(); + } + } + + a->set(node_attrs); + } else if (const auto& a = ov::as_type>(&adapter)) { + ov::element::TypeVector types; + if (!getParameters(m_node.child("data"), name, types)) + return; + a->set(types); + } else { + OPENVINO_THROW("Error IR reading. Attribute adapter can not be found for ", name, " parameter"); + } +} + std::shared_ptr deserialize_ir_model(std::string_view serialized_graph, const ov::Tensor& weights) { ov::util::StringViewStreamBuf mb(serialized_graph); std::istream modelStream(&mb);