diff --git a/src/core/src/pass/serialize.cpp b/src/core/src/pass/serialize.cpp index f179630b155d22..025bdca44fe754 100644 --- a/src/core/src/pass/serialize.cpp +++ b/src/core/src/pass/serialize.cpp @@ -921,7 +921,7 @@ void serialize_rt_info(pugi::xml_node& root, const std::string& name, const ov:: child.append_attribute("name").set_value(name.c_str()); } if (data.is>()) { - auto meta = data.as>(); + const 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()) { @@ -944,11 +944,32 @@ void serialize_rt_info(pugi::xml_node& root, const std::string& name, const ov:: serialize_rt_info(child, it.first, it.second); } } else { - std::string value = data.as(); + const auto& value = data.as(); child.append_attribute("value").set_value(value.c_str()); } } +bool append_custom_rt_info(pugi::xml_node& node, const std::string& name, const ov::Any& data) { + auto custom_node = node.append_child("custom"); + custom_node.append_attribute("name").set_value(name.c_str()); + bool appended = false; + + if (data.is()) { + const auto& any_map = data.as(); + for (const auto& it : any_map) + appended |= append_custom_rt_info(custom_node, it.first, it.second); + + } else { + const auto& value = data.as(); + custom_node.append_attribute("value").set_value(value.c_str()); + appended = true; + } + + if (!appended) + node.remove_child(custom_node); + return appended; +} + void ngfunction_2_ir(pugi::xml_node& netXml, const ov::Model& model, ConstantWriter& constant_node_write_handler, @@ -1012,10 +1033,10 @@ void ngfunction_2_ir(pugi::xml_node& netXml, // general attributes pugi::xml_node data = layer.append_child("data"); - auto append_runtime_info = [](pugi::xml_node& node, ov::RTMap& attributes) { + auto append_runtime_info = [](pugi::xml_node& node, const ov::RTMap& attributes) { pugi::xml_node rt_node = node.append_child("rt_info"); bool has_attrs = false; - for (auto& item : attributes) { + for (const auto& item : attributes) { if (item.second.is()) { auto attribute_node = rt_node.append_child("attribute"); auto& rt_attribute = item.second.as(); @@ -1030,9 +1051,13 @@ void ngfunction_2_ir(pugi::xml_node& netXml, } } } - if (!has_attrs) { + + for (const auto& item : attributes) + if (!item.second.is()) + has_attrs |= append_custom_rt_info(rt_node, item.first, item.second); + + if (!has_attrs) node.remove_child(rt_node); - } }; if (version >= 11) { diff --git a/src/core/tests/pass/serialization/rt_info_serialization.cpp b/src/core/tests/pass/serialization/rt_info_serialization.cpp index 412625d45d53d9..a18dea19c70bd8 100644 --- a/src/core/tests/pass/serialization/rt_info_serialization.cpp +++ b/src/core/tests/pass/serialization/rt_info_serialization.cpp @@ -4,6 +4,9 @@ #include +#include +#include + #include "common_test_utils/common_utils.hpp" #include "common_test_utils/file_utils.hpp" #include "common_test_utils/test_common.hpp" @@ -299,3 +302,180 @@ TEST(OvSerializationTests, SerializeRawMeta) { EXPECT_EQ(0, serialized_model.compare(ir_with_rt_info)); } } + +namespace ov { +namespace test { + +TEST(RTInfoSerialization, custom_info) { + std::string ref_ir_xml = R"V0G0N( + + + + + + + + + + 10 + 10 + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + 10 + 10 + + + 1 + + + + + 10 + 10 + + + + + + + + + + + + + + 10 + 10 + + + + + + + + + + + +)V0G0N"; + + const auto data = std::make_shared(element::Type_t::f32, Shape{10, 10}); + const auto one = std::make_shared(element::f32, Shape{1}, std::vector{1.f}); + const auto add = std::make_shared(data, one); + const auto result = std::make_shared(add); + + const auto add_info = [](const std::shared_ptr& node, const std::string& value) { + node->set_friendly_name("node_" + value); + node->get_rt_info()["node_info_" + value] = "v_" + value; + node->output(0).get_rt_info()["output_info_" + value] = "o_" + value; + }; + add_info(data, "A"); + add_info(one, "B"); + add_info(add, "C"); + add_info(result, "D"); + + const auto model = std::make_shared(ResultVector{result}, ParameterVector{data}); + model->set_friendly_name("CustomRTI"); + + std::stringstream model_ss, weights_ss; + EXPECT_NO_THROW((ov::pass::Serialize{model_ss, weights_ss}.run_on_model(model))); + EXPECT_EQ(ref_ir_xml.compare(model_ss.str()), 0); +} + +TEST(RTInfoSerialization, AnyMap_info) { + std::string ref_ir_xml = R"V0G0N( + + + + + + + 111 + + + + + + + + + + + + + + + + + 111 + + + + + 111 + + + + + + + 111 + + + + + + + + + + +)V0G0N"; + + const auto data = std::make_shared(element::Type_t::f64, Shape{111}); + const auto abs = std::make_shared(data); + const auto result = std::make_shared(abs); + + data->set_friendly_name("data"); + abs->set_friendly_name("abs"); + result->set_friendly_name("result"); + + const auto empty = AnyMap{}; + const auto nested = AnyMap{{"c", "d"}}; + abs->get_rt_info()["AnyMap"] = AnyMap{{"a", "b"}, {"empty", empty}, {"i", 7}, {"x", 3.14}, {"nested", nested}}; + + const auto model = std::make_shared(ResultVector{result}, ParameterVector{data}); + model->set_friendly_name("CustomRTI"); + + std::stringstream model_ss, weights_ss; + EXPECT_NO_THROW((ov::pass::Serialize{model_ss, weights_ss}.run_on_model(model))); + EXPECT_EQ(ref_ir_xml.compare(model_ss.str()), 0); +} +} // namespace test +} // namespace ov diff --git a/src/frontends/ir/src/ir_deserializer.cpp b/src/frontends/ir/src/ir_deserializer.cpp index d7bc89b96c4358..fe7e714ce1cfb5 100644 --- a/src/frontends/ir/src/ir_deserializer.cpp +++ b/src/frontends/ir/src/ir_deserializer.cpp @@ -819,6 +819,25 @@ static const std::string& translate_type_name(const std::string& name) { return name; } +namespace { +void set_custom_rt_info(const pugi::xml_node& rt_attrs, ov::AnyMap& rt_info) { + std::string custom_name, custom_value; + for (const auto& item : rt_attrs) { + if (std::strcmp(item.name(), "custom") == 0) { + if (ov::getStrAttribute(item, "name", custom_name)) { + if (ov::getStrAttribute(item, "value", custom_value)) { + rt_info.emplace(custom_name, custom_value); + } else { + rt_info.emplace(custom_name, ov::AnyMap{}); + auto& nested = rt_info[custom_name].as(); + set_custom_rt_info(item, nested); + } + } + } + } +} +} // namespace + std::shared_ptr ov::XmlDeserializer::create_node(const std::vector>& inputs, const pugi::xml_node& node, const std::shared_ptr& weights, @@ -972,33 +991,36 @@ std::shared_ptr ov::XmlDeserializer::create_node(const std::vector - if (!getStrAttribute(item, "name", attribute_name) || !getStrAttribute(item, "version", attribute_version)) - continue; - - const auto& type_info = ov::DiscreteTypeInfo(attribute_name.c_str(), attribute_version.c_str()); - auto attr = attrs_factory.create_by_type_info(type_info); - if (!attr.empty()) { - if (attr.is()) { - RTInfoDeserializer attribute_visitor(item); - if (attr.as().visit_attributes(attribute_visitor)) { - auto res = rt_info.emplace(type_info, attr); - if (!res.second) { - OPENVINO_THROW("multiple rt_info attributes are detected: ", attribute_name); + if (std::strcmp(item.name(), "attribute") == 0) { + std::string attribute_name, attribute_version; + if (!getStrAttribute(item, "name", attribute_name) || + !getStrAttribute(item, "version", attribute_version)) + continue; + + const auto& type_info = ov::DiscreteTypeInfo(attribute_name.c_str(), attribute_version.c_str()); + auto attr = attrs_factory.create_by_type_info(type_info); + if (!attr.empty()) { + if (attr.is()) { + RTInfoDeserializer attribute_visitor(item); + if (attr.as().visit_attributes(attribute_visitor)) { + auto res = rt_info.emplace(type_info, attr); + if (!res.second) { + OPENVINO_THROW("multiple rt_info attributes are detected: ", attribute_name); + } + } else { + OPENVINO_THROW("VisitAttributes is not supported for: ", item.name(), " attribute"); } } else { - OPENVINO_THROW("VisitAttributes is not supported for: ", item.name(), " attribute"); + OPENVINO_THROW("Attribute: ", item.name(), " is not recognized as runtime attribute"); } } else { - OPENVINO_THROW("Attribute: ", item.name(), " is not recognized as runtime attribute"); + // As runtime attributes are optional, so we skip attribute if it is unknown to avoid exception + // when loading new IR with new attribute in old OV version. } - } else { - // As runtime attributes are optional, so we skip attribute if it is unknown to avoid exception - // when loading new IR with new attribute in old OV version. } } + + set_custom_rt_info(rt_attrs, rt_info); }; // read runtime info only for IR v11+ diff --git a/src/frontends/ir/tests/rt_info_custom.cpp b/src/frontends/ir/tests/rt_info_custom.cpp new file mode 100644 index 00000000000000..5699f1ed785fc2 --- /dev/null +++ b/src/frontends/ir/tests/rt_info_custom.cpp @@ -0,0 +1,188 @@ +// Copyright (C) 2018-2024 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#include + +#include "common_test_utils/test_assertions.hpp" +#include "openvino/runtime/core.hpp" + +namespace ov { +namespace test { + +TEST(RTInfoCustom, simple_entries) { + std::string ref_ir_xml = R"V0G0N( + + + + + + + + + + + + + + 27 + + + + + + + + + + + 27 + + + + + + + + 27 + + + + + + + + + + + 27 + + + + + + + + + +)V0G0N"; + + Core core; + auto model = core.read_model(ref_ir_xml, Tensor{}); + ASSERT_NE(nullptr, model); + std::string value; + + const auto& param_rti = model->get_parameters().at(0)->get_rt_info(); + EXPECT_EQ(param_rti.size(), 3); + + OV_ASSERT_NO_THROW(value = param_rti.at("fused_names_0").as()); + EXPECT_EQ(value.compare("the_name"), 0); + + OV_ASSERT_NO_THROW(value = param_rti.at("infoA").as()); + EXPECT_EQ(value.compare("A"), 0); + + OV_ASSERT_NO_THROW(value = param_rti.at("infoB").as()); + EXPECT_EQ(value.compare("B"), 0); + + const auto& result = model->get_results().at(0); + const auto abs = result->get_input_node_ptr(0); + + const auto& abs_rti = abs->get_rt_info(); + EXPECT_EQ(abs_rti.size(), 2); + OV_ASSERT_NO_THROW(value = abs_rti.at("infoC").as()); + EXPECT_EQ(value.compare("C"), 0); + + const auto& abs_output_rti = abs->output(0).get_rt_info(); + EXPECT_EQ(abs_output_rti.size(), 1); + OV_ASSERT_NO_THROW(value = abs_output_rti.at("infoD").as()); + EXPECT_EQ(value.compare("D"), 0); + + const auto& result_rti = result->get_rt_info(); + EXPECT_EQ(result_rti.size(), 1); + OV_ASSERT_NO_THROW(value = result_rti.at("primitives_priority_0").as()); + EXPECT_EQ(value.compare("the_prior"), 0); +} + +TEST(RTInfoCustom, nested_entries) { + std::string ref_ir_xml = R"V0G0N( + + + + + + + + + + + + + + 27 + + + + + + + 27 + + + + + + + + + + + + 27 + + + + + + + 27 + + + + + + + + + +)V0G0N"; + + Core core; + auto model = core.read_model(ref_ir_xml, Tensor{}); + ASSERT_NE(nullptr, model); + std::string value; + AnyMap any_map; + + const auto& param_rti = model->get_parameters().at(0)->get_rt_info(); + EXPECT_EQ(param_rti.size(), 2); + OV_ASSERT_NO_THROW(any_map = param_rti.at("nested").as()); + EXPECT_EQ(any_map.size(), 2); + OV_ASSERT_NO_THROW(value = any_map.at("infoB").as()); + EXPECT_EQ(value.compare("B"), 0); + OV_ASSERT_NO_THROW(value = any_map.at("infoC").as()); + EXPECT_EQ(value.compare("C"), 0); + + const auto abs = model->get_results().at(0)->get_input_node_ptr(0); + const auto& abs_rti = abs->output(0).get_rt_info(); + EXPECT_EQ(abs_rti.size(), 1); + OV_ASSERT_NO_THROW(any_map = abs_rti.at("nested_0").as()); + EXPECT_EQ(any_map.size(), 1); + + AnyMap nested_map; + OV_ASSERT_NO_THROW(nested_map = any_map.at("nested_1").as()); + EXPECT_EQ(nested_map.size(), 1); + OV_ASSERT_NO_THROW(value = nested_map.at("infoD").as()); + EXPECT_EQ(value.compare("D"), 0); +} + +} // namespace test +} // namespace ov