IVGCVSW-1946: Remove armnn/src from the include paths
[platform/upstream/armnn.git] / src / armnn / test / SubGraphTests.cpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include <boost/test/unit_test.hpp>
6
7 #include <armnn/ArmNN.hpp>
8
9 #include <Graph.hpp>
10 #include <SubGraph.hpp>
11 #include <SubGraphSelector.hpp>
12
13 #include <backendsCommon/CpuTensorHandle.hpp>
14
15 using namespace armnn;
16
17 namespace
18 {
19
20 //
21 // this helper only works if all layers where the inputs connect to are not selected
22 //
23 SubGraph::InputSlots CreateInputsFrom(const std::vector<Layer *> & layers)
24 {
25     SubGraph::InputSlots result;
26     for (auto&& layer : layers)
27     {
28         for (auto&& it = layer->BeginInputSlots(); it != layer->EndInputSlots(); ++it)
29         {
30             result.push_back(&(*it));
31         }
32     }
33     return result;
34 }
35
36 //
37 // this helper only works if all layers where the outputs connect to are not selected
38 //
39 SubGraph::OutputSlots CreateOutputsFrom(const std::vector<Layer *> & layers)
40 {
41     SubGraph::OutputSlots result;
42     for (auto && layer : layers)
43     {
44         for (auto&& it = layer->BeginOutputSlots(); it != layer->EndOutputSlots(); ++it)
45         {
46             result.push_back(&(*it));
47         }
48     }
49     return result;
50 }
51
52 //
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
55 //
56 SubGraphSelector::SubGraphPtr CreateSubGraphFrom(SubGraph::InputSlots inputs,
57                                                  SubGraph::OutputSlots outputs,
58                                                  SubGraph::Layers layers)
59 {
60     return std::make_unique<SubGraph>(std::move(inputs), std::move(outputs), std::move(layers));
61 }
62
63 template <typename T, typename Iterator>
64 std::vector<T> ToSortedArray(Iterator begin, Iterator end)
65 {
66     std::vector<T> result(begin, end);
67     std::sort(result.begin(), result.end());
68     return result;
69 }
70
71 template <typename T>
72 void CompareVectors(const std::vector<T> & result, const std::vector<T> & expected)
73 {
74     BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
75 }
76
77 void CompareSubGraphs(SubGraphSelector::SubGraphPtr & result,
78                       SubGraphSelector::SubGraphPtr & expected)
79 {
80     // expect both to be valid subgraphs
81     BOOST_TEST((result.get() != nullptr));
82     BOOST_TEST((expected.get() != nullptr));
83
84     if (result.get() != nullptr && expected.get() != nullptr)
85     {
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());
92
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);
98
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);
104
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);
110     }
111 }
112
113 } // namespace <anonymous>
114
115 BOOST_AUTO_TEST_SUITE(SubGraphSelection)
116
117 BOOST_AUTO_TEST_CASE(NoSubGraphsForNoMatch)
118 {
119     Graph graph;
120
121     auto output = graph.AddLayer<OutputLayer>(0, "output");
122     graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input");
123
124     SubGraphSelector::SubGraphs subGraphs =
125         SubGraphSelector::SelectSubGraphs(graph, [](const Layer &) { return false; });
126
127     BOOST_TEST(subGraphs.empty());
128 }
129
130 BOOST_AUTO_TEST_CASE(OneSubGraphsSelectedASingleMatch)
131 {
132     Graph graph;
133
134     auto output = graph.AddLayer<OutputLayer>(0, "output");
135     graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input");
136
137     SubGraphSelector::SubGraphs subGraphs =
138         SubGraphSelector::SelectSubGraphs(
139             graph,
140             // select the output layer only
141             [](const Layer & l)
142             {
143                 bool isOutput = l.GetNameStr().compare("output") == 0;
144                 return isOutput;
145             });
146
147     BOOST_TEST(subGraphs.size() == 1);
148     if (subGraphs.size() == 1)
149     {
150         auto expected = CreateSubGraphFrom(CreateInputsFrom({output}),
151                                            // outputs of 'output' will be empty
152                                            CreateOutputsFrom({output}),
153                                            {output});
154
155         CompareSubGraphs(subGraphs[0], expected);
156     }
157 }
158
159 BOOST_AUTO_TEST_CASE(MultipleLayersSelectedInTheMiddle)
160 {
161     Graph graph;
162
163     auto output = graph.AddLayer<OutputLayer>(0, "output");
164     auto mid0 = graph.InsertNewLayer<ActivationLayer>(output->GetInputSlot(0),
165                                                       ActivationDescriptor{},
166                                                       "mid0");
167     auto mid1 = graph.InsertNewLayer<ActivationLayer>(mid0->GetInputSlot(0),
168                                                       ActivationDescriptor{},
169                                                       "mid1");
170     graph.InsertNewLayer<InputLayer>(mid1->GetInputSlot(0), 0, "input");
171
172     SubGraphSelector::SubGraphs subGraphs =
173         SubGraphSelector::SelectSubGraphs(
174             graph,
175             // select the middle layers only
176             [](const Layer & l)
177             {
178                 bool toSelect = (l.GetType() == LayerType::Activation);
179                 return toSelect;
180             });
181
182     BOOST_TEST(subGraphs.size() == 1);
183     if (subGraphs.size() == 1)
184     {
185         auto expected = CreateSubGraphFrom(CreateInputsFrom({mid1}),
186                                            CreateOutputsFrom({mid0}),
187                                            {mid1, mid0});
188
189         CompareSubGraphs(subGraphs[0], expected);
190     }
191 }
192
193 BOOST_AUTO_TEST_CASE(IslandInTheMiddle)
194 {
195     // This case represent the scenario when a non-selected X1 node placed in the middle
196     // of the selected M* nodes:
197     //
198     // X0 -> M1 -> M2 -> M3 -> X2
199     // X0 -> M4 -> X1 -> M5 -> X2
200     //
201     /*
202           X0
203           / \
204         M1   M4
205          |   |
206         M2   X1 < the island in the middle !
207          |   |
208         M3   M5
209           \ /
210           X2
211     */
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.
215     //
216     Graph graph;
217
218     OriginsDescriptor mergerDescriptor(2);
219     auto x2 = graph.AddLayer<MergerLayer>(mergerDescriptor, "x2");
220     auto m3 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0),
221                                                     ActivationDescriptor{},
222                                                     "m3");
223     auto m2 = graph.InsertNewLayer<ActivationLayer>(m3->GetInputSlot(0),
224                                                     ActivationDescriptor{},
225                                                     "m2");
226     auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
227                                                     ActivationDescriptor{},
228                                                     "m1");
229     auto x0 = graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x0");
230
231     auto m5 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(1),
232                                                     ActivationDescriptor{},
233                                                     "m5");
234     auto x1 = graph.InsertNewLayer<Convolution2dLayer>(m5->GetInputSlot(0),
235                                                        Convolution2dDescriptor{},
236                                                        "x1");
237     auto m4 = graph.InsertNewLayer<ActivationLayer>(x1->GetInputSlot(0),
238                                                     ActivationDescriptor{},
239                                                     "m4");
240
241     // Connect the other branch to the input layer
242     x0->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
243
244     // All selected 'M*' layers will be of Activation type
245     SubGraphSelector::SubGraphs subGraphs =
246         SubGraphSelector::SelectSubGraphs(
247             graph,
248             // select the middle layers only
249             [](const Layer & l)
250             {
251                 bool toSelect = (l.GetType() == LayerType::Activation);
252                 return toSelect;
253             });
254
255     // expected results to test against
256     auto largerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m1, m4}),
257                                              CreateOutputsFrom({m3, m4}),
258                                              {m1, m4, m2, m3});
259
260     auto smallerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m5}),
261                                               CreateOutputsFrom({m5}),
262                                               {m5});
263
264     BOOST_TEST(subGraphs.size() == 2);
265     if (subGraphs.size() == 2)
266     {
267         // we need to have valid subgraph pointers here
268         BOOST_TEST((subGraphs[0] != nullptr));
269         BOOST_TEST((subGraphs[1] != nullptr));
270
271         if (subGraphs[0].get() != nullptr && subGraphs[1].get() != nullptr)
272         {
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)
276                 {
277                     return (lhs->GetLayers().size() < rhs->GetLayers().size());
278                 }
279             );
280
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);
284
285             CompareSubGraphs(subGraphs[0], smallerSubGraph);
286             CompareSubGraphs(subGraphs[1], largerSubGraph);
287         }
288     }
289 }
290
291 BOOST_AUTO_TEST_CASE(MultipleSimpleSubGraphs)
292 {
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*
296     //
297     // X1 -> M1 -> M2 -> X2 -> M3 -> X3
298     //
299     // The expected results is two subgraphs, one with {M1, M2} and another one
300     // with {M3}
301     //
302     Graph graph;
303
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{},
308                                                     "m3");
309     auto x2 = graph.InsertNewLayer<Convolution2dLayer>(m3->GetInputSlot(0),
310                                                        Convolution2dDescriptor{},
311                                                        "x2");
312     auto m2 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0),
313                                                     ActivationDescriptor{},
314                                                     "m2");
315     auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
316                                                     ActivationDescriptor{},
317                                                     "m1");
318     graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x1");
319
320     // All selected 'M*' layers will be of Activation type
321     SubGraphSelector::SubGraphs subGraphs =
322         SubGraphSelector::SelectSubGraphs(
323             graph,
324             // select the middle layers only
325             [](const Layer & l)
326             {
327                 bool toSelect = (l.GetType() == LayerType::Activation);
328                 return toSelect;
329             });
330
331     // expected results to test against
332     auto largerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m1}),
333                                              CreateOutputsFrom({m2}),
334                                              {m1, m2});
335
336     auto smallerSubGraph = CreateSubGraphFrom(CreateInputsFrom({m3}),
337                                               CreateOutputsFrom({m3}),
338                                               {m3});
339
340     BOOST_TEST(subGraphs.size() == 2);
341     if (subGraphs.size() == 2)
342     {
343         // we need to have valid subgraph pointers here
344         BOOST_TEST((subGraphs[0] != nullptr));
345         BOOST_TEST((subGraphs[1] != nullptr));
346
347         if (subGraphs[0].get() != nullptr && subGraphs[1].get() != nullptr)
348         {
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)
352                 {
353                     return (lhs->GetLayers().size() < rhs->GetLayers().size());
354                 }
355             );
356
357             BOOST_TEST(subGraphs[0]->GetLayers().size() == 1);
358             BOOST_TEST(subGraphs[1]->GetLayers().size() == 2);
359
360             CompareSubGraphs(subGraphs[0], smallerSubGraph);
361             CompareSubGraphs(subGraphs[1], largerSubGraph);
362         }
363     }
364 }
365
366 BOOST_AUTO_TEST_CASE(SimpleLinearTest)
367 {
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.
370     Graph graph;
371
372     ActivationDescriptor activationDefaults;
373
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");
378
379     //      X1
380     //      |
381     //      M1
382     //      |
383     //      M2
384     //      |
385     //      X2
386
387     layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
388     layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
389     layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0));
390
391     SubGraphSelector::SubGraphs subGraphs =
392             SubGraphSelector::SelectSubGraphs(
393                     graph,
394                     // select the activation layers M1 and M2
395                     [](const Layer & l)
396                     {
397                         bool toSelect = (l.GetType() == LayerType::Activation);
398                         return toSelect;
399                     });
400
401     BOOST_CHECK(subGraphs.size() == 1);
402     if(subGraphs.size() == 1)
403     {
404         auto expected = CreateSubGraphFrom(CreateInputsFrom({layerM1}),
405                                            CreateOutputsFrom({layerM2}),
406                                            {layerM1, layerM2});
407
408         CompareSubGraphs(subGraphs[0], expected);
409     }
410 }
411
412 BOOST_AUTO_TEST_CASE(MultiInputSingleOutput)
413 {
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.
417     Graph graph;
418
419     ActivationDescriptor activationDefaults;
420
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");
427
428     //  X1  X2
429     //  |   |
430     //  M1  M2
431     //   \  |
432     //    \ |
433     //     \|
434     //      M3
435     //      |
436     //      |
437     //      X3
438
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));
444
445     SubGraphSelector::SubGraphs subGraphs =
446             SubGraphSelector::SelectSubGraphs(
447                     graph,
448                     // select Activation and Addition Layers M1, M2 and M3
449                     [](const Layer & l)
450                     {
451                         bool toSelect = (l.GetType() == LayerType::Activation
452                                          || l.GetType() == LayerType::Addition);
453                         return toSelect;
454                     });
455
456     BOOST_CHECK(subGraphs.size() == 1);
457     if (subGraphs.size() == 1)
458     {
459         auto expected = CreateSubGraphFrom(CreateInputsFrom({layerM1, layerM2}),
460                                            CreateOutputsFrom({layerM3}),
461                                            {layerM1, layerM2, layerM3});
462
463         CompareSubGraphs(subGraphs[0], expected);
464     }
465 }
466
467 BOOST_AUTO_TEST_CASE(SingleInputMultiOutput)
468 {
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.
472     Graph graph;
473
474     ActivationDescriptor activationDefaults;
475     ViewsDescriptor viewDefaults(2,4);
476
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");
483
484     //      X2
485     //      |
486     //      M1
487     //     /|
488     //    / |
489     //   /  |
490     //  M2  M3
491     //  |   |
492     //  |   |
493     //  X2  X3
494
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));
500
501     SubGraphSelector::SubGraphs subGraphs =
502             SubGraphSelector::SelectSubGraphs(
503                     graph,
504                     // select Activation and Splitter Layers M1, M2 and M3
505                     [](const Layer & l)
506                     {
507                         bool toSelect = (l.GetType() == LayerType::Activation
508                                          || l.GetType() == LayerType::Splitter);
509                         return toSelect;
510                     });
511
512     BOOST_CHECK(subGraphs.size() == 1);
513     if(subGraphs.size() == 1)
514     {
515         auto expected = CreateSubGraphFrom(CreateInputsFrom({layerM1}),
516                                            CreateOutputsFrom({layerM2, layerM3}),
517                                            {layerM1, layerM2, layerM3});
518
519         CompareSubGraphs(subGraphs[0], expected);
520     }
521 }
522
523 BOOST_AUTO_TEST_CASE(MultiInputMultiOutput)
524 {
525     // This case represents the scenario with multiple inputs and multiple outputs
526     //
527     // X1 -> M1 -> M3 -> M4 -> X3
528     // X2 -> M2 -> M3 -> M5 -> X4
529     //
530     // Where the input slots of {M1, M2} and the output slots of {M4, M5} are to be the subgraph
531     // boundaries.
532
533     Graph graph;
534
535     ActivationDescriptor activationDefaults;
536     OriginsDescriptor mergerDescriptor(2);
537
538     auto x1 = graph.AddLayer<InputLayer>(0, "x1");
539     auto x2 = graph.AddLayer<InputLayer>(1, "x2");
540
541     auto m1 = graph.AddLayer<ActivationLayer>(activationDefaults, "m1");
542     auto m2 = graph.AddLayer<ActivationLayer>(activationDefaults, "m2");
543     auto m3 = graph.AddLayer<MergerLayer>(mergerDescriptor, "m3");
544
545     auto m4 = graph.AddLayer<ActivationLayer>(activationDefaults, "m4");
546     auto m5 = graph.AddLayer<ActivationLayer>(activationDefaults, "m5");
547
548     auto x3 = graph.AddLayer<OutputLayer>(0, "x3");
549     auto x4 = graph.AddLayer<OutputLayer>(1, "x4");
550
551     x1->GetOutputSlot(0).Connect(m1->GetInputSlot(0));
552     x2->GetOutputSlot(0).Connect(m2->GetInputSlot(0));
553
554     m1->GetOutputSlot(0).Connect(m3->GetInputSlot(0));
555     m2->GetOutputSlot(0).Connect(m3->GetInputSlot(1));
556
557     m3->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
558     m3->GetOutputSlot(0).Connect(m5->GetInputSlot(0));
559
560     m4->GetOutputSlot(0).Connect(x3->GetInputSlot(0));
561     m5->GetOutputSlot(0).Connect(x4->GetInputSlot(0));
562
563
564     SubGraphSelector::SubGraphs subGraphs =
565         SubGraphSelector::SelectSubGraphs(
566             graph,
567             // select Activation and Merger Layers M1, M2, M3, M4, M5
568             [](const Layer & l)
569             {
570                 bool toSelect = (l.GetType() == LayerType::Activation
571                                  || l.GetType() == LayerType::Merger);
572                 return toSelect;
573             });
574
575
576     BOOST_CHECK(subGraphs.size() == 1);
577     if (subGraphs.size() == 1)
578     {
579         auto expected = CreateSubGraphFrom(CreateInputsFrom({m1, m2}),
580                                            CreateOutputsFrom({m4, m5}),
581                                            {m1, m2, m3, m4, m5});
582
583         CompareSubGraphs(subGraphs[0], expected);
584     }
585 }
586
587 BOOST_AUTO_TEST_SUITE_END()