2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
5 #include <boost/test/unit_test.hpp>
7 #include <armnn/ArmNN.hpp>
10 #include <SubGraph.hpp>
11 #include <SubGraphSelector.hpp>
13 #include <backendsCommon/CpuTensorHandle.hpp>
15 using namespace armnn;
21 // this helper only works if all layers where the inputs connect to are not selected
23 SubGraph::InputSlots CreateInputsFrom(const std::vector<Layer *> & layers)
25 SubGraph::InputSlots result;
26 for (auto&& layer : layers)
28 for (auto&& it = layer->BeginInputSlots(); it != layer->EndInputSlots(); ++it)
30 result.push_back(&(*it));
37 // this helper only works if all layers where the outputs connect to are not selected
39 SubGraph::OutputSlots CreateOutputsFrom(const std::vector<Layer *> & layers)
41 SubGraph::OutputSlots result;
42 for (auto && layer : layers)
44 for (auto&& it = layer->BeginOutputSlots(); it != layer->EndOutputSlots(); ++it)
46 result.push_back(&(*it));
53 // this takes the inputs, outputs and layers as a copy and the move these copies into the
54 // resulting subgraph, so the pass bay value is intentional
56 SubGraphSelector::SubGraphPtr CreateSubGraphFrom(SubGraph::InputSlots inputs,
57 SubGraph::OutputSlots outputs,
58 SubGraph::Layers layers)
60 return std::make_unique<SubGraph>(std::move(inputs), std::move(outputs), std::move(layers));
63 template <typename T, typename Iterator>
64 std::vector<T> ToSortedArray(Iterator begin, Iterator end)
66 std::vector<T> result(begin, end);
67 std::sort(result.begin(), result.end());
72 void CompareVectors(const std::vector<T> & result, const std::vector<T> & expected)
74 BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
77 void CompareSubGraphs(SubGraphSelector::SubGraphPtr & result,
78 SubGraphSelector::SubGraphPtr & expected)
80 // expect both to be valid subgraphs
81 BOOST_TEST((result.get() != nullptr));
82 BOOST_TEST((expected.get() != nullptr));
84 if (result.get() != nullptr && expected.get() != nullptr)
86 // try to detect all other obvious errors too, mainly because here
87 // we can get a nicer error message from boost, the collection test
88 // also report error for these
89 BOOST_TEST(result->GetInputSlots().size() == expected->GetInputSlots().size());
90 BOOST_TEST(result->GetOutputSlots().size() == expected->GetOutputSlots().size());
91 BOOST_TEST(result->GetLayers().size() == expected->GetLayers().size());
93 auto resultLayers = ToSortedArray<Layer *>(result->GetLayers().begin(),
94 result->GetLayers().end());
95 auto expectedLayers = ToSortedArray<Layer *>(expected->GetLayers().begin(),
96 expected->GetLayers().end());
97 CompareVectors(resultLayers, expectedLayers);
99 auto resultInputs = ToSortedArray<InputSlot *>(result->GetInputSlots().begin(),
100 result->GetInputSlots().end());
101 auto expectedInputs = ToSortedArray<InputSlot *>(expected->GetInputSlots().begin(),
102 expected->GetInputSlots().end());
103 CompareVectors(resultInputs, expectedInputs);
105 auto resultOutputs = ToSortedArray<OutputSlot *>(result->GetOutputSlots().begin(),
106 result->GetOutputSlots().end());
107 auto expectedOutputs = ToSortedArray<OutputSlot *>(expected->GetOutputSlots().begin(),
108 expected->GetOutputSlots().end());
109 CompareVectors(resultOutputs, expectedOutputs);
113 } // namespace <anonymous>
115 BOOST_AUTO_TEST_SUITE(SubGraphSelection)
117 BOOST_AUTO_TEST_CASE(NoSubGraphsForNoMatch)
121 auto output = graph.AddLayer<OutputLayer>(0, "output");
122 graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input");
124 SubGraphSelector::SubGraphs subGraphs =
125 SubGraphSelector::SelectSubGraphs(graph, [](const Layer &) { return false; });
127 BOOST_TEST(subGraphs.empty());
130 BOOST_AUTO_TEST_CASE(OneSubGraphsSelectedASingleMatch)
134 auto output = graph.AddLayer<OutputLayer>(0, "output");
135 graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input");
137 SubGraphSelector::SubGraphs subGraphs =
138 SubGraphSelector::SelectSubGraphs(
140 // select the output layer only
143 bool isOutput = l.GetNameStr().compare("output") == 0;
147 BOOST_TEST(subGraphs.size() == 1);
148 if (subGraphs.size() == 1)
150 auto expected = CreateSubGraphFrom(CreateInputsFrom({output}),
151 // outputs of 'output' will be empty
152 CreateOutputsFrom({output}),
155 CompareSubGraphs(subGraphs[0], expected);
159 BOOST_AUTO_TEST_CASE(MultipleLayersSelectedInTheMiddle)
163 auto output = graph.AddLayer<OutputLayer>(0, "output");
164 auto mid0 = graph.InsertNewLayer<ActivationLayer>(output->GetInputSlot(0),
165 ActivationDescriptor{},
167 auto mid1 = graph.InsertNewLayer<ActivationLayer>(mid0->GetInputSlot(0),
168 ActivationDescriptor{},
170 graph.InsertNewLayer<InputLayer>(mid1->GetInputSlot(0), 0, "input");
172 SubGraphSelector::SubGraphs subGraphs =
173 SubGraphSelector::SelectSubGraphs(
175 // select the middle layers only
178 bool toSelect = (l.GetType() == LayerType::Activation);
182 BOOST_TEST(subGraphs.size() == 1);
183 if (subGraphs.size() == 1)
185 auto expected = CreateSubGraphFrom(CreateInputsFrom({mid1}),
186 CreateOutputsFrom({mid0}),
189 CompareSubGraphs(subGraphs[0], expected);
193 BOOST_AUTO_TEST_CASE(IslandInTheMiddle)
195 // This case represent the scenario when a non-selected X1 node placed in the middle
196 // of the selected M* nodes:
198 // X0 -> M1 -> M2 -> M3 -> X2
199 // X0 -> M4 -> X1 -> M5 -> X2
206 M2 X1 < the island in the middle !
212 // The expected result for this is that M1,M2,M3,M4 will be part of one subgraph and
213 // M5 will be part of another subgraph and the input and output slots in the subgraphs
214 // will be set accordingly.
218 OriginsDescriptor mergerDescriptor(2);
219 auto x2 = graph.AddLayer<MergerLayer>(mergerDescriptor, "x2");
220 auto m3 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0),
221 ActivationDescriptor{},
223 auto m2 = graph.InsertNewLayer<ActivationLayer>(m3->GetInputSlot(0),
224 ActivationDescriptor{},
226 auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
227 ActivationDescriptor{},
229 auto x0 = graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x0");
231 auto m5 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(1),
232 ActivationDescriptor{},
234 auto x1 = graph.InsertNewLayer<Convolution2dLayer>(m5->GetInputSlot(0),
235 Convolution2dDescriptor{},
237 auto m4 = graph.InsertNewLayer<ActivationLayer>(x1->GetInputSlot(0),
238 ActivationDescriptor{},
241 // Connect the other branch to the input layer
242 x0->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
244 // All selected 'M*' layers will be of Activation type
245 SubGraphSelector::SubGraphs subGraphs =
246 SubGraphSelector::SelectSubGraphs(
248 // select the middle layers only
251 bool toSelect = (l.GetType() == LayerType::Activation);
255 // expected results to test against
256 auto largerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m1, m4}),
257 CreateOutputsFrom({m3, m4}),
260 auto smallerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m5}),
261 CreateOutputsFrom({m5}),
264 BOOST_TEST(subGraphs.size() == 2);
265 if (subGraphs.size() == 2)
267 // we need to have valid subgraph pointers here
268 BOOST_TEST((subGraphs[0] != nullptr));
269 BOOST_TEST((subGraphs[1] != nullptr));
271 if (subGraphs[0].get() != nullptr && subGraphs[1].get() != nullptr)
273 // sort the subgraphs by layer size, so it is simpler to test
274 std::sort(subGraphs.begin(), subGraphs.end(),
275 [](SubGraphSelector::SubGraphPtr & lhs, SubGraphSelector::SubGraphPtr & rhs)
277 return (lhs->GetLayers().size() < rhs->GetLayers().size());
281 // one subgraph needs to be size=1 and the other one is 4
282 BOOST_TEST(subGraphs[0]->GetLayers().size() == 1);
283 BOOST_TEST(subGraphs[1]->GetLayers().size() == 4);
285 CompareSubGraphs(subGraphs[0], smallerSubGraph);
286 CompareSubGraphs(subGraphs[1], largerSubGraph);
291 BOOST_AUTO_TEST_CASE(MultipleSimpleSubGraphs)
293 // This test case represents the scenario when we have two distinct subgraphs
294 // in a simple linear network. The selected nodes are the M* and the
295 // non-selected ones are the X*
297 // X1 -> M1 -> M2 -> X2 -> M3 -> X3
299 // The expected results is two subgraphs, one with {M1, M2} and another one
304 // the graph is constructed in reverse order
305 auto x3 = graph.AddLayer<OutputLayer>(0, "output");
306 auto m3 = graph.InsertNewLayer<ActivationLayer>(x3->GetInputSlot(0),
307 ActivationDescriptor{},
309 auto x2 = graph.InsertNewLayer<Convolution2dLayer>(m3->GetInputSlot(0),
310 Convolution2dDescriptor{},
312 auto m2 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0),
313 ActivationDescriptor{},
315 auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
316 ActivationDescriptor{},
318 graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x1");
320 // All selected 'M*' layers will be of Activation type
321 SubGraphSelector::SubGraphs subGraphs =
322 SubGraphSelector::SelectSubGraphs(
324 // select the middle layers only
327 bool toSelect = (l.GetType() == LayerType::Activation);
331 // expected results to test against
332 auto largerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m1}),
333 CreateOutputsFrom({m2}),
336 auto smallerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m3}),
337 CreateOutputsFrom({m3}),
340 BOOST_TEST(subGraphs.size() == 2);
341 if (subGraphs.size() == 2)
343 // we need to have valid subgraph pointers here
344 BOOST_TEST((subGraphs[0] != nullptr));
345 BOOST_TEST((subGraphs[1] != nullptr));
347 if (subGraphs[0].get() != nullptr && subGraphs[1].get() != nullptr)
349 // sort the subgraphs by layer size, so it is simpler to test
350 std::sort(subGraphs.begin(), subGraphs.end(),
351 [](SubGraphSelector::SubGraphPtr & lhs, SubGraphSelector::SubGraphPtr & rhs)
353 return (lhs->GetLayers().size() < rhs->GetLayers().size());
357 BOOST_TEST(subGraphs[0]->GetLayers().size() == 1);
358 BOOST_TEST(subGraphs[1]->GetLayers().size() == 2);
360 CompareSubGraphs(subGraphs[0], smallerSubGraph);
361 CompareSubGraphs(subGraphs[1], largerSubGraph);
366 BOOST_AUTO_TEST_CASE(SimpleLinearTest)
368 //X1 -> M1 -> M2 -> X2
369 //Where the input slots of M1 and the output slots of M2 are to be the sub graph boundaries.
372 ActivationDescriptor activationDefaults;
374 auto layerX1 = graph.AddLayer<InputLayer>(0, "layerX1");
375 auto layerX2 = graph.AddLayer<OutputLayer>(0, "layerX2");
376 auto layerM1 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM1");
377 auto layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2");
387 layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
388 layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
389 layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0));
391 SubGraphSelector::SubGraphs subGraphs =
392 SubGraphSelector::SelectSubGraphs(
394 // select the activation layers M1 and M2
397 bool toSelect = (l.GetType() == LayerType::Activation);
401 BOOST_CHECK(subGraphs.size() == 1);
402 if(subGraphs.size() == 1)
404 auto expected = CreateSubGraphFrom(CreateInputsFrom({layerM1}),
405 CreateOutputsFrom({layerM2}),
408 CompareSubGraphs(subGraphs[0], expected);
412 BOOST_AUTO_TEST_CASE(MultiInputSingleOutput)
414 //X1 -> M1 -> M3 -> X3
415 //X2 -> M2 -> M3 -> X3
416 //Where the input slots of {M1, M2} and the output slots of M3 are to be the subgraph boundaries.
419 ActivationDescriptor activationDefaults;
421 auto layerX1 = graph.AddLayer<InputLayer>(0, "layerX1");
422 auto layerX2 = graph.AddLayer<InputLayer>(1, "layerX2");
423 auto layerM1 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM1");
424 auto layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2");
425 auto layerM3 = graph.AddLayer<AdditionLayer>("layerM3");
426 auto layerX3 = graph.AddLayer<OutputLayer>(0, "layerX3");
439 layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
440 layerX2->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
441 layerM1->GetOutputSlot(0).Connect(layerM3->GetInputSlot(0));
442 layerM2->GetOutputSlot(0).Connect(layerM3->GetInputSlot(1));
443 layerM3->GetOutputSlot(0).Connect(layerX3->GetInputSlot(0));
445 SubGraphSelector::SubGraphs subGraphs =
446 SubGraphSelector::SelectSubGraphs(
448 // select Activation and Addition Layers M1, M2 and M3
451 bool toSelect = (l.GetType() == LayerType::Activation
452 || l.GetType() == LayerType::Addition);
456 BOOST_CHECK(subGraphs.size() == 1);
457 if (subGraphs.size() == 1)
459 auto expected = CreateSubGraphFrom(CreateInputsFrom({layerM1, layerM2}),
460 CreateOutputsFrom({layerM3}),
461 {layerM1, layerM2, layerM3});
463 CompareSubGraphs(subGraphs[0], expected);
467 BOOST_AUTO_TEST_CASE(SingleInputMultiOutput)
469 //X1 -> M1 -> M2 -> X2
470 //X1 -> M1 -> M3 -> X3
471 //Where the input slots of M1 and the output slots of {M2, M3} are to be the subgraph boundaries.
474 ActivationDescriptor activationDefaults;
475 ViewsDescriptor viewDefaults(2,4);
477 Layer* layerX1 = graph.AddLayer<InputLayer>(0, "layerX1");
478 Layer* layerM1 = graph.AddLayer<SplitterLayer>(viewDefaults, "layerM1");
479 Layer* layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2");
480 Layer* layerM3 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM3");
481 Layer* layerX2 = graph.AddLayer<OutputLayer>(0, "layerX2");
482 Layer* layerX3 = graph.AddLayer<OutputLayer>(1, "layerX3");
495 layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
496 layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
497 layerM1->GetOutputSlot(1).Connect(layerM3->GetInputSlot(0));
498 layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0));
499 layerM3->GetOutputSlot(0).Connect(layerX3->GetInputSlot(0));
501 SubGraphSelector::SubGraphs subGraphs =
502 SubGraphSelector::SelectSubGraphs(
504 // select Activation and Splitter Layers M1, M2 and M3
507 bool toSelect = (l.GetType() == LayerType::Activation
508 || l.GetType() == LayerType::Splitter);
512 BOOST_CHECK(subGraphs.size() == 1);
513 if(subGraphs.size() == 1)
515 auto expected = CreateSubGraphFrom(CreateInputsFrom({layerM1}),
516 CreateOutputsFrom({layerM2, layerM3}),
517 {layerM1, layerM2, layerM3});
519 CompareSubGraphs(subGraphs[0], expected);
523 BOOST_AUTO_TEST_CASE(MultiInputMultiOutput)
525 // This case represents the scenario with multiple inputs and multiple outputs
527 // X1 -> M1 -> M3 -> M4 -> X3
528 // X2 -> M2 -> M3 -> M5 -> X4
530 // Where the input slots of {M1, M2} and the output slots of {M4, M5} are to be the subgraph
535 ActivationDescriptor activationDefaults;
536 OriginsDescriptor mergerDescriptor(2);
538 auto x1 = graph.AddLayer<InputLayer>(0, "x1");
539 auto x2 = graph.AddLayer<InputLayer>(1, "x2");
541 auto m1 = graph.AddLayer<ActivationLayer>(activationDefaults, "m1");
542 auto m2 = graph.AddLayer<ActivationLayer>(activationDefaults, "m2");
543 auto m3 = graph.AddLayer<MergerLayer>(mergerDescriptor, "m3");
545 auto m4 = graph.AddLayer<ActivationLayer>(activationDefaults, "m4");
546 auto m5 = graph.AddLayer<ActivationLayer>(activationDefaults, "m5");
548 auto x3 = graph.AddLayer<OutputLayer>(0, "x3");
549 auto x4 = graph.AddLayer<OutputLayer>(1, "x4");
551 x1->GetOutputSlot(0).Connect(m1->GetInputSlot(0));
552 x2->GetOutputSlot(0).Connect(m2->GetInputSlot(0));
554 m1->GetOutputSlot(0).Connect(m3->GetInputSlot(0));
555 m2->GetOutputSlot(0).Connect(m3->GetInputSlot(1));
557 m3->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
558 m3->GetOutputSlot(0).Connect(m5->GetInputSlot(0));
560 m4->GetOutputSlot(0).Connect(x3->GetInputSlot(0));
561 m5->GetOutputSlot(0).Connect(x4->GetInputSlot(0));
564 SubGraphSelector::SubGraphs subGraphs =
565 SubGraphSelector::SelectSubGraphs(
567 // select Activation and Merger Layers M1, M2, M3, M4, M5
570 bool toSelect = (l.GetType() == LayerType::Activation
571 || l.GetType() == LayerType::Merger);
576 BOOST_CHECK(subGraphs.size() == 1);
577 if (subGraphs.size() == 1)
579 auto expected = CreateSubGraphFrom(CreateInputsFrom({m1, m2}),
580 CreateOutputsFrom({m4, m5}),
581 {m1, m2, m3, m4, m5});
583 CompareSubGraphs(subGraphs[0], expected);
587 BOOST_AUTO_TEST_SUITE_END()