* Fix serialization of execution graph.
* Add exec graph comparison.
* Align execution graph serialization to old aproach.
* Revise error massages.
* Fixed execution graph comparison.
Now only attribute names are compared since values can differ on
different devices.
* Readability refactoring.
* Refactoring regarding readability.
return cloned;
}
- bool visit_attributes(ngraph::AttributeVisitor&) override {
+ bool visit_attributes(ngraph::AttributeVisitor& visitor) override {
return true;
}
};
#include <unordered_map>
#include <unordered_set>
+#include <ngraph/variant.hpp>
#include "ngraph/ops.hpp"
#include "ngraph/opsets/opset.hpp"
#include "pugixml.hpp"
};
class XmlVisitor : public ngraph::AttributeVisitor {
- pugi::xml_node m_data;
+ pugi::xml_node& m_data;
+ std::string& m_node_type_name;
template <typename T>
std::string create_atribute_list(
}
public:
- std::string ie_generic_type_name = "";
-
- XmlVisitor(pugi::xml_node& data) : m_data(data) {}
+ XmlVisitor(pugi::xml_node& data, std::string& node_type_name)
+ : m_data(data), m_node_type_name(node_type_name) {}
void on_adapter(const std::string& name,
ngraph::ValueAccessor<void>& adapter) override {
}
void on_adapter(const std::string& name,
ngraph::ValueAccessor<std::string>& adapter) override {
- // __generic_ie_type__ should not be serialized as a <data> attribute
- // it is a WA to retrieve layer type name without introducing dependency on
- // plugi_api library on transformations library
- if (name == "__generic_ie_type__") {
- ie_generic_type_name = adapter.get();
+ if ((m_node_type_name == "GenericIE") &&
+ (name == "__generic_ie_type__")) {
+ // __generic_ie_type__ in GenericIE should not be serialized as a
+ // <data> since it's purpose is to hold name of the layer type
+ // it is a WA to not introduce dependency on plugin_api library
+ m_node_type_name = adapter.get();
} else {
m_data.append_attribute(name.c_str())
.set_value(adapter.get().c_str());
}
};
+void visit_exec_graph_node(pugi::xml_node& data, std::string& node_type_name,
+ const ngraph::Node* n) {
+ for (const auto& param : n->get_rt_info()) {
+ if (auto variant =
+ std::dynamic_pointer_cast<ngraph::VariantImpl<std::string>>(param.second)) {
+ std::string name = param.first;
+ std::string value = variant->get();
+
+ if (name == "layerType") {
+ node_type_name = value;
+ } else {
+ data.append_attribute(name.c_str()).set_value(value.c_str());
+ }
+ }
+ }
+}
+
const std::unordered_map<ngraph::Node*, int> create_layer_ids(
const ngraph::Function& f) {
std::unordered_map<ngraph::Node*, int> layer_ids;
// convention. Most of them are the same, but there are exceptions, e.g
// Constant (ngraph name) and Const (IR name). If there will be more
// discrepancies discoverd, translations needs to be added here.
-std::string get_type_name(const ngraph::Node* n) {
- std::string name = n->get_type_name();
+std::string translate_type_name(std::string name) {
const std::unordered_map<std::string, std::string> translator = {
{"Constant", "Const"}};
if (translator.count(name) > 0) {
return name;
}
+bool is_exec_graph(const ngraph::Function& f) {
+ // go over all operations and check whether performance stat is set
+ for (const auto& op : f.get_ops()) {
+ const auto& rtInfo = op->get_rt_info();
+ if (rtInfo.find("execTimeMcs") != rtInfo.end()) {
+ return true;
+ }
+ }
+ return false;
+}
+
void ngfunction_2_irv10(
pugi::xml_document& doc, std::vector<uint8_t>& bin,
const ngraph::Function& f,
const std::map<std::string, ngraph::OpSet>& custom_opsets) {
+ const bool exec_graph = is_exec_graph(f);
+
pugi::xml_node netXml = doc.append_child("net");
netXml.append_attribute("name").set_value(f.get_friendly_name().c_str());
netXml.append_attribute("version").set_value("10");
layer.append_attribute("name").set_value(
get_node_unique_name(unique_names, node).c_str());
auto layer_type_attribute = layer.append_attribute("type");
- layer.append_attribute("version").set_value(
- get_opset_name(node, custom_opsets).c_str());
-
+ if (!exec_graph) {
+ layer.append_attribute("version").set_value(
+ get_opset_name(node, custom_opsets).c_str());
+ }
// <layers/data>
pugi::xml_node data = layer.append_child("data");
// <layers/data> general atributes
- XmlVisitor visitor{data};
- NGRAPH_CHECK(node->visit_attributes(visitor),
- "Visitor API is not supported in ", node);
- std::string node_type_name {node->get_type_name()};
- if (node_type_name == "GenericIE") {
- layer_type_attribute.set_value(
- visitor.ie_generic_type_name.c_str());
+ std::string node_type_name{node->get_type_name()};
+ if (exec_graph) {
+ visit_exec_graph_node(data, node_type_name, node);
} else {
- layer_type_attribute.set_value(get_type_name(node).c_str());
+ XmlVisitor visitor(data, node_type_name);
+ NGRAPH_CHECK(node->visit_attributes(visitor),
+ "Visitor API is not supported in ", node);
}
+ layer_type_attribute.set_value(
+ translate_type_name(node_type_name).c_str());
+
// <layers/data> constant atributes (special case)
if (auto constant = dynamic_cast<ngraph::op::Constant*>(node)) {
ConstantAtributes attr = dump_constant_data(bin, *constant);
--- /dev/null
+// Copyright (C) 2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <fstream>
+
+#include "common_test_utils/ngraph_test_utils.hpp"
+#include "gtest/gtest.h"
+#include "ie_core.hpp"
+#include "pugixml.hpp"
+
+#ifndef IR_SERIALIZATION_MODELS_PATH // should be already defined by cmake
+#define IR_SERIALIZATION_MODELS_PATH ""
+#endif
+
+// walker traverse (DFS) xml document and store layer & data nodes in
+// vector which is later used for comparison
+struct exec_graph_walker : pugi::xml_tree_walker {
+ std::vector<pugi::xml_node> nodes;
+
+ virtual bool for_each(pugi::xml_node& node) {
+ std::string node_name{node.name()};
+ if (node_name == "layer" || node_name == "data") {
+ nodes.push_back(node);
+ }
+ return true; // continue traversal
+ }
+};
+
+// compare_docs() helper
+std::pair<bool, std::string> compare_nodes(const pugi::xml_node& node1,
+ const pugi::xml_node& node2) {
+ // node names must be the same
+ const std::string node1_name{node1.name()};
+ const std::string node2_name{node2.name()};
+ if (node1_name != node2_name) {
+ return {false, "Node name differ: " + node1_name + " != " + node2_name};
+ }
+
+ // node attribute count must be the same
+ const auto attr1 = node1.attributes();
+ const auto attr2 = node2.attributes();
+ const auto attr1_size = std::distance(attr1.begin(), attr1.end());
+ const auto attr2_size = std::distance(attr2.begin(), attr2.end());
+ if (attr1_size != attr2_size) {
+ return {false, "Attribute count differ in <" + node1_name + "> :" +
+ std::to_string(attr1_size) + " != " +
+ std::to_string(attr2_size)};
+ }
+
+ // every node attribute name must be the same
+ auto a1 = attr1.begin();
+ auto a2 = attr2.begin();
+ for (int j = 0; j < attr1_size; ++j, ++a1, ++a2) {
+ const std::string a1_name{a1->name()};
+ const std::string a2_name{a2->name()};
+ const std::string a1_value{a1->value()};
+ const std::string a2_value{a2->value()};
+ if ((a1_name != a2_name)) {
+ return {false, "Attributes differ in <" + node1_name + "> : " +
+ a1_name + "=" + a1_value + " != " + a2_name +
+ "=" + a2_value};
+ }
+ }
+
+ return {true, ""};
+}
+
+// checks if two exec graph xml's are equivalent:
+// - the same count of <layer> and <data> nodes
+// - the same count of attributes of each node
+// - the same name of each attribute (value is not checked, since it can differ
+// beetween different devices)
+std::pair<bool, std::string> compare_docs(const pugi::xml_document& doc1,
+ const pugi::xml_document& doc2) {
+ // traverse document and prepare vector of <layer> & <data> nodes to compare
+ exec_graph_walker walker1, walker2;
+ doc1.child("net").child("layers").traverse(walker1);
+ doc2.child("net").child("layers").traverse(walker2);
+
+ // nodes count must be the same
+ const auto& nodes1 = walker1.nodes;
+ const auto& nodes2 = walker2.nodes;
+ if (nodes1.size() != nodes2.size()) {
+ return {false, "Node count differ: " + std::to_string(nodes1.size()) +
+ " != " + std::to_string(nodes2.size())};
+ }
+
+ // every node must be equivalent
+ for (int i = 0; i < nodes1.size(); i++) {
+ const auto res = compare_nodes(nodes1[i], nodes2[i]);
+ if (res.first == false) {
+ return res;
+ }
+ }
+ return {true, ""};
+}
+
+class ExecGraphSerializationTest : public ::testing::Test {
+protected:
+ std::string test_name =
+ ::testing::UnitTest::GetInstance()->current_test_info()->name();
+ std::string m_out_xml_path = test_name + ".xml";
+ std::string m_out_bin_path = test_name + ".bin";
+
+ void TearDown() override {
+ std::remove(m_out_xml_path.c_str());
+ std::remove(m_out_bin_path.c_str());
+ }
+};
+
+TEST_F(ExecGraphSerializationTest, ExecutionGraph_CPU) {
+ const std::string source_model =
+ IR_SERIALIZATION_MODELS_PATH "addmul_abc.xml";
+ const std::string expected_model =
+ IR_SERIALIZATION_MODELS_PATH "addmul_abc_execution.xml";
+
+ InferenceEngine::Core ie;
+ auto devices = ie.GetAvailableDevices();
+ if (std::find(devices.begin(), devices.end(), "CPU") != devices.end()) {
+ auto cnnNet = ie.ReadNetwork(source_model);
+ auto execNet = ie.LoadNetwork(cnnNet, "CPU");
+ auto execGraph = execNet.GetExecGraphInfo();
+ InferenceEngine::InferRequest req = execNet.CreateInferRequest();
+ execGraph.serialize(m_out_xml_path, m_out_bin_path);
+
+ pugi::xml_document expected;
+ pugi::xml_document result;
+ ASSERT_TRUE(expected.load_file(expected_model.c_str()));
+ ASSERT_TRUE(result.load_file(m_out_xml_path.c_str()));
+
+ bool success;
+ std::string message;
+ std::tie(success, message) = compare_docs(expected, result);
+
+ ASSERT_TRUE(success) << message;
+ } else {
+ // no CPU device available so we are ignoring this test
+ GTEST_SKIP();
+ }
+}
--- /dev/null
+<?xml version="1.0"?>
+<net name="addmul_abc" version="10">
+ <layers>
+ <layer id="0" name="C" type="Input">
+ <data execOrder="3" execTimeMcs="not_executed" originalLayersNames="C" outputLayouts="x" outputPrecisions="FP32" primitiveType="unknown_FP32" />
+ <output>
+ <port id="0" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="1" name="B" type="Input">
+ <data execOrder="1" execTimeMcs="not_executed" originalLayersNames="B" outputLayouts="x" outputPrecisions="FP32" primitiveType="unknown_FP32" />
+ <output>
+ <port id="0" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="2" name="A" type="Input">
+ <data execOrder="0" execTimeMcs="not_executed" originalLayersNames="A" outputLayouts="x" outputPrecisions="FP32" primitiveType="unknown_FP32" />
+ <output>
+ <port id="0" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="3" name="add_node2" type="Eltwise">
+ <data execOrder="2" execTimeMcs="not_executed" originalLayersNames="add_node2" outputLayouts="x" outputPrecisions="FP32" primitiveType="jit_avx512_FP32" />
+ <input>
+ <port id="0">
+ <dim>1</dim>
+ </port>
+ <port id="1">
+ <dim>1</dim>
+ </port>
+ </input>
+ <output>
+ <port id="2" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="4" name="add_node1" type="Eltwise">
+ <data execOrder="4" execTimeMcs="not_executed" originalLayersNames="add_node1,add_node3,add_node4" outputLayouts="x" outputPrecisions="FP32" primitiveType="jit_avx512_FP32" />
+ <input>
+ <port id="0">
+ <dim>1</dim>
+ </port>
+ <port id="1">
+ <dim>1</dim>
+ </port>
+ <port id="2">
+ <dim>1</dim>
+ </port>
+ <port id="3">
+ <dim>1</dim>
+ </port>
+ </input>
+ <output>
+ <port id="4" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="5" name="Y" type="Eltwise">
+ <data execOrder="5" execTimeMcs="not_executed" originalLayersNames="Y" outputLayouts="x" outputPrecisions="FP32" primitiveType="jit_avx512_FP32" />
+ <input>
+ <port id="0">
+ <dim>1</dim>
+ </port>
+ <port id="1">
+ <dim>1</dim>
+ </port>
+ </input>
+ <output>
+ <port id="2" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="6" name="out_Y" type="Output">
+ <data execOrder="6" execTimeMcs="not_executed" originalLayersNames="" outputLayouts="undef" outputPrecisions="FP32" primitiveType="unknown_FP32" />
+ <input>
+ <port id="0">
+ <dim>1</dim>
+ </port>
+ </input>
+ </layer>
+ </layers>
+ <edges>
+ <edge from-layer="0" from-port="0" to-layer="4" to-port="3" />
+ <edge from-layer="0" from-port="0" to-layer="5" to-port="1" />
+ <edge from-layer="1" from-port="0" to-layer="3" to-port="1" />
+ <edge from-layer="1" from-port="0" to-layer="4" to-port="1" />
+ <edge from-layer="2" from-port="0" to-layer="3" to-port="0" />
+ <edge from-layer="2" from-port="0" to-layer="4" to-port="0" />
+ <edge from-layer="3" from-port="2" to-layer="4" to-port="2" />
+ <edge from-layer="4" from-port="4" to-layer="5" to-port="0" />
+ <edge from-layer="5" from-port="2" to-layer="6" to-port="0" />
+ </edges>
+</net>