| // |
| // Copyright © 2017 Arm Ltd. All rights reserved. |
| // SPDX-License-Identifier: MIT |
| // |
| #include <boost/test/unit_test.hpp> |
| |
| #include <armnn/ArmNN.hpp> |
| |
| #include <Graph.hpp> |
| #include <SubgraphView.hpp> |
| #include <SubgraphViewSelector.hpp> |
| |
| #include <backendsCommon/CpuTensorHandle.hpp> |
| |
| using namespace armnn; |
| |
| namespace |
| { |
| |
| bool AreAnySubgraphLayersPresentInGraph(const SubgraphView::Layers &subgraphLayers, const Graph &graph) |
| { |
| for(auto&& layer : subgraphLayers) |
| { |
| auto posInGraph = std::find(graph.begin(), graph.end(), layer); |
| if(posInGraph != graph.end()) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // |
| // this helper only works if all layers where the inputs connect to are not selected |
| // |
| SubgraphView::InputSlots CreateInputsFrom(const std::vector<Layer*>& layers) |
| { |
| SubgraphView::InputSlots result; |
| for (auto&& layer : layers) |
| { |
| for (auto&& it = layer->BeginInputSlots(); it != layer->EndInputSlots(); ++it) |
| { |
| result.push_back(&(*it)); |
| } |
| } |
| return result; |
| } |
| |
| // |
| // this helper only works if all layers where the outputs connect to are not selected |
| // |
| SubgraphView::OutputSlots CreateOutputsFrom(const std::vector<Layer*>& layers) |
| { |
| SubgraphView::OutputSlots result; |
| for (auto && layer : layers) |
| { |
| for (auto&& it = layer->BeginOutputSlots(); it != layer->EndOutputSlots(); ++it) |
| { |
| result.push_back(&(*it)); |
| } |
| } |
| return result; |
| } |
| |
| // |
| // this takes the inputs, outputs and layers as a copy and the move these copies into the |
| // resulting subgraph, so the pass bay value is intentional |
| // |
| SubgraphViewSelector::SubgraphViewPtr CreateSubgraphViewFrom(Graph& graph, |
| SubgraphView::InputSlots&& inputs, |
| SubgraphView::OutputSlots&& outputs, |
| SubgraphView::Layers&& layers) |
| { |
| return std::make_unique<SubgraphView>(&graph, std::move(inputs), std::move(outputs), std::move(layers)); |
| } |
| |
| template <typename T, typename Iterator> |
| std::vector<T> ToSortedArray(Iterator begin, Iterator end) |
| { |
| std::vector<T> result(begin, end); |
| std::sort(result.begin(), result.end()); |
| return result; |
| } |
| |
| template <typename T> |
| void CompareVectors(const std::vector<T>& result, const std::vector<T>& expected) |
| { |
| BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); |
| } |
| |
| void CompareSubgraphViews(SubgraphViewSelector::SubgraphViewPtr& result, |
| SubgraphViewSelector::SubgraphViewPtr& expected) |
| { |
| // expect both to be valid subgraphs |
| BOOST_TEST((result.get() != nullptr)); |
| BOOST_TEST((expected.get() != nullptr)); |
| |
| if (result.get() != nullptr && expected.get() != nullptr) |
| { |
| // try to detect all other obvious errors too, mainly because here |
| // we can get a nicer error message from boost, the collection test |
| // also report error for these |
| BOOST_TEST(result->GetInputSlots().size() == expected->GetInputSlots().size()); |
| BOOST_TEST(result->GetOutputSlots().size() == expected->GetOutputSlots().size()); |
| BOOST_TEST(result->GetLayers().size() == expected->GetLayers().size()); |
| |
| auto resultLayers = ToSortedArray<Layer *>(result->GetLayers().begin(), |
| result->GetLayers().end()); |
| auto expectedLayers = ToSortedArray<Layer *>(expected->GetLayers().begin(), |
| expected->GetLayers().end()); |
| CompareVectors(resultLayers, expectedLayers); |
| |
| auto resultInputs = ToSortedArray<InputSlot *>(result->GetInputSlots().begin(), |
| result->GetInputSlots().end()); |
| auto expectedInputs = ToSortedArray<InputSlot *>(expected->GetInputSlots().begin(), |
| expected->GetInputSlots().end()); |
| CompareVectors(resultInputs, expectedInputs); |
| |
| auto resultOutputs = ToSortedArray<OutputSlot *>(result->GetOutputSlots().begin(), |
| result->GetOutputSlots().end()); |
| auto expectedOutputs = ToSortedArray<OutputSlot *>(expected->GetOutputSlots().begin(), |
| expected->GetOutputSlots().end()); |
| CompareVectors(resultOutputs, expectedOutputs); |
| } |
| } |
| |
| } // namespace <anonymous> |
| |
| BOOST_AUTO_TEST_SUITE(SubgraphSubstitution) |
| |
| BOOST_AUTO_TEST_CASE(SingleInputSingleOutput) |
| { |
| // Construct graph |
| Graph graph; |
| |
| Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input"); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| |
| Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output"); |
| |
| inputLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0)); |
| convLayer1->GetOutputSlot(0).Connect(convLayer2->GetInputSlot(0)); |
| convLayer2->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); |
| |
| // Construct sub-graph |
| SubgraphViewSelector::SubgraphViewPtr subgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({convLayer1}), |
| CreateOutputsFrom({convLayer2}), |
| {}); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraphInputConn = subgraph->GetInputSlot(0)->GetConnection(); |
| IInputSlot* subgraphOutputConn = subgraph->GetOutputSlot(0)->GetConnection(0); |
| |
| // Construct dummy pre-compiled layer |
| PreCompiledDescriptor preCompiledDescriptor(1, 1); |
| Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled"); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph(std::move(subgraph), preCompiledLayer); |
| |
| // Check that connections are correct after substitution |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn); |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn); |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultiInputSingleOutput) |
| { |
| // Construct graph |
| Graph graph; |
| |
| Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input"); |
| |
| ViewsDescriptor splitterDescriptor(2); |
| Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter"); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| |
| OriginsDescriptor mergerDescriptor(2); |
| Layer* const mergerLayer = graph.AddLayer<MergerLayer>(mergerDescriptor, "merger"); |
| |
| Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output"); |
| |
| inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0)); |
| convLayer1->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(0)); |
| convLayer2->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(1)); |
| mergerLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); |
| |
| // Construct sub-graph |
| SubgraphViewSelector::SubgraphViewPtr subgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({convLayer1, convLayer2}), |
| CreateOutputsFrom({mergerLayer}), |
| {}); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraphInputConn1 = subgraph->GetInputSlot(0)->GetConnection(); |
| IOutputSlot* subgraphInputConn2 = subgraph->GetInputSlot(1)->GetConnection(); |
| |
| IInputSlot* subgraphOutputConn = subgraph->GetOutputSlot(0)->GetConnection(0); |
| |
| // Construct dummy pre-compiled layer |
| PreCompiledDescriptor preCompiledDescriptor(2, 1); |
| Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled"); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph(std::move(subgraph), preCompiledLayer); |
| |
| // Check that connections are correct after substitution |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1); |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(1).GetConnection(), subgraphInputConn2); |
| |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn); |
| } |
| |
| BOOST_AUTO_TEST_CASE(SingleInputMultiOutput) |
| { |
| // Construct graph |
| Graph graph; |
| |
| Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input"); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| OriginsDescriptor mergerDescriptor(2); |
| Layer* const mergerLayer = graph.AddLayer<MergerLayer>(mergerDescriptor, "merger"); |
| Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output"); |
| |
| ViewsDescriptor splitterDescriptor(2); |
| Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter"); |
| |
| inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0)); |
| convLayer1->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(0)); |
| convLayer2->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(1)); |
| mergerLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); |
| |
| // Construct sub-graph |
| SubgraphViewSelector::SubgraphViewPtr subgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({splitterLayer}), |
| CreateOutputsFrom({convLayer1, convLayer2}), |
| {}); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraphInputConn1 = subgraph->GetInputSlot(0)->GetConnection(); |
| |
| IInputSlot* subgraphOutputConn1 = subgraph->GetOutputSlot(0)->GetConnection(0); |
| IInputSlot* subgraphOutputConn2 = subgraph->GetOutputSlot(1)->GetConnection(0); |
| |
| // Construct dummy pre-compiled layer |
| PreCompiledDescriptor preCompiledDescriptor(1, 2); |
| Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled"); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph(std::move(subgraph), preCompiledLayer); |
| |
| // Check that connections are correct after substitution |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1); |
| |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn1); |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(1).GetConnection(0), subgraphOutputConn2); |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultiInputMultiOutput) |
| { |
| // Construct graph |
| Graph graph; |
| |
| Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input"); |
| |
| ViewsDescriptor splitterDescriptor(2); |
| Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter"); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| |
| OriginsDescriptor mergerDescriptor(2); |
| Layer* const mergerLayer = graph.AddLayer<MergerLayer>(mergerDescriptor, "merger"); |
| |
| Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output"); |
| |
| inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0)); |
| convLayer1->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(0)); |
| convLayer2->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(1)); |
| mergerLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); |
| |
| // Construct sub-graph |
| SubgraphViewSelector::SubgraphViewPtr subgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({convLayer1, convLayer2}), |
| CreateOutputsFrom({convLayer1, convLayer2}), |
| {}); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraphInputConn1 = subgraph->GetInputSlot(0)->GetConnection(); |
| IOutputSlot* subgraphInputConn2 = subgraph->GetInputSlot(1)->GetConnection(); |
| |
| IInputSlot* subgraphOutputConn1 = subgraph->GetOutputSlot(0)->GetConnection(0); |
| IInputSlot* subgraphOutputConn2 = subgraph->GetOutputSlot(1)->GetConnection(0); |
| |
| // Construct dummy pre-compiled layer |
| PreCompiledDescriptor preCompiledDescriptor(2, 2); |
| Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled"); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph(std::move(subgraph), preCompiledLayer); |
| |
| // Check that connections are correct after substitution |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1); |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(1).GetConnection(), subgraphInputConn2); |
| |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn1); |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(1).GetConnection(0), subgraphOutputConn2); |
| } |
| |
| BOOST_AUTO_TEST_CASE(EraseReplacedLayers) |
| { |
| // Construct graph |
| Graph graph; |
| |
| graph.AddLayer<InputLayer>(0, "input"); |
| |
| ViewsDescriptor splitterDescriptor(2); |
| Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter"); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| |
| OriginsDescriptor mergerDescriptor(2); |
| Layer* const mergerLayer = graph.AddLayer<MergerLayer>(mergerDescriptor, "merger"); |
| |
| graph.AddLayer<OutputLayer>(0, "output"); |
| |
| // Construct sub-graph |
| SubgraphViewSelector::SubgraphViewPtr subgraph = CreateSubgraphViewFrom(graph, |
| {}, |
| {}, |
| {splitterLayer, |
| convLayer1, |
| convLayer2, |
| mergerLayer}); |
| |
| // Construct dummy pre-compiled layer |
| PreCompiledDescriptor preCompiledDescriptor(0, 0); |
| Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled"); |
| |
| // Save sub-graph layers for later verification |
| const SubgraphView::Layers subgraphLayers = subgraph->GetLayers(); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph(std::move(subgraph), preCompiledLayer); |
| |
| // Check that the layers belonging to the sub-graph have been erased from the graph after substitution |
| BOOST_CHECK(!AreAnySubgraphLayersPresentInGraph(subgraphLayers, graph)); |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() |
| |
| BOOST_AUTO_TEST_SUITE(SubgraphSelection) |
| |
| BOOST_AUTO_TEST_CASE(SubgraphForEmptyGraph) |
| { |
| Graph graph; |
| SubgraphView subgraph(graph); |
| |
| BOOST_TEST(subgraph.GetInputSlots().empty()); |
| BOOST_TEST(subgraph.GetOutputSlots().empty()); |
| BOOST_TEST(subgraph.GetLayers().empty()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(SubgraphForEntireGraph) |
| { |
| Graph graph; |
| |
| auto output = graph.AddLayer<OutputLayer>(0, "output"); |
| auto mid0 = graph.InsertNewLayer<ActivationLayer>(output->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "mid0"); |
| auto mid1 = graph.InsertNewLayer<ActivationLayer>(mid0->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "mid1"); |
| graph.InsertNewLayer<InputLayer>(mid1->GetInputSlot(0), 0, "input"); |
| |
| SubgraphView subgraph(graph); |
| |
| BOOST_TEST(subgraph.GetInputSlots().empty()); |
| BOOST_TEST(subgraph.GetOutputSlots().empty()); |
| BOOST_TEST(subgraph.GetLayers().size() == graph.GetNumLayers()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(NoSubgraphsForNoMatch) |
| { |
| Graph graph; |
| |
| auto output = graph.AddLayer<OutputLayer>(0, "output"); |
| graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input"); |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs(graph, [](const Layer &) { return false; }); |
| |
| BOOST_TEST(subgraphs.empty()); |
| } |
| |
| BOOST_AUTO_TEST_CASE(OneSubgraphsSelectedASingleMatch) |
| { |
| Graph graph; |
| |
| auto output = graph.AddLayer<OutputLayer>(0, "output"); |
| graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input"); |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the output layer only |
| [](const Layer & l) |
| { |
| bool isOutput = l.GetNameStr().compare("output") == 0; |
| return isOutput; |
| }); |
| |
| BOOST_TEST(subgraphs.size() == 1); |
| if (subgraphs.size() == 1) |
| { |
| auto expected = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({output}), |
| // outputs of 'output' will be empty |
| CreateOutputsFrom({output}), |
| {output}); |
| |
| CompareSubgraphViews(subgraphs[0], expected); |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultipleLayersSelectedInTheMiddle) |
| { |
| Graph graph; |
| |
| auto output = graph.AddLayer<OutputLayer>(0, "output"); |
| auto mid0 = graph.InsertNewLayer<ActivationLayer>(output->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "mid0"); |
| auto mid1 = graph.InsertNewLayer<ActivationLayer>(mid0->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "mid1"); |
| graph.InsertNewLayer<InputLayer>(mid1->GetInputSlot(0), 0, "input"); |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the middle layers only |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation); |
| return toSelect; |
| }); |
| |
| BOOST_TEST(subgraphs.size() == 1); |
| if (subgraphs.size() == 1) |
| { |
| auto expected = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({mid1}), |
| CreateOutputsFrom({mid0}), |
| {mid1, mid0}); |
| |
| CompareSubgraphViews(subgraphs[0], expected); |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(IslandInTheMiddle) |
| { |
| // This case represent the scenario when a non-selected X1 node placed in the middle |
| // of the selected M* nodes: |
| // |
| // X0 -> M1 -> M2 -> M3 -> X2 |
| // X0 -> M4 -> X1 -> M5 -> X2 |
| // |
| /* |
| X0 |
| / \ |
| M1 M4 |
| | | |
| M2 X1 < the island in the middle ! |
| | | |
| M3 M5 |
| \ / |
| X2 |
| */ |
| // The expected result for this is that M1,M2,M3,M4 will be part of one subgraph and |
| // M5 will be part of another subgraph and the input and output slots in the subgraphs |
| // will be set accordingly. |
| // |
| Graph graph; |
| |
| OriginsDescriptor mergerDescriptor(2); |
| auto x2 = graph.AddLayer<MergerLayer>(mergerDescriptor, "x2"); |
| auto m3 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m3"); |
| auto m2 = graph.InsertNewLayer<ActivationLayer>(m3->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m2"); |
| auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m1"); |
| auto x0 = graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x0"); |
| |
| auto m5 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(1), |
| ActivationDescriptor{}, |
| "m5"); |
| auto x1 = graph.InsertNewLayer<Convolution2dLayer>(m5->GetInputSlot(0), |
| Convolution2dDescriptor{}, |
| "x1"); |
| auto m4 = graph.InsertNewLayer<ActivationLayer>(x1->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m4"); |
| |
| // Connect the other branch to the input layer |
| x0->GetOutputSlot(0).Connect(m4->GetInputSlot(0)); |
| |
| // All selected 'M*' layers will be of Activation type |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the middle layers only |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation); |
| return toSelect; |
| }); |
| |
| // expected results to test against |
| auto largerSubgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({m1, m4}), |
| CreateOutputsFrom({m3, m4}), |
| {m1, m4, m2, m3}); |
| |
| auto smallerSubgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({m5}), |
| CreateOutputsFrom({m5}), |
| {m5}); |
| |
| BOOST_TEST(subgraphs.size() == 2); |
| if (subgraphs.size() == 2) |
| { |
| // we need to have valid subgraph pointers here |
| BOOST_TEST((subgraphs[0] != nullptr)); |
| BOOST_TEST((subgraphs[1] != nullptr)); |
| |
| if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr) |
| { |
| // sort the subgraphs by layer size, so it is simpler to test |
| std::sort(subgraphs.begin(), subgraphs.end(), |
| [](SubgraphViewSelector::SubgraphViewPtr & lhs, SubgraphViewSelector::SubgraphViewPtr & rhs) |
| { |
| return (lhs->GetLayers().size() < rhs->GetLayers().size()); |
| } |
| ); |
| |
| // one subgraph needs to be size=1 and the other one is 4 |
| BOOST_TEST(subgraphs[0]->GetLayers().size() == 1); |
| BOOST_TEST(subgraphs[1]->GetLayers().size() == 4); |
| |
| CompareSubgraphViews(subgraphs[0], smallerSubgraph); |
| CompareSubgraphViews(subgraphs[1], largerSubgraph); |
| } |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultipleSimpleSubgraphs) |
| { |
| // This test case represents the scenario when we have two distinct subgraphs |
| // in a simple linear network. The selected nodes are the M* and the |
| // non-selected ones are the X* |
| // |
| // X1 -> M1 -> M2 -> X2 -> M3 -> X3 |
| // |
| // The expected results is two subgraphs, one with {M1, M2} and another one |
| // with {M3} |
| // |
| Graph graph; |
| |
| // the graph is constructed in reverse order |
| auto x3 = graph.AddLayer<OutputLayer>(0, "output"); |
| auto m3 = graph.InsertNewLayer<ActivationLayer>(x3->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m3"); |
| auto x2 = graph.InsertNewLayer<Convolution2dLayer>(m3->GetInputSlot(0), |
| Convolution2dDescriptor{}, |
| "x2"); |
| auto m2 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m2"); |
| auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0), |
| ActivationDescriptor{}, |
| "m1"); |
| graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x1"); |
| |
| // All selected 'M*' layers will be of Activation type |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the middle layers only |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation); |
| return toSelect; |
| }); |
| |
| // expected results to test against |
| auto largerSubgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({m1}), |
| CreateOutputsFrom({m2}), |
| {m1, m2}); |
| |
| auto smallerSubgraph = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({m3}), |
| CreateOutputsFrom({m3}), |
| {m3}); |
| |
| BOOST_TEST(subgraphs.size() == 2); |
| if (subgraphs.size() == 2) |
| { |
| // we need to have valid subgraph pointers here |
| BOOST_TEST((subgraphs[0] != nullptr)); |
| BOOST_TEST((subgraphs[1] != nullptr)); |
| |
| if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr) |
| { |
| // sort the subgraphs by layer size, so it is simpler to test |
| std::sort(subgraphs.begin(), subgraphs.end(), |
| [](SubgraphViewSelector::SubgraphViewPtr & lhs, SubgraphViewSelector::SubgraphViewPtr & rhs) |
| { |
| return (lhs->GetLayers().size() < rhs->GetLayers().size()); |
| } |
| ); |
| |
| BOOST_TEST(subgraphs[0]->GetLayers().size() == 1); |
| BOOST_TEST(subgraphs[1]->GetLayers().size() == 2); |
| |
| CompareSubgraphViews(subgraphs[0], smallerSubgraph); |
| CompareSubgraphViews(subgraphs[1], largerSubgraph); |
| } |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(SimpleLinearTest) |
| { |
| //X1 -> M1 -> M2 -> X2 |
| //Where the input slots of M1 and the output slots of M2 are to be the sub graph boundaries. |
| Graph graph; |
| |
| ActivationDescriptor activationDefaults; |
| |
| auto layerX1 = graph.AddLayer<InputLayer>(0, "layerX1"); |
| auto layerX2 = graph.AddLayer<OutputLayer>(0, "layerX2"); |
| auto layerM1 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM1"); |
| auto layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2"); |
| |
| // X1 |
| // | |
| // M1 |
| // | |
| // M2 |
| // | |
| // X2 |
| |
| layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0)); |
| layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0)); |
| layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0)); |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the activation layers M1 and M2 |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation); |
| return toSelect; |
| }); |
| |
| BOOST_CHECK(subgraphs.size() == 1); |
| if(subgraphs.size() == 1) |
| { |
| auto expected = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({layerM1}), |
| CreateOutputsFrom({layerM2}), |
| {layerM1, layerM2}); |
| |
| CompareSubgraphViews(subgraphs[0], expected); |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultiInputSingleOutput) |
| { |
| //X1 -> M1 -> M3 -> X3 |
| //X2 -> M2 -> M3 -> X3 |
| //Where the input slots of {M1, M2} and the output slots of M3 are to be the subgraph boundaries. |
| Graph graph; |
| |
| ActivationDescriptor activationDefaults; |
| |
| auto layerX1 = graph.AddLayer<InputLayer>(0, "layerX1"); |
| auto layerX2 = graph.AddLayer<InputLayer>(1, "layerX2"); |
| auto layerM1 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM1"); |
| auto layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2"); |
| auto layerM3 = graph.AddLayer<AdditionLayer>("layerM3"); |
| auto layerX3 = graph.AddLayer<OutputLayer>(0, "layerX3"); |
| |
| // X1 X2 |
| // | | |
| // M1 M2 |
| // \ | |
| // \ | |
| // \| |
| // M3 |
| // | |
| // | |
| // X3 |
| |
| layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0)); |
| layerX2->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0)); |
| layerM1->GetOutputSlot(0).Connect(layerM3->GetInputSlot(0)); |
| layerM2->GetOutputSlot(0).Connect(layerM3->GetInputSlot(1)); |
| layerM3->GetOutputSlot(0).Connect(layerX3->GetInputSlot(0)); |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select Activation and Addition Layers M1, M2 and M3 |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation |
| || l.GetType() == LayerType::Addition); |
| return toSelect; |
| }); |
| |
| BOOST_CHECK(subgraphs.size() == 1); |
| if (subgraphs.size() == 1) |
| { |
| auto expected = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({layerM1, layerM2}), |
| CreateOutputsFrom({layerM3}), |
| {layerM1, layerM2, layerM3}); |
| |
| CompareSubgraphViews(subgraphs[0], expected); |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(SingleInputMultiOutput) |
| { |
| //X1 -> M1 -> M2 -> X2 |
| //X1 -> M1 -> M3 -> X3 |
| //Where the input slots of M1 and the output slots of {M2, M3} are to be the subgraph boundaries. |
| Graph graph; |
| |
| ActivationDescriptor activationDefaults; |
| ViewsDescriptor viewDefaults(2,4); |
| |
| Layer* layerX1 = graph.AddLayer<InputLayer>(0, "layerX1"); |
| Layer* layerM1 = graph.AddLayer<SplitterLayer>(viewDefaults, "layerM1"); |
| Layer* layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2"); |
| Layer* layerM3 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM3"); |
| Layer* layerX2 = graph.AddLayer<OutputLayer>(0, "layerX2"); |
| Layer* layerX3 = graph.AddLayer<OutputLayer>(1, "layerX3"); |
| |
| // X2 |
| // | |
| // M1 |
| // /| |
| // / | |
| // / | |
| // M2 M3 |
| // | | |
| // | | |
| // X2 X3 |
| |
| layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0)); |
| layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0)); |
| layerM1->GetOutputSlot(1).Connect(layerM3->GetInputSlot(0)); |
| layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0)); |
| layerM3->GetOutputSlot(0).Connect(layerX3->GetInputSlot(0)); |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select Activation and Splitter Layers M1, M2 and M3 |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation |
| || l.GetType() == LayerType::Splitter); |
| return toSelect; |
| }); |
| |
| BOOST_CHECK(subgraphs.size() == 1); |
| if(subgraphs.size() == 1) |
| { |
| auto expected = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({layerM1}), |
| CreateOutputsFrom({layerM2, layerM3}), |
| {layerM1, layerM2, layerM3}); |
| |
| CompareSubgraphViews(subgraphs[0], expected); |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultiInputMultiOutput) |
| { |
| // This case represents the scenario with multiple inputs and multiple outputs |
| // |
| // X1 -> M1 -> M3 -> M4 -> X3 |
| // X2 -> M2 -> M3 -> M5 -> X4 |
| // |
| // Where the input slots of {M1, M2} and the output slots of {M4, M5} are to be the subgraph |
| // boundaries. |
| |
| Graph graph; |
| |
| ActivationDescriptor activationDefaults; |
| OriginsDescriptor mergerDescriptor(2); |
| |
| auto x1 = graph.AddLayer<InputLayer>(0, "x1"); |
| auto x2 = graph.AddLayer<InputLayer>(1, "x2"); |
| |
| auto m1 = graph.AddLayer<ActivationLayer>(activationDefaults, "m1"); |
| auto m2 = graph.AddLayer<ActivationLayer>(activationDefaults, "m2"); |
| auto m3 = graph.AddLayer<MergerLayer>(mergerDescriptor, "m3"); |
| |
| auto m4 = graph.AddLayer<ActivationLayer>(activationDefaults, "m4"); |
| auto m5 = graph.AddLayer<ActivationLayer>(activationDefaults, "m5"); |
| |
| auto x3 = graph.AddLayer<OutputLayer>(0, "x3"); |
| auto x4 = graph.AddLayer<OutputLayer>(1, "x4"); |
| |
| x1->GetOutputSlot(0).Connect(m1->GetInputSlot(0)); |
| x2->GetOutputSlot(0).Connect(m2->GetInputSlot(0)); |
| |
| m1->GetOutputSlot(0).Connect(m3->GetInputSlot(0)); |
| m2->GetOutputSlot(0).Connect(m3->GetInputSlot(1)); |
| |
| m3->GetOutputSlot(0).Connect(m4->GetInputSlot(0)); |
| m3->GetOutputSlot(0).Connect(m5->GetInputSlot(0)); |
| |
| m4->GetOutputSlot(0).Connect(x3->GetInputSlot(0)); |
| m5->GetOutputSlot(0).Connect(x4->GetInputSlot(0)); |
| |
| |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select Activation and Merger Layers M1, M2, M3, M4, M5 |
| [](const Layer & l) |
| { |
| bool toSelect = (l.GetType() == LayerType::Activation |
| || l.GetType() == LayerType::Merger); |
| return toSelect; |
| }); |
| |
| |
| BOOST_CHECK(subgraphs.size() == 1); |
| if (subgraphs.size() == 1) |
| { |
| auto expected = CreateSubgraphViewFrom(graph, |
| CreateInputsFrom({m1, m2}), |
| CreateOutputsFrom({m4, m5}), |
| {m1, m2, m3, m4, m5}); |
| |
| CompareSubgraphViews(subgraphs[0], expected); |
| } |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() |
| |
| BOOST_AUTO_TEST_SUITE(IntegrationTests) |
| |
| BOOST_AUTO_TEST_CASE(SingleSubgraph) |
| { |
| // This test case represents the scenario when we have one subgraph |
| // in which two layers have GpuAcc backend assigned |
| |
| //Construct graph |
| Graph graph; |
| |
| Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input"); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| convLayer1->SetBackendId(Compute::GpuAcc); |
| |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| convLayer2->SetBackendId(Compute::GpuAcc); |
| |
| Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output"); |
| |
| inputLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0)); |
| convLayer1->GetOutputSlot(0).Connect(convLayer2->GetInputSlot(0)); |
| convLayer2->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); |
| |
| // GpuAcc sub graph selector |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the GpuAcc layers only |
| [](const Layer & l){ |
| bool toSelect = (l.GetBackendId() == Compute::GpuAcc); |
| return toSelect; |
| }); |
| |
| BOOST_TEST(subgraphs.size() == 1); |
| if(subgraphs.size() == 1) |
| { |
| BOOST_TEST((subgraphs[0] != nullptr)); |
| |
| if (subgraphs[0].get() != nullptr) |
| { |
| unsigned int numInputSlots = boost::numeric_cast<unsigned int>(subgraphs[0]->GetInputSlots().size()); |
| unsigned int numOutputSlots = boost::numeric_cast<unsigned int>(subgraphs[0]->GetOutputSlots().size()); |
| |
| BOOST_TEST((numInputSlots == 1)); |
| BOOST_TEST((numOutputSlots == 1)); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraphInputConn1 = subgraphs[0]->GetInputSlot(0)->GetConnection(); |
| IInputSlot* subgraphOutputConn1 = subgraphs[0]->GetOutputSlot(0)->GetConnection(0); |
| |
| // Construct dummy pre-compiled layer |
| PreCompiledDescriptor preCompiledDescriptor(numInputSlots, numOutputSlots); |
| Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled"); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph((std::move(subgraphs[0])), preCompiledLayer); |
| |
| // Check that connections are correct after substitution |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1); |
| |
| BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn1); |
| } |
| } |
| } |
| |
| BOOST_AUTO_TEST_CASE(MultipleSubgraphs) |
| { |
| // This test case represents the scenario when we have two subgraphs |
| // in which two layers have CpuAcc backend assigned |
| |
| //Construct graph |
| Graph graph; |
| |
| Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input"); |
| |
| ViewsDescriptor splitterDescriptor(2); |
| Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter"); |
| splitterLayer->SetBackendId(Compute::CpuAcc); |
| |
| Convolution2dDescriptor convDescriptor; |
| Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1"); |
| Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2"); |
| |
| OriginsDescriptor mergerDescriptor(2); |
| Layer* const mergerLayer = graph.AddLayer<MergerLayer>(mergerDescriptor, "merger"); |
| mergerLayer->SetBackendId(Compute::CpuAcc); |
| |
| Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output"); |
| |
| inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0)); |
| splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0)); |
| convLayer1->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(0)); |
| convLayer2->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(1)); |
| mergerLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); |
| |
| // CpuAcc sub graph selector |
| SubgraphViewSelector::Subgraphs subgraphs = |
| SubgraphViewSelector::SelectSubgraphs( |
| graph, |
| // select the CpuAcc layers only |
| [](const Layer & l){ |
| bool toSelect = (l.GetBackendId() == Compute::CpuAcc); |
| return toSelect; |
| }); |
| |
| BOOST_TEST(subgraphs.size() == 2); |
| if(subgraphs.size() == 2) |
| { |
| BOOST_TEST((subgraphs[0] != nullptr)); |
| BOOST_TEST((subgraphs[1] != nullptr)); |
| |
| if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr) |
| { |
| //Sort subgraphs by their inputSlot size. |
| std::sort(subgraphs.begin(), subgraphs.end(), |
| [](SubgraphViewSelector::SubgraphViewPtr & lhs, SubgraphViewSelector::SubgraphViewPtr & rhs) |
| { |
| return (lhs->GetInputSlots().size() < rhs->GetInputSlots().size()); |
| } |
| ); |
| |
| unsigned int numInputSlots1 = boost::numeric_cast<unsigned int>(subgraphs[0]->GetInputSlots().size()); |
| unsigned int numOutputSlots1 = boost::numeric_cast<unsigned int>(subgraphs[0]->GetOutputSlots().size()); |
| |
| unsigned int numInputSlots2 = boost::numeric_cast<unsigned int>(subgraphs[1]->GetInputSlots().size()); |
| unsigned int numOutputSlots2 = boost::numeric_cast<unsigned int>(subgraphs[1]->GetOutputSlots().size()); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraph1InputConn = subgraphs[0]->GetInputSlot(0)->GetConnection(); |
| IInputSlot* subgraph1OutputConn1 = subgraphs[0]->GetOutputSlot(0)->GetConnection(0); |
| IInputSlot* subgraph1OutputConn2 = subgraphs[0]->GetOutputSlot(1)->GetConnection(0); |
| |
| // Save sub-graph connections for comparison after substitution |
| IOutputSlot* subgraph2InputConn1 = subgraphs[1]->GetInputSlot(0)->GetConnection(); |
| IOutputSlot* subgraph2InputConn2 = subgraphs[1]->GetInputSlot(1)->GetConnection(); |
| IInputSlot* subgraph2OutputConn = subgraphs[1]->GetOutputSlot(0)->GetConnection(0); |
| |
| PreCompiledDescriptor preCompiledDescriptor1(numInputSlots1, numOutputSlots1); |
| Layer* const preCompiledLayer1 = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor1, "pre-compiled1"); |
| |
| PreCompiledDescriptor preCompiledDescriptor2(numInputSlots2, numOutputSlots2); |
| Layer* const preCompiledLayer2 = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor2, "pre-compiled2"); |
| |
| // Substitute sub-graph with pre-compiled layer |
| graph.SubstituteSubgraph((std::move(subgraphs[0])), preCompiledLayer1); |
| graph.SubstituteSubgraph((std::move(subgraphs[1])), preCompiledLayer2); |
| |
| // Check that connections are correct after substitution |
| BOOST_CHECK_EQUAL(preCompiledLayer1->GetInputSlot(0).GetConnection(), subgraph1InputConn); |
| BOOST_CHECK_EQUAL(preCompiledLayer1->GetOutputSlot(0).GetConnection(0), subgraph1OutputConn1); |
| BOOST_CHECK_EQUAL(preCompiledLayer1->GetOutputSlot(1).GetConnection(0), subgraph1OutputConn2); |
| |
| BOOST_CHECK_EQUAL(preCompiledLayer2->GetInputSlot(0).GetConnection(), subgraph2InputConn1); |
| BOOST_CHECK_EQUAL(preCompiledLayer2->GetInputSlot(1).GetConnection(), subgraph2InputConn2); |
| BOOST_CHECK_EQUAL(preCompiledLayer2->GetOutputSlot(0).GetConnection(0), subgraph2OutputConn); |
| } |
| } |
| } |
| |
| BOOST_AUTO_TEST_SUITE_END() |