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