Fixed convertFunctionToCNNNetwork to support non unique names (#2864)
authorGleb Kazantaev <gleb.kazantaev@intel.com>
Fri, 30 Oct 2020 04:24:37 +0000 (07:24 +0300)
committerGitHub <noreply@github.com>
Fri, 30 Oct 2020 04:24:37 +0000 (07:24 +0300)
* Unique names normalization during nGraph to CNNNetwork conversion

* Added tests

* Code refactoring

inference-engine/src/legacy_api/src/convert_function_to_cnn_network.cpp
inference-engine/tests/functional/inference_engine/cnn_network/convert_ngraph_to_cnn_network_tests.cpp

index 97fb7b7..1b1efd1 100644 (file)
@@ -942,6 +942,54 @@ void convertFunctionToICNNNetwork(const std::shared_ptr<const ::ngraph::Function
     const ngraph::NodeVector& nodes = graph->get_ops();
     bool keep_constants = keep_constant_inputs || ::ngraph::op::util::has_op_with_type<::ngraph::op::FakeQuantize>(graph);
 
+    std::unordered_map<std::string, std::shared_ptr<ngraph::Node>> unique_names;
+    auto can_change_name = [](const std::shared_ptr<ngraph::Node> & node) -> bool {
+        if (ngraph::as_type_ptr<ngraph::op::Parameter>(node) ||
+            ngraph::as_type_ptr<ngraph::op::Result>(node)) {
+            return false;
+        }
+        for (const auto & output : node->outputs()) {
+            for (const auto & consumer : output.get_target_inputs()) {
+                if (ngraph::is_type<ngraph::op::Result>(consumer.get_node())) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    };
+
+    auto generate_unique_name = [&unique_names](std::string name) -> std::string {
+        size_t suffix = 1;
+        while(unique_names.count(name + "/" + std::to_string(suffix))) {
+            ++suffix;
+        }
+        return name + "/" + std::to_string(suffix);
+    };
+
+    // normalize nodes names to be unique
+    for (auto & node : nodes) {
+        // skip Result operations as they have the same friendly name as their parent
+        if (ngraph::is_type<ngraph::op::Result>(node.get())) {
+            continue;
+        }
+
+        auto & duplicate = unique_names[node->get_friendly_name()];
+        if (!duplicate) {
+            duplicate = node;
+            continue;
+        }
+
+        if (!can_change_name(duplicate) && !can_change_name(node)) {
+            THROW_IE_EXCEPTION << "Detected two output operations with the same name: " << duplicate << " and " << node;
+        }
+
+        auto & renamed = can_change_name(duplicate) ? duplicate : node;
+        renamed->set_friendly_name(generate_unique_name(renamed->get_friendly_name()));
+
+        unique_names[duplicate->get_friendly_name()] = duplicate;
+        unique_names[node->get_friendly_name()] = node;
+    }
+
     // Create layers and output data
     for (const auto &layer : nodes) {
         if (isInternalLayer(layer, keep_constants)) continue;
@@ -1090,7 +1138,8 @@ void convertFunctionToICNNNetwork(const std::shared_ptr<const ::ngraph::Function
 
             CNNLayerPtr cnnLayer;
             ret = cnnNetworkImpl->getLayerByName(layer->get_friendly_name().c_str(), cnnLayer, nullptr);
-            if (ret != OK) THROW_IE_EXCEPTION << "Cannot find layer with name: " << layer->get_friendly_name();
+            if (ret != OK)
+                THROW_IE_EXCEPTION << "Cannot find layer with name: " << layer->get_friendly_name();
 
             auto inIndex = layer->input(i).get_index();
             if (cnnLayer->insData.size() <= (inIndex - count_of_skipped) ||
index bdb93cc..b21e946 100644 (file)
@@ -236,4 +236,161 @@ TEST(ConvertFunctionToCNNNetworkTests, UnsupportedDynamicOps) {
                                                              "v3::NonZero non_zero (relu[0]:f32?) -> (i64{?,?})\n"
                                                              "v0::Result result (non_zero[0]:i64{?,?}) -> (i64{?,?})")));
     }
+}
+
+TEST(ConvertFunctionToCNNNetworkTests, NonUniqueNamesAllInternal) {
+    std::shared_ptr<ngraph::Function> f(nullptr);
+    {
+        auto input = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+        auto begin = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        auto end = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        end->set_friendly_name(begin->get_name());
+        auto stride = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {1, 1});
+        auto ss = std::make_shared<ngraph::opset1::StridedSlice>(
+                input,
+                begin,
+                end,
+                stride,
+                std::vector<int64_t>{1, 1}, std::vector<int64_t>{1, 1});
+
+        f = std::make_shared<ngraph::Function>(ngraph::NodeVector{ss}, ngraph::ParameterVector{input});
+    }
+
+    InferenceEngine::CNNNetwork nGraphImpl(f);
+    nGraphImpl = CNNNetwork(InferenceEngine::details::convertFunctionToICNNNetwork(f, nGraphImpl));
+    ASSERT_EQ(nGraphImpl.layerCount(), 5);
+}
+
+TEST(ConvertFunctionToCNNNetworkTests, NonUniqueNamesHasResult1) {
+    std::shared_ptr<ngraph::Function> f(nullptr);
+    {
+        auto input = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+        auto begin = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        auto end = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        end->set_friendly_name(begin->get_name());
+        auto stride = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {1, 1});
+        auto ss = std::make_shared<ngraph::opset1::StridedSlice>(
+                input,
+                begin,
+                end,
+                stride,
+                std::vector<int64_t>{1, 1}, std::vector<int64_t>{1, 1});
+
+        f = std::make_shared<ngraph::Function>(ngraph::NodeVector{ss, begin}, ngraph::ParameterVector{input});
+    }
+
+    InferenceEngine::CNNNetwork nGraphImpl(f);
+    nGraphImpl = CNNNetwork(InferenceEngine::details::convertFunctionToICNNNetwork(f, nGraphImpl));
+    ASSERT_EQ(nGraphImpl.layerCount(), 5);
+}
+
+TEST(ConvertFunctionToCNNNetworkTests, NonUniqueNamesHasResult2) {
+    std::shared_ptr<ngraph::Function> f(nullptr);
+    {
+        auto input = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+        auto begin = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        begin->set_friendly_name("const");
+        auto end = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        end->set_friendly_name("const");
+        auto stride = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {1, 1});
+        stride->set_friendly_name("const");
+        auto ss = std::make_shared<ngraph::opset1::StridedSlice>(
+                input,
+                begin,
+                end,
+                stride,
+                std::vector<int64_t>{1, 1}, std::vector<int64_t>{1, 1});
+
+        f = std::make_shared<ngraph::Function>(ngraph::NodeVector{ss, begin}, ngraph::ParameterVector{input});
+    }
+
+    InferenceEngine::CNNNetwork nGraphImpl(f);
+    nGraphImpl = CNNNetwork(InferenceEngine::details::convertFunctionToICNNNetwork(f, nGraphImpl));
+    ASSERT_EQ(nGraphImpl.layerCount(), 5);
+}
+
+TEST(ConvertFunctionToCNNNetworkTests, NonUniqueNamesHasResult3) {
+    std::shared_ptr<ngraph::Function> f(nullptr);
+    {
+        auto input = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+        auto begin = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        begin->set_friendly_name("const");
+        auto end = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        end->set_friendly_name("const");
+        auto stride = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {1, 1});
+        stride->set_friendly_name("const");
+        auto ss = std::make_shared<ngraph::opset1::StridedSlice>(
+                input,
+                begin,
+                end,
+                stride,
+                std::vector<int64_t>{1, 1}, std::vector<int64_t>{1, 1});
+        ss->set_friendly_name("node");
+        auto squeeze = std::make_shared<ngraph::opset1::Squeeze>(ss, ngraph::opset1::Constant::create(ngraph::element::i64, {1}, {0}));
+        squeeze->set_friendly_name("node");
+        f = std::make_shared<ngraph::Function>(ngraph::NodeVector{squeeze, begin}, ngraph::ParameterVector{input});
+    }
+
+    InferenceEngine::CNNNetwork nGraphImpl(f);
+    nGraphImpl = CNNNetwork(InferenceEngine::details::convertFunctionToICNNNetwork(f, nGraphImpl));
+    ASSERT_EQ(nGraphImpl.layerCount(), 7);
+    auto outputs_info = nGraphImpl.getOutputsInfo();
+    ASSERT_TRUE(outputs_info.count("node"));
+    ASSERT_TRUE(outputs_info.count("const"));
+}
+
+TEST(ConvertFunctionToCNNNetworkTests, NonUniqueNamesNegative) {
+    std::shared_ptr<ngraph::Function> f(nullptr);
+    {
+        auto input = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+        auto begin = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        begin->set_friendly_name("const");
+        auto end = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+        end->set_friendly_name("const");
+        auto stride = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {1, 1});
+        auto ss = std::make_shared<ngraph::opset1::StridedSlice>(
+                input,
+                begin,
+                end,
+                stride,
+                std::vector<int64_t>{1, 1}, std::vector<int64_t>{1, 1});
+
+        f = std::make_shared<ngraph::Function>(ngraph::NodeVector{ss, begin, end}, ngraph::ParameterVector{input});
+    }
+
+    InferenceEngine::CNNNetwork nGraphImpl(f);
+    try {
+        InferenceEngine::details::convertFunctionToICNNNetwork(f, nGraphImpl);
+        FAIL() << "InferenceEngineException must be thrown";
+    } catch(InferenceEngine::details::InferenceEngineException & e) {
+        EXPECT_THAT(e.what(), testing::HasSubstr(std::string("Detected two output operations with the same name:")));
+    }
+}
+
+TEST(ConvertFunctionToCNNNetworkTests, NonUniqueNamesParametersNegative) {
+    std::shared_ptr<ngraph::Function> f(nullptr);
+    auto input = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+    input->set_friendly_name("param");
+    auto begin = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+    auto end = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {0, 0});
+    auto stride = ngraph::opset1::Constant::create(ngraph::element::i64, {2}, {1, 1});
+    auto ss = std::make_shared<ngraph::opset1::StridedSlice>(
+            input,
+            begin,
+            end,
+            stride,
+            std::vector<int64_t>{1, 1}, std::vector<int64_t>{1, 1});
+    auto input2 = std::make_shared<ngraph::opset1::Parameter>(ngraph::element::f32, ngraph::Shape{1, 3});
+    auto concat = std::make_shared<ngraph::opset1::Concat>(ngraph::NodeVector{ss, input2}, 0);
+
+    f = std::make_shared<ngraph::Function>(ngraph::NodeVector{concat}, ngraph::ParameterVector{input, input2});
+
+    InferenceEngine::CNNNetwork nGraphImpl(f);
+    try {
+        input2->set_friendly_name("param");
+        InferenceEngine::details::convertFunctionToICNNNetwork(f, nGraphImpl);
+        FAIL() << "InferenceEngineException must be thrown";
+    } catch(InferenceEngine::details::InferenceEngineException & e) {
+        EXPECT_THAT(e.what(), testing::HasSubstr(std::string("Detected two output operations with the same name:")));
+    }
 }
\ No newline at end of file