ArmNN
 20.05
SubgraphViewTests.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include <boost/test/unit_test.hpp>
6 
7 
8 #include <Graph.hpp>
9 #include <SubgraphView.hpp>
10 #include <SubgraphViewSelector.hpp>
11 
13 #include <fstream>
14 #include <map>
15 #include <queue>
16 #include <random>
17 #include <chrono>
18 using namespace armnn;
19 
20 namespace
21 {
22 
23 bool AreAnySubgraphLayersPresentInGraph(const SubgraphView::Layers &subgraphLayers, const Graph &graph)
24 {
25  for(auto&& layer : subgraphLayers)
26  {
27  auto posInGraph = std::find(graph.begin(), graph.end(), layer);
28  if(posInGraph != graph.end())
29  {
30  return true;
31  }
32  }
33 
34  return false;
35 }
36 
37 //
38 // this helper only works if all layers where the inputs connect to are not selected
39 //
40 SubgraphView::InputSlots CreateInputsFrom(const std::vector<Layer*>& layers)
41 {
43  for (auto&& layer : layers)
44  {
45  for (auto&& it = layer->BeginInputSlots(); it != layer->EndInputSlots(); ++it)
46  {
47  result.push_back(&(*it));
48  }
49  }
50  return result;
51 }
52 
53 //
54 // this helper only works if all layers where the outputs connect to are not selected
55 //
56 SubgraphView::OutputSlots CreateOutputsFrom(const std::vector<Layer*>& layers)
57 {
59  for (auto && layer : layers)
60  {
61  for (auto&& it = layer->BeginOutputSlots(); it != layer->EndOutputSlots(); ++it)
62  {
63  result.push_back(&(*it));
64  }
65  }
66  return result;
67 }
68 
69 //
70 // this takes the inputs, outputs and layers as a copy and the move these copies into the
71 // resulting subgraph, so the pass by value is intentional
72 //
74  SubgraphView::OutputSlots&& outputs,
75  SubgraphView::Layers&& layers)
76 {
77  return std::make_unique<SubgraphView>(std::move(inputs), std::move(outputs), std::move(layers));
78 }
79 
80 template <typename T, typename Iterator>
81 std::vector<T> ToSortedArray(Iterator begin, Iterator end)
82 {
83  std::vector<T> result(begin, end);
84  std::sort(result.begin(), result.end());
85  return result;
86 }
87 
88 template <typename T>
89 void CompareVectors(const std::vector<T>& result, const std::vector<T>& expected)
90 {
91  BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end());
92 }
93 
94 void CompareSubgraphViews(SubgraphViewSelector::SubgraphViewPtr& result,
96 {
97  // expect both to be valid subgraphs
98  BOOST_TEST((result.get() != nullptr));
99  BOOST_TEST((expected.get() != nullptr));
100 
101  if (result.get() != nullptr && expected.get() != nullptr)
102  {
103  // try to detect all other obvious errors too, mainly because here
104  // we can get a nicer error message from boost, the collection test
105  // also report error for these
106  BOOST_TEST(result->GetInputSlots().size() == expected->GetInputSlots().size());
107  BOOST_TEST(result->GetOutputSlots().size() == expected->GetOutputSlots().size());
108  BOOST_TEST(result->GetLayers().size() == expected->GetLayers().size());
109 
110  auto resultLayers = ToSortedArray<Layer *>(result->GetLayers().begin(),
111  result->GetLayers().end());
112  auto expectedLayers = ToSortedArray<Layer *>(expected->GetLayers().begin(),
113  expected->GetLayers().end());
114  CompareVectors(resultLayers, expectedLayers);
115 
116  auto resultInputs = ToSortedArray<InputSlot *>(result->GetInputSlots().begin(),
117  result->GetInputSlots().end());
118  auto expectedInputs = ToSortedArray<InputSlot *>(expected->GetInputSlots().begin(),
119  expected->GetInputSlots().end());
120  CompareVectors(resultInputs, expectedInputs);
121 
122  auto resultOutputs = ToSortedArray<OutputSlot *>(result->GetOutputSlots().begin(),
123  result->GetOutputSlots().end());
124  auto expectedOutputs = ToSortedArray<OutputSlot *>(expected->GetOutputSlots().begin(),
125  expected->GetOutputSlots().end());
126  CompareVectors(resultOutputs, expectedOutputs);
127  }
128 }
129 
130 } // namespace <anonymous>
131 
132 BOOST_AUTO_TEST_SUITE(SubgraphSubstitution)
133 
134 BOOST_AUTO_TEST_CASE(SingleInputSingleOutput)
135 {
136  // Construct graph
137  Graph graph;
138 
139  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
140 
141  Convolution2dDescriptor convDescriptor;
142  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
143  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
144 
145  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
146 
147  inputLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
148  convLayer1->GetOutputSlot(0).Connect(convLayer2->GetInputSlot(0));
149  convLayer2->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
150 
151  // Construct sub-graph
153  CreateOutputsFrom({convLayer2}),
154  {});
155 
156  // Save sub-graph connections for comparison after substitution
157  IOutputSlot* subgraphInputConn = subgraph->GetInputSlot(0)->GetConnection();
158  IInputSlot* subgraphOutputConn = subgraph->GetOutputSlot(0)->GetConnection(0);
159 
160  // Construct dummy pre-compiled layer
161  PreCompiledDescriptor preCompiledDescriptor(1, 1);
162  Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
163 
164  // Substitute sub-graph with pre-compiled layer
165  graph.SubstituteSubgraph(*subgraph, preCompiledLayer);
166 
167  // Check that connections are correct after substitution
168  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn);
169  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn);
170 }
171 
172 BOOST_AUTO_TEST_CASE(SingleInputSingleOutputSubstituteGraph)
173 {
174  // Construct graph
175  Graph graph;
176 
177  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
178 
179  Convolution2dDescriptor convDescriptor;
180  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
181  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
182 
183  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
184 
185  inputLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
186  convLayer1->GetOutputSlot(0).Connect(convLayer2->GetInputSlot(0));
187  convLayer2->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
188 
189  // Construct sub-graph
191  CreateOutputsFrom({convLayer2}),
192  {});
193 
194  // Save sub-graph connections for comparison after substitution
195  IOutputSlot* subgraphInputConn = subgraph->GetInputSlot(0)->GetConnection();
196  IInputSlot* subgraphOutputConn = subgraph->GetOutputSlot(0)->GetConnection(0);
197 
198  // Construct second graph with a single pre-compiled layer
199  Graph substituteGraph;
200  PreCompiledDescriptor preCompiledDescriptor(1, 1);
201  Layer* const preCompiledLayer = substituteGraph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
202 
203  SubgraphViewSelector::SubgraphViewPtr substituteSubgraph =
204  CreateSubgraphViewFrom(CreateInputsFrom({preCompiledLayer}),
205  CreateOutputsFrom({preCompiledLayer}),
206  {preCompiledLayer});
207  // Substitute subgraph with pre-compiled layer
208  graph.SubstituteSubgraph(*subgraph, *substituteSubgraph);
209 
210  // Check that connections are correct after substitution
211  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn);
212  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn);
213 }
214 
215 BOOST_AUTO_TEST_CASE(MultiInputSingleOutput)
216 {
217  // Construct graph
218  Graph graph;
219 
220  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
221 
222  ViewsDescriptor splitterDescriptor(2);
223  Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter");
224 
225  Convolution2dDescriptor convDescriptor;
226  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
227  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
228 
229  OriginsDescriptor concatDescriptor(2);
230  Layer* const concatLayer = graph.AddLayer<ConcatLayer>(concatDescriptor, "concat");
231 
232  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
233 
234  inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0));
235  splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
236  splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0));
237  convLayer1->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(0));
238  convLayer2->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(1));
239  concatLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
240 
241  // Construct sub-graph
243  CreateOutputsFrom({concatLayer}),
244  {});
245 
246  // Save sub-graph connections for comparison after substitution
247  IOutputSlot* subgraphInputConn1 = subgraph->GetInputSlot(0)->GetConnection();
248  IOutputSlot* subgraphInputConn2 = subgraph->GetInputSlot(1)->GetConnection();
249 
250  IInputSlot* subgraphOutputConn = subgraph->GetOutputSlot(0)->GetConnection(0);
251 
252  // Construct dummy pre-compiled layer
253  PreCompiledDescriptor preCompiledDescriptor(2, 1);
254  Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
255 
256  // Substitute sub-graph with pre-compiled layer
257  graph.SubstituteSubgraph(*subgraph, preCompiledLayer);
258 
259  // Check that connections are correct after substitution
260  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1);
261  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(1).GetConnection(), subgraphInputConn2);
262 
263  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn);
264 }
265 
266 BOOST_AUTO_TEST_CASE(SingleInputMultiOutput)
267 {
268  // Construct graph
269  Graph graph;
270 
271  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
272 
273  Convolution2dDescriptor convDescriptor;
274  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
275  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
276  OriginsDescriptor concatDescriptor(2);
277  Layer* const concatLayer = graph.AddLayer<ConcatLayer>(concatDescriptor, "concat");
278  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
279 
280  ViewsDescriptor splitterDescriptor(2);
281  Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter");
282 
283  inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0));
284  splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
285  splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0));
286  convLayer1->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(0));
287  convLayer2->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(1));
288  concatLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
289 
290  // Construct sub-graph
292  CreateOutputsFrom({convLayer1, convLayer2}),
293  {});
294 
295  // Save sub-graph connections for comparison after substitution
296  IOutputSlot* subgraphInputConn1 = subgraph->GetInputSlot(0)->GetConnection();
297 
298  IInputSlot* subgraphOutputConn1 = subgraph->GetOutputSlot(0)->GetConnection(0);
299  IInputSlot* subgraphOutputConn2 = subgraph->GetOutputSlot(1)->GetConnection(0);
300 
301  // Construct dummy pre-compiled layer
302  PreCompiledDescriptor preCompiledDescriptor(1, 2);
303  Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
304 
305  // Substitute sub-graph with pre-compiled layer
306  graph.SubstituteSubgraph(*subgraph, preCompiledLayer);
307 
308  // Check that connections are correct after substitution
309  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1);
310 
311  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn1);
312  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(1).GetConnection(0), subgraphOutputConn2);
313 }
314 
315 BOOST_AUTO_TEST_CASE(MultiInputMultiOutput)
316 {
317  // Construct graph
318  Graph graph;
319 
320  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
321 
322  ViewsDescriptor splitterDescriptor(2);
323  Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter");
324 
325  Convolution2dDescriptor convDescriptor;
326  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
327  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
328 
329  OriginsDescriptor concatDescriptor(2);
330  Layer* const concatLayer = graph.AddLayer<ConcatLayer>(concatDescriptor, "concat");
331 
332  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
333 
334  inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0));
335  splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
336  splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0));
337  convLayer1->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(0));
338  convLayer2->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(1));
339  concatLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
340 
341  // Construct sub-graph
343  CreateOutputsFrom({convLayer1, convLayer2}),
344  {});
345 
346  // Save sub-graph connections for comparison after substitution
347  IOutputSlot* subgraphInputConn1 = subgraph->GetInputSlot(0)->GetConnection();
348  IOutputSlot* subgraphInputConn2 = subgraph->GetInputSlot(1)->GetConnection();
349 
350  IInputSlot* subgraphOutputConn1 = subgraph->GetOutputSlot(0)->GetConnection(0);
351  IInputSlot* subgraphOutputConn2 = subgraph->GetOutputSlot(1)->GetConnection(0);
352 
353  // Construct dummy pre-compiled layer
354  PreCompiledDescriptor preCompiledDescriptor(2, 2);
355  Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
356 
357  // Substitute sub-graph with pre-compiled layer
358  graph.SubstituteSubgraph(*subgraph, preCompiledLayer);
359 
360  // Check that connections are correct after substitution
361  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1);
362  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(1).GetConnection(), subgraphInputConn2);
363 
364  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn1);
365  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(1).GetConnection(0), subgraphOutputConn2);
366 }
367 
368 BOOST_AUTO_TEST_CASE(EraseReplacedLayers)
369 {
370  // Construct graph
371  Graph graph;
372 
373  graph.AddLayer<InputLayer>(0, "input");
374 
375  ViewsDescriptor splitterDescriptor(2);
376  Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter");
377 
378  Convolution2dDescriptor convDescriptor;
379  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
380  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
381 
382  OriginsDescriptor concatDescriptor(2);
383  Layer* const concatLayer = graph.AddLayer<ConcatLayer>(concatDescriptor, "concat");
384 
385  graph.AddLayer<OutputLayer>(0, "output");
386 
387  // Construct sub-graph
389  {},
390  {splitterLayer,
391  convLayer1,
392  convLayer2,
393  concatLayer});
394 
395  // Construct dummy pre-compiled layer
396  PreCompiledDescriptor preCompiledDescriptor(0, 0);
397  Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
398 
399  // Save sub-graph layers for later verification
400  const SubgraphView::Layers subgraphLayers = subgraph->GetLayers();
401 
402  // Substitute sub-graph with pre-compiled layer
403  graph.SubstituteSubgraph(*subgraph, preCompiledLayer);
404 
405  // Check that the layers belonging to the sub-graph have been erased from the graph after substitution
406  BOOST_CHECK(!AreAnySubgraphLayersPresentInGraph(subgraphLayers, graph));
407 }
408 
410 
411 BOOST_AUTO_TEST_SUITE(SubgraphSelection)
412 
413 BOOST_AUTO_TEST_CASE(SubgraphForEmptyGraph)
414 {
415  Graph graph;
416  SubgraphView subgraph(graph);
417 
418  BOOST_TEST(subgraph.GetInputSlots().empty());
419  BOOST_TEST(subgraph.GetOutputSlots().empty());
420  BOOST_TEST(subgraph.GetLayers().empty());
421 }
422 
423 BOOST_AUTO_TEST_CASE(SubgraphForEntireGraph)
424 {
425  Graph graph;
426 
427  auto output = graph.AddLayer<OutputLayer>(0, "output");
428  auto mid0 = graph.InsertNewLayer<ActivationLayer>(output->GetInputSlot(0),
430  "mid0");
431  auto mid1 = graph.InsertNewLayer<ActivationLayer>(mid0->GetInputSlot(0),
433  "mid1");
434  graph.InsertNewLayer<InputLayer>(mid1->GetInputSlot(0), 0, "input");
435 
436  SubgraphView subgraph(graph);
437 
438  BOOST_TEST(subgraph.GetInputSlots().empty());
439  BOOST_TEST(subgraph.GetOutputSlots().empty());
440  BOOST_TEST(subgraph.GetLayers().size() == graph.GetNumLayers());
441 }
442 
443 BOOST_AUTO_TEST_CASE(NoSubgraphsForNoMatch)
444 {
445  Graph graph;
446 
447  auto output = graph.AddLayer<OutputLayer>(0, "output");
448  graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input");
449 
451  SubgraphViewSelector::SelectSubgraphs(graph, [](const Layer &) { return false; });
452 
453  BOOST_TEST(subgraphs.empty());
454 }
455 
456 BOOST_AUTO_TEST_CASE(OneSubgraphsSelectedASingleMatch)
457 {
458  Graph graph;
459 
460  auto output = graph.AddLayer<OutputLayer>(0, "output");
461  graph.InsertNewLayer<InputLayer>(output->GetInputSlot(0), 0, "input");
462 
465  graph,
466  // select the output layer only
467  [](const Layer & l)
468  {
469  bool isOutput = l.GetNameStr().compare("output") == 0;
470  return isOutput;
471  });
472 
473  BOOST_TEST(subgraphs.size() == 1);
474  if (subgraphs.size() == 1)
475  {
476  auto expected = CreateSubgraphViewFrom(CreateInputsFrom({output}),
477  // outputs of 'output' will be empty
478  CreateOutputsFrom({output}),
479  {output});
480 
481  CompareSubgraphViews(subgraphs[0], expected);
482  }
483 }
484 
485 BOOST_AUTO_TEST_CASE(MultipleLayersSelectedInTheMiddle)
486 {
487  Graph graph;
488 
489  auto output = graph.AddLayer<OutputLayer>(0, "output");
490  auto mid0 = graph.InsertNewLayer<ActivationLayer>(output->GetInputSlot(0),
492  "mid0");
493  auto mid1 = graph.InsertNewLayer<ActivationLayer>(mid0->GetInputSlot(0),
495  "mid1");
496  graph.InsertNewLayer<InputLayer>(mid1->GetInputSlot(0), 0, "input");
497 
500  graph,
501  // select the middle layers only
502  [](const Layer & l)
503  {
504  bool toSelect = (l.GetType() == LayerType::Activation);
505  return toSelect;
506  });
507 
508  BOOST_TEST(subgraphs.size() == 1);
509  if (subgraphs.size() == 1)
510  {
511  auto expected = CreateSubgraphViewFrom(CreateInputsFrom({mid1}),
512  CreateOutputsFrom({mid0}),
513  {mid1, mid0});
514 
515  CompareSubgraphViews(subgraphs[0], expected);
516  }
517 }
518 
519 BOOST_AUTO_TEST_CASE(DisjointGraphs)
520 {
521  // The input graph has two disjoint sections and all layers are selected.
522  // This should result in two subgraphs being produced.
523  Graph graph;
524 
525  // the graph is constructed in reverse order
526  auto o0 = graph.AddLayer<OutputLayer>(0, "output0");
527  auto n0 = graph.InsertNewLayer<ActivationLayer>(o0->GetInputSlot(0), ActivationDescriptor{}, "intermediate0");
528  auto i0 = graph.InsertNewLayer<InputLayer>(n0->GetInputSlot(0), 0, "input0");
529 
530  auto o1 = graph.AddLayer<OutputLayer>(1, "output1");
531  auto n1 = graph.InsertNewLayer<ActivationLayer>(o1->GetInputSlot(0), ActivationDescriptor{}, "intermediate1");
532  auto i1 = graph.InsertNewLayer<InputLayer>(n1->GetInputSlot(0), 1, "input1");
533 
536  // select the middle layers only
537  [](const Layer&) {
538  return true;
539  });
540 
541  // expected results to test against
542  auto expected1 = CreateSubgraphViewFrom({}, {}, { o0, n0, i0 });
543  auto expected2 = CreateSubgraphViewFrom({}, {}, { o1, n1, i1 });
544  BOOST_TEST(subgraphs.size() == 2);
545  if (subgraphs.size() == 2)
546  {
547  BOOST_TEST((subgraphs[0] != nullptr));
548  BOOST_TEST((subgraphs[1] != nullptr));
549  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr)
550  {
551  if (std::find(subgraphs[0]->GetLayers().begin(), subgraphs[0]->GetLayers().end(), i0) !=
552  subgraphs[0]->GetLayers().end())
553  {
554  CompareSubgraphViews(subgraphs[0], expected1);
555  CompareSubgraphViews(subgraphs[1], expected2);
556  }
557  else
558  {
559  CompareSubgraphViews(subgraphs[0], expected2);
560  CompareSubgraphViews(subgraphs[1], expected1);
561  }
562  }
563  }
564 }
565 
566 BOOST_AUTO_TEST_CASE(IslandInTheMiddle)
567 {
568  // This case represent the scenario when a non-selected X1 node placed in the middle
569  // of the selected M* nodes.
570  // This checks that we don't merge M6 and M3 and create a dependency loop.
571  /*
572  M0
573  / \
574  M1 M4
575  | |
576  M2 X1 < the island in the middle !
577  | |
578  M3 M5
579  \ /
580  M6
581  */
582  Graph graph;
583 
584  OriginsDescriptor concatDescriptor(2);
585  auto m6 = graph.AddLayer<ConcatLayer>(concatDescriptor, "m6");
586  auto m3 = graph.InsertNewLayer<ActivationLayer>(m6->GetInputSlot(0),
588  "m3");
589  auto m2 = graph.InsertNewLayer<ActivationLayer>(m3->GetInputSlot(0),
591  "m2");
592  auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
594  "m1");
595  auto m0 = graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "m0");
596 
597  auto m5 = graph.InsertNewLayer<ActivationLayer>(m6->GetInputSlot(1),
599  "m5");
600  auto x1 = graph.InsertNewLayer<ActivationLayer>(m5->GetInputSlot(0),
602  "x1");
603  auto m4 = graph.InsertNewLayer<ActivationLayer>(x1->GetInputSlot(0),
605  "m4");
606 
607  // Connect the other branch to the input layer
608  m0->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
609 
610  // All selected 'M*' layers will be of Activation type
613  graph,
614  // select the middle layers only
615  [](const Layer& l)
616  {
617  bool toSelect = std::string(l.GetName())[0] == 'm';
618  return toSelect;
619  });
620 
621  // expected results to test against
622  auto largerSubgraph = CreateSubgraphViewFrom(CreateInputsFrom({ m0 }),
623  CreateOutputsFrom({ m3, m4 }),
624  { m0, m1, m2, m3, m4 });
625 
626  auto smallerSubgraph =
627  CreateSubgraphViewFrom(std::vector<InputSlot*>{ &m5->GetInputSlot(0), & m6->GetInputSlot(0) },
628  std::vector<OutputSlot*>{},
629  { m5, m6 });
630 
631  BOOST_TEST(subgraphs.size() == 2);
632  if (subgraphs.size() == 2)
633  {
634  // we need to have valid subgraph pointers here
635  BOOST_TEST((subgraphs[0] != nullptr));
636  BOOST_TEST((subgraphs[1] != nullptr));
637 
638  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr)
639  {
640  // sort the subgraphs by layer size, so it is simpler to test
641  std::sort(subgraphs.begin(), subgraphs.end(),
643  {
644  return (lhs->GetLayers().size() < rhs->GetLayers().size());
645  }
646  );
647 
648  BOOST_TEST(subgraphs[0]->GetLayers().size() == 2);
649  BOOST_TEST(subgraphs[1]->GetLayers().size() == 5);
650 
651  CompareSubgraphViews(subgraphs[0], smallerSubgraph);
652  CompareSubgraphViews(subgraphs[1], largerSubgraph);
653  }
654  }
655 }
656 
657 BOOST_AUTO_TEST_CASE(MultipleSimpleSubgraphs)
658 {
659  // This test case represents the scenario when we have two distinct subgraphs
660  // in a simple linear network. The selected nodes are the M* and the
661  // non-selected ones are the X*
662  //
663  // X1 -> M1 -> M2 -> X2 -> M3 -> X3
664  //
665  // The expected results is two subgraphs, one with {M1, M2} and another one
666  // with {M3}
667  //
668  Graph graph;
669 
670  // the graph is constructed in reverse order
671  auto x3 = graph.AddLayer<OutputLayer>(0, "output");
672  auto m3 = graph.InsertNewLayer<ActivationLayer>(x3->GetInputSlot(0),
674  "m3");
675  auto x2 = graph.InsertNewLayer<Convolution2dLayer>(m3->GetInputSlot(0),
677  "x2");
678  auto m2 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0),
680  "m2");
681  auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
683  "m1");
684  graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "x1");
685 
686  // All selected 'M*' layers will be of Activation type
689  graph,
690  // select the middle layers only
691  [](const Layer & l)
692  {
693  bool toSelect = (l.GetType() == LayerType::Activation);
694  return toSelect;
695  });
696 
697  // expected results to test against
698  auto largerSubgraph = CreateSubgraphViewFrom(CreateInputsFrom({m1}),
699  CreateOutputsFrom({m2}),
700  {m1, m2});
701 
702  auto smallerSubgraph = CreateSubgraphViewFrom(CreateInputsFrom({m3}),
703  CreateOutputsFrom({m3}),
704  {m3});
705 
706  BOOST_TEST(subgraphs.size() == 2);
707  if (subgraphs.size() == 2)
708  {
709  // we need to have valid subgraph pointers here
710  BOOST_TEST((subgraphs[0] != nullptr));
711  BOOST_TEST((subgraphs[1] != nullptr));
712 
713  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr)
714  {
715  // sort the subgraphs by layer size, so it is simpler to test
716  std::sort(subgraphs.begin(), subgraphs.end(),
718  {
719  return (lhs->GetLayers().size() < rhs->GetLayers().size());
720  }
721  );
722 
723  BOOST_TEST(subgraphs[0]->GetLayers().size() == 1);
724  BOOST_TEST(subgraphs[1]->GetLayers().size() == 2);
725 
726  CompareSubgraphViews(subgraphs[0], smallerSubgraph);
727  CompareSubgraphViews(subgraphs[1], largerSubgraph);
728  }
729  }
730 }
731 
732 BOOST_AUTO_TEST_CASE(SimpleLinearTest)
733 {
734  //X1 -> M1 -> M2 -> X2
735  //Where the input slots of M1 and the output slots of M2 are to be the sub graph boundaries.
736  Graph graph;
737 
738  ActivationDescriptor activationDefaults;
739 
740  auto layerX1 = graph.AddLayer<InputLayer>(0, "layerX1");
741  auto layerX2 = graph.AddLayer<OutputLayer>(0, "layerX2");
742  auto layerM1 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM1");
743  auto layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2");
744 
745  // X1
746  // |
747  // M1
748  // |
749  // M2
750  // |
751  // X2
752 
753  layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
754  layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
755  layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0));
756 
759  graph,
760  // select the activation layers M1 and M2
761  [](const Layer & l)
762  {
763  bool toSelect = (l.GetType() == LayerType::Activation);
764  return toSelect;
765  });
766 
767  BOOST_CHECK(subgraphs.size() == 1);
768  if(subgraphs.size() == 1)
769  {
770  auto expected = CreateSubgraphViewFrom(CreateInputsFrom({layerM1}),
771  CreateOutputsFrom({layerM2}),
772  {layerM1, layerM2});
773 
774  CompareSubgraphViews(subgraphs[0], expected);
775  }
776 }
777 
778 BOOST_AUTO_TEST_CASE(MultiInputSingleOutput)
779 {
780  //X1 -> M1 -> M3 -> X3
781  //X2 -> M2 -> M3 -> X3
782  //Where the input slots of {M1, M2} and the output slots of M3 are to be the subgraph boundaries.
783  Graph graph;
784 
785  ActivationDescriptor activationDefaults;
786 
787  auto layerX1 = graph.AddLayer<InputLayer>(0, "layerX1");
788  auto layerX2 = graph.AddLayer<InputLayer>(1, "layerX2");
789  auto layerM1 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM1");
790  auto layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2");
791  auto layerM3 = graph.AddLayer<AdditionLayer>("layerM3");
792  auto layerX3 = graph.AddLayer<OutputLayer>(0, "layerX3");
793 
794  // X1 X2
795  // | |
796  // M1 M2
797  // \ |
798  // \ |
799  // \|
800  // M3
801  // |
802  // |
803  // X3
804 
805  layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
806  layerX2->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
807  layerM1->GetOutputSlot(0).Connect(layerM3->GetInputSlot(0));
808  layerM2->GetOutputSlot(0).Connect(layerM3->GetInputSlot(1));
809  layerM3->GetOutputSlot(0).Connect(layerX3->GetInputSlot(0));
810 
813  graph,
814  // select Activation and Addition Layers M1, M2 and M3
815  [](const Layer & l)
816  {
817  bool toSelect = (l.GetType() == LayerType::Activation
818  || l.GetType() == LayerType::Addition);
819  return toSelect;
820  });
821 
822  BOOST_CHECK(subgraphs.size() == 1);
823  if (subgraphs.size() == 1)
824  {
825  auto expected = CreateSubgraphViewFrom(CreateInputsFrom({layerM1, layerM2}),
826  CreateOutputsFrom({layerM3}),
827  {layerM1, layerM2, layerM3});
828 
829  CompareSubgraphViews(subgraphs[0], expected);
830  }
831 }
832 
833 BOOST_AUTO_TEST_CASE(SingleInputMultiOutput)
834 {
835  //X1 -> M1 -> M2 -> X2
836  //X1 -> M1 -> M3 -> X3
837  //Where the input slots of M1 and the output slots of {M2, M3} are to be the subgraph boundaries.
838  Graph graph;
839 
840  ActivationDescriptor activationDefaults;
841  ViewsDescriptor viewDefaults(2,4);
842 
843  Layer* layerX1 = graph.AddLayer<InputLayer>(0, "layerX1");
844  Layer* layerM1 = graph.AddLayer<SplitterLayer>(viewDefaults, "layerM1");
845  Layer* layerM2 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM2");
846  Layer* layerM3 = graph.AddLayer<ActivationLayer>(activationDefaults, "layerM3");
847  Layer* layerX2 = graph.AddLayer<OutputLayer>(0, "layerX2");
848  Layer* layerX3 = graph.AddLayer<OutputLayer>(1, "layerX3");
849 
850  // X1
851  // |
852  // M1
853  // /|
854  // / |
855  // / |
856  // M2 M3
857  // | |
858  // | |
859  // X2 X3
860 
861  layerX1->GetOutputSlot(0).Connect(layerM1->GetInputSlot(0));
862  layerM1->GetOutputSlot(0).Connect(layerM2->GetInputSlot(0));
863  layerM1->GetOutputSlot(1).Connect(layerM3->GetInputSlot(0));
864  layerM2->GetOutputSlot(0).Connect(layerX2->GetInputSlot(0));
865  layerM3->GetOutputSlot(0).Connect(layerX3->GetInputSlot(0));
866 
869  graph,
870  // select Activation and Splitter Layers M1, M2 and M3
871  [](const Layer & l)
872  {
873  bool toSelect = (l.GetType() == LayerType::Activation
874  || l.GetType() == LayerType::Splitter);
875  return toSelect;
876  });
877 
878  BOOST_CHECK(subgraphs.size() == 1);
879  if(subgraphs.size() == 1)
880  {
881  auto expected = CreateSubgraphViewFrom(CreateInputsFrom({layerM1}),
882  CreateOutputsFrom({layerM2, layerM3}),
883  {layerM1, layerM2, layerM3});
884 
885  CompareSubgraphViews(subgraphs[0], expected);
886  }
887 }
888 
889 BOOST_AUTO_TEST_CASE(MultiInputMultiOutput)
890 {
891  // This case represents the scenario with multiple inputs and multiple outputs
892  //
893  // X1 -> M1 -> M3 -> M4 -> X3
894  // X2 -> M2 -> M3 -> M5 -> X4
895  //
896  // Where the input slots of {M1, M2} and the output slots of {M4, M5} are to be the subgraph
897  // boundaries.
898 
899  Graph graph;
900 
901  ActivationDescriptor activationDefaults;
902  OriginsDescriptor concatDescriptor(2);
903 
904  auto x1 = graph.AddLayer<InputLayer>(0, "x1");
905  auto x2 = graph.AddLayer<InputLayer>(1, "x2");
906 
907  auto m1 = graph.AddLayer<ActivationLayer>(activationDefaults, "m1");
908  auto m2 = graph.AddLayer<ActivationLayer>(activationDefaults, "m2");
909  auto m3 = graph.AddLayer<ConcatLayer>(concatDescriptor, "m3");
910 
911  auto m4 = graph.AddLayer<ActivationLayer>(activationDefaults, "m4");
912  auto m5 = graph.AddLayer<ActivationLayer>(activationDefaults, "m5");
913 
914  auto x3 = graph.AddLayer<OutputLayer>(0, "x3");
915  auto x4 = graph.AddLayer<OutputLayer>(1, "x4");
916 
917  x1->GetOutputSlot(0).Connect(m1->GetInputSlot(0));
918  x2->GetOutputSlot(0).Connect(m2->GetInputSlot(0));
919 
920  m1->GetOutputSlot(0).Connect(m3->GetInputSlot(0));
921  m2->GetOutputSlot(0).Connect(m3->GetInputSlot(1));
922 
923  m3->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
924  m3->GetOutputSlot(0).Connect(m5->GetInputSlot(0));
925 
926  m4->GetOutputSlot(0).Connect(x3->GetInputSlot(0));
927  m5->GetOutputSlot(0).Connect(x4->GetInputSlot(0));
928 
929 
932  graph,
933  // select Activation and Concat Layers M1, M2, M3, M4, M5
934  [](const Layer & l)
935  {
936  bool toSelect = (l.GetType() == LayerType::Activation
937  || l.GetType() == LayerType::Concat);
938  return toSelect;
939  });
940 
941 
942  BOOST_CHECK(subgraphs.size() == 1);
943  if (subgraphs.size() == 1)
944  {
945  auto expected = CreateSubgraphViewFrom(CreateInputsFrom({m1, m2}),
946  CreateOutputsFrom({m4, m5}),
947  {m1, m2, m3, m4, m5});
948 
949  CompareSubgraphViews(subgraphs[0], expected);
950  }
951 }
952 
954 {
955  // Checks that a node that has multiple choices for merge candidates (M3 in this case) correctly merges with the
956  // one that it can (M0), and doesn't merge with the ones it can't (X2 and M2).
957  //
958  // X1
959  // |
960  // M1
961  // / \'
962  // X2 M2 M0
963  // \ | /
964  // M3
965  //
966  Graph graph;
967 
968  ActivationDescriptor activationDefaults;
969  OriginsDescriptor concatDescriptor(3);
970 
971  auto x1 = graph.AddLayer<InputLayer>(0, "x1");
972  auto x2 = graph.AddLayer<ActivationLayer>(activationDefaults, "x2");
973  auto m0 = graph.AddLayer<InputLayer>(1, "m0");
974  auto m1 = graph.AddLayer<ActivationLayer>(activationDefaults, "m1");
975  auto m2 = graph.AddLayer<ActivationLayer>(activationDefaults, "m2");
976  auto m3 = graph.AddLayer<ConcatLayer>(concatDescriptor, "m3");
977 
978  x1->GetOutputSlot(0).Connect(m1->GetInputSlot(0));
979  m1->GetOutputSlot(0).Connect(x2->GetInputSlot(0));
980  m1->GetOutputSlot(0).Connect(m2->GetInputSlot(0));
981  x2->GetOutputSlot(0).Connect(m3->GetInputSlot(0));
982  m2->GetOutputSlot(0).Connect(m3->GetInputSlot(1));
983  m0->GetOutputSlot(0).Connect(m3->GetInputSlot(2));
984 
986  graph,
987  [](const Layer& l) {
988  return std::string(l.GetName())[0] == 'm';
989  });
990 
991  // expected results to test against
992  auto expectedSubgraph0 =
994  CreateInputsFrom({ m1 }),
995  std::vector<OutputSlot*>{ &m1->GetOutputSlot(0), &m2->GetOutputSlot(0) },
996  { m1, m2 });
997 
998  auto expectedSubgraph1 = CreateSubgraphViewFrom(
999  std::vector<InputSlot*>{ &m3->GetInputSlot(0), & m3->GetInputSlot(1) },
1000  CreateOutputsFrom({ }),
1001  { m0, m3 });
1002 
1003  BOOST_TEST(subgraphs.size() == 2);
1004  if (subgraphs.size() == 2)
1005  {
1006  // we need to have valid subgraph pointers here
1007  BOOST_TEST((subgraphs[0] != nullptr));
1008  BOOST_TEST((subgraphs[1] != nullptr));
1009 
1010  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr)
1011  {
1012  if (subgraphs[0]->GetInputSlots().size() == 1)
1013  {
1014  CompareSubgraphViews(subgraphs[0], expectedSubgraph0);
1015  CompareSubgraphViews(subgraphs[1], expectedSubgraph1);
1016  }
1017  else
1018  {
1019  CompareSubgraphViews(subgraphs[0], expectedSubgraph1);
1020  CompareSubgraphViews(subgraphs[1], expectedSubgraph0);
1021  }
1022  }
1023  }
1024 }
1025 
1026 BOOST_AUTO_TEST_CASE(PropagatedDependencies)
1027 {
1028  // Version of IslandInTheMiddle with longer chain
1029  // to make sure antecedents are propagated.
1030  /*
1031  M0
1032  / \
1033  M1 M4
1034  | |
1035  M2 X1 < the island in the middle !
1036  | |
1037  | M10
1038  | |
1039  | X2 < another island in the middle !
1040  | |
1041  M3 M5
1042  \ /
1043  M6
1044  */
1045  Graph graph;
1046 
1047  OriginsDescriptor concatDescriptor(2);
1048  auto m6 = graph.AddLayer<ConcatLayer>(concatDescriptor, "m6");
1049  auto m3 = graph.InsertNewLayer<ActivationLayer>(m6->GetInputSlot(0),
1051  "m3");
1052  auto m2 = graph.InsertNewLayer<ActivationLayer>(m3->GetInputSlot(0),
1054  "m2");
1055  auto m1 = graph.InsertNewLayer<ActivationLayer>(m2->GetInputSlot(0),
1057  "m1");
1058  auto m0 = graph.InsertNewLayer<InputLayer>(m1->GetInputSlot(0), 0, "m0");
1059 
1060  auto m5 = graph.InsertNewLayer<ActivationLayer>(m6->GetInputSlot(1),
1062  "m5");
1063  auto x2 = graph.InsertNewLayer<ActivationLayer>(m5->GetInputSlot(0), ActivationDescriptor{}, "x2");
1064  auto m10 = graph.InsertNewLayer<ActivationLayer>(x2->GetInputSlot(0), ActivationDescriptor{}, "m10");
1065  auto x1 = graph.InsertNewLayer<ActivationLayer>(m10->GetInputSlot(0),
1067  "x1");
1068  auto m4 = graph.InsertNewLayer<ActivationLayer>(x1->GetInputSlot(0),
1070  "m4");
1071 
1072  // Connect the other branch to the input layer
1073  m0->GetOutputSlot(0).Connect(m4->GetInputSlot(0));
1074 
1075  // All selected 'M*' layers will be of Activation type
1078  graph,
1079  // select the middle layers only
1080  [](const Layer& l)
1081  {
1082  bool toSelect = std::string(l.GetName())[0] == 'm';
1083  return toSelect;
1084  });
1085 
1086  // expected results to test against
1087  auto largerSubgraph = CreateSubgraphViewFrom(CreateInputsFrom({ m0 }),
1088  CreateOutputsFrom({ m3, m4 }),
1089  { m0, m1, m2, m3, m4 });
1090 
1091  auto mediumSubgraph = CreateSubgraphViewFrom(std::vector<InputSlot*>{ &m5->GetInputSlot(0), &m6->GetInputSlot(0) },
1092  std::vector<OutputSlot*>{}, { m5, m6 });
1093 
1094  auto smallerSubgraph =
1095  CreateSubgraphViewFrom(CreateInputsFrom({ m10 }), CreateOutputsFrom({ m10 }), { m10 });
1096 
1097  BOOST_TEST(subgraphs.size() == 3);
1098  if (subgraphs.size() == 3)
1099  {
1100  // we need to have valid subgraph pointers here
1101  BOOST_TEST((subgraphs[0] != nullptr));
1102  BOOST_TEST((subgraphs[1] != nullptr));
1103  BOOST_TEST((subgraphs[2] != nullptr));
1104 
1105  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr && subgraphs[2].get() != nullptr)
1106  {
1107  // sort the subgraphs by layer size, so it is simpler to test
1108  std::sort(subgraphs.begin(), subgraphs.end(),
1110  {
1111  return (lhs->GetLayers().size() < rhs->GetLayers().size());
1112  }
1113  );
1114 
1115  CompareSubgraphViews(subgraphs[0], smallerSubgraph);
1116  CompareSubgraphViews(subgraphs[1], mediumSubgraph);
1117  CompareSubgraphViews(subgraphs[2], largerSubgraph);
1118  }
1119  }
1120 }
1121 
1123 {
1124  // Creates random networks, splits them into subgraphs and checks the resulting subgraphs obey the required
1125  // dependency rules. We can easily generate very large networks which helps cover corner cases the other
1126  // small, manually crafted tests have missed. We can also use this to measure performance on large networks.
1127  constexpr bool debug = false; // Enable this to dump dot files and performance timings.
1128 
1129  std::mt19937 randomGenerator;
1130 
1131  // Helper function to get a random number in [0, maxExclusive)
1132  auto GetRandom = [&randomGenerator](auto maxExclusive) {
1133  // Note we could use uniform_int_distribution here, but that gives inconsistent results across platforms
1134  // which makes it harder to reproduce results.
1135  // It appears that uniform_real_distribution is consistent across MSVC and gcc so we use that and round it.
1136  std::uniform_real_distribution<float> uniform(0.0f, 1.0f);
1137  return static_cast<decltype(maxExclusive)>(uniform(randomGenerator) * static_cast<float>(maxExclusive));
1138  };
1139  // Helper function to get a bool that has probability 'trueProb' of being true.
1140  auto GetRandomFlag = [&randomGenerator](float trueProb) {
1141  std::uniform_real_distribution<float> uniform(0.0f, 1.0f);
1142  return uniform(randomGenerator) < trueProb;
1143  };
1144 
1145  constexpr uint32_t numTests = 100;
1146  for (uint32_t testIdx = 0; testIdx < numTests; ++testIdx)
1147  {
1148  randomGenerator.seed(testIdx); // Set a deterministic seed for reproducibility.
1149 
1150  // Create random graph
1151  Graph graph;
1152  {
1153  // First add the layers, without any connections. The following random constants determine the number of
1154  // each layer to add, along with the chance that each layer will be 'supported' (i.e. selected for
1155  // inclusion in the resulting subgraphs).
1156  uint32_t numInputs = 1 + GetRandom(4u);
1157  uint32_t numConstants = 1 + GetRandom(4u);
1158  uint32_t numOutputs = 1 + GetRandom(4u);
1159  uint32_t numConcats = 0 + GetRandom(500u);
1160  uint32_t numSplits = 0 + GetRandom(500u);
1161  float supportedProb = 0.7f;
1162 
1163  for (uint32_t i = 0; i < numInputs; ++i)
1164  {
1165  std::string name = "input" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
1166  graph.AddLayer<InputLayer>(static_cast<LayerBindingId>(i), name.c_str());
1167  }
1168  for (uint32_t i = 0; i < numConstants; ++i)
1169  {
1170  std::string name = "constant" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
1171  graph.AddLayer<ConstantLayer>(name.c_str());
1172  }
1173  for (uint32_t i = 0; i < numOutputs; ++i)
1174  {
1175  std::string name = "output" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
1176  graph.AddLayer<OutputLayer>(static_cast<LayerBindingId>(i), name.c_str());
1177  }
1178  for (uint32_t i = 0; i < numConcats; ++i)
1179  {
1180  std::string name = "concat" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
1181  uint32_t numInputs = 1 + GetRandom(3u);
1182  OriginsDescriptor concatDesc(numInputs);
1183  graph.AddLayer<ConcatLayer>(concatDesc, name.c_str());
1184  }
1185  for (uint32_t i = 0; i < numSplits; ++i)
1186  {
1187  std::string name = "split" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
1188  uint32_t numOutputs = 1 + GetRandom(3u);
1189  ViewsDescriptor splitDesc(numOutputs);
1190  graph.AddLayer<SplitterLayer>(splitDesc, name.c_str());
1191  }
1192 
1193  // Associate each layer with a "depth" parameter. This is used when creating connections to ensure
1194  // that we don't have any loops, by only connecting to layers with a lower "depth".
1195  // This can be thought of as distance from the "top" of the graph (assuming the graph flows top-to-bottom).
1196  // Unfortunately this approach ends up producing very "wide" graphs,
1197  // which probably isn't very representative of 'real' networks.
1198  uint32_t maxLayerDepth = 5 + GetRandom(2000u);
1199  std::map<Layer*, uint32_t> layerDepths;
1200  std::map<uint32_t, std::vector<Layer*>> layersAtDepth;
1201  for (Layer* layer : graph)
1202  {
1203  uint32_t depth;
1204  if (layer->GetType() == LayerType::Input || layer->GetType() == LayerType::Constant)
1205  {
1206  // There needs to be at least one input-like layer above everything else, otherwise would be
1207  // nothing for them to connect to!
1208  depth = 0;
1209  }
1210  else
1211  {
1212  // Other layers are randomly assigned to later depths.
1213  depth = 1 + GetRandom(maxLayerDepth);
1214  }
1215  layerDepths[layer] = depth;
1216  layersAtDepth[depth].push_back(layer);
1217  }
1218 
1219  // Connect layers to each other. Every input slot of every layer must be connected, but it doesn't
1220  // matter if an output slot goes unused.
1221  for (Layer* layer : graph)
1222  {
1223  for (uint32_t inputSlotIdx = 0; inputSlotIdx < layer->GetNumInputSlots(); ++inputSlotIdx)
1224  {
1225  InputSlot& inputSlot = layer->GetInputSlot(inputSlotIdx);
1226  uint32_t maxLayerDepthToConnectTo = layerDepths[layer]; // This prevents a connection causing a loop
1227  // Finding a layer to connect to may take multiple attempts, so keep trying until it works.
1228  while (inputSlot.GetConnectedOutputSlot() == nullptr)
1229  {
1230  uint32_t layerDepth = GetRandom(maxLayerDepthToConnectTo);
1231  const std::vector<Layer*>& layersToChooseFrom = layersAtDepth[layerDepth];
1232  if (layersToChooseFrom.size() == 0)
1233  {
1234  continue;
1235  }
1236  Layer* layerToConnectWith = layersToChooseFrom[GetRandom(layersToChooseFrom.size())];
1237  if (layerToConnectWith->GetNumOutputSlots() == 0)
1238  {
1239  continue;
1240  }
1241  uint32_t outputSlotIdx = GetRandom(layerToConnectWith->GetNumOutputSlots());
1242  layerToConnectWith->GetOutputSlot(outputSlotIdx).Connect(inputSlot);
1243  }
1244  }
1245  }
1246  }
1247 
1248  if (debug)
1249  {
1250  std::ofstream f("INPUT_" + std::to_string(testIdx) + ".dot");
1251  graph.SerializeToDot(f);
1252  }
1253 
1254  // Run the splitting algorithm, selecting all nodes ending in an 'S' (as randomly assigned above).
1255  auto startTime = std::chrono::high_resolution_clock::now();
1256 
1259  [](const Layer& l) { return std::string(l.GetName()).back() == 'S'; });
1260 
1261  auto endTime = std::chrono::high_resolution_clock::now();
1262  auto duration = std::chrono::duration_cast<std::chrono::microseconds>(endTime - startTime);
1263  if (debug)
1264  {
1265  std::cout << "Test " << testIdx << ": " << duration.count() << " microseconds" << std::endl;
1266  }
1267 
1268  // Build a map of which subgraph is assigned to each layer.
1269  // This helps some of the following code.
1270  std::map<Layer*, SubgraphView*> layerToSubgraph;
1271  for (Layer* layer : graph)
1272  {
1273  size_t i = 0;
1274  for (std::unique_ptr<SubgraphView>& subgraph : subgraphs)
1275  {
1276  std::string name = std::to_string(i++);
1277  if (std::find(subgraph->begin(), subgraph->end(), layer) != subgraph->end())
1278  {
1279  layerToSubgraph[layer] = subgraph.get();
1280  break;
1281  }
1282  }
1283  }
1284 
1285  if (debug)
1286  {
1287  // Before dumping the dot file, set each Layer's BackendId property so that the dot file
1288  // shows the resulting subgraph assignments.
1289  for (Layer* layer : graph)
1290  {
1291  std::string name = "NotAssigned";
1292  auto subgraphIt = layerToSubgraph.find(layer);
1293  if (subgraphIt != layerToSubgraph.end())
1294  {
1295  auto subgraphIdx = std::distance(subgraphs.begin(),
1296  std::find_if(subgraphs.begin(), subgraphs.end(),
1297  [&](auto& s) { return s.get() == subgraphIt->second; }));
1298  name = std::to_string(subgraphIdx);
1299  }
1300  layer->SetBackendId(armnn::BackendId(name));
1301  }
1302 
1303  std::ofstream f("GRAPH_" + std::to_string(testIdx) + ".dot");
1304  graph.SerializeToDot(f);
1305  }
1306 
1307  // Check the dependencies between subgraphs to make sure that the algorithm has produced a valid result.
1308  // Starting from each of the input slots of each subgraph, recurse up the graph and ensure that we never
1309  // encounter a layer that belongs to the subgraph that we started from.
1310  for (std::unique_ptr<SubgraphView>& subgraph : subgraphs)
1311  {
1312  for (InputSlot* inputSlot : subgraph->GetInputSlots())
1313  {
1314  std::queue<Layer*> toProcess;
1315  toProcess.push(&inputSlot->GetConnectedOutputSlot()->GetOwningLayer());
1316  while (toProcess.size() > 0)
1317  {
1318  Layer* l = toProcess.front();
1319  toProcess.pop();
1320 
1321  BOOST_CHECK(layerToSubgraph[l] != subgraph.get());
1322 
1323  for (const InputSlot& is : l->GetInputSlots())
1324  {
1325  toProcess.push(&is.GetConnectedOutputSlot()->GetOwningLayer());
1326  }
1327  }
1328  }
1329  }
1330  }
1331 }
1332 
1334 
1335 BOOST_AUTO_TEST_SUITE(IntegrationTests)
1336 
1337 BOOST_AUTO_TEST_CASE(SingleSubgraph)
1338 {
1339  // This test case represents the scenario when we have one subgraph
1340  // in which two layers have GpuAcc backend assigned
1341 
1342  //Construct graph
1343  Graph graph;
1344 
1345  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
1346 
1347  Convolution2dDescriptor convDescriptor;
1348  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
1349  convLayer1->SetBackendId(Compute::GpuAcc);
1350 
1351  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
1352  convLayer2->SetBackendId(Compute::GpuAcc);
1353 
1354  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
1355 
1356  inputLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
1357  convLayer1->GetOutputSlot(0).Connect(convLayer2->GetInputSlot(0));
1358  convLayer2->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
1359 
1360  // GpuAcc sub graph selector
1363  graph,
1364  // select the GpuAcc layers only
1365  [](const Layer & l){
1366  bool toSelect = (l.GetBackendId() == Compute::GpuAcc);
1367  return toSelect;
1368  });
1369 
1370  BOOST_TEST(subgraphs.size() == 1);
1371  if(subgraphs.size() == 1)
1372  {
1373  BOOST_TEST((subgraphs[0] != nullptr));
1374 
1375  if (subgraphs[0].get() != nullptr)
1376  {
1377  unsigned int numInputSlots = boost::numeric_cast<unsigned int>(subgraphs[0]->GetInputSlots().size());
1378  unsigned int numOutputSlots = boost::numeric_cast<unsigned int>(subgraphs[0]->GetOutputSlots().size());
1379 
1380  BOOST_TEST((numInputSlots == 1));
1381  BOOST_TEST((numOutputSlots == 1));
1382 
1383  // Save sub-graph connections for comparison after substitution
1384  IOutputSlot* subgraphInputConn1 = subgraphs[0]->GetInputSlot(0)->GetConnection();
1385  IInputSlot* subgraphOutputConn1 = subgraphs[0]->GetOutputSlot(0)->GetConnection(0);
1386 
1387  // Construct dummy pre-compiled layer
1388  PreCompiledDescriptor preCompiledDescriptor(numInputSlots, numOutputSlots);
1389  Layer* const preCompiledLayer = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor, "pre-compiled");
1390 
1391  // Substitute sub-graph with pre-compiled layer
1392  graph.SubstituteSubgraph(*subgraphs[0], preCompiledLayer);
1393 
1394  // Check that connections are correct after substitution
1395  BOOST_CHECK_EQUAL(preCompiledLayer->GetInputSlot(0).GetConnection(), subgraphInputConn1);
1396 
1397  BOOST_CHECK_EQUAL(preCompiledLayer->GetOutputSlot(0).GetConnection(0), subgraphOutputConn1);
1398  }
1399  }
1400 }
1401 
1402 BOOST_AUTO_TEST_CASE(MultipleSubgraphs)
1403 {
1404  // This test case represents the scenario when we have two subgraphs
1405  // in which two layers have CpuAcc backend assigned
1406 
1407  //Construct graph
1408  Graph graph;
1409 
1410  Layer* const inputLayer = graph.AddLayer<InputLayer>(0, "input");
1411 
1412  ViewsDescriptor splitterDescriptor(2);
1413  Layer* const splitterLayer = graph.AddLayer<SplitterLayer>(splitterDescriptor, "splitter");
1414  splitterLayer->SetBackendId(Compute::CpuAcc);
1415 
1416  Convolution2dDescriptor convDescriptor;
1417  Layer* const convLayer1 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv1");
1418  Layer* const convLayer2 = graph.AddLayer<Convolution2dLayer>(convDescriptor, "conv2");
1419 
1420  OriginsDescriptor concatDescriptor(2);
1421  Layer* const pConcatLayer = graph.AddLayer<ConcatLayer>(concatDescriptor, "concat");
1422  pConcatLayer->SetBackendId(Compute::CpuAcc);
1423 
1424  Layer* const outputLayer = graph.AddLayer<OutputLayer>(0, "output");
1425 
1426  inputLayer->GetOutputSlot(0).Connect(splitterLayer->GetInputSlot(0));
1427  splitterLayer->GetOutputSlot(0).Connect(convLayer1->GetInputSlot(0));
1428  splitterLayer->GetOutputSlot(1).Connect(convLayer2->GetInputSlot(0));
1429  convLayer1->GetOutputSlot(0).Connect(pConcatLayer->GetInputSlot(0));
1430  convLayer2->GetOutputSlot(0).Connect(pConcatLayer->GetInputSlot(1));
1431  pConcatLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
1432 
1433  // CpuAcc sub graph selector
1436  graph,
1437  // select the CpuAcc layers only
1438  [](const Layer & l){
1439  bool toSelect = (l.GetBackendId() == Compute::CpuAcc);
1440  return toSelect;
1441  });
1442 
1443  BOOST_TEST(subgraphs.size() == 2);
1444  if(subgraphs.size() == 2)
1445  {
1446  BOOST_TEST((subgraphs[0] != nullptr));
1447  BOOST_TEST((subgraphs[1] != nullptr));
1448 
1449  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr)
1450  {
1451  //Sort subgraphs by their inputSlot size.
1452  std::sort(subgraphs.begin(), subgraphs.end(),
1454  {
1455  return (lhs->GetInputSlots().size() < rhs->GetInputSlots().size());
1456  }
1457  );
1458 
1459  unsigned int numInputSlots1 = boost::numeric_cast<unsigned int>(subgraphs[0]->GetInputSlots().size());
1460  unsigned int numOutputSlots1 = boost::numeric_cast<unsigned int>(subgraphs[0]->GetOutputSlots().size());
1461 
1462  unsigned int numInputSlots2 = boost::numeric_cast<unsigned int>(subgraphs[1]->GetInputSlots().size());
1463  unsigned int numOutputSlots2 = boost::numeric_cast<unsigned int>(subgraphs[1]->GetOutputSlots().size());
1464 
1465  // Save sub-graph connections for comparison after substitution
1466  IOutputSlot* subgraph1InputConn = subgraphs[0]->GetInputSlot(0)->GetConnection();
1467  IInputSlot* subgraph1OutputConn1 = subgraphs[0]->GetOutputSlot(0)->GetConnection(0);
1468  IInputSlot* subgraph1OutputConn2 = subgraphs[0]->GetOutputSlot(1)->GetConnection(0);
1469 
1470  // Save sub-graph connections for comparison after substitution
1471  IOutputSlot* subgraph2InputConn1 = subgraphs[1]->GetInputSlot(0)->GetConnection();
1472  IOutputSlot* subgraph2InputConn2 = subgraphs[1]->GetInputSlot(1)->GetConnection();
1473  IInputSlot* subgraph2OutputConn = subgraphs[1]->GetOutputSlot(0)->GetConnection(0);
1474 
1475  PreCompiledDescriptor preCompiledDescriptor1(numInputSlots1, numOutputSlots1);
1476  Layer* const preCompiledLayer1 = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor1, "pre-compiled1");
1477 
1478  PreCompiledDescriptor preCompiledDescriptor2(numInputSlots2, numOutputSlots2);
1479  Layer* const preCompiledLayer2 = graph.AddLayer<PreCompiledLayer>(preCompiledDescriptor2, "pre-compiled2");
1480 
1481  // Substitute sub-graph with pre-compiled layer
1482  graph.SubstituteSubgraph(*subgraphs[0], preCompiledLayer1);
1483  graph.SubstituteSubgraph(*subgraphs[1], preCompiledLayer2);
1484 
1485  // Check that connections are correct after substitution
1486  BOOST_CHECK_EQUAL(preCompiledLayer1->GetInputSlot(0).GetConnection(), subgraph1InputConn);
1487  BOOST_CHECK_EQUAL(preCompiledLayer1->GetOutputSlot(0).GetConnection(0), subgraph1OutputConn1);
1488  BOOST_CHECK_EQUAL(preCompiledLayer1->GetOutputSlot(1).GetConnection(0), subgraph1OutputConn2);
1489 
1490  BOOST_CHECK_EQUAL(preCompiledLayer2->GetInputSlot(0).GetConnection(), subgraph2InputConn1);
1491  BOOST_CHECK_EQUAL(preCompiledLayer2->GetInputSlot(1).GetConnection(), subgraph2InputConn2);
1492  BOOST_CHECK_EQUAL(preCompiledLayer2->GetOutputSlot(0).GetConnection(0), subgraph2OutputConn);
1493  }
1494  }
1495 }
1496 
1497 BOOST_AUTO_TEST_CASE(SubgraphCycles)
1498 {
1499  // This case represent the scenario when a naive split could lead to a cyclic dependency between two subgraphs
1500  //
1501  // X0 -> M0 -> X1 -> M2 -> X2
1502  // X0 -> M0 -> M1 -> M2 -> X2
1503  //
1504  /*
1505  X0
1506  |
1507  |
1508  M0
1509  / |
1510  / |
1511  X1 M1
1512  \ /
1513  M2
1514  |
1515  X2
1516  */
1517  // The expected result for this is that M0,M1 will be part of one subgraph and M2 in another and the
1518  // input and output slots in the subgraphs will be set accordingly.
1519  //
1520  Graph graph;
1521 
1522  OriginsDescriptor originsDescriptor(2);
1523  auto x0 = graph.AddLayer<InputLayer>(0, "x0");
1524  auto m0 = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "m0");
1525  auto x1 = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "x1");
1526  auto m1 = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "m1");
1527  auto m2 = graph.AddLayer<AdditionLayer>("m2");
1528  auto x2 = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "x2");
1529 
1530  x0->GetOutputSlot(0).Connect(m0->GetInputSlot(0));
1531  m0->GetOutputSlot(0).Connect(x1->GetInputSlot(0));
1532  m0->GetOutputSlot(0).Connect(m1->GetInputSlot(0));
1533  x1->GetOutputSlot(0).Connect(m2->GetInputSlot(0));
1534  m1->GetOutputSlot(0).Connect(m2->GetInputSlot(1));
1535  m2->GetOutputSlot(0).Connect(x2->GetInputSlot(0));
1536 
1537  // All selected 'M*' layers will be have 'm' in the name
1540  graph,
1541  // select the middle layers only
1542  [](const Layer & l)
1543  {
1544  bool toSelect = (l.GetNameStr().find('m') != std::string::npos);
1545  return toSelect;
1546  });
1547 
1548  // expected results to test against
1549  auto inputSubgraph = CreateSubgraphViewFrom(CreateInputsFrom({m0}),
1550  CreateOutputsFrom({m0, m1}),
1551  {m0, m1});
1552 
1553  auto outputSubgraph = CreateSubgraphViewFrom(CreateInputsFrom({m2}),
1554  CreateOutputsFrom({m2}),
1555  {m2});
1556 
1557  BOOST_TEST(subgraphs.size() == 2);
1558  if (subgraphs.size() == 2)
1559  {
1560  // we need to have valid subgraph pointers here
1561  BOOST_TEST((subgraphs[0] != nullptr));
1562  BOOST_TEST((subgraphs[1] != nullptr));
1563 
1564  if (subgraphs[0].get() != nullptr && subgraphs[1].get() != nullptr)
1565  {
1566  // sort the subgraphs by layer size, so it is simpler to test
1567  std::sort(subgraphs.begin(), subgraphs.end(),
1569  {
1570  return (lhs->GetLayers().size() < rhs->GetLayers().size());
1571  }
1572  );
1573 
1574  // one subgraph needs to be size=1 and the other one is 4
1575  BOOST_TEST(subgraphs[0]->GetLayers().size() == 1);
1576  BOOST_TEST(subgraphs[1]->GetLayers().size() == 2);
1577 
1578  CompareSubgraphViews(subgraphs[0], outputSubgraph);
1579  CompareSubgraphViews(subgraphs[1], inputSubgraph);
1580  }
1581  }
1582 }
1583 
A layer that the constant data can be bound to.
BOOST_AUTO_TEST_SUITE(TensorflowLiteParser)
Iterator begin()
Returns iterator pointing to the beginning of the list. Lowercase for range-based for loops...
Definition: Graph.hpp:159
This layer represents a split operation.
A ViewsDescriptor for the SplitterLayer.
Status SerializeToDot(std::ostream &stream)
Definition: Graph.cpp:80
LayerT * AddLayer(Args &&... args)
Adds a new layer, of type LayerType, to the graph constructed with the arguments passed.
Definition: Graph.hpp:398
A Convolution2dDescriptor for the Convolution2dLayer.
int Connect(InputSlot &destination)
Definition: Layer.cpp:79
std::vector< OutputSlot * > OutputSlots
This layer represents an activation operation with the specified activation function.
Copyright (c) 2020 ARM Limited.
void SetBackendId(const BackendId &id)
Definition: Layer.hpp:264
const std::vector< InputSlot > & GetInputSlots() const
Definition: Layer.hpp:231
virtual const IInputSlot * GetConnection(unsigned int index) const =0
const IOutputSlot * GetConnection() const override
Definition: Layer.hpp:199
unsigned int GetNumOutputSlots() const override
Returns the number of connectable output slots.
Definition: Layer.hpp:308
BOOST_CHECK(profilingService.GetCurrentState()==ProfilingState::WaitingForAck)
int LayerBindingId
Type of identifiers for bindable layers (inputs, outputs).
Definition: Types.hpp:171
The SubgraphView class represents a subgraph of a Graph.
const InputSlot & GetInputSlot(unsigned int index) const override
Get a const input slot handle by slot index.
Definition: Layer.hpp:310
A layer user-provided data can be bound to (e.g. inputs, outputs).
Definition: OutputLayer.hpp:13
An output connection slot for a layer.
Definition: INetwork.hpp:37
SubgraphView::InputSlots CreateInputsFrom(const std::vector< Layer *> &layers)
An OriginsDescriptor for the ConcatLayer.
This layer represents a merge operation.
Definition: ConcatLayer.hpp:13
const std::string & GetNameStr() const
Definition: Layer.hpp:216
std::vector< SubgraphViewPtr > Subgraphs
const OutputSlot * GetConnectedOutputSlot() const
Definition: Layer.hpp:55
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
Definition: NumericCast.hpp:33
GPU Execution: OpenCL: ArmCompute.
BOOST_AUTO_TEST_CASE(CheckConvolution2dLayer)
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:20
const BackendId & GetBackendId() const
Definition: Layer.hpp:263
std::vector< InputSlot * > InputSlots
This layer represents an addition operation.
void SubstituteSubgraph(SubgraphView &subgraph, IConnectableLayer *substituteLayer)
Substitutes the given sub-graph with either a new layer or a new sub-graph.
Definition: Graph.cpp:395
const InputSlots & GetInputSlots() const
std::unique_ptr< SubgraphView > SubgraphViewPtr
BOOST_AUTO_TEST_SUITE_END()
SubgraphView::SubgraphViewPtr CreateSubgraphViewFrom(SubgraphView::InputSlots &&inputs, SubgraphView::OutputSlots &&outputs, SubgraphView::Layers &&layers)
SubgraphView::OutputSlots CreateOutputsFrom(const std::vector< Layer *> &layers)
static Subgraphs SelectSubgraphs(Graph &graph, const LayerSelectorFunction &selector)
Selects subgraphs from a graph based on the selector function and the algorithm.
const OutputSlots & GetOutputSlots() const
CPU Execution: NEON: ArmCompute.
A layer user-provided data can be bound to (e.g. inputs, outputs).
Definition: InputLayer.hpp:13
Iterator end()
Returns iterator pointing to the end of the list. Lowercase for range-based for loops.
Definition: Graph.hpp:161
const Layers & GetLayers() const
virtual const IOutputSlot * GetConnection() const =0
LayerType GetType() const
Definition: Layer.hpp:259
const OutputSlot & GetOutputSlot(unsigned int index=0) const override
Get the const output slot handle by slot index.
Definition: Layer.hpp:312
const char * GetName() const override
Returns the name of the layer.
Definition: Layer.hpp:305
This layer represents a convolution 2d operation.
A PreCompiledDescriptor for the PreCompiledLayer.
std::list< Layer * > Layers
size_t GetNumLayers() const
Definition: Graph.hpp:188
LayerT * InsertNewLayer(InputSlot &insertBefore, Args &&... args)
Inserts a new layer between the output slot currently connected to insertBefore and insertBefore itse...
Definition: Graph.hpp:410
An input connection slot for a layer.
Definition: INetwork.hpp:24
const InputSlot * GetConnection(unsigned int index) const override
Definition: Layer.cpp:46