* Add IEGeneric node type handling.
* Remove dependency on plugin_api library.
IEGeneric type name is passed via Visiotr API as new syntetic atribute.
* Add custom operations support
* Fix charachter literals comparison.
* Pass custom opsets to CNNNetwork:serialize().
IE extensions are stored in ngraph CNNNetwork and later used to pass
custom opsets to serialization transformation.
* Refactor custom ops tests to use template_extension library.
* Add comment on __generic_ie_type__ purpose.
#include "ie_common.h"
#include "ie_data.h"
#include "details/ie_exception_conversion.hpp"
+#include "ie_extension.h"
namespace ngraph {
* This constructor wraps existing ngraph::Function
* If you want to avoid modification of original Function, please create a copy
* @param network Pointer to the ngraph::Function object
+ * @param exts Vector of pointers to IE extension objects
*/
- explicit CNNNetwork(const std::shared_ptr<ngraph::Function>& network);
+ explicit CNNNetwork(const std::shared_ptr<ngraph::Function>& network,
+ const std::vector<IExtensionPtr>& exts = {});
/**
* @brief A destructor
return specialized_function;
}
-CNNNetwork::CNNNetwork(const std::shared_ptr<ngraph::Function>& graph) {
+CNNNetwork::CNNNetwork(const std::shared_ptr<ngraph::Function>& graph,
+ const std::vector<IExtensionPtr>& exts) {
OV_ITT_SCOPED_TASK(itt::domains::IE, "CNNNetwork::CNNNetwork");
if (graph == nullptr) {
}
// Create CNNNetworkNGraphImpl
- network = std::make_shared<CNNNetworkNGraphImpl>(graph);
+ network = std::make_shared<CNNNetworkNGraphImpl>(graph, exts);
actual = network.get();
if (actual == nullptr) {
THROW_IE_EXCEPTION << "CNNNetwork was not initialized.";
}
}
-CNNNetworkNGraphImpl::CNNNetworkNGraphImpl(const std::shared_ptr<Function>& nGraph)
- : _ngraph_function(nGraph) {
+CNNNetworkNGraphImpl::CNNNetworkNGraphImpl(
+ const std::shared_ptr<Function>& nGraph,
+ const std::vector<IExtensionPtr>& exts)
+ : _ngraph_function(nGraph), _ie_extensions(exts) {
// Restore usual attributes for ICNNNetwork
auto keep_input_info = [](CNNNetworkNGraphImpl& network, const DataPtr& inData) {
InputInfo::Ptr info(new InputInfo());
ResponseDesc* resp) const noexcept {
try {
if (getFunction()) {
+ std::map<std::string, ngraph::OpSet> custom_opsets;
+ for (auto extension : _ie_extensions) {
+ auto opset = extension->getOpSets();
+ custom_opsets.insert(begin(opset), end(opset));
+ }
ngraph::pass::Manager manager;
- manager.register_pass<ngraph::pass::Serialize>(xmlPath, binPath);
+ manager.register_pass<ngraph::pass::Serialize>(
+ xmlPath, binPath, ngraph::pass::Serialize::Version::IR_V10,
+ custom_opsets);
manager.run_passes(_ngraph_function);
} else {
#ifdef ENABLE_V7_SERIALIZE
#include "ie_common.h"
#include "ie_data.h"
#include "ie_input_info.hpp"
+#include "ie_extension.h"
namespace InferenceEngine {
namespace details {
*/
class INFERENCE_ENGINE_API_CLASS(CNNNetworkNGraphImpl): public ICNNNetwork {
public:
- CNNNetworkNGraphImpl(const std::shared_ptr<::ngraph::Function>& nGraph);
+ CNNNetworkNGraphImpl(const std::shared_ptr<::ngraph::Function>& nGraph,
+ const std::vector<IExtensionPtr>& exts = {});
CNNNetworkNGraphImpl(const ICNNNetwork& nGraph);
~CNNNetworkNGraphImpl() override = default;
private:
InferenceEngine::InputsDataMap _inputData;
std::map<std::string, DataPtr> _outputData;
+ const std::vector<IExtensionPtr> _ie_extensions;
/**
* @brief Create DataPtr for nGraph operation
<< " with type " << type;
}
}
+
+bool ngraph::op::GenericIE::visit_attributes(ngraph::AttributeVisitor& visitor) {
+ for (const auto& p : params) {
+ std::string name = p.first;
+ std::string value = p.second;
+ visitor.on_attribute(name, value);
+ }
+ // This is a way to pass type name to transformations::Serialize() without
+ // adding plugin_api dependency on transformation library
+ std::string name = "__generic_ie_type__";
+ std::string value = getType();
+ visitor.on_attribute(name, value);
+ return true;
+}
std::shared_ptr<Node> clone_with_new_inputs(const OutputVector& new_args) const override;
+ bool visit_attributes(ngraph::AttributeVisitor& visitor) override;
+
static void addExtension(std::shared_ptr<const ngraph::Function> func, const InferenceEngine::IShapeInferExtensionPtr& ext);
static std::vector<InferenceEngine::IShapeInferExtensionPtr> getExtensions(std::shared_ptr<const ngraph::Function> func);
originBlob(weights) { }
};
-V10Parser::V10Parser(const std::vector<IExtensionPtr>& exts) {
+V10Parser::V10Parser(const std::vector<IExtensionPtr>& exts) : _exts(exts) {
// Load default opsets
opsets["opset1"] = ngraph::get_opset1();
opsets["opset2"] = ngraph::get_opset2();
result_nodes[0]->add_control_dependency(assign);
}
}
- CNNNetwork net(function);
+ CNNNetwork net(function, _exts);
parsePreProcess(net, root, binStream);
return net;
}
private:
std::map<std::string, ngraph::OpSet> opsets;
+ const std::vector<IExtensionPtr> _exts;
struct GenericLayerParams {
struct LayerPortData {
}
CNNNetwork ONNXReader::read(std::istream& model, const std::vector<IExtensionPtr>& exts) const {
- return CNNNetwork(ngraph::onnx_import::import_onnx_model(model, readPathFromStream(model)));
+ return CNNNetwork(ngraph::onnx_import::import_onnx_model(model, readPathFromStream(model)), exts);
}
INFERENCE_PLUGIN_API(StatusCode) InferenceEngine::CreateReader(IReader*& reader, ResponseDesc *resp) noexcept {
bool run_on_function(std::shared_ptr<ngraph::Function> f) override;
Serialize(const std::string& xmlPath, const std::string& binPath,
- Version version = Version::IR_V10)
- : m_xmlPath{xmlPath}, m_binPath{binPath}, m_version{version} {}
+ Version version = Version::IR_V10, std::map<std::string, ngraph::OpSet> custom_opsets = {})
+ : m_xmlPath{xmlPath}, m_binPath{binPath}, m_version{version}, m_custom_opsets{custom_opsets} {}
private:
const std::string m_xmlPath;
const std::string m_binPath;
const Version m_version;
+ const std::map<std::string, ngraph::OpSet> m_custom_opsets;
};
namespace { // helpers
template <typename T, typename A>
-std::string joinVec(std::vector<T, A> const& vec,
- std::string const& glue = std::string(",")) {
+std::string joinVec(const std::vector<T, A>& vec,
+ const std::string& glue = std::string(",")) {
if (vec.empty()) return "";
std::stringstream oss;
oss << vec[0];
}
public:
+ std::string ie_generic_type_name = "";
+
XmlVisitor(pugi::xml_node& data) : m_data(data) {}
void on_adapter(const std::string& name,
}
void on_adapter(const std::string& name,
ngraph::ValueAccessor<std::string>& adapter) override {
- m_data.append_attribute(name.c_str()).set_value(adapter.get().c_str());
+ // __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();
+ } else {
+ m_data.append_attribute(name.c_str())
+ .set_value(adapter.get().c_str());
+ }
}
void on_adapter(const std::string& name,
ngraph::ValueAccessor<int64_t>& adapter) override {
return attr;
}
-std::string get_opset_name(const ngraph::Node* n) {
+std::string get_opset_name(
+ const ngraph::Node* n,
+ const std::map<std::string, ngraph::OpSet>& custom_opsets) {
auto opsets = std::array<std::reference_wrapper<const ngraph::OpSet>, 5>{
ngraph::get_opset1(), ngraph::get_opset2(), ngraph::get_opset3(),
ngraph::get_opset4(), ngraph::get_opset5()};
return "opset" + std::to_string(idx + 1);
}
}
+
+ for (const auto& custom_opset : custom_opsets) {
+ std::string name = custom_opset.first;
+ ngraph::OpSet opset = custom_opset.second;
+ if (opset.contains_op_type(n)) {
+ return name;
+ }
+ }
+
return "experimental";
}
// discrepancies discoverd, translations needs to be added here.
std::string get_type_name(const ngraph::Node* n) {
std::string name = n->get_type_name();
- NGRAPH_CHECK(name != "GenericIE", "Unsupported type in ", n);
-
const std::unordered_map<std::string, std::string> translator = {
{"Constant", "Const"}};
if (translator.count(name) > 0) {
return name;
}
-void ngfunction_2_irv10(pugi::xml_document& doc, std::vector<uint8_t>& bin,
- const ngraph::Function& f) {
+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) {
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("id").set_value(layer_ids.find(node)->second);
layer.append_attribute("name").set_value(
get_node_unique_name(unique_names, node).c_str());
- layer.append_attribute("type").set_value(get_type_name(node).c_str());
+ auto layer_type_attribute = layer.append_attribute("type");
layer.append_attribute("version").set_value(
- get_opset_name(node).c_str());
+ get_opset_name(node, custom_opsets).c_str());
// <layers/data>
pugi::xml_node data = layer.append_child("data");
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());
+ } else {
+ layer_type_attribute.set_value(get_type_name(node).c_str());
+ }
// <layers/data> constant atributes (special case)
if (auto constant = dynamic_cast<ngraph::op::Constant*>(node)) {
ConstantAtributes attr = dump_constant_data(bin, *constant);
std::vector<uint8_t> constants;
switch (m_version) {
case Version::IR_V10:
- ngfunction_2_irv10(xml_doc, constants, *f);
+ ngfunction_2_irv10(xml_doc, constants, *f, m_custom_opsets);
break;
default:
NGRAPH_UNREACHABLE("Unsupported version");
--- /dev/null
+// Copyright (C) 2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <gtest/gtest.h>
+
+#include <file_utils.h>
+#include <ie_api.h>
+#include <ie_iextension.h>
+#include "common_test_utils/ngraph_test_utils.hpp"
+#include "ie_core.hpp"
+#include "ngraph/ngraph.hpp"
+#include "transformations/serialize.hpp"
+
+#ifndef IR_SERIALIZATION_MODELS_PATH // should be already defined by cmake
+#define IR_SERIALIZATION_MODELS_PATH ""
+#endif
+
+#ifndef IE_BUILD_POSTFIX // should be already defined by cmake
+#define IE_BUILD_POSTFIX ""
+#endif
+
+static std::string get_extension_path() {
+ return FileUtils::makeSharedLibraryName<char>(
+ {}, std::string("template_extension") + IE_BUILD_POSTFIX);
+}
+
+class CustomOpsSerializationTest : 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(CustomOpsSerializationTest, CustomOpUser_MO) {
+ const std::string model = IR_SERIALIZATION_MODELS_PATH "custom_op.xml";
+
+ InferenceEngine::Core ie;
+ ie.AddExtension(
+ InferenceEngine::make_so_pointer<InferenceEngine::IExtension>(
+ get_extension_path()));
+
+ auto expected = ie.ReadNetwork(model);
+ expected.serialize(m_out_xml_path, m_out_bin_path);
+ auto result = ie.ReadNetwork(m_out_xml_path, m_out_bin_path);
+
+ bool success;
+ std::string message;
+ std::tie(success, message) =
+ compare_functions(result.getFunction(), expected.getFunction());
+
+ ASSERT_TRUE(success) << message;
+}
+
+TEST_F(CustomOpsSerializationTest, CustomOpUser_ONNXImporter) {
+ const std::string model = IR_SERIALIZATION_MODELS_PATH "custom_op.prototxt";
+
+ InferenceEngine::Core ie;
+ ie.AddExtension(
+ InferenceEngine::make_so_pointer<InferenceEngine::IExtension>(
+ get_extension_path()));
+
+ auto expected = ie.ReadNetwork(model);
+ expected.serialize(m_out_xml_path, m_out_bin_path);
+ auto result = ie.ReadNetwork(m_out_xml_path, m_out_bin_path);
+
+ bool success;
+ std::string message;
+ std::tie(success, message) =
+ compare_functions(result.getFunction(), expected.getFunction());
+
+ ASSERT_TRUE(success) << message;
+}
+
+TEST_F(CustomOpsSerializationTest, CustomOpTransformation) {
+ const std::string model = IR_SERIALIZATION_MODELS_PATH "custom_op.xml";
+
+ InferenceEngine::Core ie;
+ auto extension =
+ InferenceEngine::make_so_pointer<InferenceEngine::IExtension>(
+ get_extension_path());
+ ie.AddExtension(extension);
+ auto expected = ie.ReadNetwork(model);
+ ngraph::pass::Manager manager;
+ manager.register_pass<ngraph::pass::Serialize>(
+ m_out_xml_path, m_out_bin_path,
+ ngraph::pass::Serialize::Version::IR_V10, extension->getOpSets());
+ manager.run_passes(expected.getFunction());
+ auto result = ie.ReadNetwork(m_out_xml_path, m_out_bin_path);
+
+ bool success;
+ std::string message;
+ std::tie(success, message) =
+ compare_functions(result.getFunction(), expected.getFunction());
+
+ ASSERT_TRUE(success) << message;
+}
--- /dev/null
+# This is syntetic model created by hand desined only for white-box unit testing
+ir_version: 3
+producer_name: "nGraph ONNX Importer"
+graph {
+ node {
+ input: "A"
+ output: "Y"
+ name: "operation"
+ op_type: "Template"
+ domain: "custom_domain"
+ attribute {
+ name: "add"
+ type: INT
+ i: 11
+ }
+ }
+ name: "test_graph"
+ input {
+ name: "A"
+ type {
+ tensor_type {
+ elem_type: 1
+ shape {
+ dim {
+ dim_value: 2
+ }
+ dim {
+ dim_value: 2
+ }
+ dim {
+ dim_value: 2
+ }
+ dim {
+ dim_value: 1
+ }
+ }
+ }
+ }
+ }
+ output {
+ name: "Y"
+ type {
+ tensor_type {
+ elem_type: 1
+ shape {
+ dim {
+ dim_value: 2
+ }
+ dim {
+ dim_value: 2
+ }
+ dim {
+ dim_value: 2
+ }
+ dim {
+ dim_value: 1
+ }
+ }
+ }
+ }
+ }
+}
+opset_import {
+ version: 1
+ domain: "com.example"
+}
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" ?>
+<!--This is syntetic model created by hand desined only for white-box unit testing-->
+<net name="Network" version="10">
+ <layers>
+ <layer name="in1" type="Parameter" id="0" version="opset1">
+ <data element_type="f32" shape="2,2,2,1"/>
+ <output>
+ <port id="0" precision="FP32">
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer name="operation" id="1" type="Template" version="custom_opset">
+ <data add="11"/>
+ <input>
+ <port id="1" precision="FP32">
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>1</dim>
+ </port>
+ </input>
+ <output>
+ <port id="2" precision="FP32">
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer name="output" type="Result" id="2" version="opset1">
+ <input>
+ <port id="0" precision="FP32">
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>2</dim>
+ <dim>1</dim>
+ </port>
+ </input>
+ </layer>
+ </layers>
+ <edges>
+ <edge from-layer="0" from-port="0" to-layer="1" to-port="1"/>
+ <edge from-layer="1" from-port="2" to-layer="2" to-port="0"/>
+ </edges>
+</net>
\ No newline at end of file
--- /dev/null
+<?xml version="1.0" ?>
+<!--This is syntetic model created by hand desined only for white-box unit testing-->
+<net name="add_abc" version="10">
+ <layers>
+ <layer id="0" name="A" type="Parameter" version="opset1">
+ <data element_type="f32" shape="1"/>
+ <output>
+ <port id="0" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="1" name="DetectionOutput" type="ExperimentalDetectronDetectionOutput" version="experimental">
+ <data class_agnostic_box_regression="0" deltas_weights="10.0,10.0,5.0,5.0" max_delta_log_wh="4.135166645050049" max_detections_per_image="100" nms_threshold="0.5" num_classes="81" post_nms_count="2000" score_threshold="0.05"/>
+ <input>
+ <port id="0">
+ <dim>1</dim>
+ </port>
+ </input>
+ <output>
+ <port id="1" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="2" name="Y" type="Result" version="opset1">
+ <input>
+ <port id="0">
+ <dim>1</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>
+</net>
--- /dev/null
+<?xml version="1.0" ?>
+<!--This is syntetic model created by hand desined only for white-box unit testing-->
+<net name="add_abc" version="10">
+ <layers>
+ <layer id="0" name="A" type="Parameter" version="opset1">
+ <data element_type="f32" shape="1"/>
+ <output>
+ <port id="0" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="1" name="ROIFeatureExtractor_1" type="ExperimentalDetectronROIFeatureExtractor" version="experimental">
+ <data distribute_rois_between_levels="1" image_id="0" output_size="14" preserve_rois_order="1" pyramid_scales="4,8,16,32,64" sampling_ratio="2"/>
+ <input>
+ <port id="0">
+ <dim>1</dim>
+ </port>
+ </input>
+ <output>
+ <port id="1" precision="FP32">
+ <dim>1</dim>
+ </port>
+ </output>
+ </layer>
+ <layer id="2" name="Y" type="Result" version="opset1">
+ <input>
+ <port id="0">
+ <dim>1</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>
+</net>
ASSERT_TRUE(success) << message;
}
+
+TEST_F(SerializationTest, ExperimentalDetectronROIFeatureExtractor_MO) {
+ const std::string model = IR_SERIALIZATION_MODELS_PATH
+ "experimental_detectron_roi_feature_extractor.xml";
+
+ InferenceEngine::Core ie;
+ auto expected = ie.ReadNetwork(model);
+ expected.serialize(m_out_xml_path, m_out_bin_path);
+ auto result = ie.ReadNetwork(m_out_xml_path, m_out_bin_path);
+
+ bool success;
+ std::string message;
+ std::tie(success, message) =
+ compare_functions(result.getFunction(), expected.getFunction());
+
+ ASSERT_TRUE(success) << message;
+}
+
+TEST_F(SerializationTest, ExperimentalDetectronDetectionOutput_MO) {
+ const std::string model = IR_SERIALIZATION_MODELS_PATH
+ "experimental_detectron_detection_output.xml";
+
+ InferenceEngine::Core ie;
+ auto expected = ie.ReadNetwork(model);
+ expected.serialize(m_out_xml_path, m_out_bin_path);
+ auto result = ie.ReadNetwork(m_out_xml_path, m_out_bin_path);
+
+ bool success;
+ std::string message;
+ std::tie(success, message) =
+ compare_functions(result.getFunction(), expected.getFunction());
+
+ ASSERT_TRUE(success) << message;
+}