// // Copyright © 2017 Arm Ltd. All rights reserved. // SPDX-License-Identifier: MIT // #include #include #include #include #include #include namespace { template bool IsLayerOfType(const armnn::Layer* const layer) { return (layer->GetType() == armnn::LayerEnumOf()); } bool CheckSequence(const armnn::Graph::ConstIterator first, const armnn::Graph::ConstIterator last) { return (first == last); } /// Checks each unary function in Us evaluates true for each correspondent layer in the sequence [first, last). template bool CheckSequence(const armnn::Graph::ConstIterator first, const armnn::Graph::ConstIterator last, U&& u, Us&&... us) { return u(*first) && CheckSequence(std::next(first), last, us...); } template bool CheckRelatedLayers(armnn::Graph& graph, const std::list& testRelatedLayers) { for (auto& layer : graph) { if (layer->GetType() == armnn::LayerEnumOf()) { auto& relatedLayers = layer->GetRelatedLayerNames(); if(!std::equal(relatedLayers.begin(), relatedLayers.end(), testRelatedLayers.begin(), testRelatedLayers.end())) { return false; } } } return true; } // connects two layers using namespace armnn; void Connect(Layer* from, Layer* to, const TensorInfo& tensorInfo, unsigned int fromIndex = 0, unsigned int toIndex = 0) { from->GetOutputSlot(fromIndex).Connect(to->GetInputSlot(toIndex)); from->GetOutputHandler(fromIndex).SetTensorInfo(tensorInfo); } void CreateLSTMLayerHelper(Graph &graph, bool CifgEnabled) { LstmDescriptor layerDesc; layerDesc.m_ActivationFunc = 4; layerDesc.m_ClippingThresCell = 0.2f; layerDesc.m_ClippingThresProj = 0.4f; layerDesc.m_CifgEnabled = CifgEnabled; layerDesc.m_PeepholeEnabled = false; layerDesc.m_ProjectionEnabled = false; LstmLayer* const layer = graph.AddLayer(layerDesc, "layer"); unsigned int batchSize = 3; unsigned int inputSize = 2; unsigned int numUnits = 4; unsigned int outputSize = 4; layer->m_BasicParameters.m_InputToForgetWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_BasicParameters.m_InputToCellWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_BasicParameters.m_InputToOutputWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_BasicParameters.m_RecurrentToForgetWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_BasicParameters.m_RecurrentToCellWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_BasicParameters.m_RecurrentToOutputWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_BasicParameters.m_ForgetGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_BasicParameters.m_CellBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_BasicParameters.m_OutputGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_BasicParameters.m_InputToForgetWeights->Allocate(); layer->m_BasicParameters.m_InputToCellWeights->Allocate(); layer->m_BasicParameters.m_InputToOutputWeights->Allocate(); layer->m_BasicParameters.m_RecurrentToForgetWeights->Allocate(); layer->m_BasicParameters.m_RecurrentToCellWeights->Allocate(); layer->m_BasicParameters.m_RecurrentToOutputWeights->Allocate(); layer->m_BasicParameters.m_ForgetGateBias->Allocate(); layer->m_BasicParameters.m_CellBias->Allocate(); layer->m_BasicParameters.m_OutputGateBias->Allocate(); if (!layerDesc.m_CifgEnabled) { layer->m_CifgParameters.m_InputToInputWeights = std::make_unique (TensorInfo({ numUnits, inputSize }, DataType::Float32)); layer->m_CifgParameters.m_RecurrentToInputWeights = std::make_unique (TensorInfo({ numUnits, outputSize }, DataType::Float32)); layer->m_CifgParameters.m_CellToInputWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_CifgParameters.m_InputGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_CifgParameters.m_InputToInputWeights->Allocate(); layer->m_CifgParameters.m_RecurrentToInputWeights->Allocate(); layer->m_CifgParameters.m_CellToInputWeights->Allocate(); layer->m_CifgParameters.m_InputGateBias->Allocate(); } if (layerDesc.m_ProjectionEnabled) { layer->m_ProjectionParameters.m_ProjectionWeights = std::make_unique (TensorInfo({ outputSize, numUnits }, DataType::Float32)); layer->m_ProjectionParameters.m_ProjectionBias = std::make_unique (TensorInfo({ outputSize }, DataType::Float32)); layer->m_ProjectionParameters.m_ProjectionWeights->Allocate(); layer->m_ProjectionParameters.m_ProjectionBias->Allocate(); } if (layerDesc.m_PeepholeEnabled) { layer->m_PeepholeParameters.m_CellToForgetWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_PeepholeParameters.m_CellToOutputWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_PeepholeParameters.m_CellToForgetWeights->Allocate(); layer->m_PeepholeParameters.m_CellToOutputWeights->Allocate(); } // create input and output layers Layer* const input = graph.AddLayer(0, "input"); Layer* const outputStateIn = graph.AddLayer(1, "outputStateIn"); Layer* const cellStateIn = graph.AddLayer(2, "cellStateIn"); Layer* const scratchBuffer = graph.AddLayer(0, "scratchBuffer"); Layer* const outputStateOut = graph.AddLayer(1, "outputStateOut"); Layer* const cellStateOut = graph.AddLayer(2, "cellStateOut"); Layer* const output = graph.AddLayer(3, "output"); // connect up armnn::TensorInfo lstmTensorInfo1({ batchSize, inputSize }, DataType::Float32); armnn::TensorInfo lstmTensorInfo2({ batchSize, numUnits}, DataType::Float32); armnn::TensorInfo lstmTensorInfo3({ batchSize, outputSize }, DataType::Float32); armnn::TensorInfo lstmTensorInfoScratchBuff({ batchSize, numUnits*3 }, DataType::Float32); if (layerDesc.m_CifgEnabled) { lstmTensorInfoScratchBuff.SetShape({ batchSize, numUnits*4 }); } Connect(input, layer, lstmTensorInfo1, 0, 0); Connect(cellStateIn, layer, lstmTensorInfo2, 0, 1); Connect(outputStateIn, layer, lstmTensorInfo3, 0, 2); Connect(layer, scratchBuffer, lstmTensorInfoScratchBuff, 0, 0); Connect(layer, outputStateOut, lstmTensorInfo3, 1, 0); Connect(layer, cellStateOut, lstmTensorInfo2, 2, 0); Connect(layer, output, lstmTensorInfo3, 3, 0); } } BOOST_AUTO_TEST_SUITE(Optimizer) using namespace armnn::optimizations; BOOST_AUTO_TEST_CASE(OptimizeInversePermutesTest) { armnn::Graph graph; auto output = graph.AddLayer(0, "output"); graph.InsertNewLayer(output->GetInputSlot(0), 0, "input"); // Inserts two permutes, one the inverse of the other. graph.InsertNewLayer(output->GetInputSlot(0), armnn::PermuteDescriptor({0, 2, 3, 1}), "perm0231"); graph.InsertNewLayer(output->GetInputSlot(0), armnn::PermuteDescriptor({0, 3, 1, 2}), "perm0312"); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeInversePermutes())); // The permutes are removed. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(LSTMValidateTensorShapesFromInputsCIFGDisabledTest) { Graph graph; //Helper function creates graph containing LSTM layer with required input and output layers CreateLSTMLayerHelper(graph, false); //This function used to call ValidateShapesFromInputs(); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(LSTMValidateTensorShapesFromInputsCIFGEnabledTest) { Graph graph; //Helper function creates graph containing LSTM layer with required input and output layers CreateLSTMLayerHelper(graph, true); //This function used to call ValidateShapesFromInputs(); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(MovePermuteUpTest) { const armnn::TensorInfo info({ 1, 5, 2, 3 }, armnn::DataType::Float32); const armnn::TensorInfo permuted({ 1, 3, 5, 2 }, armnn::DataType::Float32); armnn::Graph graph; armnn::LayerBindingId inputId = 0; armnn::Layer* head = graph.AddLayer(0, "output"); std::string permuteLayerName = "original_permute"; // Insert permute head = graph.InsertNewLayer(head->GetInputSlot(0), armnn::PermuteDescriptor({ 0, 2, 3, 1 }), permuteLayerName.c_str()); head->GetOutputHandler().SetTensorInfo(permuted); // Inserts layers that don't care about data format. head = graph.InsertNewLayer(head->GetInputSlot(0), armnn::ActivationDescriptor{}, ""); head->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); // Inserts input for 2nd input of Addition. graph.InsertNewLayer(head->GetInputSlot(1), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), armnn::FakeQuantizationDescriptor{}, ""); head->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); // Inserts input for 2nd input of Multiplication. graph.InsertNewLayer(head->GetInputSlot(1), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); // Inserts input. graph.InsertNewLayer(head->GetInputSlot(0), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(MovePermuteUp())); // The permute is moved to the top. New permutes for layers with multiple inputs. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); std::list testRelatedLayers = { permuteLayerName }; BOOST_TEST(CheckRelatedLayers(graph, testRelatedLayers)); } BOOST_AUTO_TEST_CASE(PermuteAsReshapeTest) { armnn::Graph graph; std::string permuteLayerName = "permute"; const armnn::TensorInfo infoIn({ 1, 2, 3, 1 }, armnn::DataType::Float32); const armnn::TensorInfo infoOut({ 1, 1, 2, 3 }, armnn::DataType::Float32); auto output = graph.AddLayer(0, "output"); graph.InsertNewLayer(output->GetInputSlot(0), 0, "input") ->GetOutputHandler().SetTensorInfo(infoIn); // Inserts permute. graph.InsertNewLayer(output->GetInputSlot(0), armnn::PermuteDescriptor({ 0, 2, 3, 1 }), permuteLayerName.c_str()) ->GetOutputHandler().SetTensorInfo(infoOut); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(PermuteAsReshape())); // The permute is replaced by an equivalent reshape. auto checkReshape = [&infoOut](const armnn::Layer* const layer) -> bool { const auto reshapeLayer = static_cast(layer); return IsLayerOfType(layer) && (reshapeLayer->GetParameters().m_TargetShape == infoOut.GetShape()) && (reshapeLayer->GetOutputHandler().GetTensorInfo().GetShape() == infoOut.GetShape()); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkReshape, &IsLayerOfType)); std::list testRelatedLayers = { permuteLayerName }; BOOST_TEST(CheckRelatedLayers(graph, testRelatedLayers)); } BOOST_AUTO_TEST_CASE(OptimizeConsecutiveReshapesTest) { armnn::Graph graph; const armnn::TensorInfo info0({ 1, 2, 3, 5 }, armnn::DataType::Float32); auto output = graph.AddLayer(0, "output"); auto input = graph.InsertNewLayer(output->GetInputSlot(0), 0, "input"); input->GetOutputHandler().SetTensorInfo(info0); { // Inserts two reshapes. const armnn::TensorInfo info1({1, 30, 1, 1}, armnn::DataType::Float32); const armnn::TensorInfo info2({1, 2, 1, 15}, armnn::DataType::Float32); std::string reshape1Name = "reshape1"; std::string reshape2Name = "reshape2"; auto reshape1 = graph.InsertNewLayer(output->GetInputSlot(0), armnn::ReshapeDescriptor{ info1.GetShape() }, reshape1Name.c_str()); auto reshape2 = graph.InsertNewLayer(output->GetInputSlot(0), armnn::ReshapeDescriptor{ info2.GetShape() }, reshape2Name.c_str()); reshape1->GetOutputHandler().SetTensorInfo(info1); reshape2->GetOutputHandler().SetTensorInfo(info2); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeConsecutiveReshapes())); auto checkReshape = [&info2](const armnn::Layer* const layer) -> bool { const auto reshapeLayer = static_cast(layer); return IsLayerOfType(layer) && (reshapeLayer->GetParameters().m_TargetShape == info2.GetShape()) && (reshapeLayer->GetOutputHandler().GetTensorInfo().GetShape() == info2.GetShape()); }; // The two reshapes are replaced by a single equivalent reshape. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkReshape, &IsLayerOfType)); // Check the new reshape layer has the other two reshapes as related layers std::list testRelatedLayers = { reshape2Name, reshape1Name }; BOOST_TEST(CheckRelatedLayers(graph, testRelatedLayers)); } { // Inserts a reshape to the input shape. auto reshapeToIn = graph.InsertNewLayer(output->GetInputSlot(0), armnn::ReshapeDescriptor{ info0.GetShape() }, "reshapeToIn"); reshapeToIn->GetOutputHandler().SetTensorInfo(info0); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeConsecutiveReshapes())); // The two reshapes are removed. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType)); } } BOOST_AUTO_TEST_CASE(SquashEqualSiblingsTest) { armnn::Graph graph; armnn::LayerBindingId outputId = 0; const armnn::TensorInfo info({ 1, 2, 3, 5 }, armnn::DataType::Float32); const armnn::TensorInfo permuted({ 1, 5, 2, 3 }, armnn::DataType::Float32); auto input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(info); // Inserts equal permutes, equal reshapes and something else. const armnn::PermuteDescriptor permDesc({ 0, 2, 3, 1 }); const armnn::ReshapeDescriptor reshapeDesc{ { 1, 3, 1, 5 } }; armnn::Layer* layer; layer = graph.AddLayer(permDesc, ""); layer->GetOutputSlot().SetTensorInfo(permuted); layer->GetOutputSlot().Connect(graph.AddLayer(outputId++, "")->GetInputSlot(0)); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer = graph.AddLayer(reshapeDesc, ""); layer->GetOutputSlot().Connect(graph.AddLayer(outputId++, "")->GetInputSlot(0)); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer = graph.AddLayer(""); layer->GetOutputSlot().Connect(graph.AddLayer(outputId++, "")->GetInputSlot(0)); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer = graph.AddLayer(reshapeDesc, ""); layer->GetOutputSlot().Connect(graph.AddLayer(outputId++, "")->GetInputSlot(0)); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer = graph.AddLayer(permDesc, ""); layer->GetOutputSlot().SetTensorInfo(permuted); layer->GetOutputSlot().Connect(graph.AddLayer(outputId++, "")->GetInputSlot(0)); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(SquashEqualPermuteSiblings(), SquashEqualReshapeSiblings())); // The permutes and reshapes are squashed. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(ConvertConstantsHalfToFloatTest) { armnn::Graph graph; const armnn::TensorInfo info({ 1,1,1,2 }, armnn::DataType::Float32); // Create the half precision input data unsigned int dims[] = { 4,1,1,1 }; std::vector convWeightsData{1.f, 2.f, 3.f, 4.f}; std::vector halfWeights(4); armnnUtils::FloatingPointConverter::ConvertFloat32To16(convWeightsData.data(), convWeightsData.size(), halfWeights.data()); armnn::ConstTensor weights(armnn::TensorInfo(4, dims, armnn::DataType::Float16), halfWeights); //Create the simple test network auto input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(info); auto fc = graph.AddLayer(armnn::FullyConnectedDescriptor(), "fc"); fc->m_Weight = std::make_unique(weights); fc->GetOutputSlot().SetTensorInfo(info); auto output = graph.AddLayer(1, "output"); //Connect up the layers input->GetOutputSlot().Connect(fc->GetInputSlot(0)); fc->GetOutputSlot().Connect(output->GetInputSlot(0)); //Test the tensor info is correct. BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float16); // Run the optimizer armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(ConvertConstantsHalfToFloat())); //Test the tensor info is correct. BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float32); // Now test the data matches float32 data float* data = fc->m_Weight->GetTensor(); BOOST_CHECK(1.0f == data[0]); BOOST_CHECK(2.0f == data[1]); BOOST_CHECK(3.0f == data[2]); BOOST_CHECK(4.0f == data[3]); } BOOST_AUTO_TEST_CASE(ConvertConstantsFloatToHalfTest) { armnn::Graph graph; const armnn::TensorInfo info({ 1, 1, 1, 2 }, armnn::DataType::Float16); // Create const tensor from fp32 data unsigned int dims[] = { 4, 1, 1, 1 }; std::vector floatWeights{ 1.0f, 2.0f, 3.0f, 4.0f }; armnn::ConstTensor weights(armnn::TensorInfo(4, dims, armnn::DataType::Float32), floatWeights); // Create simple test network auto input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(info); auto fc = graph.AddLayer(armnn::FullyConnectedDescriptor(), "fc"); fc->m_Weight = std::make_unique(weights); fc->GetOutputSlot().SetTensorInfo(info); auto output = graph.AddLayer(1, "output"); // Connect up the layers input->GetOutputSlot().Connect(fc->GetInputSlot(0)); fc->GetOutputSlot().Connect(output->GetInputSlot(0)); // Check tensor data type before conversion BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float32); // Run the optimizer armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(ConvertConstantsFloatToHalf())); // Check tensor data type after conversion BOOST_CHECK(fc->m_Weight->GetTensorInfo().GetDataType() == armnn::DataType::Float16); // Check whether data matches expected fp16 data Half* data = fc->m_Weight->GetTensor(); BOOST_CHECK(data[0] == Half(1.0f)); BOOST_CHECK(data[1] == Half(2.0f)); BOOST_CHECK(data[2] == Half(3.0f)); BOOST_CHECK(data[3] == Half(4.0f)); } BOOST_AUTO_TEST_CASE(OptimizeInverseConversionsTest) { armnn::Graph graph; auto output = graph.AddLayer(0, "output"); graph.InsertNewLayer(output->GetInputSlot(0), 0, "input"); // Fp32ToFp16 conversion followed by an inverse Fp16ToFp32 conversion graph.InsertNewLayer(output->GetInputSlot(0), "convert1"); graph.InsertNewLayer(output->GetInputSlot(0), "convert2"); graph.InsertNewLayer(output->GetInputSlot(0), Convolution2dDescriptor(), "conv"); // Fp16ToFp32 conversion followed by an inverse Fp32ToFp16 conversion graph.InsertNewLayer(output->GetInputSlot(0), "convert3"); graph.InsertNewLayer(output->GetInputSlot(0), "convert4"); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(OptimizeInverseConversionsFp16(), OptimizeInverseConversionsFp32())); // Check that all consecutive inverse conversions are removed BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(InsertConvertersTest) { const armnn::TensorInfo info({ 1, 5, 2, 3 }, armnn::DataType::Float16); armnn::Graph graph; armnn::LayerBindingId inputId = 0; armnn::Layer* head = graph.AddLayer(0, "output"); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); graph.InsertNewLayer(head->GetInputSlot(1), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); head = graph.InsertNewLayer(head->GetInputSlot(0), ""); head->GetOutputHandler().SetTensorInfo(info); graph.InsertNewLayer(head->GetInputSlot(0), inputId++, "") ->GetOutputHandler().SetTensorInfo(info); // Check graph layer sequence before inserting convert layers BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Check layers have Float16 DataType for (auto& layer : graph) { if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition) { BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16); BOOST_ASSERT(layer->GetDataType() == DataType::Float16); } } // Insert convert layers either side of unsupported layer for (auto& layer : graph) { if(layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition) { InsertConvertFp16ToFp32LayersBefore(graph, *layer); InsertConvertFp32ToFp16LayersAfter(graph, *layer); } } // Check layers have correct DataType after inserting convert layers for (auto& layer : graph) { if (layer->GetType()==LayerType::Floor || layer->GetType() == LayerType::Addition) { BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32); BOOST_ASSERT(layer->GetDataType() == DataType::Float32); } else if (layer->GetType() == LayerType::ConvertFp16ToFp32) { BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32); BOOST_ASSERT(layer->GetDataType() == DataType::Float16); } else if (layer->GetType() == LayerType::ConvertFp32ToFp16) { BOOST_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16); BOOST_ASSERT(layer->GetDataType() == DataType::Float32); } } // Check sequence of layers after inserting convert layers BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(Fp32NetworkToFp16OptimizationTest) { armnn::Graph graph; const armnn::TensorInfo infoFP32({ 2,2,1,3 }, armnn::DataType::Float32); // Create the simple test network auto input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(infoFP32); auto floor = graph.AddLayer("floor"); floor->GetOutputSlot().SetTensorInfo(infoFP32); auto output = graph.AddLayer(1, "output"); // Connect up the layers input->GetOutputSlot().Connect(floor->GetInputSlot(0)); floor->GetOutputSlot().Connect(output->GetInputSlot(0)); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Run the optimizer armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(Fp32NetworkToFp16Converter())); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_SUITE_END()