Fix serialization of execution graph. (#2901)
authorJozef Daniecki <jozef.daniecki@intel.com>
Thu, 5 Nov 2020 03:52:08 +0000 (04:52 +0100)
committerGitHub <noreply@github.com>
Thu, 5 Nov 2020 03:52:08 +0000 (06:52 +0300)
* 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.

inference-engine/src/plugin_api/exec_graph_info.hpp
inference-engine/src/transformations/src/transformations/serialize.cpp
inference-engine/tests/functional/inference_engine/ir_serialization/exec_graph.cpp [new file with mode: 0644]
inference-engine/tests/functional/inference_engine/ir_serialization/models/addmul_abc_execution.xml [new file with mode: 0644]

index 5965c46..becd982 100644 (file)
@@ -129,7 +129,7 @@ public:
         return cloned;
     }
 
-    bool visit_attributes(ngraph::AttributeVisitor&) override {
+    bool visit_attributes(ngraph::AttributeVisitor& visitor) override {
         return true;
     }
 };
index 0b0a468..9bf24e8 100644 (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);
diff --git a/inference-engine/tests/functional/inference_engine/ir_serialization/exec_graph.cpp b/inference-engine/tests/functional/inference_engine/ir_serialization/exec_graph.cpp
new file mode 100644 (file)
index 0000000..4f5f253
--- /dev/null
@@ -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();
+    }
+}
diff --git a/inference-engine/tests/functional/inference_engine/ir_serialization/models/addmul_abc_execution.xml b/inference-engine/tests/functional/inference_engine/ir_serialization/models/addmul_abc_execution.xml
new file mode 100644 (file)
index 0000000..2e2de8b
--- /dev/null
@@ -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>