[GNA] Fix for concat layer with >2 inputs (#1475)
authorBartosz Sochacki <bartosz.sochacki@intel.com>
Wed, 9 Sep 2020 11:55:07 +0000 (13:55 +0200)
committerGitHub <noreply@github.com>
Wed, 9 Sep 2020 11:55:07 +0000 (14:55 +0300)
* Fix for concat layer with more than 2 inputs

Signed-off-by: Bartosz Sochacki <bartosz.sochacki@intel.com>
* Fixed check if affine is used for crop layer

Signed-off-by: Bartosz Sochacki <bartosz.sochacki@intel.com>
* code cleanup for fix affine layer check

Signed-off-by: Bartosz Sochacki <bartosz.sochacki@intel.com>
* added test for concat layer with multiple inputs

* simplified test to use less number of layers

* fixed code style

* fixed coding style

* addressed review comments and one more issue that appeared during testing

* fixed code style errors

* scale factor propagation for concat layer with multiple inputs

* fix for a case when all inputs to concat are activation layers

* fix for linux compilation - C++14 is not enabled and fails on lambda with auto parameters

* corrected current year in headers in concat multi input tests

* fixes for code review issues raised by Denis Orlov

* enabled integer mode computation in GNA concat multi input test

* removed 1 space per review comment

* a fix to fail when not all scale factors are equal

* added GNA_DEVICE_MODE config to concat multi input test

* corrected searching for a next input to concat layer

* changed selection of 2nd candidate for source quant value

* code style fix - else and brackets should be in the same line

* small code improvement

* fix for mixing line endings

* addressed with endless requantization loop and fixed failing tests

inference-engine/src/gna_plugin/frontend/scale_factor_calc.hpp
inference-engine/src/gna_plugin/gna_graph_compiler.cpp
inference-engine/src/gna_plugin/layers/gna_layer_info.hpp
inference-engine/src/gna_plugin/optimizer/gna_pass_manager.cpp
inference-engine/tests/functional/plugin/gna/shared_tests_instances/subgraph_tests/concat_multi_input.cpp [new file with mode: 0644]
inference-engine/tests/functional/plugin/shared/include/subgraph_tests/concat_multi_input.hpp [new file with mode: 0644]
inference-engine/tests/functional/plugin/shared/src/subgraph_tests/concat_multi_input.cpp [new file with mode: 0644]

index 5da54c8..037dc35 100644 (file)
@@ -170,7 +170,7 @@ class ScaleFactorPerLayer<InferenceEngine::CNNLayer *> {
                     if (!fp32eq(quantSibling->_dst_quant.scale, 1)) {
                         // means we already restarted propagation input memory layer
                         // need to search for requantiseable layer prior memory output layer
-                        InferenceEngine::CNNLayerPtr  restartedLayer;
+                        InferenceEngine::CNNLayerPtr restartedLayer;
 
                         gnalog() << "Memory layer :"<< input->name << " scale factor: " << quantSibling->_dst_quant.scale
                             << " doesn't match its outputs counterpart: " << cnnLayer->name << " scale factor: " << inputQuant->_dst_quant.scale << "\n";
@@ -382,119 +382,166 @@ class ScaleFactorPerLayer<InferenceEngine::ConcatLayer*> {
         if ( !concatLayer ) {
             THROW_GNA_EXCEPTION << "Incorrect Concat Layer pointer \n";
         }
-        auto in0 = InferenceEngine::CNNNetPrevLayer(concatLayer, 0);
-        auto in1 = InferenceEngine::CNNNetPrevLayer(concatLayer, 1);
-        auto infoIn0 = LayerInfo(in0);
-        auto infoIn1 = LayerInfo(in1);
-        auto quantParams0 = InferenceEngine::getInjectedData<QuantizedLayerParams>(in0);
-        auto quantParams1 = InferenceEngine::getInjectedData<QuantizedLayerParams>(in1);
-        GNAPluginNS::QuantizedLayerParams* sourceQuantParams = NULL;
-        auto quantData = InferenceEngine::getInjectedData<QuantizedLayerParams>(*concatLayer);
+
+        if (concatLayer->insData.size() < 2) {
+            THROW_GNA_EXCEPTION << "Concat layer has unsupported number of incoming layers.";
+        }
 
         auto fp32eq = [](float p1, float p2) -> bool {
             return (std::abs(p1 - p2) <= 0.00001f * std::min(std::abs(p1), std::abs(p2)));
         };
 
-        // if both inputs have same quant value - trivial propagation
-        if (fp32eq(quantParams0->_dst_quant.scale, quantParams1->_dst_quant.scale)) {
+        auto quantData = InferenceEngine::getInjectedData<QuantizedLayerParams>(*concatLayer);
+        std::vector<InferenceEngine::CNNLayerPtr> inputLayers;
+        for (auto input_idx = 0; input_idx != concatLayer->insData.size(); input_idx++) {
+            inputLayers.push_back(InferenceEngine::CNNNetPrevLayer(concatLayer, input_idx));
+        }
+
+        // if all inputs have same quant value - trivial propagation
+        auto in0 = inputLayers.front();
+        auto quantParams0 = InferenceEngine::getInjectedData<QuantizedLayerParams>(in0);
+        auto scaleFactor = quantParams0->_dst_quant.scale;
+        auto scaleFactorCheck = [scaleFactor, &fp32eq](InferenceEngine::CNNLayerPtr& inputLayer) {
+            auto quantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(inputLayer);
+            return fp32eq(quantParams->_dst_quant.scale, scaleFactor);
+        };
+
+        if (std::find_if_not(inputLayers.begin() + 1, inputLayers.end(), scaleFactorCheck) == inputLayers.end()) {
             quantData->_dst_quant.scale = quantParams0->_dst_quant.scale;
             quantData->_src_quant.scale = quantParams0->_dst_quant.scale;
             return true;
         }
-        // support only cases when one of input is network input
-        if (infoIn0.isInput() && infoIn1.isInput()) {
-            THROW_GNA_EXCEPTION << "Two Input layers " << in0->name << "and" << in1->name << " has different scales in concat!!! \n";
-        }
 
-        int concatIdxToUpdate = -1;
+        // check if all inputs have the same quant value
+        auto inputLayerCheck = [](InferenceEngine::CNNLayerPtr& inputLayer) {
+            auto info = LayerInfo(inputLayer);
+            return info.isInput();
+        };
 
-        if (infoIn0.isInput()) {
-            sourceQuantParams = quantParams0;
-        } else if (infoIn1.isInput()) {
-            concatIdxToUpdate = 0;
-            sourceQuantParams = quantParams1;
+        GNAPluginNS::QuantizedLayerParams* sourceQuantParams = nullptr;
+        auto firstInputIt = std::find_if(inputLayers.begin(), inputLayers.end(), inputLayerCheck);
+        if (firstInputIt != inputLayers.end()) {
+            auto quantParamsFirst = InferenceEngine::getInjectedData<QuantizedLayerParams>(*firstInputIt);
+            auto nextInputIt = firstInputIt + 1;
+            while ((nextInputIt = std::find_if(nextInputIt, inputLayers.end(), inputLayerCheck)) != inputLayers.end()) {
+                auto quantParamsSecond = InferenceEngine::getInjectedData<QuantizedLayerParams>(*nextInputIt);
+                if (!fp32eq(quantParamsSecond->_dst_quant.scale, quantParamsFirst->_dst_quant.scale)) {
+                    THROW_GNA_EXCEPTION << "Two Input layers " << (*firstInputIt)->name
+                        << " and " << (*nextInputIt)->name << " have different scales in concat!!! \n";
+                }
+            }
         }
 
-        // possible case when some of the concat inputs are free to select scale ex: const->concat<-affine
-        if (quantParams1->_dst_quant.scale == 1.0) {
-            quantParams1->_weights_quant = quantParams0->_dst_quant;
-            quantParams1->_dst_quant     = quantParams0->_dst_quant;
+        // find a source quant value
+        // - 1st candidate - non-activation layer with non-1 scale factor
+        // - 2nd candidate - 1st layer with non-1 scale factor
+        auto sourceLayerCheck = [&fp32eq](InferenceEngine::CNNLayerPtr& inputLayer) {
+            auto quantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(inputLayer);
+            LayerInfo info(inputLayer);
+            return !info.isActivation() && !fp32eq(quantParams->_dst_quant.scale, 1.0f);
+        };
 
-            sourceQuantParams = quantParams0;
+        static std::map<std::string, size_t> restarted_counter;
+        auto restartedCountIt = restarted_counter.find(concatLayer->name);
+        if (restartedCountIt == restarted_counter.end()) {
+            auto pos = restarted_counter.insert({ "concatLayer->name", 0 });
+            restartedCountIt = pos.first;
         }
 
-        if (quantParams0->_dst_quant.scale == 1.0) {
-            quantParams0->_weights_quant = quantParams1->_dst_quant;
-            quantParams0->_dst_quant     = quantParams1->_dst_quant;
-            sourceQuantParams = quantParams1;
+        if (restartedCountIt->second % 2 == 1) {
+            std::reverse(inputLayers.begin(), inputLayers.end());
         }
+        ++restartedCountIt->second;
 
-        if (!sourceQuantParams) {
-            auto in0LayerInfo = LayerInfo(in0);
-            auto in1LayerInfo = LayerInfo(in1);
-            if (in0LayerInfo.isActivation()) {
-                quantParams0->_weights_quant = quantParams1->_dst_quant;
-                quantParams0->_dst_quant = quantParams1->_dst_quant;
-                sourceQuantParams = quantParams1;
-            } else if (in1LayerInfo.isActivation()) {
-                quantParams1->_weights_quant = quantParams0->_dst_quant;
-                quantParams1->_dst_quant = quantParams0->_dst_quant;
-                sourceQuantParams = quantParams0;
-            } else {
-                THROW_GNA_LAYER_EXCEPTION(concatLayer) << "Concat quantization for " << in0->type << ": " << in0->name
-                    << " and " << in1->type << ": " << in1->name
-                    << " as inputs needs to be implemented! None of these inputs is an activation.\n";
+        auto sourceLayerIt = std::find_if(inputLayers.begin(), inputLayers.end(), sourceLayerCheck);
+        if (sourceLayerIt == inputLayers.end()) {
+            auto nonDefaultScaleFactor = [&fp32eq](InferenceEngine::CNNLayerPtr& inputLayer) {
+                auto quantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(inputLayer);
+                return !fp32eq(quantParams->_dst_quant.scale, 1.0f);
+            };
+
+            sourceLayerIt = std::find_if(inputLayers.begin(), inputLayers.end(), nonDefaultScaleFactor);
+        }
+
+        std::set<size_t> concatIdxToUpdate;
+        if (sourceLayerIt != inputLayers.end()) {
+            auto quantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(*sourceLayerIt);
+            auto scaleFactor = quantParams->_dst_quant.scale;
+            sourceQuantParams = quantParams;
+
+            for (auto it = inputLayers.begin(); it != inputLayers.end(); ++it) {
+                auto quantParamsIn = InferenceEngine::getInjectedData<QuantizedLayerParams>(*it);
+                if (fp32eq(quantParamsIn->_dst_quant.scale, scaleFactor)) {
+                    continue;
+                }
+
+                // possible case when some of the concat inputs are free to select scale ex: const->concat<-affine
+                if (!fp32eq(quantParamsIn->_dst_quant.scale, 1.0f) && !LayerInfo(*it).isActivation()) {
+                    concatIdxToUpdate.insert(std::distance(inputLayers.begin(), it));
+                }
+
+                quantParamsIn->_weights_quant = quantParams->_dst_quant;
+                quantParamsIn->_dst_quant = quantParams->_dst_quant;
             }
         }
 
-        if (!fp32eq(quantParams0->_dst_quant.scale, quantParams1->_dst_quant.scale) && concatIdxToUpdate == -1) {
+        auto updatedScaleFactor = InferenceEngine::getInjectedData<QuantizedLayerParams>(in0)->_dst_quant.scale;
+        auto equalScaleFactor = [updatedScaleFactor, &fp32eq](InferenceEngine::CNNLayerPtr& inputLayer) {
+            auto quantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(inputLayer);
+            return fp32eq(quantParams->_dst_quant.scale, updatedScaleFactor);
+        };
+
+        auto layerIt = std::find_if_not(inputLayers.begin() + 1, inputLayers.end(), equalScaleFactor);
+        if (layerIt != inputLayers.end()) {
             THROW_GNA_EXCEPTION << "layers entered into concat have different scale factors" << concatLayer->name;
         }
 
         quantData->_dst_quant.scale = sourceQuantParams->_dst_quant.scale;
         quantData->_src_quant.scale = sourceQuantParams->_dst_quant.scale;
 
-        if (fp32eq(quantParams0->_dst_quant.scale, quantParams1->_dst_quant.scale) || concatIdxToUpdate == -1) {
+        if (layerIt == inputLayers.end() && concatIdxToUpdate.empty()) {
             return true;
         }
 
-        auto destinationQuantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(*concatLayer);
-        destinationQuantParams->_dst_quant.scale = sourceQuantParams->_dst_quant.scale;
-
-        InferenceEngine::CNNLayerPtr  restartedLayer;
-        // making a link activation possible without extra layer if first input to concat not a parent / indirect parent of second input
-        // using ufs - upper first search
-        gnalog() << "[UFS] searching for quantizeable layer prior: "<< concatLayer->name << ", via " << concatIdxToUpdate << "\n";
-
-        CNNNetDFS(InferenceEngine::CNNLayerPtr(concatLayer, [](InferenceEngine::CNNLayer *) {}),
-                  [&restartedLayer, concatLayer](InferenceEngine::CNNLayerPtr layer) {
-                      gnalog() << "[UFS] from : " << concatLayer->name << " reached: " << layer->name;
-                      // found that direct input to concat is a indirect parent of align filter - so no link required
-                      auto info = LayerInfo(layer);
-                      if (!info.isWeightable() && !info.isActivation()) {
-                          gnalog() << "... skipped\n";
-                          return;
-                      }
-                      restartedLayer = layer;
-                      gnalog() << "... OK,  need requantize\n";
-                  }, true, [&restartedLayer, &concatLayer, &concatIdxToUpdate](InferenceEngine::CNNLayer *from) {
+        for (auto& layerIdToUpdate : concatIdxToUpdate) {
+            auto destinationQuantParams = InferenceEngine::getInjectedData<QuantizedLayerParams>(*concatLayer);
+            destinationQuantParams->_dst_quant.scale = sourceQuantParams->_dst_quant.scale;
+
+            InferenceEngine::CNNLayerPtr restartedLayer;
+            // making a link activation possible without extra layer if first input to concat not a parent / indirect parent of second input
+            // using ufs - upper first search
+            gnalog() << "[UFS] searching for quantizeable layer prior: " << concatLayer->name << ", via " << layerIdToUpdate << "\n";
+
+            CNNNetDFS(InferenceEngine::CNNLayerPtr(concatLayer, [](InferenceEngine::CNNLayer*) {}),
+                [&restartedLayer, concatLayer](InferenceEngine::CNNLayerPtr layer) {
+                    gnalog() << "[UFS] from : " << concatLayer->name << " reached: " << layer->name;
+                    // found that direct input to concat is a indirect parent of align filter - so no link required
+                    auto info = LayerInfo(layer);
+                    if (!info.isWeightable() && !info.isActivation()) {
+                        gnalog() << "... skipped\n";
+                        return;
+                    }
+                    restartedLayer = layer;
+                    gnalog() << "... OK,  need requantize\n";
+                }, true, [&restartedLayer, &concatLayer, &layerIdToUpdate](InferenceEngine::CNNLayer* from) {
                     // aborting UFS once found functional layer, and using only specified input of concat
                     return make_upstream_order(restartedLayer == nullptr ? from : nullptr,
-                                               from == concatLayer ? concatIdxToUpdate : -1);
+                        from == concatLayer ? layerIdToUpdate : -1);
                 });
 
-        if (restartedLayer == nullptr) {
-            THROW_GNA_EXCEPTION << "cannot requantize " << concatIdxToUpdate << "input to concat: " << concatLayer->name;
-        }
-        auto quantDataForConCatInput = InferenceEngine::getInjectedData<QuantizedLayerParams>(*restartedLayer);
+            if (restartedLayer == nullptr) {
+                THROW_GNA_EXCEPTION << "cannot requantize " << layerIdToUpdate << "input to concat: " << concatLayer->name;
+            }
+            auto quantDataForConCatInput = InferenceEngine::getInjectedData<QuantizedLayerParams>(*restartedLayer);
 
-        auto restarLayerInfo = LayerInfo(restartedLayer);
-        if (restarLayerInfo.isActivation()) {
-            // requantize activation by just changing it's output scale factor
-            quantDataForConCatInput->_dst_quant.scale = sourceQuantParams->_dst_quant.scale;
-        }
+            auto restarLayerInfo = LayerInfo(restartedLayer);
+            if (restarLayerInfo.isActivation()) {
+                // requantize activation by just changing it's output scale factor
+                quantDataForConCatInput->_dst_quant.scale = sourceQuantParams->_dst_quant.scale;
+            }
 
-        result = ScaleFactorUpdateResult(restartedLayer.get());
+            result = ScaleFactorUpdateResult(restartedLayer.get());
+        }
 
         return true;
     }
index 31372b5..3c463b5 100644 (file)
@@ -110,6 +110,12 @@ void GNAGraphCompiler::fillConcatConnections(InferenceEngine::CNNLayerPtr layer)
                 InferenceEngine::details::product(begin(dataInput->getDims()),
                                                   end(dataInput->getDims())) * dataInput->getPrecision().size();
 
+        // concat align layer can have additional padding, so the size of layer needs to be calculated
+        // based on original number of rows
+        if (ptrConcatLayerInput->CheckParamPresence("original_num_rows")) {
+            layer_size = ptrConcatLayerInput->GetParamAsInt("original_num_rows") * dataInput->getPrecision().size();
+        }
+
         layerInfoItem.concatInputLayers.emplace_back(GNAConcatLayer::ConcatConnectedLayerInfo{ptrConcatLayerInput->name, concat_size, layer_size});
 
         concat_size += layer_size;
@@ -848,7 +854,7 @@ void GNAGraphCompiler::CropPrimitive(InferenceEngine::CNNLayerPtr layer) {
     size_t cropOffset = offset.front() * cropLayer->precision.size();
     size_t cropOutputSize = dim.front() * cropLayer->precision.size();
 
-    if (ALIGN64(cropOffset) == cropOffset) {
+    if (!LayerInfo(cropLayer).isCropAffined()) {
         // leave crop as it is
         GNAPluginNS::GNACropLayer cropLayerInfoItem(layer);
         std::string& id = layer->name;
index 8137fba..fc3c44e 100644 (file)
@@ -260,10 +260,11 @@ class LayerInfo {
     bool isCropAffined() const noexcept {
         auto cropLayer = dynamic_cast<InferenceEngine::CropLayer *> (layer);
         if (cropLayer != nullptr && !cropLayer->offset.empty()) {
-            try {
-                size_t cropOffset = cropLayer->offset.back() * cropLayer->precision.size();
-                return (ALIGN64(cropOffset) != cropOffset);
-            } catch (InferenceEngine::details::InferenceEngineException) {}
+            // currently crop layer only supports 2 bytes in int16 and int8 mode.
+            // In fp32 mode this is not necessary but is useful for testing
+            auto bytesPerCropElement = 2;
+            size_t cropOffset = cropLayer->offset.back() * bytesPerCropElement;
+            return (ALIGN64(cropOffset) != cropOffset);
         }
         return false;
     }
index 760838b..4cd259d 100644 (file)
@@ -703,14 +703,10 @@ void InsertCopyLayerPass::run() {
             if (LayerInfo(l).isConcat() && LayerInfo(prevIndirectLayer).isCrop()) { bInsert = true; }
 
             if (bInsert) {
-                if (LayerInfo(prevIndirectLayer).isCrop()) {
-                    auto cropLayer = LayerInfo(prevIndirectLayer).as<CropLayer *>();
-                    size_t cropOffset = cropLayer->offset.back() * cropLayer->precision.size();
-                    if (ALIGN(cropOffset, 8) != cropOffset) {
-                        // The crop will be replaced by affine.
-                        // Copy layer insertion is not required
-                        continue;
-                    }
+                if (LayerInfo(prevIndirectLayer).isCropAffined()) {
+                    // The crop will be replaced by affine.
+                    // Copy layer insertion is not required
+                    continue;
                 }
                 auto prevLayer = CNNNetPrevLayer(l, i);
                 InsertCopyLayer(prevLayer, l, i, getPassManager());
@@ -788,8 +784,9 @@ void InsertConcatAligningFilterPass::run() {
                 size_t num_rows_out = num_rows_padded + num_rows_in;
 
                 // encodes offset to beginning of split layer input
+                size_t bytesOffset = (aligned64_offset / bytesPerConcatElement) * (quantized ? bytesPerConcatElement : 4);
                 concatAligningFilter->params["output_offset"] =
-                        std::to_string((aligned64_offset / bytesPerConcatElement) * (quantized ? bytesPerConcatElement : 4));
+                        std::to_string(bytesOffset);
 
                 // for padded rows we cannot use copy layer - TBD how to implement
                 concatAligningFilter->params["num_rows_padded"] = std::to_string(num_rows_padded);
@@ -843,84 +840,92 @@ void ReorderConcatInputsPass::run() {
     }
     int numOfLinkLayers = 0;
 
-    for (auto & l : *pLayers) {
-        // 1st stage locate concat align filter
+    for (auto& l : *pLayers) {
+        // 1st stage locate concat
         LayerInfo info(l);
-        if (!info.isConcatAlignFilter()) {
+        if (!info.isConcat()) {
             continue;
         }
 
-        // 2rd locating concat
-        if (l->outData.size() != 1) {
-            THROW_GNA_EXCEPTION << "no concat layer after concat aligning layer" << l->name;
+        // 2nd stage locate first input in concat
+        if (l->insData.size() < 2) {
+            THROW_GNA_EXCEPTION << "Concat layer has unsupported number of incoming layers: " << l->name;
         }
-        auto nextLayers = getInputTo(l->outData.front());
 
-        if (nextLayers.size() != 1) {
-            THROW_GNA_EXCEPTION << "Invalid concat connection in align filter : " << l->name;
-        }
-        auto concat = nextLayers.begin()->second;
-        if (!LayerInfo(concat).isConcat()) {
-            THROW_GNA_EXCEPTION << "no concat layer after concat-aligning layer" << l->name << ", but was: " << concat->type;
-        }
-        // 3stage locate first input in concat
-        if (concat->insData.size() < 2) {
-            THROW_GNA_EXCEPTION << "Concat layer has unsupported number of incoming layers: " << concat->name;
-        }
-        auto inputsToConcatFirst = CNNNetGetPrevLayersSkip(concat, [](CNNLayerPtr origin){
-            return !LayerInfo(origin).isNonFunctional() && !LayerInfo(origin).isSplit();
-        }, 0);
+        auto concatLayer = info.as<ConcatLayer*>();
+        auto getLayerByIndex = [&concatLayer](int idx) {
+            auto input = concatLayer->insData[idx];
+            auto lockedInput = input.lock();
+            if (!lockedInput) {
+                THROW_GNA_EXCEPTION << "cannot get insdata : " << idx << " for layer: " << concatLayer->name;
+            }
+            return lockedInput;
+        };
 
-        if (inputsToConcatFirst.empty()) {
-            THROW_GNA_EXCEPTION << "cannot locate first input into concat layer: " << l;
-        }
+        for (auto input_idx = 1; input_idx != concatLayer->insData.size(); input_idx++) {
+            auto concatInput = getLayerByIndex(input_idx);
+            auto currConcatLayer = getCreatorLayer(concatInput).lock();
 
-        auto firstInputToConcat = inputsToConcatFirst.front().first;
+            LayerInfo infoConcatInput(currConcatLayer);
+            if (!infoConcatInput.isConcatAlignFilter()) {
+                continue;
+            }
 
-        // concat has first input of concat align filter - dont need to reorder it
-        if (firstInputToConcat == l) {
-            continue;
-        }
+            auto inputsToConcatPrev = CNNNetGetPrevLayersSkip(l, [](CNNLayerPtr origin) {
+                return !LayerInfo(origin).isNonFunctional() && !LayerInfo(origin).isSplit();
+                }, input_idx - 1);
 
-        bool bFinish = false;
-        // making a link activation possible without extra layer if first input to concat not a parent / indirect parent of second input
-        // using ufs - upper first search
-        gnalog() << "[UFS] searching for: "<< firstInputToConcat->name << "\n";
-
-        CNNNetDFS(l, [&l, &firstInputToConcat, &bFinish](CNNLayerPtr layer) {
-            gnalog() << "[UFS] from : "<< l->name <<" reached: " << layer->name << "\n";
-            // found that direct input to concat is a indirect parent of align filter - so no link required
-            if (layer.get() == firstInputToConcat.get() || LayerInfo(firstInputToConcat).isInput()) {
-                gnalog() << "[UFS] copy layer insertion needed\n";
-                bFinish = true;
+            if (inputsToConcatPrev.empty()) {
+                THROW_GNA_EXCEPTION << "cannot locate first input into concat layer: " << currConcatLayer;
             }
-        }, true, [&bFinish](InferenceEngine::CNNLayer* from) {
-            // aborting UFS once link not need
-            return make_upstream_order(!bFinish ? from : nullptr);
-        });
 
-        auto linkName = std::string("link_") + std::to_string(numOfLinkLayers++);
+            auto prevInputToConcat = inputsToConcatPrev.front().first;
+
+            // concat has first input of concat align filter - dont need to reorder it
+            if (prevInputToConcat == currConcatLayer) {
+                continue;
+            }
+
+            bool bFinish = false;
+            // making a link activation possible without extra layer if first input to concat not a parent / indirect parent of second input
+            // using ufs - upper first search
+            gnalog() << "[UFS] searching for: " << prevInputToConcat->name << "\n";
+
+            CNNNetDFS(currConcatLayer, [&currConcatLayer, &prevInputToConcat, &bFinish](CNNLayerPtr layer) {
+                gnalog() << "[UFS] from : " << currConcatLayer->name << " reached: " << layer->name << "\n";
+                // found that direct input to concat is a indirect parent of align filter - so no link required
+                if (layer.get() == prevInputToConcat.get() || LayerInfo(prevInputToConcat).isInput()) {
+                    gnalog() << "[UFS] copy layer insertion needed\n";
+                    bFinish = true;
+                }
+                }, true, [&bFinish](InferenceEngine::CNNLayer* from) {
+                    // aborting UFS once link not needed
+                    return make_upstream_order(!bFinish ? from : nullptr);
+                });
+
+            auto linkName = std::string("link_") + std::to_string(numOfLinkLayers++);
 
-        auto linkWithoutQuant = std::make_shared<CNNLayer>(LayerParams({linkName, "link", Precision::FP32}));
+            auto linkWithoutQuant = std::make_shared<CNNLayer>(LayerParams({ linkName, "link", Precision::FP32 }));
 
-        auto link = quantized ?
-                    InferenceEngine::injectData<QuantizedLayerParams>(linkWithoutQuant) :
-                    linkWithoutQuant;
+            auto link = quantized ?
+                InferenceEngine::injectData<QuantizedLayerParams>(linkWithoutQuant) :
+                linkWithoutQuant;
 
 
-        auto linkOutData = std::make_shared<Data>(linkName,
-                                              TensorDesc(Precision::FP32,
-                                                         SizeVector({1}),
-                                                         Layout::C));
-        getCreatorLayer(linkOutData) = link;
+            auto linkOutData = std::make_shared<Data>(linkName,
+                TensorDesc(Precision::FP32,
+                    SizeVector({ 1 }),
+                    Layout::C));
+            getCreatorLayer(linkOutData) = link;
 
-        link->outData.push_back(linkOutData);
-        link->insData.push_back(l->outData.front());
+            link->outData.push_back(linkOutData);
+            link->insData.push_back(currConcatLayer->outData.front());
 
-        getInputTo(linkOutData)[firstInputToConcat->name + ".via.link"] = firstInputToConcat;
-        firstInputToConcat->insData.push_back(linkOutData);
+            getInputTo(linkOutData)[prevInputToConcat->name + ".via.link"] = prevInputToConcat;
+            prevInputToConcat->insData.push_back(linkOutData);
 
-        getInputTo(l->outData.front())[linkName] = link;
+            getInputTo(currConcatLayer->outData.front())[linkName] = link;
+        }
     }
 }
 
diff --git a/inference-engine/tests/functional/plugin/gna/shared_tests_instances/subgraph_tests/concat_multi_input.cpp b/inference-engine/tests/functional/plugin/gna/shared_tests_instances/subgraph_tests/concat_multi_input.cpp
new file mode 100644 (file)
index 0000000..8d3f05c
--- /dev/null
@@ -0,0 +1,43 @@
+// Copyright (C) 2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <vector>
+
+#include "subgraph_tests/concat_multi_input.hpp"
+#include "common_test_utils/test_constants.hpp"
+
+using namespace LayerTestsDefinitions;
+
+namespace {
+
+std::vector<std::vector<std::vector<size_t>>> inShapes = {
+    {{1, 8}, {1, 8}},
+    {{1, 3}, {1, 3}, {1, 3}},
+    {{1, 16}, {1, 16}, {1, 16}},
+    {{1, 16}, {1, 16}, {1, 16}, {1, 16}},
+    {{1, 32}, {1, 32}, {1, 32}, {1, 32}},
+    {{1, 16}, {1, 32}, {1, 16}, {1, 32}, {1, 16}, {1, 32}},
+};
+
+const std::vector<InferenceEngine::Precision> netPrecisions = {
+    InferenceEngine::Precision::FP32,
+    InferenceEngine::Precision::FP16,
+};
+
+std::map<std::string, std::string> additional_config = {
+    {"GNA_DEVICE_MODE", "GNA_SW_EXACT"},
+    {"GNA_COMPACT_MODE", "NO"},
+    {"GNA_SCALE_FACTOR_0", "2048"},
+    {"GNA_PRECISION", "I16"},
+};
+
+INSTANTIATE_TEST_CASE_P(concat_multi_input, ConcatMultiInput,
+    ::testing::Combine(
+        ::testing::ValuesIn(inShapes),
+        ::testing::ValuesIn(netPrecisions),
+        ::testing::Values(CommonTestUtils::DEVICE_GNA),
+        ::testing::Values(additional_config)),
+    ConcatMultiInput::getTestCaseName);
+
+} //namespace
diff --git a/inference-engine/tests/functional/plugin/shared/include/subgraph_tests/concat_multi_input.hpp b/inference-engine/tests/functional/plugin/shared/include/subgraph_tests/concat_multi_input.hpp
new file mode 100644 (file)
index 0000000..8f0e06d
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright (C) 2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#pragma once
+
+#include <tuple>
+#include <vector>
+#include <string>
+#include <memory>
+
+#include "functional_test_utils/layer_test_utils.hpp"
+#include "ngraph_functions/utils/ngraph_helpers.hpp"
+#include "ngraph_functions/builders.hpp"
+
+typedef std::tuple<
+    std::vector<std::vector<size_t>>,   // Input shapes
+    InferenceEngine::Precision,         // Network Precision
+    std::string,                        // Target Device
+    std::map<std::string, std::string>  // Config
+> concatMultiParams;
+
+namespace LayerTestsDefinitions {
+
+class ConcatMultiInput : public testing::WithParamInterface<concatMultiParams>,
+    virtual public LayerTestsUtils::LayerTestsCommon {
+public:
+    static std::string getTestCaseName(testing::TestParamInfo<concatMultiParams> obj);
+
+protected:
+    void SetUp() override;
+};
+
+}  // namespace LayerTestsDefinitions
diff --git a/inference-engine/tests/functional/plugin/shared/src/subgraph_tests/concat_multi_input.cpp b/inference-engine/tests/functional/plugin/shared/src/subgraph_tests/concat_multi_input.cpp
new file mode 100644 (file)
index 0000000..1d70dfe
--- /dev/null
@@ -0,0 +1,88 @@
+// Copyright (C) 2020 Intel Corporation
+// SPDX-License-Identifier: Apache-2.0
+//
+
+#include <memory>
+#include <tuple>
+#include <vector>
+#include <string>
+
+#include <ie_core.hpp>
+
+#include "common_test_utils/common_utils.hpp"
+#include "functional_test_utils/plugin_cache.hpp"
+#include "functional_test_utils/layer_test_utils.hpp"
+#include "functional_test_utils/blob_utils.hpp"
+
+#include "ngraph_functions/pass/convert_prc.hpp"
+
+#include "subgraph_tests/concat_multi_input.hpp"
+
+
+namespace LayerTestsDefinitions {
+
+
+std::string ConcatMultiInput::getTestCaseName(testing::TestParamInfo<concatMultiParams> obj) {
+    std::vector<std::vector<size_t>> inputShapes;
+    InferenceEngine::Precision netPrecision;
+    std::string targetDevice;
+    std::map<std::string, std::string> additional_config;
+    std::tie(inputShapes, netPrecision, targetDevice, additional_config) = obj.param;
+
+    std::ostringstream result;
+    result << "IS=" << CommonTestUtils::vec2str(inputShapes) << "_";
+    result << "netPRC=" << netPrecision.name() << "_";
+    result << "targetDevice=" << targetDevice;
+
+    return result.str();
+}
+
+void ConcatMultiInput::SetUp() {
+    std::vector<std::vector<size_t>> inputShapes;
+    InferenceEngine::Precision netPrecision;
+    std::map<std::string, std::string> additional_config;
+    std::tie(inputShapes, netPrecision, targetDevice, additional_config) = this->GetParam();
+    configuration.insert(additional_config.begin(), additional_config.end());
+
+    auto ngPrc = FuncTestUtils::PrecisionUtils::convertIE2nGraphPrc(netPrecision);
+    std::vector<size_t> paramSize = { 1, 0 };
+    for (const auto& val : inputShapes) {
+        paramSize[1] += val[1];
+    }
+    auto params = ngraph::builder::makeParams(ngPrc, { paramSize });
+    auto stride = std::make_shared<ngraph::op::Constant>(ngraph::element::i64, ngraph::Shape{ 2 }, std::vector<int64_t>{ 1, 1 });
+
+    std::vector<int64_t> newAxis = { 0, 0 };
+    std::vector<int64_t> begin_mask = { 0, 0 };
+    std::vector<int64_t> end_mask = { 0, 0 };
+    std::vector<std::shared_ptr<ngraph::opset1::StridedSlice>> ssArray;
+    ngraph::OutputVector concatInput;
+
+    auto relu = std::make_shared<ngraph::opset1::Relu>(params[0]);
+    std::vector<int64_t> startOffset = { 0, 0 };
+    for (size_t i = 0; i < inputShapes.size(); ++i) {
+        std::vector<int64_t> shape = { static_cast<int64_t>(inputShapes[i][0]),
+                                       static_cast<int64_t>(inputShapes[i][1]) };
+        std::vector<int64_t> endoffset = { static_cast<int64_t>(inputShapes[i][0]) + startOffset[0],
+                                           static_cast<int64_t>(inputShapes[i][1]) + startOffset[1]};
+        auto begin = std::make_shared<ngraph::op::Constant>(ngraph::element::i64, ngraph::Shape{ 2 }, startOffset);
+        auto end = std::make_shared<ngraph::op::Constant>(ngraph::element::i64, ngraph::Shape{ 2 }, endoffset);
+        auto ss = std::make_shared<ngraph::opset1::StridedSlice>(relu, begin, end, stride, begin_mask, end_mask, newAxis);
+        ssArray.push_back(ss);
+        concatInput.push_back(ssArray[i]);
+
+        startOffset[1] += shape[1];
+    }
+
+    auto concat = std::make_shared<ngraph::opset1::Concat>(concatInput, 1);
+
+    ngraph::ResultVector results{ std::make_shared<ngraph::opset1::Result>(concat) };
+    function = std::make_shared<ngraph::Function>(results, params, "ConcatMultiInput");
+}
+
+TEST_P(ConcatMultiInput, CompareWithRefImpl) {
+    Run();
+};
+
+
+}  // namespace LayerTestsDefinitions