Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RTInfo] Serialize and deserialize custom rt info #26298

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Draft
37 changes: 31 additions & 6 deletions src/core/src/pass/serialize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,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<std::shared_ptr<ov::Meta>>()) {
auto meta = data.as<std::shared_ptr<ov::Meta>>();
const auto& meta = data.as<std::shared_ptr<ov::Meta>>();
do {
if (auto meta_with_pugixml_node = std::dynamic_pointer_cast<ov::MetaDataWithPugixml>(meta)) {
if (auto pugi_node = meta_with_pugixml_node->get_pugi_node()) {
Expand All @@ -939,11 +939,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<std::string>();
const auto& value = data.as<std::string>();
child.append_attribute("value").set_value(value.c_str());
}
}

bool append_custom_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 has_value = false;

if (data.is<ov::AnyMap>()) {
const auto& any_map = data.as<ov::AnyMap>();
for (const auto& it : any_map)
has_value |= append_custom_info(custom_node, it.first, it.second);

} else {
const auto& value = data.as<std::string>();
custom_node.append_attribute("value").set_value(value.c_str());
has_value = true;
}

if (!has_value)
node.remove_child(custom_node);
return has_value;
}

void ngfunction_2_ir(pugi::xml_node& netXml,
const ov::Model& model,
ConstantWriter& constant_node_write_handler,
Expand Down Expand Up @@ -1007,10 +1028,10 @@ void ngfunction_2_ir(pugi::xml_node& netXml,
// <layers/data> 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<ov::RuntimeAttribute>()) {
auto attribute_node = rt_node.append_child("attribute");
auto& rt_attribute = item.second.as<ov::RuntimeAttribute>();
Expand All @@ -1025,9 +1046,13 @@ void ngfunction_2_ir(pugi::xml_node& netXml,
}
}
}
if (!has_attrs) {

for (const auto& item : attributes)
if (!item.second.is<ov::RuntimeAttribute>())
has_attrs |= append_custom_info(rt_node, item.first, item.second);

if (!has_attrs)
node.remove_child(rt_node);
}
};

if (version >= 11) {
Expand Down
181 changes: 181 additions & 0 deletions src/core/tests/pass/serialization/rt_info_serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,3 +299,184 @@ TEST(OvSerializationTests, SerializeRawMeta) {
EXPECT_EQ(0, serialized_model.compare(ir_with_rt_info));
}
}

#include <iostream>
#include <sstream>

using namespace ov;
using ov::op::v0::Abs;
using ov::op::v0::Parameter;
using ov::op::v0::Result;
using ov::op::v1::Add;

TEST(SerializeCustomRTI, basic_RENAMEME) {
std::string ir = R"V0G0N(<?xml version="1.0"?>
<net name="CustomRTI" version="11">
<layers>
<layer id="0" name="node_A" type="Parameter" version="opset1">
<data shape="10,10" element_type="f32" />
<rt_info>
<custom name="node_info_A" value="v_A" />
</rt_info>
<output>
<port id="0" precision="FP32">
<dim>10</dim>
<dim>10</dim>
<rt_info>
<custom name="output_info_A" value="o_A" />
</rt_info>
</port>
</output>
</layer>
<layer id="1" name="node_B" type="Const" version="opset1">
<data element_type="f32" shape="1" offset="0" size="4" />
<rt_info>
<custom name="node_info_B" value="v_B" />
</rt_info>
<output>
<port id="0" precision="FP32">
<dim>1</dim>
<rt_info>
<custom name="output_info_B" value="o_B" />
</rt_info>
</port>
</output>
</layer>
<layer id="2" name="node_C" type="Add" version="opset1">
<data auto_broadcast="numpy" />
<rt_info>
<custom name="node_info_C" value="v_C" />
</rt_info>
<input>
<port id="0" precision="FP32">
<dim>10</dim>
<dim>10</dim>
</port>
<port id="1" precision="FP32">
<dim>1</dim>
</port>
</input>
<output>
<port id="2" precision="FP32">
<dim>10</dim>
<dim>10</dim>
<rt_info>
<custom name="output_info_C" value="o_C" />
<custom name="output_info_D" value="o_D" />
</rt_info>
</port>
</output>
</layer>
<layer id="3" name="node_D" type="Result" version="opset1">
<rt_info>
<custom name="node_info_D" value="v_D" />
</rt_info>
<input>
<port id="0" precision="FP32">
<dim>10</dim>
<dim>10</dim>
</port>
</input>
</layer>
</layers>
<edges>
<edge from-layer="0" from-port="0" to-layer="2" to-port="0" />
<edge from-layer="1" from-port="0" to-layer="2" to-port="1" />
<edge from-layer="2" from-port="2" to-layer="3" to-port="0" />
</edges>
<rt_info />
</net>
)V0G0N";

