From 9d74ba6e85a043e9603445e062315f5c4965fbd6 Mon Sep 17 00:00:00 2001 From: Francis Murtagh Date: Wed, 19 Jan 2022 16:31:58 +0000 Subject: IVGCVSW-6634 SubgraphView: Add method of returning a GetSubgraphWorkingCopy * Add pointer to SubgraphView allowing it to store a working copy implementation of its own representation of graph. * Make SubgraphView a friend of Graph to allow access to layers. * Add constructor to SubgraphView taking SubgraphViewWorkingCopyPtr * Rewrite Graph::SubstituteSubgraph for use on SubgraphView * Add GetWorkingCopy() method * Add tests for replacement of multiplication with DepthwiseConv2d * Check GetBackendHint() has value before passing to PrecompiledLayer * Add GetOwningIConnectableLayer to IInputSlot to allow traversing from IConnectableLayer->IOutputSlot->IInputSlot->IConnectableLayer Signed-off-by: Francis Murtagh Change-Id: Iaaef14448d8b73867eaee9d69f4f98d5d1bf171c --- include/armnn/INetwork.hpp | 1 + include/armnn/backends/SubgraphView.hpp | 26 +++ src/armnn/Graph.hpp | 2 + src/armnn/Layer.cpp | 5 + src/armnn/Layer.hpp | 2 + src/armnn/Network.cpp | 2 +- src/armnn/SubgraphView.cpp | 233 +++++++++++++++++++- src/armnn/test/SubgraphViewTests.cpp | 364 +++++++++++++++++++++++++++++++- 8 files changed, 628 insertions(+), 7 deletions(-) diff --git a/include/armnn/INetwork.hpp b/include/armnn/INetwork.hpp index 6a2193cbc6..505edf86c9 100644 --- a/include/armnn/INetwork.hpp +++ b/include/armnn/INetwork.hpp @@ -28,6 +28,7 @@ class IInputSlot public: virtual const IOutputSlot* GetConnection() const = 0; virtual IOutputSlot* GetConnection() = 0; + virtual const IConnectableLayer& GetOwningIConnectableLayer() const = 0; protected: /// Not user deletable. diff --git a/include/armnn/backends/SubgraphView.hpp b/include/armnn/backends/SubgraphView.hpp index 33593319cf..dbf0544651 100644 --- a/include/armnn/backends/SubgraphView.hpp +++ b/include/armnn/backends/SubgraphView.hpp @@ -151,7 +151,27 @@ public: void Clear(); + /// This method returns a copy of the original SubgraphView provided by OptimizeSubgraphView with a separate + /// underlying graph from the main ArmNN graph. + /// Backend users should edit this working copy and then add it as a SubstitutionPair, along with original + /// SubgraphView, to the OptimizationViews returned by OptimizeSubgraphView. + /// ArmNN will then decide on whether or not to carry out Substitution of the two SubgraphViews. + SubgraphView GetWorkingCopy(); + + /// These methods should be called on a working copy subgraph created from GetWorkingCopy. + /// They take a SubgraphView pattern to replace and the substitute layer or subgraphView to substitute in. + void SubstituteSubgraph(SubgraphView&, IConnectableLayer*); + void SubstituteSubgraph(SubgraphView&, const SubgraphView&); + private: + struct SubgraphViewWorkingCopy; + + /// Constructs a sub-graph with the given arguments. + SubgraphView(IConnectableLayers&& layers, + IInputSlots&& inputs, + IOutputSlots&& outputs, + std::shared_ptr ptr); + void CheckSubgraph(); /// Arrange the order of layers topologically so that nodes can be visited in valid order @@ -168,5 +188,11 @@ private: /// The list of pointers to the layers of the parent graph. Layers m_Layers; IConnectableLayers m_IConnectableLayers; + + /// Pointer to internal graph implementation. This stores a working copy of a graph, separate from the main + /// ArmNN graph, for use by Backends so that they can edit it and add as a SubstitutionPair to OptimizationViews + /// along with the original SubgraphView. + /// ArmNN will then decide on whether or not to substitute in the provided SubgraphView working copy. + std::shared_ptr p_WorkingCopyImpl; }; } // namespace armnn diff --git a/src/armnn/Graph.hpp b/src/armnn/Graph.hpp index d71149d069..0c34d35685 100644 --- a/src/armnn/Graph.hpp +++ b/src/armnn/Graph.hpp @@ -280,6 +280,8 @@ private: /// Also verifies weights and bias are set for FullyConnected layers. void ConstructErrorMessageForUnconnectedInputs(Layer* const layer, unsigned int slotIndex); + + friend class SubgraphView; }; /// Common base class for layers in the graph. diff --git a/src/armnn/Layer.cpp b/src/armnn/Layer.cpp index 88bc6d56e1..4ed179fa22 100644 --- a/src/armnn/Layer.cpp +++ b/src/armnn/Layer.cpp @@ -488,4 +488,9 @@ const IConnectableLayer& OutputSlot::GetOwningIConnectableLayer() const return m_OwningLayer; } +const IConnectableLayer& InputSlot::GetOwningIConnectableLayer() const +{ + return m_OwningLayer; +} + } // namespace armnn diff --git a/src/armnn/Layer.hpp b/src/armnn/Layer.hpp index 23aa86a414..23561cb75e 100644 --- a/src/armnn/Layer.hpp +++ b/src/armnn/Layer.hpp @@ -55,6 +55,8 @@ public: const OutputSlot* GetConnectedOutputSlot() const { return m_Connection; } OutputSlot* GetConnectedOutputSlot() { return m_Connection; } + const IConnectableLayer& GetOwningIConnectableLayer() const override; + /// Links the slot to an output slot or breaks an existing link if passing nullptr. void SetConnection(OutputSlot* source) { diff --git a/src/armnn/Network.cpp b/src/armnn/Network.cpp index de60e11eef..8ec8b42ee5 100644 --- a/src/armnn/Network.cpp +++ b/src/armnn/Network.cpp @@ -2857,7 +2857,7 @@ IConnectableLayer* NetworkImpl::AddPrecompiledLayer(const PreCompiledDescriptor& { layer->SetBackendId(backend.value()); } - else + else if (layer->GetBackendHint().has_value()) { layer->SetBackendId(layer->GetBackendHint().value()); } diff --git a/src/armnn/SubgraphView.cpp b/src/armnn/SubgraphView.cpp index aac2a354d1..5f972a9767 100644 --- a/src/armnn/SubgraphView.cpp +++ b/src/armnn/SubgraphView.cpp @@ -65,9 +65,9 @@ SubgraphView::SubgraphView(InputSlots&& inputs, OutputSlots&& outputs, Layers&& } /// IConnectable Duplication to maintain backwards compatibility -SubgraphView::SubgraphView(SubgraphView::IConnectableLayers &&layers, - SubgraphView::IInputSlots &&inputs, - SubgraphView::IOutputSlots &&outputs) +SubgraphView::SubgraphView(SubgraphView::IConnectableLayers&& layers, + SubgraphView::IInputSlots&& inputs, + SubgraphView::IOutputSlots&& outputs) : m_IInputSlots{inputs} , m_IOutputSlots{outputs} , m_IConnectableLayers(IConnectableLayers{layers.begin(), layers.end()}) @@ -79,6 +79,42 @@ SubgraphView::SubgraphView(SubgraphView::IConnectableLayers &&layers, }; std::transform(layers.begin(), layers.end(), std::back_inserter(m_Layers), f); + m_InputSlots.resize(inputs.size()); + m_IInputSlots.resize(inputs.size()); + for (unsigned int i = 0; i < inputs.size(); i++) + { + m_InputSlots.at(i) = PolymorphicDowncast(inputs[i]); + m_IInputSlots.at(i) = inputs[i]; + } + + m_OutputSlots.resize(outputs.size()); + m_IOutputSlots.resize(outputs.size()); + for (unsigned int i = 0; i < outputs.size(); i++) + { + m_OutputSlots.at(i) = PolymorphicDowncast(outputs[i]); + m_IOutputSlots.at(i) = outputs[i]; + } + + ArrangeBySortOrder(); + CheckSubgraph(); +} + +/// IConnectable Duplication to maintain backwards compatibility +SubgraphView::SubgraphView(SubgraphView::IConnectableLayers&& layers, + SubgraphView::IInputSlots&& inputs, + SubgraphView::IOutputSlots&& outputs, + std::shared_ptr ptr) + : m_IInputSlots{inputs} + , m_IOutputSlots{outputs} + , m_IConnectableLayers(IConnectableLayers{layers.begin(), layers.end()}) + , p_WorkingCopyImpl(std::move(ptr)) +{ + // Cast from IConnectableLayer to Layer for backward compatibility + auto f = [](IConnectableLayer* value) + { + return PolymorphicDowncast(value); + }; + std::transform(layers.begin(), layers.end(), std::back_inserter(m_Layers), f); m_InputSlots.resize(inputs.size()); m_IInputSlots.resize(inputs.size()); @@ -367,4 +403,195 @@ void SubgraphView::ArrangeBySortOrder() m_IConnectableLayers.sort(compareIConnectableLayerPriority); } +struct SubgraphView::SubgraphViewWorkingCopy +{ +public: + + SubgraphViewWorkingCopy() = default; + SubgraphViewWorkingCopy(Graph graph) + : m_Graph(graph) + {}; + + Graph m_Graph; + +}; + +SubgraphView SubgraphView::GetWorkingCopy() +{ + if (p_WorkingCopyImpl) + { + throw Exception("The SubgraphView calling GetWorkingCopy() is already a working copy. This function " + "should be called on original SubgraphView obtained from OptimizeSubgraphView()"); + } + + // Create a cut down SubgraphView with underlying graph containing only the relevant layers. + // It needs its own underlying layers so that they can be replaced safely. + Graph newGraph = Graph(); + std::unordered_map originalToClonedLayerMap; + std::list originalSubgraphLayers = GetIConnectableLayers(); + + auto ptr = std::make_shared(std::move(newGraph)); + SubgraphView::IInputSlots workingCopyInputs; + + for (auto&& originalLayer : originalSubgraphLayers) + { + Layer* const layer = PolymorphicDowncast(originalLayer)->Clone(ptr->m_Graph); + originalToClonedLayerMap.emplace(originalLayer, layer); + } + + // Add IInputSlots to workingCopy + std::vector processed; + for (auto originalSubgraphInputSlot : GetIInputSlots()) + { + const IConnectableLayer& originalSubgraphLayer = + PolymorphicDowncast(originalSubgraphInputSlot)->GetOwningLayer(); + + // Only need process Slots of layer once + if (std::find(processed.begin(), processed.end(), &originalSubgraphLayer) == processed.end()) + { + IConnectableLayer* clonedLayer = originalToClonedLayerMap[&originalSubgraphLayer]; + + // Add the InputSlot to WorkingCopy InputSlots + for (unsigned int i = 0; i < clonedLayer->GetNumInputSlots(); i++) + { + workingCopyInputs.push_back(&clonedLayer->GetInputSlot(i)); + } + processed.push_back(&originalSubgraphLayer); + } + } + // Empty processed + processed.clear(); + + for (auto originalSubgraphLayer : originalSubgraphLayers) + { + IConnectableLayer* const clonedLayer = originalToClonedLayerMap[originalSubgraphLayer]; + + // connect all cloned layers as per original subgraph + for (unsigned int i = 0; i < clonedLayer->GetNumOutputSlots(); i++) + { + // OutputLayers have no OutputSlots to be connected + if (clonedLayer->GetType() != LayerType::Output) + { + auto& outputSlot = clonedLayer->GetOutputSlot(i); + for (unsigned int k = 0; k < originalSubgraphLayer->GetNumOutputSlots(); k++) + { + auto& originalOutputSlot = originalSubgraphLayer->GetOutputSlot(k); + for (unsigned int j = 0; j < originalOutputSlot.GetNumConnections(); j++) + { + // nextLayer is the layer with IInputSlot connected to IOutputSlot we are working on + const IConnectableLayer& nextLayer = + originalOutputSlot.GetConnection(j)->GetOwningIConnectableLayer(); + + // Check the layer is in our map and so has a clonedLayer + if (originalToClonedLayerMap.find(&nextLayer) != originalToClonedLayerMap.end()) + { + IConnectableLayer* newGraphTargetLayer = originalToClonedLayerMap[&nextLayer]; + + IInputSlot& inputSlot = + newGraphTargetLayer->GetInputSlot( + PolymorphicDowncast( + &originalOutputSlot)->GetConnection(j)->GetSlotIndex()); + + // Then make the connection + outputSlot.Connect(inputSlot); + } + } + // Copy the tensorInfo to the clonedOutputSlot + outputSlot.SetTensorInfo(originalOutputSlot.GetTensorInfo()); + } + } + } + } + + SubgraphView::IOutputSlots workingCopyOutputs; + + // Add IOutputSlots to workingCopy + for (auto outputSlot : GetIOutputSlots()) + { + + const IConnectableLayer& originalSubgraphLayer = outputSlot->GetOwningIConnectableLayer(); + + // OutputLayers have no OutputSlots to be connected + // Only need process Slots of layer once + if (originalSubgraphLayer.GetType() != LayerType::Output && + std::find(processed.begin(), processed.end(), &originalSubgraphLayer) == processed.end()) + { + IConnectableLayer* clonedLayer = originalToClonedLayerMap[&originalSubgraphLayer]; + + // Add the OutputSlot to WorkingCopy InputSlots + for (unsigned int i = 0; i < clonedLayer->GetNumOutputSlots(); i++) + { + workingCopyOutputs.push_back(&clonedLayer->GetOutputSlot(i)); + } + processed.push_back(&originalSubgraphLayer); + } + } + processed.clear(); + + SubgraphView::IConnectableLayers workingCopyLayers; + for (auto& pair : originalToClonedLayerMap) + { + workingCopyLayers.push_back(pair.second); + } + + return {std::move(workingCopyLayers), + std::move(workingCopyInputs), + std::move(workingCopyOutputs), + ptr}; +} + +void SubgraphView::SubstituteSubgraph(SubgraphView& subgraph, IConnectableLayer* substituteLayer) +{ + ARMNN_ASSERT(substituteLayer != nullptr); + SubgraphView substituteSubgraph(substituteLayer); + + SubstituteSubgraph(subgraph, substituteSubgraph); +} + +void SubgraphView::SubstituteSubgraph(SubgraphView& patternSubgraph, const SubgraphView& substituteSubgraph) +{ + if (!p_WorkingCopyImpl) + { + throw NullPointerException("The SubgraphView calling SubstituteSubgraphView is not a working copy. " + "Call this function on SubgraphView returned from SubgraphView::GetWorkingCopy()"); + } + + // Add substitute layer to the Main graph i.e. graph in p_WorkingCopyImpl + auto workingCopyGraph = &p_WorkingCopyImpl->m_Graph; + substituteSubgraph.ForEachIConnectableLayer([workingCopyGraph](IConnectableLayer* iConnectableLayer) + { + // Search WorkingCopy Graph for substituteLayer and add if missing + if (std::find(std::begin(workingCopyGraph->m_Layers), + std::end(workingCopyGraph->m_Layers), + iConnectableLayer) == + std::end(workingCopyGraph->m_Layers)) + { + auto layer = PolymorphicDowncast(iConnectableLayer); + + layer->Reparent(*workingCopyGraph, + (workingCopyGraph->m_Layers).end()); + + workingCopyGraph->m_LayersInOrder = false; + } + }); + + // Replace the old connections with connections to new layer + workingCopyGraph->ReplaceSubgraphConnections(patternSubgraph, substituteSubgraph); + + // Update input/outputSlot pointers + m_IInputSlots = std::move(substituteSubgraph.m_IInputSlots); + m_IOutputSlots = std::move(substituteSubgraph.m_IOutputSlots); + + // Delete the old layers. + workingCopyGraph->EraseSubgraphLayers(patternSubgraph); + + // Sort + workingCopyGraph->TopologicalSort(); + + // Update SubgraphView layer pointers to match those of the internal WorkingCopy layer pointers + m_IConnectableLayers = IConnectableLayers{ workingCopyGraph->m_Layers.begin(), + workingCopyGraph->m_Layers.end() }; +} + + } // namespace armnn diff --git a/src/armnn/test/SubgraphViewTests.cpp b/src/armnn/test/SubgraphViewTests.cpp index 14c18eeae1..e8012b575c 100644 --- a/src/armnn/test/SubgraphViewTests.cpp +++ b/src/armnn/test/SubgraphViewTests.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -87,7 +88,7 @@ SubgraphView::OutputSlots CreateOutputsFrom(const std::vector& layers) SubgraphView::IOutputSlots CreateIOutputsFrom(const std::vector& layers) { SubgraphView::IOutputSlots result; - for (auto &&layer: layers) + for (auto&& layer: layers) { for (unsigned int i = 0; i < layer->GetNumOutputSlots(); ++i) { @@ -1459,14 +1460,14 @@ TEST_CASE("Random") for (uint32_t i = 0; i < numConcats; ++i) { std::string name = "concat" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N"); - uint32_t numInputs = 1 + GetRandom(3u); + numInputs = 1 + GetRandom(3u); OriginsDescriptor concatDesc(numInputs); graph.AddLayer(concatDesc, name.c_str()); } for (uint32_t i = 0; i < numSplits; ++i) { std::string name = "split" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N"); - uint32_t numOutputs = 1 + GetRandom(3u); + numOutputs = 1 + GetRandom(3u); ViewsDescriptor splitDesc(numOutputs); graph.AddLayer(splitDesc, name.c_str()); } @@ -1891,4 +1892,361 @@ TEST_CASE("SubgraphOrder") ); } +TEST_CASE("SubgraphViewWorkingCopy") +{ + Graph graph; + + auto input = graph.AddLayer(0, "Input"); + auto activation = graph.AddLayer(ActivationDescriptor{}, "Activation"); + auto output = graph.AddLayer(1, "Output"); + + input->GetOutputSlot(0).Connect(activation->GetInputSlot(0)); + activation->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + //Add in out of order + auto view = CreateSubgraphViewFrom({output, input, activation}, + {}, + {}); + + SubgraphView workingCopy = view->GetWorkingCopy(); + + // Check the layers are sorted topologically in the view + int idx=0; + LayerType expectedSorted[] = {LayerType::Input, LayerType::Activation, LayerType::Output}; + workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l) + { + CHECK((expectedSorted[idx] == l->GetType())); + idx++; + } + ); +} + +bool ReplaceConstantMultiplicationWithDepthwise(SubgraphView& subgraph, + IConnectableLayer* layer) +{ + if (layer->GetType() == LayerType::Multiplication) + { + IInputSlot* patternSubgraphInput = &layer->GetInputSlot(0); + + const IConnectableLayer* inputLayer = &patternSubgraphInput->GetConnection()->GetOwningIConnectableLayer(); + const IConnectableLayer* constantLayer = &layer->GetInputSlot(1).GetConnection()->GetOwningIConnectableLayer(); + + // Figure out which of the two inputs is the constant + if (constantLayer->GetType() != LayerType::Constant) + { + patternSubgraphInput = &layer->GetInputSlot(1); + std::swap(inputLayer, constantLayer); + } + + if (constantLayer->GetType() == LayerType::Constant) + { + const TensorInfo& inputInfo = inputLayer->GetOutputSlot(0).GetTensorInfo(); + const TensorInfo& constInfo = constantLayer->GetOutputSlot(0).GetTensorInfo(); + + // Add a Depthwise only where the constant input is a scalar that takes the form { 1, 1, 1, C }. + // The scalar is used as weights for the convolution. + if (constInfo.GetShape() == TensorShape({ 1, 1, 1, inputInfo.GetShape()[3] })) + { + auto replacementGraph = INetwork::Create(); + + DepthwiseConvolution2dDescriptor desc; + desc.m_DataLayout = DataLayout::NHWC; + + TensorInfo weightInfo = constInfo; + const TensorInfo& outputInfo = layer->GetOutputSlot(0).GetTensorInfo(); + unsigned int M = outputInfo.GetShape()[3] / inputInfo.GetShape()[3]; + ARMNN_ASSERT_MSG(M == 1, "Constant multiplication only support 1x1x1xC, so M should always be 1 here"); + weightInfo.SetShape({ 1, 1, 1, constInfo.GetShape()[3] * M }); //1HW(I*M) + + const void* weightData = PolymorphicPointerDowncast(constantLayer) + ->m_LayerOutput->GetConstTensor(); + TensorInfo weightsInfo = constInfo; + ConstTensor weights(weightsInfo, weightData); + + const auto depthwiseLayer = replacementGraph->AddDepthwiseConvolution2dLayer( + desc, weights, armnn::EmptyOptional(), "Replacement for Constant-Multiplication"); + + auto& outslot = layer->GetOutputSlot(0); + SubgraphView::IOutputSlots outputs{ &outslot }; + SubgraphView::IConnectableLayers layers; + layers.push_back(layer); + layers.push_back(const_cast(constantLayer)); + + SubgraphView patternSubgraph(std::move(layers), {patternSubgraphInput}, {&layer->GetOutputSlot(0)}); + + subgraph.SubstituteSubgraph(patternSubgraph, depthwiseLayer ); + + return true; + } + } + } + return false; +} + +bool ReplaceTestMultiplication(SubgraphView& subgraph, + IConnectableLayer* layer) +{ + if (layer->GetType() == LayerType::Multiplication) + { + + switch (layer->GetType()) + { + case LayerType::Multiplication: + return ReplaceConstantMultiplicationWithDepthwise(subgraph, layer); + break; + default: + throw Exception("Found unknown MultiplicationSupportedMode value"); + break; + } + } + return false; +} + +void ReplaceUnsupportedLayers(SubgraphView& subgraph) +{ + using ReplacementFunc = bool (*)(SubgraphView&, IConnectableLayer*); + const ReplacementFunc replacementFuncs[] = { + &ReplaceTestMultiplication, + }; + + subgraph.ForEachLayer([replacementFuncs, &subgraph](IConnectableLayer* layer) + { + auto madeChange = false; + for (const ReplacementFunc f : replacementFuncs) + { + madeChange = f(subgraph, layer); + if (madeChange) + { + goto nextIteration; + } + } + nextIteration:; + } + ); +} + +TEST_CASE("SubgraphViewWorkingCopyReplacementFunc") +{ + Graph graph; + + const TensorInfo inputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0); + const TensorInfo constInfo({ 1, 1, 1, 16 }, DataType::QAsymmU8, 0.9f, 0, true); + const TensorInfo outputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0); + + std::vector constData(constInfo.GetNumElements(), 0); + std::iota(constData.begin(), constData.end(), 0); + ConstTensor constTensor(constInfo, constData); + + // Add the original pattern + IConnectableLayer* input = graph.AddLayer(0, "input"); + auto constant = graph.AddLayer("const"); + + constant->m_LayerOutput = std::make_shared(constTensor); + IConnectableLayer* mul = graph.AddLayer("mul"); + IConnectableLayer* output = graph.AddLayer(0, "output"); + + // Create connections between layers + input->GetOutputSlot(0).SetTensorInfo(inputInfo); + constant->GetOutputSlot(0).SetTensorInfo(constInfo); + mul->GetOutputSlot(0).SetTensorInfo(outputInfo); + + input->GetOutputSlot(0).Connect(mul->GetInputSlot(0)); + constant->GetOutputSlot(0).Connect(mul->GetInputSlot(1)); + mul->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + //Add in out of order + auto view = CreateSubgraphViewFrom({output, input, mul, constant}, + {}, + {}); + + SubgraphView workingCopy = view->GetWorkingCopy(); + + // Check the WorkingCopy is as expected before replacement + CHECK(workingCopy.GetIConnectableLayers().size() == 4); + int idx=0; + LayerType expectedSorted[] = {LayerType::Input, LayerType::Constant, LayerType::Multiplication, LayerType::Output}; + workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l) + { + CHECK((expectedSorted[idx] == l->GetType())); + idx++; + } + ); + + // Replace Multiplication and Constant with Depthwise + ReplaceUnsupportedLayers(workingCopy); + + // Check the layers are as expected + CHECK(workingCopy.GetIConnectableLayers().size() == 3); + idx=0; + LayerType expectedSortedReplaced[] = {LayerType::Input, LayerType::DepthwiseConvolution2d, LayerType::Output}; + workingCopy.ForEachIConnectableLayer([&idx, &expectedSortedReplaced](const IConnectableLayer* l) + { + CHECK((expectedSortedReplaced[idx] == l->GetType())); + idx++; + } + ); +} + +TEST_CASE("SubgraphViewWorkingCopySubstituteSubgraph") +{ + Graph graph; + + auto input = graph.AddLayer(0, "Input"); + auto activation = graph.AddLayer(ActivationDescriptor{}, "Activation"); + auto output = graph.AddLayer(1, "Output"); + + input->GetOutputSlot(0).Connect(activation->GetInputSlot(0)); + activation->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + //Add in out of order + auto view = CreateSubgraphViewFrom({output, input, activation}, + {}, + {}); + + // Check SubstituteSubgraphView throws when called on original SubgraphView + SubgraphView temp(input); + CHECK_THROWS_AS(view->SubstituteSubgraph(temp, input), NullPointerException); + + // Check that GetWorkingCopy() being called on a working copy throws an exception + auto workingCopy = view->GetWorkingCopy(); + CHECK_THROWS_AS(workingCopy.GetWorkingCopy(), Exception); +} + +TEST_CASE("SubgraphViewWorkingCopyOptimizationViews") +{ + Graph graph; + + const TensorInfo inputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0); + const TensorInfo constInfo({ 1, 1, 1, 16 }, DataType::QAsymmU8, 0.9f, 0, true); + const TensorInfo outputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0); + + std::vector constData(constInfo.GetNumElements(), 0); + std::iota(constData.begin(), constData.end(), 0); + ConstTensor constTensor(constInfo, constData); + + // Add the original pattern + IConnectableLayer* input = graph.AddLayer(0, "input"); + auto constant = graph.AddLayer("const"); + + constant->m_LayerOutput = std::make_shared(constTensor); + IConnectableLayer* mul = graph.AddLayer("mul"); + IConnectableLayer* output = graph.AddLayer(0, "output"); + + // Create connections between layers + input->GetOutputSlot(0).SetTensorInfo(inputInfo); + constant->GetOutputSlot(0).SetTensorInfo(constInfo); + mul->GetOutputSlot(0).SetTensorInfo(outputInfo); + + input->GetOutputSlot(0).Connect(mul->GetInputSlot(0)); + constant->GetOutputSlot(0).Connect(mul->GetInputSlot(1)); + mul->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + //Add in out of order + auto view = CreateSubgraphViewFrom({output, input, mul, constant}, + {}, + {}); + + SubgraphView workingCopy = view->GetWorkingCopy(); + + // Check the WorkingCopy is as expected before replacement + int idx=0; + LayerType expectedSorted[] = {LayerType::Input, LayerType::Constant, LayerType::Multiplication, LayerType::Output}; + workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l) + { + CHECK((expectedSorted[idx] == l->GetType())); + idx++; + } + ); + + // Replace Multiplication and Constant with Depthwise + ReplaceUnsupportedLayers(workingCopy); + + // Check the layers are as expected + idx=0; + LayerType expectedSortedReplaced[] = {LayerType::Input, LayerType::DepthwiseConvolution2d, LayerType::Output}; + workingCopy.ForEachIConnectableLayer([&idx, &expectedSortedReplaced](const IConnectableLayer* l) + { + CHECK((expectedSortedReplaced[idx] == l->GetType())); + idx++; + } + ); + + + // At this stage NPU would take the working copy and create CompiledBlocPtr with it. + + // We will just check that the procompiledLayer can still be added to the optimizationViews via a SubgraphView. + OptimizationViews optimizationViews; + + CompiledBlobPtr ptr; + IConnectableLayer* preCompiledLayer = optimizationViews.GetINetwork()->AddPrecompiledLayer( + PreCompiledDescriptor(view->GetNumInputSlots(), view->GetNumOutputSlots()), + std::move(ptr), + EmptyOptional(), + "pre-compiled"); + + + optimizationViews.AddSubstitution({ *view, SubgraphView(preCompiledLayer) }); + CHECK(optimizationViews.Validate(*view)); +} + +TEST_CASE("SubgraphViewWorkingCopyReplaceSlots") +{ + Graph graph; + const TensorInfo inputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0); + const TensorInfo constInfo({ 1, 1, 1, 16 }, DataType::QAsymmU8, 0.9f, 0, true); + const TensorInfo outputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0); + + std::vector constData(constInfo.GetNumElements(), 0); + std::iota(constData.begin(), constData.end(), 0); + ConstTensor constTensor(constInfo, constData); + + // Add the original pattern + IConnectableLayer* input = graph.AddLayer(0, "input"); + auto constant = graph.AddLayer("const"); + + constant->m_LayerOutput = std::make_shared(constTensor); + IConnectableLayer* mul = graph.AddLayer("mul"); + IConnectableLayer* output = graph.AddLayer(0, "output"); + + // Create connections between layers + input->GetOutputSlot(0).SetTensorInfo(inputInfo); + constant->GetOutputSlot(0).SetTensorInfo(constInfo); + mul->GetOutputSlot(0).SetTensorInfo(outputInfo); + + input->GetOutputSlot(0).Connect(mul->GetInputSlot(0)); + constant->GetOutputSlot(0).Connect(mul->GetInputSlot(1)); + mul->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + auto view = CreateSubgraphViewFrom({output, input, mul, constant}, + CreateIInputsFrom({mul}), + CreateIOutputsFrom({mul})); + + SubgraphView workingCopy = view->GetWorkingCopy(); + + // Check the WorkingCopy is as expected before replacement + CHECK(workingCopy.GetIConnectableLayers().size() == 4); + int idx=0; + LayerType expectedSorted[] = {LayerType::Input, LayerType::Constant, LayerType::Multiplication, LayerType::Output}; + workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l) + { + CHECK((expectedSorted[idx] == l->GetType())); + idx++; + } + ); + + // Replace Multiplication and Constant with Depthwise + ReplaceUnsupportedLayers(workingCopy); + + // Check the layers are as expected + idx=0; + LayerType expectedSortedReplaced[] = {LayerType::Input, LayerType::DepthwiseConvolution2d, LayerType::Output}; + CHECK(workingCopy.GetIConnectableLayers().size() == 3); + workingCopy.ForEachIConnectableLayer([&idx, &expectedSortedReplaced](const IConnectableLayer* l) + { + CHECK((expectedSortedReplaced[idx] == l->GetType())); + idx++; + } + ); +} + } -- cgit v1.2.1