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:
@@ -129,7 +129,7 @@ public:
|
||||
return cloned;
|
||||
}
|
||||
|
||||
bool visit_attributes(ngraph::AttributeVisitor&) override {
|
||||
bool visit_attributes(ngraph::AttributeVisitor& visitor) override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user