const auto data = std::make_shared<Parameter>(element::Type_t::f32, Shape{10, 10});
const auto one = std::make_shared<op::v0::Constant>(element::f32, Shape{1}, std::vector<float>{1.f});
const auto add = std::make_shared<Add>(data, one);
const auto result = std::make_shared<Result>(add);

const auto add_info = [](const std::shared_ptr<Node>& node, const std::string& value) {
const_cast<std::string&>(node->get_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<Model>(ResultVector{result}, ParameterVector{data});
const_cast<std::string&>(model->get_name()) = "CustomRTI";

std::stringstream model_ss, weights_ss;
EXPECT_NO_THROW((ov::pass::Serialize{model_ss, weights_ss}.run_on_model(model)));
EXPECT_EQ(ir.compare(model_ss.str()), 0);
}

TEST(SerializeCustomRTI, AnyMap_RENAMEME) {
std::string ir = R"V0G0N(<?xml version="1.0"?>
<net name="CustomRTI" version="11">
<layers>
<layer id="0" name="data" type="Parameter" version="opset1">
<data shape="111" element_type="f64" />
<output>
<port id="0" precision="FP64">
<dim>111</dim>
</port>
</output>
</layer>
<layer id="1" name="abs" type="Abs" version="opset1">
<rt_info>
<custom name="AnyMap">
<custom name="a" value="b" />
<custom name="i" value="7" />
<custom name="nested">
<custom name="c" value="d" />
</custom>
<custom name="x" value="3.14" />
</custom>
</rt_info>
<input>
<port id="0" precision="FP64">
<dim>111</dim>
</port>
</input>
<output>
<port id="1" precision="FP64">
<dim>111</dim>
</port>
</output>
</layer>
<layer id="2" name="result" type="Result" version="opset1">
<input>
<port id="0" precision="FP64">
<dim>111</dim>
</port>
</input>
</layer>
</layers>
<edges>
<edge from-layer="0" from-port="0" to-layer="1" to-port="0" />
<edge from-layer="1" from-port="1" to-layer="2" to-port="0" />
</edges>
<rt_info />
</net>
)V0G0N";

const auto data = std::make_shared<Parameter>(element::Type_t::f64, Shape{111});
const auto abs = std::make_shared<Abs>(data);
const auto result = std::make_shared<Result>(abs);

const_cast<std::string&>(data->get_name()) = "data";
const_cast<std::string&>(abs->get_name()) = "abs";
const_cast<std::string&>(result->get_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<Model>(ResultVector{result}, ParameterVector{data});
const_cast<std::string&>(model->get_name()) = "CustomRTI";

std::stringstream model_ss, weights_ss;
EXPECT_NO_THROW((ov::pass::Serialize{model_ss, weights_ss}.run_on_model(model)));
EXPECT_EQ(ir.compare(model_ss.str()), 0);
}
67 changes: 40 additions & 27 deletions src/frontends/ir/src/ir_deserializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -957,38 +957,51 @@ std::shared_ptr<ov::Node> ov::XmlDeserializer::create_node(const std::vector<ov:
if (!rt_attrs)
return;
for (const auto& item : rt_attrs) {
std::string attribute_name, attribute_version;
// For view:
// <attribute name="old_api_map_order" version="0" value="0,3,1,2"/>
if (!getStrAttribute(item, "name", attribute_name)) {
std::stringstream ss;
item.print(ss);
OPENVINO_THROW("rt_info attribute has no \"name\" field: ", ss.str());
}
if (!getStrAttribute(item, "version", attribute_version)) {
std::stringstream ss;
item.print(ss);
OPENVINO_THROW("rt_info attribute: ", attribute_name, " has no \"version\" field: ", ss.str());
}
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<ov::RuntimeAttribute>()) {
RTInfoDeserializer attribute_visitor(item);
if (attr.as<ov::RuntimeAttribute>().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;
// For view:
// <attribute name="old_api_map_order" version="0" value="0,3,1,2"/>
if (!getStrAttribute(item, "name", attribute_name)) {
std::stringstream ss;
item.print(ss);
OPENVINO_THROW("rt_info attribute has no \"name\" field: ", ss.str());
}
if (!getStrAttribute(item, "version", attribute_version)) {
std::stringstream ss;
item.print(ss);
OPENVINO_THROW("rt_info attribute: ", attribute_name, " has no \"version\" field: ", ss.str());
}
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<ov::RuntimeAttribute>()) {
RTInfoDeserializer attribute_visitor(item);
if (attr.as<ov::RuntimeAttribute>().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.
}
}
}

// Predefined attributes take precedence over custom rt info
for (const auto& item : rt_attrs) {
if (std::strcmp(item.name(), "custom") == 0) {
std::string custom_name, custom_value;
if (getStrAttribute(item, "name", custom_name) && getStrAttribute(item, "value", custom_value)) {
// Duplicates are not added and are ignored
rt_info.emplace(custom_name, custom_value);
}
} 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.
}
}
};
Expand Down
Loading
Loading