xref: /aosp_15_r20/external/armnn/src/armnn/test/OptimizerTests.cpp (revision 89c4ff92f2867872bb9e2354d150bf0c8c502810)
1 //
2 // Copyright © 2017,2022 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include <TestUtils.hpp>
7 
8 #include <BackendSettings.hpp>
9 #include <Graph.hpp>
10 #include <Network.hpp>
11 #include <Optimizer.hpp>
12 
13 #include <armnn/BackendHelper.hpp>
14 #include <armnn/BackendRegistry.hpp>
15 #include <armnn/INetwork.hpp>
16 #include <armnn/StrategyBase.hpp>
17 
18 #include <armnn/utility/Assert.hpp>
19 #include <armnn/utility/PolymorphicDowncast.hpp>
20 #include <armnn/backends/IBackendInternal.hpp>
21 
22 #include <backendsCommon/LayerSupportBase.hpp>
23 #include <armnn/backends/TensorHandle.hpp>
24 
25 #include <doctest/doctest.h>
26 
27 using namespace armnn;
28 
29 namespace
30 {
31 
CreateLSTMLayerHelper(Graph & graph,bool CifgEnabled)32 void CreateLSTMLayerHelper(Graph &graph, bool CifgEnabled)
33 {
34     LstmDescriptor layerDesc;
35     layerDesc.m_ActivationFunc = 4;
36     layerDesc.m_ClippingThresCell = 0.2f;
37     layerDesc.m_ClippingThresProj = 0.4f;
38     layerDesc.m_CifgEnabled = CifgEnabled;
39     layerDesc.m_PeepholeEnabled = false;
40     layerDesc.m_ProjectionEnabled = false;
41 
42     LstmLayer* const layer = graph.AddLayer<LstmLayer>(layerDesc, "layer");
43     unsigned int batchSize = 3;
44     unsigned int inputSize = 2;
45     unsigned int numUnits = 4;
46     unsigned int outputSize = 4;
47 
48     layer->m_BasicParameters.m_InputToForgetWeights = std::make_unique<ScopedTensorHandle>
49             (TensorInfo({ numUnits, inputSize }, DataType::Float32));
50     layer->m_BasicParameters.m_InputToCellWeights = std::make_unique<ScopedTensorHandle>
51             (TensorInfo({ numUnits, inputSize }, DataType::Float32));
52     layer->m_BasicParameters.m_InputToOutputWeights = std::make_unique<ScopedTensorHandle>
53             (TensorInfo({ numUnits, inputSize }, DataType::Float32));
54     layer->m_BasicParameters.m_RecurrentToForgetWeights = std::make_unique<ScopedTensorHandle>
55             (TensorInfo({ numUnits, outputSize }, DataType::Float32));
56     layer->m_BasicParameters.m_RecurrentToCellWeights = std::make_unique<ScopedTensorHandle>
57             (TensorInfo({ numUnits, outputSize }, DataType::Float32));
58     layer->m_BasicParameters.m_RecurrentToOutputWeights = std::make_unique<ScopedTensorHandle>
59             (TensorInfo({ numUnits, outputSize }, DataType::Float32));
60     layer->m_BasicParameters.m_ForgetGateBias = std::make_unique<ScopedTensorHandle>
61             (TensorInfo({ numUnits }, DataType::Float32));
62     layer->m_BasicParameters.m_CellBias = std::make_unique<ScopedTensorHandle>
63             (TensorInfo({ numUnits }, DataType::Float32));
64     layer->m_BasicParameters.m_OutputGateBias = std::make_unique<ScopedTensorHandle>
65             (TensorInfo({ numUnits }, DataType::Float32));
66 
67     layer->m_BasicParameters.m_InputToForgetWeights->Allocate();
68     layer->m_BasicParameters.m_InputToCellWeights->Allocate();
69     layer->m_BasicParameters.m_InputToOutputWeights->Allocate();
70     layer->m_BasicParameters.m_RecurrentToForgetWeights->Allocate();
71     layer->m_BasicParameters.m_RecurrentToCellWeights->Allocate();
72     layer->m_BasicParameters.m_RecurrentToOutputWeights->Allocate();
73     layer->m_BasicParameters.m_ForgetGateBias->Allocate();
74     layer->m_BasicParameters.m_CellBias->Allocate();
75     layer->m_BasicParameters.m_OutputGateBias->Allocate();
76 
77     if (!layerDesc.m_CifgEnabled)
78     {
79         layer->m_CifgParameters.m_InputToInputWeights = std::make_unique<ScopedTensorHandle>
80                 (TensorInfo({ numUnits, inputSize }, DataType::Float32));
81         layer->m_CifgParameters.m_RecurrentToInputWeights = std::make_unique<ScopedTensorHandle>
82                 (TensorInfo({ numUnits, outputSize }, DataType::Float32));
83         layer->m_CifgParameters.m_InputGateBias = std::make_unique<ScopedTensorHandle>
84                 (TensorInfo({ numUnits }, DataType::Float32));
85         layer->m_CifgParameters.m_InputToInputWeights->Allocate();
86         layer->m_CifgParameters.m_RecurrentToInputWeights->Allocate();
87         layer->m_CifgParameters.m_InputGateBias->Allocate();
88     }
89 
90     if (layerDesc.m_ProjectionEnabled)
91     {
92         layer->m_ProjectionParameters.m_ProjectionWeights = std::make_unique<ScopedTensorHandle>
93                 (TensorInfo({ outputSize, numUnits }, DataType::Float32));
94         layer->m_ProjectionParameters.m_ProjectionBias = std::make_unique<ScopedTensorHandle>
95                 (TensorInfo({ outputSize }, DataType::Float32));
96         layer->m_ProjectionParameters.m_ProjectionWeights->Allocate();
97         layer->m_ProjectionParameters.m_ProjectionBias->Allocate();
98     }
99 
100     if (layerDesc.m_PeepholeEnabled)
101     {
102         if (!layerDesc.m_CifgEnabled)
103         {
104             layer->m_PeepholeParameters.m_CellToInputWeights = std::make_unique<ScopedTensorHandle>
105                     (TensorInfo({ numUnits }, DataType::Float32));
106             layer->m_PeepholeParameters.m_CellToInputWeights->Allocate();
107         }
108         layer->m_PeepholeParameters.m_CellToForgetWeights = std::make_unique<ScopedTensorHandle>
109                 (TensorInfo({ numUnits }, DataType::Float32));
110         layer->m_PeepholeParameters.m_CellToOutputWeights = std::make_unique<ScopedTensorHandle>
111                 (TensorInfo({ numUnits }, DataType::Float32));
112         layer->m_PeepholeParameters.m_CellToForgetWeights->Allocate();
113         layer->m_PeepholeParameters.m_CellToOutputWeights->Allocate();
114     }
115 
116     // create input and output layers
117     Layer* const input = graph.AddLayer<InputLayer>(0, "input");
118     Layer* const outputStateIn = graph.AddLayer<InputLayer>(1, "outputStateIn");
119     Layer* const cellStateIn = graph.AddLayer<InputLayer>(2, "cellStateIn");
120     Layer* const scratchBuffer = graph.AddLayer<OutputLayer>(0, "scratchBuffer");
121     Layer* const outputStateOut = graph.AddLayer<OutputLayer>(1, "outputStateOut");
122     Layer* const cellStateOut = graph.AddLayer<OutputLayer>(2, "cellStateOut");
123     Layer* const output = graph.AddLayer<OutputLayer>(3, "output");
124 
125     // connect up
126     armnn::TensorInfo lstmTensorInfo1({ batchSize, inputSize }, DataType::Float32);
127     armnn::TensorInfo lstmTensorInfo2({ batchSize, numUnits}, DataType::Float32);
128     armnn::TensorInfo lstmTensorInfo3({ batchSize, outputSize }, DataType::Float32);
129     armnn::TensorInfo lstmTensorInfoScratchBuff({ batchSize, numUnits * (layerDesc.m_CifgEnabled ? 3 : 4) },
130                                                 DataType::Float32);
131 
132     Connect(input, layer, lstmTensorInfo1, 0, 0);
133     Connect(cellStateIn, layer, lstmTensorInfo2, 0, 1);
134     Connect(outputStateIn, layer, lstmTensorInfo3, 0, 2);
135     Connect(layer, scratchBuffer, lstmTensorInfoScratchBuff, 0, 0);
136     Connect(layer, outputStateOut, lstmTensorInfo3, 1, 0);
137     Connect(layer, cellStateOut, lstmTensorInfo2, 2, 0);
138     Connect(layer, output, lstmTensorInfo3, 3, 0);
139 }
140 
141 
142 class MockLayerSupport : public LayerSupportBase
143 {
144 public:
IsLayerSupported(const LayerType & type,const std::vector<TensorInfo> & infos,const BaseDescriptor & descriptor,const Optional<LstmInputParamsInfo> &,const Optional<QuantizedLstmInputParamsInfo> &,Optional<std::string &> reasonIfUnsupported) const145     bool IsLayerSupported(const LayerType& type,
146                           const std::vector<TensorInfo>& infos,
147                           const BaseDescriptor& descriptor,
148                           const Optional<LstmInputParamsInfo>& /*lstmParamsInfo*/,
149                           const Optional<QuantizedLstmInputParamsInfo>& /*quantizedLstmParamsInfo*/,
150                           Optional<std::string&> reasonIfUnsupported) const override
151     {
152         switch (type)
153         {
154             case LayerType::Input:
155                 return IsInputSupported(infos[0], reasonIfUnsupported);
156             case LayerType::Output:
157                 return IsOutputSupported(infos[0], reasonIfUnsupported);
158             case LayerType::Activation:
159                 return IsActivationSupported(infos[0],
160                                              infos[1],
161                                              *(PolymorphicDowncast<const ActivationDescriptor*>(&descriptor)),
162                                              reasonIfUnsupported);
163             default:
164                 return false;
165         }
166     }
167 
IsInputSupported(const TensorInfo &,Optional<std::string &>) const168     bool IsInputSupported(const TensorInfo& /*input*/,
169                           Optional<std::string&> /*reasonIfUnsupported = EmptyOptional()*/) const override
170     {
171         return true;
172     }
173 
IsOutputSupported(const TensorInfo &,Optional<std::string &>) const174     bool IsOutputSupported(const TensorInfo& /*input*/,
175                            Optional<std::string&> /*reasonIfUnsupported = EmptyOptional()*/) const override
176     {
177         return true;
178     }
179 
IsActivationSupported(const TensorInfo &,const TensorInfo &,const ActivationDescriptor &,Optional<std::string &>) const180     bool IsActivationSupported(const TensorInfo& /*input0*/,
181                                const TensorInfo& /*output*/,
182                                const ActivationDescriptor& /*descriptor*/,
183                                Optional<std::string&> /*reasonIfUnsupported = EmptyOptional()*/) const override
184     {
185         return true;
186     }
187 };
188 
189 template <typename NamePolicy>
190 class CustomAllocatorBackend : public IBackendInternal
191 {
192 public:
CustomAllocatorBackend()193     CustomAllocatorBackend() :
194             m_BackendCapabilities(NamePolicy::GetIdStatic(), {{"NullCapability", false}}),
195             m_CustomAllocator(false) {};
CustomAllocatorBackend(const BackendCapabilities & capabilities)196     CustomAllocatorBackend(const BackendCapabilities& capabilities) :
197             m_BackendCapabilities(capabilities),
198             m_CustomAllocator(false) {};
199     ~CustomAllocatorBackend() = default;
200 
GetIdStatic()201     static const BackendId& GetIdStatic()
202     {
203         return NamePolicy::GetIdStatic();
204     }
GetId() const205     const BackendId& GetId() const override
206     {
207         return GetIdStatic();
208     }
209 
CreateMemoryManager() const210     IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override
211     {
212         return nullptr;
213     };
214 
215     IBackendInternal::IWorkloadFactoryPtr
CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr &) const216         CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr&) const override
217     {
218         return nullptr;
219     }
220 
CreateBackendContext(const IRuntime::CreationOptions &) const221     IBackendInternal::IBackendContextPtr CreateBackendContext(const IRuntime::CreationOptions&) const override
222     {
223         return nullptr;
224     }
225 
GetLayerSupport() const226     IBackendInternal::ILayerSupportSharedPtr GetLayerSupport() const override
227     {
228         return std::make_shared<MockLayerSupport>();
229     }
230 
OptimizeSubgraphView(const SubgraphView &) const231     OptimizationViews OptimizeSubgraphView(const SubgraphView&) const override
232     {
233         return {};
234     };
235 
GetCapabilities() const236     BackendCapabilities GetCapabilities() const override
237     {
238         return m_BackendCapabilities;
239     };
240 
UseCustomMemoryAllocator(std::shared_ptr<ICustomAllocator> allocator,armnn::Optional<std::string &> errMsg)241     virtual bool UseCustomMemoryAllocator(std::shared_ptr<ICustomAllocator> allocator,
242                                           armnn::Optional<std::string&> errMsg) override
243     {
244         IgnoreUnused(errMsg, allocator);
245         m_CustomAllocator = true;
246         return m_CustomAllocator;
247     }
248 
249     BackendCapabilities m_BackendCapabilities;
250     bool m_CustomAllocator;
251 };
252 
253 template <typename NamePolicy>
254 class NoProtectedModeMockBackend : public IBackendInternal
255 {
256 public:
NoProtectedModeMockBackend()257     NoProtectedModeMockBackend() : m_BackendCapabilities(NamePolicy::GetIdStatic(), {{"NullCapability", false}}) {};
NoProtectedModeMockBackend(const BackendCapabilities & capabilities)258     NoProtectedModeMockBackend(const BackendCapabilities& capabilities) : m_BackendCapabilities(capabilities) {};
259     ~NoProtectedModeMockBackend() = default;
260 
GetIdStatic()261     static const BackendId& GetIdStatic()
262     {
263         return NamePolicy::GetIdStatic();
264     }
GetId() const265     const BackendId& GetId() const override
266     {
267         return GetIdStatic();
268     }
269 
CreateMemoryManager() const270     IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override
271     {
272         return nullptr;
273     };
274 
275     IBackendInternal::IWorkloadFactoryPtr
CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr &) const276         CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr&) const override
277     {
278         return nullptr;
279     }
280 
CreateBackendContext(const IRuntime::CreationOptions &) const281     IBackendInternal::IBackendContextPtr CreateBackendContext(const IRuntime::CreationOptions&) const override
282     {
283         return nullptr;
284     }
285 
GetLayerSupport() const286     IBackendInternal::ILayerSupportSharedPtr GetLayerSupport() const override
287     {
288         return std::make_shared<MockLayerSupport>();
289     }
290 
OptimizeSubgraphView(const SubgraphView &) const291     OptimizationViews OptimizeSubgraphView(const SubgraphView&) const override
292     {
293         return {};
294     };
295 
GetCapabilities() const296     BackendCapabilities GetCapabilities() const override
297     {
298         return m_BackendCapabilities;
299     };
300 
301     BackendCapabilities m_BackendCapabilities;
302 };
303 
304 }    // namespace
305 
306 TEST_SUITE("Optimizer")
307 {
308 using namespace armnn::optimizations;
309 
310 TEST_CASE("LSTMValidateTensorShapesFromInputsCIFGDisabledTest")
311 {
312     Graph graph;
313 
314     //Helper function creates graph containing LSTM layer with required input and output layers
315     CreateLSTMLayerHelper(graph, false);
316 
317     //This function used to call ValidateShapesFromInputs();
318     CHECK_NOTHROW(graph.InferTensorInfos());
319 }
320 
321 TEST_CASE("LSTMValidateTensorShapesFromInputsCIFGEnabledTest")
322 {
323     Graph graph;
324 
325     //Helper function creates graph containing LSTM layer with required input and output layers
326     CreateLSTMLayerHelper(graph, true);
327 
328     //This function used to call ValidateShapesFromInputs();
329     CHECK_NOTHROW(graph.InferTensorInfos());
330 }
331 
332 TEST_CASE("InsertConvertersTest")
333 {
334     const armnn::TensorInfo info({ 1, 5, 2, 3 }, armnn::DataType::Float16);
335 
336     armnn::Graph graph;
337 
338     armnn::LayerBindingId inputId = 0;
339 
340     armnn::Layer* head = graph.AddLayer<armnn::OutputLayer>(0, "output");
341 
342     head = graph.InsertNewLayer<armnn::AdditionLayer>(head->GetInputSlot(0), "");
343     head->GetOutputHandler().SetTensorInfo(info);
344 
345     graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(1), inputId++, "")
346         ->GetOutputHandler().SetTensorInfo(info);
347 
348     head = graph.InsertNewLayer<armnn::FloorLayer>(head->GetInputSlot(0), "");
349     head->GetOutputHandler().SetTensorInfo(info);
350 
351     head = graph.InsertNewLayer<armnn::MemCopyLayer>(head->GetInputSlot(0), "");
352     head->GetOutputHandler().SetTensorInfo(info);
353 
354     graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(0), inputId++, "")
355         ->GetOutputHandler().SetTensorInfo(info);
356 
357     // Check graph layer sequence before inserting convert layers
358     CHECK(CheckSequence(graph.cbegin(),
359                              graph.cend(),
360                              &IsLayerOfType<armnn::InputLayer>,
361                              &IsLayerOfType<armnn::InputLayer>,
362                              &IsLayerOfType<armnn::MemCopyLayer>,
363                              &IsLayerOfType<armnn::FloorLayer>,
364                              &IsLayerOfType<armnn::AdditionLayer>,
365                              &IsLayerOfType<armnn::OutputLayer>));
366 
367     // Check layers have Float16 DataType
368     for (auto& layer : graph)
369     {
370         if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition)
371         {
372             ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16);
373             ARMNN_ASSERT(layer->GetDataType() == DataType::Float16);
374         }
375     }
376 
377     // Insert convert layers either side of unsupported layer
378     for (auto& layer : graph)
379     {
380         if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition)
381         {
382             InsertConvertFp16ToFp32LayersBefore(graph, *layer);
383             InsertConvertFp32ToFp16LayersAfter(graph, *layer);
384         }
385     }
386 
387     // Check layers have correct DataType after inserting convert layers
388     for (auto& layer : graph)
389     {
390         if (layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition)
391         {
392             ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32);
393             ARMNN_ASSERT(layer->GetDataType() == DataType::Float32);
394         }
395         else if (layer->GetType() == LayerType::ConvertFp16ToFp32)
396         {
397             ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32);
398             ARMNN_ASSERT(layer->GetDataType() == DataType::Float16);
399         }
400         else if (layer->GetType() == LayerType::ConvertFp32ToFp16)
401         {
402             ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16);
403             ARMNN_ASSERT(layer->GetDataType() == DataType::Float32);
404         }
405     }
406 
407     // Check sequence of layers after inserting convert layers
408     CHECK(CheckSequence(graph.cbegin(),
409                              graph.cend(),
410                              &IsLayerOfType<armnn::InputLayer>,
411                              &IsLayerOfType<armnn::InputLayer>,
412                              &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
413                              &IsLayerOfType<armnn::MemCopyLayer>,
414                              &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
415                              &IsLayerOfType<armnn::FloorLayer>,
416                              &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
417                              &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
418                              &IsLayerOfType<armnn::AdditionLayer>,
419                              &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
420                              &IsLayerOfType<armnn::OutputLayer>));
421 }
422 
CreateConvolution2dGraph(Graph & graph,const unsigned int * inputShape,const unsigned int * weightsShape,const unsigned int * outputShape,DataLayout dataLayout=DataLayout::NCHW)423 void CreateConvolution2dGraph(Graph &graph, const unsigned int* inputShape,
424                               const unsigned int* weightsShape, const unsigned int* outputShape,
425                               DataLayout dataLayout = DataLayout::NCHW)
426 {
427     armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
428     armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
429 
430     std::vector<float> weightsVector(90);
431     armnn::ConstTensor weights(
432             armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32, 0.0f, 0, true),
433             weightsVector);
434 
435     Convolution2dDescriptor desc;
436     desc.m_BiasEnabled = false;
437     desc.m_StrideX     = 1;
438     desc.m_StrideY     = 1;
439     desc.m_DataLayout  = dataLayout;
440 
441     Layer* input = graph.AddLayer<InputLayer>(0, "input");
442     input->GetOutputSlot().SetTensorInfo(inputInfo);
443 
444     ConstantLayer* weightsLayer = graph.AddLayer<ConstantLayer>("Weights");
445     weightsLayer->m_LayerOutput = std::make_shared<ScopedTensorHandle>(weights);
446     weightsLayer->GetOutputSlot(0).SetTensorInfo(weightsLayer->m_LayerOutput->GetTensorInfo());
447 
448     Convolution2dLayer* layer = graph.AddLayer<Convolution2dLayer>(desc, "conv2d");
449     layer->GetOutputSlot().SetTensorInfo(outputInfo);
450 
451     Layer* output = graph.AddLayer<OutputLayer>(0, "output");
452 
453     input->GetOutputSlot().Connect(layer->GetInputSlot(0));
454     layer->GetOutputSlot().Connect(output->GetInputSlot(0));
455     weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1));
456 }
457 
458 TEST_CASE("Conv2dValidateTensorShapesFromInputs")
459 {
460     Graph graph;
461     const unsigned int inputShape[] = { 1, 3, 8, 16 };
462     const unsigned int weightsShape[] = { 2, 3, 5, 3 };
463     const unsigned int outputShape[] = { 1, 2, 4, 14 };
464     CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape);
465 
466     CHECK_NOTHROW(graph.InferTensorInfos());
467 }
468 
469 TEST_CASE("Conv2dValidateTensorShapesFromInputsNhwc")
470 {
471     Graph graph;
472     const unsigned int inputShape[] = { 1, 8, 16, 3 };
473     const unsigned int weightsShape[] = { 2, 5, 3, 3 };
474     const unsigned int outputShape[] = { 1, 4, 14, 2 };
475     CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC);
476 
477     CHECK_NOTHROW(graph.InferTensorInfos());
478 }
479 
CreateDepthwiseConvolution2dGraph(Graph & graph,const unsigned int * inputShape,const unsigned int * weightsShape,const unsigned int * outputShape,DataLayout dataLayout=DataLayout::NCHW)480 void CreateDepthwiseConvolution2dGraph(Graph &graph, const unsigned int* inputShape,
481                                        const unsigned int* weightsShape, const unsigned int* outputShape,
482                                        DataLayout dataLayout = DataLayout::NCHW)
483 {
484     armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
485     armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
486     armnn::TensorInfo weightsInfo(TensorShape(4, weightsShape), armnn::DataType::Float32, 0.0f, 0, true);
487 
488     std::vector<float> weightsVector(18);
489     armnn::ConstTensor weights(weightsInfo, weightsVector);
490 
491     DepthwiseConvolution2dDescriptor desc;
492     desc.m_BiasEnabled = false;
493     desc.m_StrideX     = 1;
494     desc.m_StrideY     = 1;
495     desc.m_DataLayout  = dataLayout;
496 
497     InputLayer* input                  = graph.AddLayer<InputLayer>(0, "input");
498     DepthwiseConvolution2dLayer* layer = graph.AddLayer<DepthwiseConvolution2dLayer>(desc, "depthwiseConv2d");
499     ConstantLayer* weightsLayer        = graph.AddLayer<ConstantLayer>("weights");
500     OutputLayer* output                = graph.AddLayer<OutputLayer>(0, "output");
501 
502     input->GetOutputSlot().SetTensorInfo(inputInfo);
503     layer->GetOutputSlot().SetTensorInfo(outputInfo);
504     weightsLayer->GetOutputSlot().SetTensorInfo(weightsInfo);
505 
506     weightsLayer->m_LayerOutput = std::make_unique<armnn::ScopedTensorHandle>(weights);
507 
508     input->GetOutputSlot().Connect(layer->GetInputSlot(0));
509     weightsLayer->GetOutputSlot().Connect(layer->GetInputSlot(1));
510     layer->GetOutputSlot().Connect(output->GetInputSlot(0));
511 }
512 
513 TEST_CASE("DepthwiseConv2dValidateTensorShapesFromInputs")
514 {
515     Graph graph;
516     const unsigned int inputShape[] = { 1, 2, 3, 3 };
517     const unsigned int weightsShape[] = { 1, 3, 3, 2 };
518     const unsigned int outputShape[] = { 1, 2, 1, 1 };
519     CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape);
520 
521     CHECK_NOTHROW(graph.InferTensorInfos());
522 }
523 
524 TEST_CASE("DepthwiseConv2dValidateTensorShapesFromInputsNhwc")
525 {
526     Graph graph;
527     const unsigned int inputShape[] = { 1, 3, 3, 2 };
528     const unsigned int weightsShape[] = { 1, 3, 3, 2 };
529     const unsigned int outputShape[] = { 1, 1, 1, 2 };
530     CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC);
531 
532     CHECK_NOTHROW(graph.InferTensorInfos());
533 }
534 
CreatePooling2dGraph(Graph & graph,const unsigned int * inputShape,const unsigned int * outputShape,DataLayout dataLayout=DataLayout::NCHW)535 void CreatePooling2dGraph(Graph& graph, const unsigned int* inputShape,  const unsigned int* outputShape,
536                           DataLayout dataLayout = DataLayout::NCHW)
537 {
538     armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
539     armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
540 
541     Pooling2dDescriptor desc;
542     desc.m_PoolType  = armnn::PoolingAlgorithm::Average;
543     desc.m_PoolWidth = desc.m_PoolHeight = 100;
544     desc.m_StrideX = desc.m_StrideY = 5;
545     desc.m_PadLeft                  = 50;
546     desc.m_PadRight                 = 50;
547     desc.m_PadTop                   = 50;
548     desc.m_PadBottom                = 50;
549     desc.m_PaddingMethod            = armnn::PaddingMethod::Exclude;
550     desc.m_DataLayout               = dataLayout;
551 
552     Layer* input = graph.AddLayer<InputLayer>(0, "input");
553     input->GetOutputSlot().SetTensorInfo(inputInfo);
554 
555     Pooling2dLayer* layer = graph.AddLayer<Pooling2dLayer>(desc, "pooling2d");
556     layer->GetOutputSlot().SetTensorInfo(outputInfo);
557 
558     Layer* output = graph.AddLayer<OutputLayer>(0, "output");
559     input->GetOutputSlot().Connect(layer->GetInputSlot(0));
560     layer->GetOutputSlot().Connect(output->GetInputSlot(0));
561 }
562 
563 TEST_CASE("Pooling2dValidateTensorShapesFromInputs")
564 {
565     Graph graph;
566     const unsigned int inputShape[]  = { 5, 3, 52, 60 };
567     const unsigned int outputShape[] = { 5, 3, 11, 13 };
568     CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NCHW);
569 
570     CHECK_NOTHROW(graph.InferTensorInfos());
571 }
572 
573 TEST_CASE("Pooling2dValidateTensorShapesFromInputsNhwc")
574 {
575     Graph graph;
576     const unsigned int inputShape[]  = { 5, 52, 60, 3 };
577     const unsigned int outputShape[] = { 5, 11, 13, 3 };
578     CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NHWC);
579 
580     CHECK_NOTHROW(graph.InferTensorInfos());
581 }
582 
CreateResizeBilinearGraph(Graph & graph,const unsigned int * inputShape,const unsigned int * outputShape,DataLayout dataLayout=DataLayout::NCHW)583 void CreateResizeBilinearGraph(Graph& graph,
584                                const unsigned int* inputShape,
585                                const unsigned int* outputShape,
586                                DataLayout dataLayout = DataLayout::NCHW)
587 {
588     TensorInfo inputInfo(4, inputShape, DataType::Float32);
589     TensorInfo outputInfo(4, outputShape, DataType::Float32);
590 
591     ResizeDescriptor desc;
592     desc.m_Method       = ResizeMethod::Bilinear;
593     desc.m_TargetHeight = 3;
594     desc.m_TargetWidth  = 4;
595     desc.m_DataLayout   = dataLayout;
596 
597     Layer* input = graph.AddLayer<InputLayer>(0, "input");
598     input->GetOutputSlot().SetTensorInfo(inputInfo);
599 
600     ResizeLayer* layer = graph.AddLayer<ResizeLayer>(desc, "resizeBilinear");
601     layer->GetOutputSlot().SetTensorInfo(outputInfo);
602 
603     Layer* output = graph.AddLayer<OutputLayer>(0, "output");
604     input->GetOutputSlot().Connect(layer->GetInputSlot(0));
605     layer->GetOutputSlot().Connect(output->GetInputSlot(0));
606 }
607 
608 TEST_CASE("ResizeBilinearValidateTensorShapesFromInputs")
609 {
610     Graph graph;
611     const unsigned int inputShape[]  = { 1, 2, 4, 5 };
612     const unsigned int outputShape[] = { 1, 2, 3, 4 };
613     CreateResizeBilinearGraph(graph, inputShape, outputShape);
614 
615     CHECK_NOTHROW(graph.InferTensorInfos());
616 }
617 
618 TEST_CASE("ResizeBilinearValidateTensorShapesFromInputsNhwc")
619 {
620     Graph graph;
621     const unsigned int inputShape[]  = { 1, 4, 5, 2 };
622     const unsigned int outputShape[] = { 1, 3, 4, 2 };
623     CreateResizeBilinearGraph(graph, inputShape, outputShape, DataLayout::NHWC);
624 
625     CHECK_NOTHROW(graph.InferTensorInfos());
626 }
627 
CreateGatherGraph(Graph & graph,const armnn::TensorInfo & paramsInfo,const armnn::TensorInfo & indicesInfo,const armnn::TensorInfo & outputInfo)628 void CreateGatherGraph(Graph& graph,
629                        const armnn::TensorInfo& paramsInfo,
630                        const armnn::TensorInfo& indicesInfo,
631                        const armnn::TensorInfo& outputInfo)
632 {
633     Layer* input0 = graph.AddLayer<InputLayer>(0, "params");
634     input0->GetOutputSlot().SetTensorInfo(paramsInfo);
635 
636     Layer* input1 = graph.AddLayer<InputLayer>(1, "indices");
637     input1->GetOutputSlot().SetTensorInfo(indicesInfo);
638 
639     GatherDescriptor descriptor;
640     GatherLayer* layer = graph.AddLayer<GatherLayer>(descriptor, "gather");
641     layer->GetOutputSlot().SetTensorInfo(outputInfo);
642 
643     Layer* output = graph.AddLayer<OutputLayer>(0, "output");
644     input0->GetOutputSlot().Connect(layer->GetInputSlot(0));
645     input1->GetOutputSlot().Connect(layer->GetInputSlot(1));
646     layer->GetOutputSlot().Connect(output->GetInputSlot(0));
647 }
648 
649 TEST_CASE("GatherValidateTensorShapesFromInputs")
650 {
651     Graph graph;
652     armnn::TensorInfo paramsInfo({10, 5}, DataType::Float32);
653     armnn::TensorInfo indicesInfo({3}, DataType::Signed32);
654     armnn::TensorInfo outputInfo({3, 5}, DataType::Float32);
655 
656     CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo);
657 
658     CHECK_NOTHROW(graph.InferTensorInfos());
659 }
660 
661 TEST_CASE("GatherValidateTensorShapesFromInputs1DParams")
662 {
663     Graph graph;
664     armnn::TensorInfo paramsInfo({8}, DataType::Float32);
665     armnn::TensorInfo indicesInfo({5}, DataType::Signed32);
666     armnn::TensorInfo outputInfo( {5}, DataType::Float32);
667 
668     CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo);
669 
670     CHECK_NOTHROW(graph.InferTensorInfos());
671 }
672 
673 TEST_CASE("GatherValidateTensorShapesFromInputsMultiDimIndices")
674 {
675     Graph graph;
676     armnn::TensorInfo paramsInfo({3, 2, 5}, DataType::Float32);
677     armnn::TensorInfo indicesInfo({2, 2}, DataType::Signed32);
678     armnn::TensorInfo outputInfo({2, 2, 2, 5}, DataType::Float32);
679 
680     CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo);
681 
682     CHECK_NOTHROW(graph.InferTensorInfos());
683 }
684 
685 TEST_CASE("DetectionPostProcessValidateTensorShapes")
686 {
687     Graph graph;
688     armnn::TensorInfo boxEncodingsInfo({1, 10, 4}, DataType::QAsymmU8);
689     armnn::TensorInfo scoresInfo({1, 10, 4}, DataType::QAsymmU8);
690     std::vector<uint8_t> anchorsVector(40);
691     armnn::ConstTensor anchors(armnn::TensorInfo({10, 4}, armnn::DataType::QAsymmU8, 0.0f, 0, true), anchorsVector);
692 
693     armnn::TensorInfo detectionBoxesInfo({1, 3, 4}, DataType::QAsymmU8);
694     armnn::TensorInfo detectionScoresInfo({1, 3}, DataType::QAsymmU8);
695     armnn::TensorInfo detectionClassesInfo({1, 3}, DataType::QAsymmU8);
696     armnn::TensorInfo numDetectionInfo({1}, DataType::QAsymmU8);
697 
698     Layer* input0 = graph.AddLayer<InputLayer>(0, "boxEncodings");
699     input0->GetOutputSlot().SetTensorInfo(boxEncodingsInfo);
700 
701     Layer* input1 = graph.AddLayer<InputLayer>(1, "score");
702     input1->GetOutputSlot().SetTensorInfo(scoresInfo);
703 
704     DetectionPostProcessDescriptor descriptor;
705     descriptor.m_MaxDetections = 3;
706 
707     DetectionPostProcessLayer* layer = graph.AddLayer<DetectionPostProcessLayer>(descriptor, "detectionPostProcess");
708     layer->m_Anchors = std::make_unique<armnn::ScopedTensorHandle>(anchors);
709     layer->GetOutputSlot(0).SetTensorInfo(detectionBoxesInfo);
710     layer->GetOutputSlot(1).SetTensorInfo(detectionScoresInfo);
711     layer->GetOutputSlot(2).SetTensorInfo(detectionClassesInfo);
712     layer->GetOutputSlot(3).SetTensorInfo(numDetectionInfo);
713 
714     input0->GetOutputSlot().Connect(layer->GetInputSlot(0));
715     input1->GetOutputSlot().Connect(layer->GetInputSlot(1));
716 
717     CHECK_NOTHROW(graph.InferTensorInfos());
718 }
719 
720 TEST_CASE("BackendCapabilityTest")
721 {
722     BackendId backendId = "MockBackend";
723 
724     armnn::BackendOptions::BackendOption nonConstWeights{"NonConstWeights", true};
725 
726     // MockBackend does not support the NonConstWeights capability
727     CHECK(!armnn::HasCapability(nonConstWeights, backendId));
728     CHECK(!armnn::HasCapability("NonConstWeights", backendId));
729 
730     // MockBackend does not support the AsyncExecution capability
731     CHECK(!armnn::GetCapability("AsyncExecution", backendId).has_value());
732 }
733 
734 TEST_CASE("BackendHintTest")
735 {
736     class TestBackendAssignment : public StrategyBase<NoThrowStrategy>
737     {
738     public:
739 
ExecuteStrategy(const armnn::IConnectableLayer * layer,const armnn::BaseDescriptor & descriptor,const std::vector<armnn::ConstTensor> & constants,const char * name,const armnn::LayerBindingId id=0)740         void ExecuteStrategy(const armnn::IConnectableLayer* layer,
741                              const armnn::BaseDescriptor& descriptor,
742                              const std::vector<armnn::ConstTensor>& constants,
743                              const char* name,
744                              const armnn::LayerBindingId id = 0) override
745         {
746             armnn::IgnoreUnused(descriptor, constants, id, name);
747             switch (layer->GetType())
748             {
749                 case armnn::LayerType::Input:
750                 {
751                     auto inputLayer = PolymorphicDowncast<const InputLayer*>(layer);
752                     const auto connectedLayerBackendId = inputLayer->GetOutputSlot(0).GetOwningLayer().GetBackendId();
753                     CHECK((inputLayer->GetBackendId() == connectedLayerBackendId));
754                     break;
755                 }
756                 case armnn::LayerType::Output:
757                 {
758                     auto outputLayer = PolymorphicDowncast<const OutputLayer*>(layer);
759                     CHECK((outputLayer->GetBackendId() == "MockBackend"));
760                     break;
761                 }
762                 case armnn::LayerType::Activation:
763                 {
764                     auto activation = PolymorphicDowncast<const ActivationLayer*>(layer);
765                     CHECK((activation->GetBackendId() == "CustomBackend"));
766                     break;
767                 }
768                 default:
769                 {
770                     m_DefaultStrategy.Apply(GetLayerTypeAsCString(layer->GetType()));
771                 }
772             }
773         }
774     };
775 
776     struct CustomPolicy
777     {
GetIdStaticCustomPolicy778         static const BackendId& GetIdStatic()
779         {
780             static BackendId id = "CustomBackend";
781             return id;
782         }
783     };
784 
785     struct MockPolicy
786     {
GetIdStaticMockPolicy787         static const BackendId& GetIdStatic()
788         {
789             static BackendId id = "MockBackend";
790             return id;
791         }
792     };
793 
794     auto& backendRegistry = BackendRegistryInstance();
795 
__anonfee9358a0202() 796     backendRegistry.Register("MockBackend", []() { return std::make_unique<CustomAllocatorBackend<MockPolicy>>(); });
797 
798     backendRegistry.Register("CustomBackend",
__anonfee9358a0302() 799                              []() { return std::make_unique<CustomAllocatorBackend<CustomPolicy>>(); });
800 
801     // Define the network
802     auto network = INetwork::Create();
803     ActivationDescriptor desc;
804     desc.m_Function = ActivationFunction::Linear;
805 
806     std::unique_ptr<Graph> graph = std::make_unique<Graph>();
807     auto input                   = graph->AddLayer<InputLayer>(0, "input");
808     auto act                     = graph->AddLayer<ActivationLayer>(desc, "activation");
809     auto output                  = graph->AddLayer<OutputLayer>(0, "output");
810 
811     BackendId customBackendId("CustomBackend");
812     act->BackendSelectionHint(customBackendId);
813 
814     input->GetOutputSlot(0).Connect(act->GetInputSlot(0));
815     act->GetOutputSlot(0).Connect(output->GetInputSlot(0));
816 
817     OptimizedNetworkImpl optNet(std::move(graph));
818 
819     // Get the optimized graph
820     Graph& optGraph = optNet.GetGraph();
821 
822     std::vector<BackendId> prefs{ "MockBackend", "CustomBackend" };
823 
824     BackendIdSet availableBackends = { "CustomBackend", "MockBackend" };
825     DeviceSpec spec(availableBackends);
826 
827     BackendSettings backendSettings(prefs, spec);
828 
829     // Assign an available backend to each layer
830     Graph::Iterator firstLayer = optGraph.begin();
831     Graph::Iterator lastLayer  = optGraph.end();
832 
833     OptimizedNetworkImpl* optNetObjPtr = &optNet;
834     OptimizationResult res = AssignBackends(optNetObjPtr,
835                                             backendSettings,
836                                             firstLayer,
837                                             lastLayer,
838                                             EmptyOptional());
839 
840     CHECK(res.IsOk());
841 
842     TestBackendAssignment visitor;
843     for (auto it = firstLayer; it != lastLayer; ++it)
844     {
845         (*it)->ExecuteStrategy(visitor);
846     }
847     // Clean up the registry for the next test.
848     backendRegistry.Deregister("MockBackend");
849     backendRegistry.Deregister("CustomBackend");
850 }
851 
852 // Tests that OptimizeForExclusiveConnections works, fusing when needed, using BatchNorm fusing as example
853 TEST_CASE("OptimizeForExclusiveConnectionsFuseTest")
854 {
855     using namespace armnn;
856     // Define layers information
857     Convolution2dDescriptor convolution2dDescriptor;
858     convolution2dDescriptor.m_BiasEnabled = false;
859     convolution2dDescriptor.m_DataLayout  = DataLayout::NHWC;
860     BatchNormalizationDescriptor batchNormDescriptor;
861     batchNormDescriptor.m_DataLayout = DataLayout::NHWC;
862 
863     const unsigned int inputDimensionSizes[]   = { 1, 4, 4, 3 };                 // NHWCin
864     const unsigned int weightsDimensionSizes[] = { 1, 2, 2, 3 };                 // CoutHWCin
865     const unsigned int outputDimensionSizes[]  = { 1, 3, 3, 1 };                 // NHWCout
866     const unsigned int outputChannelSize[]     = { outputDimensionSizes[3] };    // Cout
867 
868     TensorInfo inputInfo(4, inputDimensionSizes, DataType::Float32);
869     TensorInfo outputInfo(4, outputDimensionSizes, DataType::Float32);
870 
871     std::vector<float> weightsVector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
872     ConstTensor weights(TensorInfo(4, weightsDimensionSizes, DataType::Float32, 0.0f, 0, true), weightsVector);
873 
874     std::vector<float> betaVector     = { 0.1f };
875     std::vector<float> gammaVector    = { 0.5f };
876     std::vector<float> meanVector     = { 0 };
877     std::vector<float> varianceVector = { 1 };
878     ConstTensor beta(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), betaVector);
879     ConstTensor gamma(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), gammaVector);
880     ConstTensor mean(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), meanVector);
881     ConstTensor variance(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), varianceVector);
882 
883     ConstantLayer* biasLayer = nullptr;
884 
885     // Define the network
886     Graph graph;
887     auto input        = graph.AddLayer<InputLayer>(0, "input");
888     auto weightsLayer = graph.AddLayer<ConstantLayer>("Weights");
889     auto conv         = graph.AddLayer<Convolution2dLayer>(convolution2dDescriptor, "convolution");
890     auto batchNorm    = graph.AddLayer<BatchNormalizationLayer>(batchNormDescriptor, "batchNorm");
891     auto output       = graph.AddLayer<OutputLayer>(0, "output");
892 
893     // Set layer information
894     input->GetOutputSlot().SetTensorInfo(inputInfo);
895 
896     weightsLayer->m_LayerOutput = std::make_shared<ScopedTensorHandle>(weights);
897     weightsLayer->GetOutputSlot(0).SetTensorInfo(weightsLayer->m_LayerOutput->GetTensorInfo());
898     conv->GetOutputSlot().SetTensorInfo(outputInfo);
899 
900     batchNorm->GetOutputSlot().SetTensorInfo(outputInfo);
901     batchNorm->m_Beta     = std::make_unique<ScopedTensorHandle>(beta);
902     batchNorm->m_Gamma    = std::make_unique<ScopedTensorHandle>(gamma);
903     batchNorm->m_Mean     = std::make_unique<ScopedTensorHandle>(mean);
904     batchNorm->m_Variance = std::make_unique<ScopedTensorHandle>(variance);
905 
906     if (convolution2dDescriptor.m_BiasEnabled)
907     {
908         std::vector<float> biasVector = { 11 };
909         ConstTensor bias(TensorInfo(1, outputChannelSize, DataType::Float32, 0.0f, 0, true), biasVector);
910         biasLayer = graph.AddLayer<ConstantLayer>("Bias");
911         biasLayer->m_LayerOutput = std::make_shared<ScopedTensorHandle>(bias);
912         biasLayer->GetOutputSlot(0).SetTensorInfo(biasLayer->m_LayerOutput->GetTensorInfo());
913         biasLayer->GetOutputSlot(0).Connect(conv->GetInputSlot(2));
914     }
915 
916     // Connect layers
917     input->GetOutputSlot(0).Connect(conv->GetInputSlot(0));
918     weightsLayer->GetOutputSlot(0).Connect(conv->GetInputSlot(1));
919     conv->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0));
920     batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0));
921 
922     if (convolution2dDescriptor.m_BiasEnabled)
923     {
924         CHECK(6 == graph.GetNumLayers());
925         CHECK(CheckSequence(graph.cbegin(), graph.cend(),
926                             &IsLayerOfType<InputLayer>,
927                             &IsLayerOfType<ConstantLayer>,
928                             &IsLayerOfType<ConstantLayer>,
929                             &IsLayerOfType<Convolution2dLayer>,
930                             &IsLayerOfType<BatchNormalizationLayer>,
931                             &IsLayerOfType<OutputLayer>));
932     }
933     else
934     {
935         CHECK(5 == graph.GetNumLayers());
936         CHECK(CheckSequence(graph.cbegin(), graph.cend(),
937                             &IsLayerOfType<InputLayer>,
938                             &IsLayerOfType<ConstantLayer>,
939                             &IsLayerOfType<Convolution2dLayer>,
940                             &IsLayerOfType<BatchNormalizationLayer>,
941                             &IsLayerOfType<OutputLayer>));
942     }
943 
944     // Optimize graph
945     armnn::Optimizer::Pass(graph, MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32()));
946 
__anonfee9358a0402(const armnn::Layer* const layer) 947     auto checkFusedConv2d = [](const armnn::Layer* const layer) -> bool {
948         return IsLayerOfType<armnn::Convolution2dLayer>(layer) &&
949                (layer->GetNameStr() == "fused-batchNorm-into-convolution");
950     };
951 
952     CHECK(5 == graph.GetNumLayers());
953     CHECK(CheckSequence(graph.cbegin(), graph.cend(),
954                         &IsLayerOfType<InputLayer>,
955                         &IsLayerOfType<ConstantLayer>,
956                         &IsLayerOfType<ConstantLayer>,
957                         checkFusedConv2d,
958                         &IsLayerOfType<OutputLayer>));
959 }
960 
961 // Tests that OptimizeForExclusiveConnections works, not fusing when not needed, using BatchNorm fusing as example
962 TEST_CASE("OptimizeForExclusiveConnectionsWithoutFuseTest")
963 {
964     // Define the network
965     Graph graph;
966     Convolution2dDescriptor convolution2dDescriptor;
967     BatchNormalizationDescriptor batchNormDescriptor;
968 
969     auto input     = graph.AddLayer<InputLayer>(0, "input");
970     auto conv      = graph.AddLayer<Convolution2dLayer>(convolution2dDescriptor, "convolution");
971     auto batchNorm = graph.AddLayer<BatchNormalizationLayer>(batchNormDescriptor, "batchNorm");
972     auto output    = graph.AddLayer<OutputLayer>(0, "output");
973     auto output2   = graph.AddLayer<OutputLayer>(1, "output2");
974 
975     // Connect layers
976     input->GetOutputSlot(0).Connect(conv->GetInputSlot(0));
977     conv->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0));
978     batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0));
979     conv->GetOutputSlot(0).Connect(output2->GetInputSlot(0));
980 
981     CHECK((5 == graph.GetNumLayers()));
982     CHECK(CheckSequence(graph.cbegin(), graph.cend(),
983                         &IsLayerOfType<armnn::InputLayer>,
984                         &IsLayerOfType<armnn::Convolution2dLayer>,
985                         &IsLayerOfType<armnn::BatchNormalizationLayer>,
986                         &IsLayerOfType<armnn::OutputLayer>,
987                         &IsLayerOfType<armnn::OutputLayer>));
988     // Optimize graph
989     armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32()));
990 
991     CHECK(5 == graph.GetNumLayers());
992     CHECK(CheckSequence(graph.cbegin(), graph.cend(),
993                         &IsLayerOfType<armnn::InputLayer>,
994                         &IsLayerOfType<armnn::Convolution2dLayer>,
995                         &IsLayerOfType<armnn::BatchNormalizationLayer>,
996                         &IsLayerOfType<armnn::OutputLayer>,
997                         &IsLayerOfType<armnn::OutputLayer>));
998 }
999 } // Optimizer TestSuite
1000