2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
5 #include <boost/test/unit_test.hpp>
7 #include <armnn/ArmNN.hpp>
9 #include <Optimizer.hpp>
10 #include <backendsCommon/CpuTensorHandle.hpp>
11 #include <FloatingPointConverter.hpp>
15 template <typename LayerT>
16 bool IsLayerOfType(const armnn::Layer* const layer)
18 return (layer->GetType() == armnn::LayerEnumOf<LayerT>());
21 bool CheckSequence(const armnn::Graph::ConstIterator first, const armnn::Graph::ConstIterator last)
23 return (first == last);
26 /// Checks each unary function in Us evaluates true for each correspondent layer in the sequence [first, last).
27 template <typename U, typename... Us>
28 bool CheckSequence(const armnn::Graph::ConstIterator first,
29 const armnn::Graph::ConstIterator last,
33 return u(*first) && CheckSequence(std::next(first), last, us...);
36 template <typename LayerT>
37 bool CheckRelatedLayers(armnn::Graph& graph, const std::list<std::string>& testRelatedLayers)
39 for (auto& layer : graph)
41 if (layer->GetType() == armnn::LayerEnumOf<LayerT>())
43 auto& relatedLayers = layer->GetRelatedLayerNames();
44 if(!std::equal(relatedLayers.begin(), relatedLayers.end(),
45 testRelatedLayers.begin(), testRelatedLayers.end()))
55 // connects two layers
56 using namespace armnn;
57 void Connect(Layer* from, Layer* to, const TensorInfo& tensorInfo, unsigned int fromIndex = 0, unsigned int toIndex = 0)
59 from->GetOutputSlot(fromIndex).Connect(to->GetInputSlot(toIndex));
60 from->GetOutputHandler(fromIndex).SetTensorInfo(tensorInfo);
63 void CreateLSTMLayerHelper(Graph &graph, bool CifgEnabled)
65 LstmDescriptor layerDesc;
66 layerDesc.m_ActivationFunc = 4;
67 layerDesc.m_ClippingThresCell = 0.2f;
68 layerDesc.m_ClippingThresProj = 0.4f;
69 layerDesc.m_CifgEnabled = CifgEnabled;
70 layerDesc.m_PeepholeEnabled = false;
71 layerDesc.m_ProjectionEnabled = false;
73 LstmLayer* const layer = graph.AddLayer<LstmLayer>(layerDesc, "layer");
74 unsigned int batchSize = 3;
75 unsigned int inputSize = 2;
76 unsigned int numUnits = 4;
77 unsigned int outputSize = 4;
79 layer->m_BasicParameters.m_InputToForgetWeights = std::make_unique<ScopedCpuTensorHandle>
80 (TensorInfo({ numUnits, inputSize }, DataType::Float32));
81 layer->m_BasicParameters.m_InputToCellWeights = std::make_unique<ScopedCpuTensorHandle>
82 (TensorInfo({ numUnits, inputSize }, DataType::Float32));
83 layer->m_BasicParameters.m_InputToOutputWeights = std::make_unique<ScopedCpuTensorHandle>
84 (TensorInfo({ numUnits, inputSize }, DataType::Float32));
85 layer->m_BasicParameters.m_RecurrentToForgetWeights = std::make_unique<ScopedCpuTensorHandle>
86 (TensorInfo({ numUnits, outputSize }, DataType::Float32));
87 layer->m_BasicParameters.m_RecurrentToCellWeights = std::make_unique<ScopedCpuTensorHandle>
88 (TensorInfo({ numUnits, outputSize }, DataType::Float32));
89 layer->m_BasicParameters.m_RecurrentToOutputWeights = std::make_unique<ScopedCpuTensorHandle>
90 (TensorInfo({ numUnits, outputSize }, DataType::Float32));
91 layer->m_BasicParameters.m_ForgetGateBias = std::make_unique<ScopedCpuTensorHandle>
92 (TensorInfo({ numUnits }, DataType::Float32));
93 layer->m_BasicParameters.m_CellBias = std::make_unique<ScopedCpuTensorHandle>
94 (TensorInfo({ numUnits }, DataType::Float32));
95 layer->m_BasicParameters.m_OutputGateBias = std::make_unique<ScopedCpuTensorHandle>
96 (TensorInfo({ numUnits }, DataType::Float32));
98 layer->m_BasicParameters.m_InputToForgetWeights->Allocate();
99 layer->m_BasicParameters.m_InputToCellWeights->Allocate();
100 layer->m_BasicParameters.m_InputToOutputWeights->Allocate();
101 layer->m_BasicParameters.m_RecurrentToForgetWeights->Allocate();
102 layer->m_BasicParameters.m_RecurrentToCellWeights->Allocate();
103 layer->m_BasicParameters.m_RecurrentToOutputWeights->Allocate();
104 layer->m_BasicParameters.m_ForgetGateBias->Allocate();
105 layer->m_BasicParameters.m_CellBias->Allocate();
106 layer->m_BasicParameters.m_OutputGateBias->Allocate();
108 if (!layerDesc.m_CifgEnabled)
110 layer->m_CifgParameters.m_InputToInputWeights = std::make_unique<ScopedCpuTensorHandle>
111 (TensorInfo({ numUnits, inputSize }, DataType::Float32));
112 layer->m_CifgParameters.m_RecurrentToInputWeights = std::make_unique<ScopedCpuTensorHandle>
113 (TensorInfo({ numUnits, outputSize }, DataType::Float32));
114 layer->m_CifgParameters.m_CellToInputWeights = std::make_unique<ScopedCpuTensorHandle>
115 (TensorInfo({ numUnits }, DataType::Float32));
116 layer->m_CifgParameters.m_InputGateBias = std::make_unique<ScopedCpuTensorHandle>
117 (TensorInfo({ numUnits }, DataType::Float32));
118 layer->m_CifgParameters.m_InputToInputWeights->Allocate();
119 layer->m_CifgParameters.m_RecurrentToInputWeights->Allocate();
120 layer->m_CifgParameters.m_CellToInputWeights->Allocate();
121 layer->m_CifgParameters.m_InputGateBias->Allocate();
124 if (layerDesc.m_ProjectionEnabled)
126 layer->m_ProjectionParameters.m_ProjectionWeights = std::make_unique<ScopedCpuTensorHandle>
127 (TensorInfo({ outputSize, numUnits }, DataType::Float32));
128 layer->m_ProjectionParameters.m_ProjectionBias = std::make_unique<ScopedCpuTensorHandle>
129 (TensorInfo({ outputSize }, DataType::Float32));
130 layer->m_ProjectionParameters.m_ProjectionWeights->Allocate();
131 layer->m_ProjectionParameters.m_ProjectionBias->Allocate();
134 if (layerDesc.m_PeepholeEnabled)
136 layer->m_PeepholeParameters.m_CellToForgetWeights = std::make_unique<ScopedCpuTensorHandle>
137 (TensorInfo({ numUnits }, DataType::Float32));
138 layer->m_PeepholeParameters.m_CellToOutputWeights = std::make_unique<ScopedCpuTensorHandle>
139 (TensorInfo({ numUnits }, DataType::Float32));
140 layer->m_PeepholeParameters.m_CellToForgetWeights->Allocate();
141 layer->m_PeepholeParameters.m_CellToOutputWeights->Allocate();
144 // create input and output layers
145 Layer* const input = graph.AddLayer<InputLayer>(0, "input");
146 Layer* const outputStateIn = graph.AddLayer<InputLayer>(1, "outputStateIn");
147 Layer* const cellStateIn = graph.AddLayer<InputLayer>(2, "cellStateIn");
148 Layer* const scratchBuffer = graph.AddLayer<OutputLayer>(0, "scratchBuffer");
149 Layer* const outputStateOut = graph.AddLayer<OutputLayer>(1, "outputStateOut");
150 Layer* const cellStateOut = graph.AddLayer<OutputLayer>(2, "cellStateOut");
151 Layer* const output = graph.AddLayer<OutputLayer>(3, "output");
154 armnn::TensorInfo lstmTensorInfo1({ batchSize, inputSize }, DataType::Float32);
155 armnn::TensorInfo lstmTensorInfo2({ batchSize, numUnits}, DataType::Float32);
156 armnn::TensorInfo lstmTensorInfo3({ batchSize, outputSize }, DataType::Float32);
157 armnn::TensorInfo lstmTensorInfoScratchBuff({ batchSize, numUnits*3 }, DataType::Float32);
158 if (layerDesc.m_CifgEnabled)
160 lstmTensorInfoScratchBuff.SetShape({ batchSize, numUnits*4 });
163 Connect(input, layer, lstmTensorInfo1, 0, 0);
164 Connect(cellStateIn, layer, lstmTensorInfo2, 0, 1);
165 Connect(outputStateIn, layer, lstmTensorInfo3, 0, 2);
166 Connect(layer, scratchBuffer, lstmTensorInfoScratchBuff, 0, 0);
167 Connect(layer, outputStateOut, lstmTensorInfo3, 1, 0);
168 Connect(layer, cellStateOut, lstmTensorInfo2, 2, 0);
169 Connect(layer, output, lstmTensorInfo3, 3, 0);
174 BOOST_AUTO_TEST_SUITE(Optimizer)
175 using namespace armnn::optimizations;
177 BOOST_AUTO_TEST_CASE(OptimizeInversePermutesTest)
181 auto output = graph.AddLayer<armnn::OutputLayer>(0, "output");
183 graph.InsertNewLayer<armnn::InputLayer>(output->GetInputSlot(0), 0, "input");
185 // Inserts two permutes, one the inverse of the other.
186 graph.InsertNewLayer<armnn::PermuteLayer>(output->GetInputSlot(0),
187 armnn::PermuteDescriptor({0, 2, 3, 1}),
189 graph.InsertNewLayer<armnn::PermuteLayer>(output->GetInputSlot(0),
190 armnn::PermuteDescriptor({0, 3, 1, 2}),
193 BOOST_TEST(CheckSequence(graph.cbegin(),
195 &IsLayerOfType<armnn::InputLayer>,
196 &IsLayerOfType<armnn::PermuteLayer>,
197 &IsLayerOfType<armnn::PermuteLayer>,
198 &IsLayerOfType<armnn::OutputLayer>));
200 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeInversePermutes()));
202 // The permutes are removed.
203 BOOST_TEST(CheckSequence(graph.cbegin(),
205 &IsLayerOfType<armnn::InputLayer>,
206 &IsLayerOfType<armnn::OutputLayer>));
209 BOOST_AUTO_TEST_CASE(LSTMValidateTensorShapesFromInputsCIFGDisabledTest)
213 //Helper function creates graph containing LSTM layer with required input and output layers
214 CreateLSTMLayerHelper(graph, false);
216 //This function used to call ValidateShapesFromInputs();
217 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
220 BOOST_AUTO_TEST_CASE(LSTMValidateTensorShapesFromInputsCIFGEnabledTest)
224 //Helper function creates graph containing LSTM layer with required input and output layers
225 CreateLSTMLayerHelper(graph, true);
227 //This function used to call ValidateShapesFromInputs();
228 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
231 BOOST_AUTO_TEST_CASE(MovePermuteUpTest)
233 const armnn::TensorInfo info({ 1, 5, 2, 3 }, armnn::DataType::Float32);
234 const armnn::TensorInfo permuted({ 1, 3, 5, 2 }, armnn::DataType::Float32);
238 armnn::LayerBindingId inputId = 0;
240 armnn::Layer* head = graph.AddLayer<armnn::OutputLayer>(0, "output");
242 std::string permuteLayerName = "original_permute";
245 head = graph.InsertNewLayer<armnn::PermuteLayer>(head->GetInputSlot(0),
246 armnn::PermuteDescriptor({ 0, 2, 3, 1 }),
247 permuteLayerName.c_str());
249 head->GetOutputHandler().SetTensorInfo(permuted);
251 // Inserts layers that don't care about data format.
252 head = graph.InsertNewLayer<armnn::ActivationLayer>(head->GetInputSlot(0),
253 armnn::ActivationDescriptor{}, "");
254 head->GetOutputHandler().SetTensorInfo(info);
256 head = graph.InsertNewLayer<armnn::AdditionLayer>(head->GetInputSlot(0), "");
257 head->GetOutputHandler().SetTensorInfo(info);
259 // Inserts input for 2nd input of Addition.
260 graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(1), inputId++, "")
261 ->GetOutputHandler().SetTensorInfo(info);
263 head = graph.InsertNewLayer<armnn::FakeQuantizationLayer>(head->GetInputSlot(0),
264 armnn::FakeQuantizationDescriptor{}, "");
265 head->GetOutputHandler().SetTensorInfo(info);
267 head = graph.InsertNewLayer<armnn::FloorLayer>(head->GetInputSlot(0), "");
268 head->GetOutputHandler().SetTensorInfo(info);
270 head = graph.InsertNewLayer<armnn::MemCopyLayer>(head->GetInputSlot(0), "");
271 head->GetOutputHandler().SetTensorInfo(info);
273 head = graph.InsertNewLayer<armnn::MultiplicationLayer>(head->GetInputSlot(0), "");
274 head->GetOutputHandler().SetTensorInfo(info);
276 // Inserts input for 2nd input of Multiplication.
277 graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(1), inputId++, "")
278 ->GetOutputHandler().SetTensorInfo(info);
281 graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(0), inputId++, "")
282 ->GetOutputHandler().SetTensorInfo(info);
284 BOOST_TEST(CheckSequence(graph.cbegin(),
286 &IsLayerOfType<armnn::InputLayer>,
287 &IsLayerOfType<armnn::InputLayer>,
288 &IsLayerOfType<armnn::InputLayer>,
289 &IsLayerOfType<armnn::MultiplicationLayer>,
290 &IsLayerOfType<armnn::MemCopyLayer>,
291 &IsLayerOfType<armnn::FloorLayer>,
292 &IsLayerOfType<armnn::FakeQuantizationLayer>,
293 &IsLayerOfType<armnn::AdditionLayer>,
294 &IsLayerOfType<armnn::ActivationLayer>,
295 &IsLayerOfType<armnn::PermuteLayer>,
296 &IsLayerOfType<armnn::OutputLayer>));
298 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(MovePermuteUp()));
300 // The permute is moved to the top. New permutes for layers with multiple inputs.
301 BOOST_TEST(CheckSequence(graph.cbegin(),
303 &IsLayerOfType<armnn::InputLayer>,
304 &IsLayerOfType<armnn::InputLayer>,
305 &IsLayerOfType<armnn::InputLayer>,
306 &IsLayerOfType<armnn::PermuteLayer>,
307 &IsLayerOfType<armnn::PermuteLayer>,
308 &IsLayerOfType<armnn::PermuteLayer>,
309 &IsLayerOfType<armnn::MultiplicationLayer>,
310 &IsLayerOfType<armnn::MemCopyLayer>,
311 &IsLayerOfType<armnn::FloorLayer>,
312 &IsLayerOfType<armnn::FakeQuantizationLayer>,
313 &IsLayerOfType<armnn::AdditionLayer>,
314 &IsLayerOfType<armnn::ActivationLayer>,
315 &IsLayerOfType<armnn::OutputLayer>));
317 std::list<std::string> testRelatedLayers = { permuteLayerName };
319 BOOST_TEST(CheckRelatedLayers<armnn::PermuteLayer>(graph, testRelatedLayers));
322 BOOST_AUTO_TEST_CASE(PermuteAsReshapeTest)
326 std::string permuteLayerName = "permute";
328 const armnn::TensorInfo infoIn({ 1, 2, 3, 1 }, armnn::DataType::Float32);
329 const armnn::TensorInfo infoOut({ 1, 1, 2, 3 }, armnn::DataType::Float32);
331 auto output = graph.AddLayer<armnn::OutputLayer>(0, "output");
333 graph.InsertNewLayer<armnn::InputLayer>(output->GetInputSlot(0), 0, "input")
334 ->GetOutputHandler().SetTensorInfo(infoIn);
337 graph.InsertNewLayer<armnn::PermuteLayer>(output->GetInputSlot(0),
338 armnn::PermuteDescriptor({ 0, 2, 3, 1 }), permuteLayerName.c_str())
339 ->GetOutputHandler().SetTensorInfo(infoOut);
341 BOOST_TEST(CheckSequence(graph.cbegin(),
343 &IsLayerOfType<armnn::InputLayer>,
344 &IsLayerOfType<armnn::PermuteLayer>,
345 &IsLayerOfType<armnn::OutputLayer>));
347 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(PermuteAsReshape()));
349 // The permute is replaced by an equivalent reshape.
351 auto checkReshape = [&infoOut](const armnn::Layer* const layer) -> bool
353 const auto reshapeLayer = static_cast<const armnn::ReshapeLayer*>(layer);
354 return IsLayerOfType<armnn::ReshapeLayer>(layer) &&
355 (reshapeLayer->GetParameters().m_TargetShape == infoOut.GetShape()) &&
356 (reshapeLayer->GetOutputHandler().GetTensorInfo().GetShape() == infoOut.GetShape());
359 BOOST_TEST(CheckSequence(graph.cbegin(),
361 &IsLayerOfType<armnn::InputLayer>,
363 &IsLayerOfType<armnn::OutputLayer>));
366 std::list<std::string> testRelatedLayers = { permuteLayerName };
367 BOOST_TEST(CheckRelatedLayers<armnn::ReshapeLayer>(graph, testRelatedLayers));
370 BOOST_AUTO_TEST_CASE(OptimizeConsecutiveReshapesTest)
374 const armnn::TensorInfo info0({ 1, 2, 3, 5 }, armnn::DataType::Float32);
376 auto output = graph.AddLayer<armnn::OutputLayer>(0, "output");
377 auto input = graph.InsertNewLayer<armnn::InputLayer>(output->GetInputSlot(0), 0, "input");
379 input->GetOutputHandler().SetTensorInfo(info0);
382 // Inserts two reshapes.
383 const armnn::TensorInfo info1({1, 30, 1, 1}, armnn::DataType::Float32);
384 const armnn::TensorInfo info2({1, 2, 1, 15}, armnn::DataType::Float32);
386 std::string reshape1Name = "reshape1";
387 std::string reshape2Name = "reshape2";
389 auto reshape1 = graph.InsertNewLayer<armnn::ReshapeLayer>(output->GetInputSlot(0),
390 armnn::ReshapeDescriptor{ info1.GetShape() },
391 reshape1Name.c_str());
392 auto reshape2 = graph.InsertNewLayer<armnn::ReshapeLayer>(output->GetInputSlot(0),
393 armnn::ReshapeDescriptor{ info2.GetShape() },
394 reshape2Name.c_str());
396 reshape1->GetOutputHandler().SetTensorInfo(info1);
397 reshape2->GetOutputHandler().SetTensorInfo(info2);
399 BOOST_TEST(CheckSequence(graph.cbegin(),
401 &IsLayerOfType<armnn::InputLayer>,
402 &IsLayerOfType<armnn::ReshapeLayer>,
403 &IsLayerOfType<armnn::ReshapeLayer>,
404 &IsLayerOfType<armnn::OutputLayer>));
406 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeConsecutiveReshapes()));
408 auto checkReshape = [&info2](const armnn::Layer* const layer) -> bool
410 const auto reshapeLayer = static_cast<const armnn::ReshapeLayer*>(layer);
411 return IsLayerOfType<armnn::ReshapeLayer>(layer) &&
412 (reshapeLayer->GetParameters().m_TargetShape == info2.GetShape()) &&
413 (reshapeLayer->GetOutputHandler().GetTensorInfo().GetShape() == info2.GetShape());
416 // The two reshapes are replaced by a single equivalent reshape.
417 BOOST_TEST(CheckSequence(graph.cbegin(),
419 &IsLayerOfType<armnn::InputLayer>,
421 &IsLayerOfType<armnn::OutputLayer>));
423 // Check the new reshape layer has the other two reshapes as related layers
424 std::list<std::string> testRelatedLayers = { reshape2Name, reshape1Name };
426 BOOST_TEST(CheckRelatedLayers<armnn::ReshapeLayer>(graph, testRelatedLayers));
430 // Inserts a reshape to the input shape.
431 auto reshapeToIn = graph.InsertNewLayer<armnn::ReshapeLayer>(output->GetInputSlot(0),
432 armnn::ReshapeDescriptor{ info0.GetShape() },
435 reshapeToIn->GetOutputHandler().SetTensorInfo(info0);
437 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeConsecutiveReshapes()));
439 // The two reshapes are removed.
440 BOOST_TEST(CheckSequence(graph.cbegin(),
442 &IsLayerOfType<armnn::InputLayer>,
443 &IsLayerOfType<armnn::OutputLayer>));
447 BOOST_AUTO_TEST_CASE(SquashEqualSiblingsTest)
451 armnn::LayerBindingId outputId = 0;
453 const armnn::TensorInfo info({ 1, 2, 3, 5 }, armnn::DataType::Float32);
454 const armnn::TensorInfo permuted({ 1, 5, 2, 3 }, armnn::DataType::Float32);
456 auto input = graph.AddLayer<armnn::InputLayer>(0, "input");
457 input->GetOutputSlot().SetTensorInfo(info);
459 // Inserts equal permutes, equal reshapes and something else.
460 const armnn::PermuteDescriptor permDesc({ 0, 2, 3, 1 });
461 const armnn::ReshapeDescriptor reshapeDesc{ { 1, 3, 1, 5 } };
465 layer = graph.AddLayer<armnn::PermuteLayer>(permDesc, "");
466 layer->GetOutputSlot().SetTensorInfo(permuted);
467 layer->GetOutputSlot().Connect(graph.AddLayer<armnn::OutputLayer>(outputId++, "")->GetInputSlot(0));
468 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
470 layer = graph.AddLayer<armnn::ReshapeLayer>(reshapeDesc, "");
471 layer->GetOutputSlot().Connect(graph.AddLayer<armnn::OutputLayer>(outputId++, "")->GetInputSlot(0));
472 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
474 layer = graph.AddLayer<armnn::FloorLayer>("");
475 layer->GetOutputSlot().Connect(graph.AddLayer<armnn::OutputLayer>(outputId++, "")->GetInputSlot(0));
476 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
478 layer = graph.AddLayer<armnn::ReshapeLayer>(reshapeDesc, "");
479 layer->GetOutputSlot().Connect(graph.AddLayer<armnn::OutputLayer>(outputId++, "")->GetInputSlot(0));
480 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
482 layer = graph.AddLayer<armnn::PermuteLayer>(permDesc, "");
483 layer->GetOutputSlot().SetTensorInfo(permuted);
484 layer->GetOutputSlot().Connect(graph.AddLayer<armnn::OutputLayer>(outputId++, "")->GetInputSlot(0));
485 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
487 BOOST_TEST(CheckSequence(graph.cbegin(),
489 &IsLayerOfType<armnn::InputLayer>,
490 &IsLayerOfType<armnn::PermuteLayer>,
491 &IsLayerOfType<armnn::ReshapeLayer>,
492 &IsLayerOfType<armnn::FloorLayer>,
493 &IsLayerOfType<armnn::ReshapeLayer>,
494 &IsLayerOfType<armnn::PermuteLayer>,
495 &IsLayerOfType<armnn::OutputLayer>,
496 &IsLayerOfType<armnn::OutputLayer>,
497 &IsLayerOfType<armnn::OutputLayer>,
498 &IsLayerOfType<armnn::OutputLayer>,
499 &IsLayerOfType<armnn::OutputLayer>));
501 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(SquashEqualPermuteSiblings(),
502 SquashEqualReshapeSiblings()));
504 // The permutes and reshapes are squashed.
506 BOOST_TEST(CheckSequence(graph.cbegin(),
508 &IsLayerOfType<armnn::InputLayer>,
509 &IsLayerOfType<armnn::PermuteLayer>,
510 &IsLayerOfType<armnn::ReshapeLayer>,
511 &IsLayerOfType<armnn::FloorLayer>,
512 &IsLayerOfType<armnn::OutputLayer>,
513 &IsLayerOfType<armnn::OutputLayer>,
514 &IsLayerOfType<armnn::OutputLayer>,
515 &IsLayerOfType<armnn::OutputLayer>,
516 &IsLayerOfType<armnn::OutputLayer>));
519 BOOST_AUTO_TEST_CASE(ConvertConstantsHalfToFloatTest)
523 const armnn::TensorInfo info({ 1,1,1,2 }, armnn::DataType::Float32);
525 // Create the half precision input data
526 unsigned int dims[] = { 4,1,1,1 };
527 std::vector<float> convWeightsData{1.f, 2.f, 3.f, 4.f};
528 std::vector<uint16_t> halfWeights(4);
529 armnnUtils::FloatingPointConverter::ConvertFloat32To16(convWeightsData.data(),
530 convWeightsData.size(),
532 armnn::ConstTensor weights(armnn::TensorInfo(4, dims, armnn::DataType::Float16), halfWeights);
534 //Create the simple test network
535 auto input = graph.AddLayer<armnn::InputLayer>(0, "input");
536 input->GetOutputSlot().SetTensorInfo(info);
538 auto fc = graph.AddLayer<armnn::FullyConnectedLayer>(armnn::FullyConnectedDescriptor(), "fc");
539 fc->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights);
540 fc->GetOutputSlot().SetTensorInfo(info);
542 auto output = graph.AddLayer<armnn::OutputLayer>(1, "output");
544 //Connect up the layers
545 input->GetOutputSlot().Connect(fc->GetInputSlot(0));
546 fc->GetOutputSlot().Connect(output->GetInputSlot(0));
548 //Test the tensor info is correct.
549 BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float16);
552 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(ConvertConstantsHalfToFloat()));
554 //Test the tensor info is correct.
555 BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float32);
557 // Now test the data matches float32 data
558 float* data = fc->m_Weight->GetTensor<float>();
559 BOOST_CHECK(1.0f == data[0]);
560 BOOST_CHECK(2.0f == data[1]);
561 BOOST_CHECK(3.0f == data[2]);
562 BOOST_CHECK(4.0f == data[3]);
565 BOOST_AUTO_TEST_CASE(ConvertConstantsFloatToHalfTest)
569 const armnn::TensorInfo info({ 1, 1, 1, 2 }, armnn::DataType::Float16);
571 // Create const tensor from fp32 data
572 unsigned int dims[] = { 4, 1, 1, 1 };
573 std::vector<float> floatWeights{ 1.0f, 2.0f, 3.0f, 4.0f };
574 armnn::ConstTensor weights(armnn::TensorInfo(4, dims, armnn::DataType::Float32), floatWeights);
576 // Create simple test network
577 auto input = graph.AddLayer<armnn::InputLayer>(0, "input");
578 input->GetOutputSlot().SetTensorInfo(info);
580 auto fc = graph.AddLayer<armnn::FullyConnectedLayer>(armnn::FullyConnectedDescriptor(), "fc");
581 fc->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights);
582 fc->GetOutputSlot().SetTensorInfo(info);
584 auto output = graph.AddLayer<armnn::OutputLayer>(1, "output");
586 // Connect up the layers
587 input->GetOutputSlot().Connect(fc->GetInputSlot(0));
588 fc->GetOutputSlot().Connect(output->GetInputSlot(0));
590 // Check tensor data type before conversion
591 BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float32);
594 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(ConvertConstantsFloatToHalf()));
596 // Check tensor data type after conversion
597 BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float16);
599 // Check whether data matches expected fp16 data
600 Half* data = fc->m_Weight->GetTensor<Half>();
601 BOOST_CHECK(data[0] == Half(1.0f));
602 BOOST_CHECK(data[1] == Half(2.0f));
603 BOOST_CHECK(data[2] == Half(3.0f));
604 BOOST_CHECK(data[3] == Half(4.0f));
607 BOOST_AUTO_TEST_CASE(OptimizeInverseConversionsTest)
611 auto output = graph.AddLayer<armnn::OutputLayer>(0, "output");
613 graph.InsertNewLayer<armnn::InputLayer>(output->GetInputSlot(0), 0, "input");
615 // Fp32ToFp16 conversion followed by an inverse Fp16ToFp32 conversion
616 graph.InsertNewLayer<armnn::ConvertFp32ToFp16Layer>(output->GetInputSlot(0), "convert1");
617 graph.InsertNewLayer<armnn::ConvertFp16ToFp32Layer>(output->GetInputSlot(0), "convert2");
619 graph.InsertNewLayer<armnn::Convolution2dLayer>(output->GetInputSlot(0), Convolution2dDescriptor(), "conv");
621 // Fp16ToFp32 conversion followed by an inverse Fp32ToFp16 conversion
622 graph.InsertNewLayer<armnn::ConvertFp16ToFp32Layer>(output->GetInputSlot(0), "convert3");
623 graph.InsertNewLayer<armnn::ConvertFp32ToFp16Layer>(output->GetInputSlot(0), "convert4");
625 BOOST_TEST(CheckSequence(graph.cbegin(),
627 &IsLayerOfType<armnn::InputLayer>,
628 &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
629 &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
630 &IsLayerOfType<armnn::Convolution2dLayer>,
631 &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
632 &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
633 &IsLayerOfType<armnn::OutputLayer>));
635 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeInverseConversionsFp16(),
636 OptimizeInverseConversionsFp32()));
638 // Check that all consecutive inverse conversions are removed
639 BOOST_TEST(CheckSequence(graph.cbegin(),
641 &IsLayerOfType<armnn::InputLayer>,
642 &IsLayerOfType<armnn::Convolution2dLayer>,
643 &IsLayerOfType<armnn::OutputLayer>));
646 BOOST_AUTO_TEST_CASE(InsertConvertersTest)
648 const armnn::TensorInfo info({ 1, 5, 2, 3 }, armnn::DataType::Float16);
652 armnn::LayerBindingId inputId = 0;
654 armnn::Layer* head = graph.AddLayer<armnn::OutputLayer>(0, "output");
656 head = graph.InsertNewLayer<armnn::AdditionLayer>(head->GetInputSlot(0), "");
657 head->GetOutputHandler().SetTensorInfo(info);
659 graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(1), inputId++, "")
660 ->GetOutputHandler().SetTensorInfo(info);
662 head = graph.InsertNewLayer<armnn::FloorLayer>(head->GetInputSlot(0), "");
663 head->GetOutputHandler().SetTensorInfo(info);
665 head = graph.InsertNewLayer<armnn::MemCopyLayer>(head->GetInputSlot(0), "");
666 head->GetOutputHandler().SetTensorInfo(info);
668 graph.InsertNewLayer<armnn::InputLayer>(head->GetInputSlot(0), inputId++, "")
669 ->GetOutputHandler().SetTensorInfo(info);
671 // Check graph layer sequence before inserting convert layers
672 BOOST_TEST(CheckSequence(graph.cbegin(),
674 &IsLayerOfType<armnn::InputLayer>,
675 &IsLayerOfType<armnn::InputLayer>,
676 &IsLayerOfType<armnn::MemCopyLayer>,
677 &IsLayerOfType<armnn::FloorLayer>,
678 &IsLayerOfType<armnn::AdditionLayer>,
679 &IsLayerOfType<armnn::OutputLayer>));
681 // Check layers have Float16 DataType
682 for (auto& layer : graph)
684 if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition)
686 BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16);
687 BOOST_ASSERT(layer->GetDataType() == DataType::Float16);
691 // Insert convert layers either side of unsupported layer
692 for (auto& layer : graph)
694 if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition)
696 InsertConvertFp16ToFp32LayersBefore(graph, *layer);
697 InsertConvertFp32ToFp16LayersAfter(graph, *layer);
701 // Check layers have correct DataType after inserting convert layers
702 for (auto& layer : graph)
704 if (layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition)
706 BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32);
707 BOOST_ASSERT(layer->GetDataType() == DataType::Float32);
709 else if (layer->GetType() == LayerType::ConvertFp16ToFp32)
711 BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32);
712 BOOST_ASSERT(layer->GetDataType() == DataType::Float16);
714 else if (layer->GetType() == LayerType::ConvertFp32ToFp16)
716 BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16);
717 BOOST_ASSERT(layer->GetDataType() == DataType::Float32);
721 // Check sequence of layers after inserting convert layers
722 BOOST_TEST(CheckSequence(graph.cbegin(),
724 &IsLayerOfType<armnn::InputLayer>,
725 &IsLayerOfType<armnn::InputLayer>,
726 &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
727 &IsLayerOfType<armnn::MemCopyLayer>,
728 &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
729 &IsLayerOfType<armnn::FloorLayer>,
730 &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
731 &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
732 &IsLayerOfType<armnn::AdditionLayer>,
733 &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
734 &IsLayerOfType<armnn::OutputLayer>));
737 BOOST_AUTO_TEST_CASE(Fp32NetworkToFp16OptimizationTest)
741 const armnn::TensorInfo infoFP32({ 2,2,1,3 }, armnn::DataType::Float32);
743 // Create the simple test network
744 auto input = graph.AddLayer<armnn::InputLayer>(0, "input");
745 input->GetOutputSlot().SetTensorInfo(infoFP32);
747 auto floor = graph.AddLayer<armnn::FloorLayer>("floor");
748 floor->GetOutputSlot().SetTensorInfo(infoFP32);
750 auto output = graph.AddLayer<armnn::OutputLayer>(1, "output");
752 // Connect up the layers
753 input->GetOutputSlot().Connect(floor->GetInputSlot(0));
754 floor->GetOutputSlot().Connect(output->GetInputSlot(0));
756 BOOST_TEST(CheckSequence(graph.cbegin(),
758 &IsLayerOfType<armnn::InputLayer>,
759 &IsLayerOfType<armnn::FloorLayer>,
760 &IsLayerOfType<armnn::OutputLayer>));
763 armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(Fp32NetworkToFp16Converter()));
765 BOOST_TEST(CheckSequence(graph.cbegin(),
767 &IsLayerOfType<armnn::InputLayer>,
768 &IsLayerOfType<armnn::ConvertFp32ToFp16Layer>,
769 &IsLayerOfType<armnn::FloorLayer>,
770 &IsLayerOfType<armnn::ConvertFp16ToFp32Layer>,
771 &IsLayerOfType<armnn::OutputLayer>));
774 void CreateConvolution2dGraph(Graph &graph, const unsigned int* inputShape,
775 const unsigned int* weightsShape, const unsigned int* outputShape,
776 DataLayout dataLayout = DataLayout::NCHW)
778 armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
779 armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
781 std::vector<float> weightsVector(90);
782 armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector);
784 Convolution2dDescriptor desc;
785 desc.m_BiasEnabled = false;
788 desc.m_DataLayout = dataLayout;
790 Layer* input = graph.AddLayer<InputLayer>(0, "input");
791 input->GetOutputSlot().SetTensorInfo(inputInfo);
793 Convolution2dLayer* layer = graph.AddLayer<Convolution2dLayer>(desc, "conv2d");
794 layer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights);
795 layer->GetOutputSlot().SetTensorInfo(outputInfo);
797 Layer* output = graph.AddLayer<OutputLayer>(0, "output");
798 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
799 layer->GetOutputSlot().Connect(output->GetInputSlot(0));
802 BOOST_AUTO_TEST_CASE(Conv2dValidateTensorShapesFromInputs)
805 const unsigned int inputShape[] = { 1, 3, 8, 16 };
806 const unsigned int weightsShape[] = { 2, 3, 5, 3 };
807 const unsigned int outputShape[] = { 1, 2, 4, 14 };
808 CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape);
810 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
813 BOOST_AUTO_TEST_CASE(Conv2dValidateTensorShapesFromInputsNhwc)
816 const unsigned int inputShape[] = { 1, 8, 16, 3 };
817 const unsigned int weightsShape[] = { 2, 5, 3, 3 };
818 const unsigned int outputShape[] = { 1, 4, 14, 2 };
819 CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC);
821 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
824 void CreateDepthwiseConvolution2dGraph(Graph &graph, const unsigned int* inputShape,
825 const unsigned int* weightsShape, const unsigned int* outputShape,
826 DataLayout dataLayout = DataLayout::NCHW)
828 armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
829 armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
831 std::vector<float> weightsVector(18);
832 armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector);
834 DepthwiseConvolution2dDescriptor desc;
835 desc.m_BiasEnabled = false;
838 desc.m_DataLayout = dataLayout;
840 Layer* input = graph.AddLayer<InputLayer>(0, "input");
841 input->GetOutputSlot().SetTensorInfo(inputInfo);
843 DepthwiseConvolution2dLayer* layer = graph.AddLayer<DepthwiseConvolution2dLayer>(desc, "depthwiseConv2d");
844 layer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights);
845 layer->GetOutputSlot().SetTensorInfo(outputInfo);
847 Layer* output = graph.AddLayer<OutputLayer>(0, "output");
848 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
849 layer->GetOutputSlot().Connect(output->GetInputSlot(0));
852 BOOST_AUTO_TEST_CASE(DepthwiseConv2dValidateTensorShapesFromInputs)
855 const unsigned int inputShape[] = { 1, 2, 3, 3 };
856 const unsigned int weightsShape[] = { 1, 2, 3, 3 };
857 const unsigned int outputShape[] = { 1, 2, 1, 1 };
858 CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape);
860 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
863 BOOST_AUTO_TEST_CASE(DepthwiseConv2dValidateTensorShapesFromInputsNhwc)
866 const unsigned int inputShape[] = { 1, 3, 3, 2 };
867 const unsigned int weightsShape[] = { 1, 3, 3, 2 };
868 const unsigned int outputShape[] = { 1, 1, 1, 2 };
869 CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC);
871 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
874 void CreatePooling2dGraph(Graph &graph, const unsigned int* inputShape, const unsigned int* outputShape,
875 DataLayout dataLayout = DataLayout::NCHW)
877 armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
878 armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
880 Pooling2dDescriptor desc;
881 desc.m_PoolType = armnn::PoolingAlgorithm::Average;
882 desc.m_PoolWidth = desc.m_PoolHeight = 100;
883 desc.m_StrideX = desc.m_StrideY = 5;
885 desc.m_PadRight = 50;
887 desc.m_PadBottom = 50;
888 desc.m_PaddingMethod = armnn::PaddingMethod::Exclude;
889 desc.m_DataLayout = dataLayout;
891 Layer* input = graph.AddLayer<InputLayer>(0, "input");
892 input->GetOutputSlot().SetTensorInfo(inputInfo);
894 Pooling2dLayer* layer = graph.AddLayer<Pooling2dLayer>(desc, "pooling2d");
895 layer->GetOutputSlot().SetTensorInfo(outputInfo);
897 Layer* output = graph.AddLayer<OutputLayer>(0, "output");
898 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
899 layer->GetOutputSlot().Connect(output->GetInputSlot(0));
902 BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputs)
905 const unsigned int inputShape[] = { 5, 3, 52, 60 };
906 const unsigned int outputShape[] = { 5, 3, 11, 13 };
907 CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NCHW);
909 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
912 BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputsNhwc)
915 const unsigned int inputShape[] = { 5, 52, 60, 3 };
916 const unsigned int outputShape[] = { 5, 11, 13, 3 };
917 CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NHWC);
919 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
922 void CreateResizeBilinearGraph(Graph &graph, const unsigned int* inputShape, const unsigned int* outputShape,
923 DataLayout dataLayout = DataLayout::NCHW)
925 armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32);
926 armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32);
928 ResizeBilinearDescriptor desc;
929 desc.m_TargetHeight = 3;
930 desc.m_TargetWidth = 4;
931 desc.m_DataLayout = dataLayout;
933 Layer* input = graph.AddLayer<InputLayer>(0, "input");
934 input->GetOutputSlot().SetTensorInfo(inputInfo);
936 ResizeBilinearLayer* layer = graph.AddLayer<ResizeBilinearLayer>(desc, "resizeBilinear");
937 layer->GetOutputSlot().SetTensorInfo(outputInfo);
939 Layer* output = graph.AddLayer<OutputLayer>(0, "output");
940 input->GetOutputSlot().Connect(layer->GetInputSlot(0));
941 layer->GetOutputSlot().Connect(output->GetInputSlot(0));
944 BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputs)
947 const unsigned int inputShape[] = { 1, 2, 4, 5 };
948 const unsigned int outputShape[] = { 1, 2, 3, 4 };
949 CreateResizeBilinearGraph(graph, inputShape, outputShape);
951 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
954 BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputsNhwc)
957 const unsigned int inputShape[] = { 1, 4, 5, 2 };
958 const unsigned int outputShape[] = { 1, 3, 4, 2 };
959 CreateResizeBilinearGraph(graph, inputShape, outputShape, DataLayout::NHWC);
961 BOOST_CHECK_NO_THROW(graph.InferTensorInfos());
964 BOOST_AUTO_TEST_SUITE_END()