Fix serialization of execution graph. (#2901)

* 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.
This commit is contained in:
Jozef Daniecki
2020-11-05 04:52:08 +01:00
committed by GitHub
parent 7c1690951c
commit ac658fb500
4 changed files with 299 additions and 23 deletions

View File

@@ -129,7 +129,7 @@ public:
return cloned;
}
bool visit_attributes(ngraph::AttributeVisitor&) override {
bool visit_attributes(ngraph::AttributeVisitor& visitor) override {
return true;
}
};

View File

@@ -7,6 +7,7 @@
#include <unordered_map>
#include <unordered_set>
#include <ngraph/variant.hpp>
#include "ngraph/ops.hpp"
#include "ngraph/opsets/opset.hpp"
#include "pugixml.hpp"
@@ -42,7 +43,8 @@ struct ConstantAtributes {
};
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(
@@ -51,9 +53,8 @@ class XmlVisitor : public ngraph::AttributeVisitor {
}
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 {
@@ -67,11 +68,12 @@ public:
}
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());
@@ -111,6 +113,23 @@ public:
}
};
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;
@@ -199,8 +218,7 @@ std::string get_opset_name(
// 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) {
@@ -269,10 +287,23 @@ std::string get_node_unique_name(std::unordered_set<std::string>& unique_names,
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");
@@ -292,23 +323,25 @@ void ngfunction_2_irv10(
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);

View File

@@ -0,0 +1,141 @@
// 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();
}
}

View File

@@ -0,0 +1,102 @@
<?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>