aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrancis Murtagh <francis.murtagh@arm.com>2022-01-19 16:31:58 +0000
committerFrancis Murtagh <francis.murtagh@arm.com>2022-02-03 17:34:45 +0000
commit9d74ba6e85a043e9603445e062315f5c4965fbd6 (patch)
tree1997aac65827e64d4270149826ba92c6fa3e4d11
parent2e24175c683bca42496104591d6b702dad360b8e (diff)
downloadarmnn-9d74ba6e85a043e9603445e062315f5c4965fbd6.tar.gz
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 <francis.murtagh@arm.com> Change-Id: Iaaef14448d8b73867eaee9d69f4f98d5d1bf171c
-rw-r--r--include/armnn/INetwork.hpp1
-rw-r--r--include/armnn/backends/SubgraphView.hpp26
-rw-r--r--src/armnn/Graph.hpp2
-rw-r--r--src/armnn/Layer.cpp5
-rw-r--r--src/armnn/Layer.hpp2
-rw-r--r--src/armnn/Network.cpp2
-rw-r--r--src/armnn/SubgraphView.cpp233
-rw-r--r--src/armnn/test/SubgraphViewTests.cpp364
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<SubgraphViewWorkingCopy> 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<SubgraphViewWorkingCopy> 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<InputSlot*>(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<OutputSlot*>(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<SubgraphViewWorkingCopy> 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<Layer*>(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<const IConnectableLayer*, IConnectableLayer*> originalToClonedLayerMap;
+ std::list<armnn::IConnectableLayer*> originalSubgraphLayers = GetIConnectableLayers();
+
+ auto ptr = std::make_shared<SubgraphViewWorkingCopy>(std::move(newGraph));
+ SubgraphView::IInputSlots workingCopyInputs;
+
+ for (auto&& originalLayer : originalSubgraphLayers)
+ {
+ Layer* const layer = PolymorphicDowncast<const Layer*>(originalLayer)->Clone(ptr->m_Graph);
+ originalToClonedLayerMap.emplace(originalLayer, layer);
+ }
+
+ // Add IInputSlots to workingCopy
+ std::vector<const IConnectableLayer*> processed;
+ for (auto originalSubgraphInputSlot : GetIInputSlots())
+ {
+ const IConnectableLayer& originalSubgraphLayer =
+ PolymorphicDowncast<InputSlot*>(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<OutputSlot*>(
+ &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<Layer*>(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 <Graph.hpp>
#include <SubgraphViewSelector.hpp>
+#include <armnn/backends/OptimizationViews.hpp>
#include <armnn/backends/SubgraphView.hpp>
#include <armnn/backends/TensorHandle.hpp>
#include <armnn/utility/NumericCast.hpp>
@@ -87,7 +88,7 @@ SubgraphView::OutputSlots CreateOutputsFrom(const std::vector<Layer*>& layers)
SubgraphView::IOutputSlots CreateIOutputsFrom(const std::vector<armnn::IConnectableLayer*>& 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<ConcatLayer>(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<SplitterLayer>(splitDesc, name.c_str());
}
@@ -1891,4 +1892,361 @@ TEST_CASE("SubgraphOrder")
);
}
+TEST_CASE("SubgraphViewWorkingCopy")
+{
+ Graph graph;
+
+ auto input = graph.AddLayer<InputLayer>(0, "Input");
+ auto activation = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "Activation");
+ auto output = graph.AddLayer<OutputLayer>(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<const ConstantLayer>(constantLayer)
+ ->m_LayerOutput->GetConstTensor<void>();
+ 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<IConnectableLayer*>(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<uint8_t> constData(constInfo.GetNumElements(), 0);
+ std::iota(constData.begin(), constData.end(), 0);
+ ConstTensor constTensor(constInfo, constData);
+
+ // Add the original pattern
+ IConnectableLayer* input = graph.AddLayer<InputLayer>(0, "input");
+ auto constant = graph.AddLayer<ConstantLayer>("const");
+
+ constant->m_LayerOutput = std::make_shared<ScopedTensorHandle>(constTensor);
+ IConnectableLayer* mul = graph.AddLayer<MultiplicationLayer>("mul");
+ IConnectableLayer* output = graph.AddLayer<OutputLayer>(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<InputLayer>(0, "Input");
+ auto activation = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "Activation");
+ auto output = graph.AddLayer<OutputLayer>(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<uint8_t> constData(constInfo.GetNumElements(), 0);
+ std::iota(constData.begin(), constData.end(), 0);
+ ConstTensor constTensor(constInfo, constData);
+
+ // Add the original pattern
+ IConnectableLayer* input = graph.AddLayer<InputLayer>(0, "input");
+ auto constant = graph.AddLayer<ConstantLayer>("const");
+
+ constant->m_LayerOutput = std::make_shared<ScopedTensorHandle>(constTensor);
+ IConnectableLayer* mul = graph.AddLayer<MultiplicationLayer>("mul");
+ IConnectableLayer* output = graph.AddLayer<OutputLayer>(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<uint8_t> constData(constInfo.GetNumElements(), 0);
+ std::iota(constData.begin(), constData.end(), 0);
+ ConstTensor constTensor(constInfo, constData);
+
+ // Add the original pattern
+ IConnectableLayer* input = graph.AddLayer<InputLayer>(0, "input");
+ auto constant = graph.AddLayer<ConstantLayer>("const");
+
+ constant->m_LayerOutput = std::make_shared<ScopedTensorHandle>(constTensor);
+ IConnectableLayer* mul = graph.AddLayer<MultiplicationLayer>("mul");
+ IConnectableLayer* output = graph.AddLayer<OutputLayer>(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++;
+ }
+ );
+}
+
}