// // Copyright © 2017 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // #include "TestUtils.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace armnn; namespace { 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_InputGateBias = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_CifgParameters.m_InputToInputWeights->Allocate(); layer->m_CifgParameters.m_RecurrentToInputWeights->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) { if (!layerDesc.m_CifgEnabled) { layer->m_PeepholeParameters.m_CellToInputWeights = std::make_unique (TensorInfo({ numUnits }, DataType::Float32)); layer->m_PeepholeParameters.m_CellToInputWeights->Allocate(); } 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 * (layerDesc.m_CifgEnabled ? 3 : 4) }, DataType::Float32); 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); } } // namespace BOOST_AUTO_TEST_SUITE(Optimizer) using namespace armnn::optimizations; 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(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) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16); ARMNN_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) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32); ARMNN_ASSERT(layer->GetDataType() == DataType::Float32); } else if (layer->GetType() == LayerType::ConvertFp16ToFp32) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float32); ARMNN_ASSERT(layer->GetDataType() == DataType::Float16); } else if (layer->GetType() == LayerType::ConvertFp32ToFp16) { ARMNN_ASSERT(layer->GetOutputSlot(0).GetTensorInfo().GetDataType() == DataType::Float16); ARMNN_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)); } void CreateConvolution2dGraph(Graph &graph, const unsigned int* inputShape, const unsigned int* weightsShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); std::vector weightsVector(90); armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector); Convolution2dDescriptor desc; desc.m_BiasEnabled = false; desc.m_StrideX = 1; desc.m_StrideY = 1; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); Convolution2dLayer* layer = graph.AddLayer(desc, "conv2d"); layer->m_Weight = std::make_unique(weights); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } BOOST_AUTO_TEST_CASE(Conv2dValidateTensorShapesFromInputs) { Graph graph; const unsigned int inputShape[] = { 1, 3, 8, 16 }; const unsigned int weightsShape[] = { 2, 3, 5, 3 }; const unsigned int outputShape[] = { 1, 2, 4, 14 }; CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(Conv2dValidateTensorShapesFromInputsNhwc) { Graph graph; const unsigned int inputShape[] = { 1, 8, 16, 3 }; const unsigned int weightsShape[] = { 2, 5, 3, 3 }; const unsigned int outputShape[] = { 1, 4, 14, 2 }; CreateConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } void CreateDepthwiseConvolution2dGraph(Graph &graph, const unsigned int* inputShape, const unsigned int* weightsShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); std::vector weightsVector(18); armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector); DepthwiseConvolution2dDescriptor desc; desc.m_BiasEnabled = false; desc.m_StrideX = 1; desc.m_StrideY = 1; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); DepthwiseConvolution2dLayer* layer = graph.AddLayer(desc, "depthwiseConv2d"); layer->m_Weight = std::make_unique(weights); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } BOOST_AUTO_TEST_CASE(DepthwiseConv2dValidateTensorShapesFromInputs) { Graph graph; const unsigned int inputShape[] = { 1, 2, 3, 3 }; const unsigned int weightsShape[] = { 1, 2, 3, 3 }; const unsigned int outputShape[] = { 1, 2, 1, 1 }; CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(DepthwiseConv2dValidateTensorShapesFromInputsNhwc) { Graph graph; const unsigned int inputShape[] = { 1, 3, 3, 2 }; const unsigned int weightsShape[] = { 1, 2, 3, 3 }; const unsigned int outputShape[] = { 1, 1, 1, 2 }; CreateDepthwiseConvolution2dGraph(graph, inputShape, weightsShape, outputShape, DataLayout::NHWC); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } void CreatePooling2dGraph(Graph& graph, const unsigned int* inputShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Pooling2dDescriptor desc; desc.m_PoolType = armnn::PoolingAlgorithm::Average; desc.m_PoolWidth = desc.m_PoolHeight = 100; desc.m_StrideX = desc.m_StrideY = 5; desc.m_PadLeft = 50; desc.m_PadRight = 50; desc.m_PadTop = 50; desc.m_PadBottom = 50; desc.m_PaddingMethod = armnn::PaddingMethod::Exclude; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); Pooling2dLayer* layer = graph.AddLayer(desc, "pooling2d"); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputs) { Graph graph; const unsigned int inputShape[] = { 5, 3, 52, 60 }; const unsigned int outputShape[] = { 5, 3, 11, 13 }; CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NCHW); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputsNhwc) { Graph graph; const unsigned int inputShape[] = { 5, 52, 60, 3 }; const unsigned int outputShape[] = { 5, 11, 13, 3 }; CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NHWC); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } void CreateResizeBilinearGraph(Graph& graph, const unsigned int* inputShape, const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { TensorInfo inputInfo(4, inputShape, DataType::Float32); TensorInfo outputInfo(4, outputShape, DataType::Float32); ResizeDescriptor desc; desc.m_Method = ResizeMethod::Bilinear; desc.m_TargetHeight = 3; desc.m_TargetWidth = 4; desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); ResizeLayer* layer = graph.AddLayer(desc, "resizeBilinear"); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input->GetOutputSlot().Connect(layer->GetInputSlot(0)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputs) { Graph graph; const unsigned int inputShape[] = { 1, 2, 4, 5 }; const unsigned int outputShape[] = { 1, 2, 3, 4 }; CreateResizeBilinearGraph(graph, inputShape, outputShape); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputsNhwc) { Graph graph; const unsigned int inputShape[] = { 1, 4, 5, 2 }; const unsigned int outputShape[] = { 1, 3, 4, 2 }; CreateResizeBilinearGraph(graph, inputShape, outputShape, DataLayout::NHWC); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } void CreateGatherGraph(Graph& graph, const armnn::TensorInfo& paramsInfo, const armnn::TensorInfo& indicesInfo, const armnn::TensorInfo& outputInfo) { Layer* input0 = graph.AddLayer(0, "params"); input0->GetOutputSlot().SetTensorInfo(paramsInfo); Layer* input1 = graph.AddLayer(1, "indices"); input1->GetOutputSlot().SetTensorInfo(indicesInfo); GatherDescriptor descriptor; GatherLayer* layer = graph.AddLayer(descriptor, "gather"); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); input0->GetOutputSlot().Connect(layer->GetInputSlot(0)); input1->GetOutputSlot().Connect(layer->GetInputSlot(1)); layer->GetOutputSlot().Connect(output->GetInputSlot(0)); } BOOST_AUTO_TEST_CASE(GatherValidateTensorShapesFromInputs) { Graph graph; armnn::TensorInfo paramsInfo({10, 5}, DataType::Float32); armnn::TensorInfo indicesInfo({3}, DataType::Signed32); armnn::TensorInfo outputInfo({3, 5}, DataType::Float32); CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(GatherValidateTensorShapesFromInputs1DParams) { Graph graph; armnn::TensorInfo paramsInfo({8}, DataType::Float32); armnn::TensorInfo indicesInfo({5}, DataType::Signed32); armnn::TensorInfo outputInfo( {5}, DataType::Float32); CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(GatherValidateTensorShapesFromInputsMultiDimIndices) { Graph graph; armnn::TensorInfo paramsInfo({3, 2, 5}, DataType::Float32); armnn::TensorInfo indicesInfo({2, 2}, DataType::Signed32); armnn::TensorInfo outputInfo({2, 2, 2, 5}, DataType::Float32); CreateGatherGraph(graph, paramsInfo, indicesInfo, outputInfo); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(DetectionPostProcessValidateTensorShapes) { Graph graph; armnn::TensorInfo boxEncodingsInfo({1, 10, 4}, DataType::QAsymmU8); armnn::TensorInfo scoresInfo({1, 10, 4}, DataType::QAsymmU8); std::vector anchorsVector(40); armnn::ConstTensor anchors(armnn::TensorInfo({10, 4}, armnn::DataType::QAsymmU8), anchorsVector); armnn::TensorInfo detectionBoxesInfo({1, 3, 4}, DataType::QAsymmU8); armnn::TensorInfo detectionScoresInfo({1, 3}, DataType::QAsymmU8); armnn::TensorInfo detectionClassesInfo({1, 3}, DataType::QAsymmU8); armnn::TensorInfo numDetectionInfo({1}, DataType::QAsymmU8); Layer* input0 = graph.AddLayer(0, "boxEncodings"); input0->GetOutputSlot().SetTensorInfo(boxEncodingsInfo); Layer* input1 = graph.AddLayer(1, "score"); input1->GetOutputSlot().SetTensorInfo(scoresInfo); DetectionPostProcessDescriptor descriptor; descriptor.m_MaxDetections = 3; DetectionPostProcessLayer* layer = graph.AddLayer(descriptor, "detectionPostProcess"); layer->m_Anchors = std::make_unique(anchors); layer->GetOutputSlot(0).SetTensorInfo(detectionBoxesInfo); layer->GetOutputSlot(1).SetTensorInfo(detectionScoresInfo); layer->GetOutputSlot(2).SetTensorInfo(detectionClassesInfo); layer->GetOutputSlot(3).SetTensorInfo(numDetectionInfo); input0->GetOutputSlot().Connect(layer->GetInputSlot(0)); input1->GetOutputSlot().Connect(layer->GetInputSlot(1)); BOOST_CHECK_NO_THROW(graph.InferTensorInfos()); } BOOST_AUTO_TEST_CASE(FoldPadLayerIntoConvolution2dLayer) { Graph graph; const unsigned int inputShape[] = { 1, 2, 2, 3 }; const unsigned int paddedShape[] = { 1, 6, 6, 3 }; const unsigned int weightsShape[] = { 1, 2, 3, 3 }; const unsigned int outputShape[] = { 1, 2, 1, 1 }; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); PadDescriptor padDescriptor({ { 0, 0 }, { 2, 2 }, { 2, 2 }, { 0, 0 } }); PadLayer* padLayer = graph.AddLayer(padDescriptor, "pad"); padLayer->GetOutputSlot().SetTensorInfo(paddedInfo); Convolution2dDescriptor convolution2dDescriptor; convolution2dDescriptor.m_BiasEnabled = false; convolution2dDescriptor.m_StrideX = 1; convolution2dDescriptor.m_StrideY = 1; convolution2dDescriptor.m_DataLayout = DataLayout::NHWC; std::vector weightsVector(18); armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector); Convolution2dLayer* conv2dLayer = graph.AddLayer(convolution2dDescriptor, "conv2d"); conv2dLayer->m_Weight = std::make_unique(weights); conv2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); // Connect up layers - input -> pad -> conv2d -> output input->GetOutputSlot().Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot().Connect(conv2dLayer->GetInputSlot(0)); conv2dLayer->GetOutputSlot().Connect(output->GetInputSlot(0)); auto checkSimpleConv2d = [](const armnn::Layer* const layer) -> bool { const auto conv2dLayer = static_cast(layer); const auto conv2dLayerParams = conv2dLayer->GetParameters(); return IsLayerOfType(layer) && (layer->GetNameStr() == "conv2d") && (conv2dLayerParams.m_PadLeft == 0) && (conv2dLayerParams.m_PadRight == 0) && (conv2dLayerParams.m_PadTop == 0) && (conv2dLayerParams.m_PadBottom == 0) && (conv2dLayerParams.m_BiasEnabled == false) && (conv2dLayerParams.m_StrideX == 1) && (conv2dLayerParams.m_StrideY == 1) && (conv2dLayerParams.m_DataLayout == DataLayout::NHWC); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimpleConv2d, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoConvolution2d())); auto checkPadFoldedIntoConv2d = [](const armnn::Layer* const layer) -> bool { const auto conv2dLayer = static_cast(layer); const auto conv2dLayerParams = conv2dLayer->GetParameters(); return IsLayerOfType(layer) && (layer->GetNameStr() == "folded-pad-into-conv2d") && (conv2dLayerParams.m_PadLeft == 2) && (conv2dLayerParams.m_PadRight == 2) && (conv2dLayerParams.m_PadTop == 2) && (conv2dLayerParams.m_PadBottom == 2) && (conv2dLayerParams.m_BiasEnabled == false) && (conv2dLayerParams.m_StrideX == 1) && (conv2dLayerParams.m_StrideY == 1) && (conv2dLayerParams.m_DataLayout == DataLayout::NHWC); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkPadFoldedIntoConv2d, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(FoldPadLayerIntoDepthwiseConvolution2dLayer) { Graph graph; const unsigned int inputShape[] = {1, 2, 2, 3}; const unsigned int paddedShape[] = {1, 6, 6, 3}; const unsigned int weightsShape[] = {1, 2, 3, 3}; const unsigned int outputShape[] = {1, 2, 1, 3}; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); PadDescriptor padDescriptor({{0, 0}, {2, 2}, {2, 2}, {0, 0}}); PadLayer* padLayer = graph.AddLayer(padDescriptor, "pad"); padLayer->GetOutputSlot().SetTensorInfo(paddedInfo); DepthwiseConvolution2dDescriptor depthwiseConvolution2dDescriptor; depthwiseConvolution2dDescriptor.m_BiasEnabled = false; depthwiseConvolution2dDescriptor.m_StrideX = 1; depthwiseConvolution2dDescriptor.m_StrideY = 1; depthwiseConvolution2dDescriptor.m_DataLayout = DataLayout::NHWC; std::vector weightsVector(18); armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector); auto* depthwiseConv2dLayer = graph .AddLayer(depthwiseConvolution2dDescriptor, "depthwiseConv2d"); depthwiseConv2dLayer->m_Weight = std::make_unique(weights); depthwiseConv2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); // Connect up layers - input -> pad -> depthwiseConv2d -> output input->GetOutputSlot().Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot().Connect(depthwiseConv2dLayer->GetInputSlot(0)); depthwiseConv2dLayer->GetOutputSlot().Connect(output->GetInputSlot(0)); auto checkSimpleDepthwiseConv2d = [](const armnn::Layer* const layer)->bool { const auto depthwiseConv2dLayer = static_cast(layer); const auto depthwiseConv2dLayerParams = depthwiseConv2dLayer->GetParameters(); return IsLayerOfType(layer) && (layer->GetNameStr() == "depthwiseConv2d")&& (depthwiseConv2dLayerParams.m_PadLeft == 0) && (depthwiseConv2dLayerParams.m_PadRight == 0) && (depthwiseConv2dLayerParams.m_PadTop == 0) && (depthwiseConv2dLayerParams.m_PadBottom == 0) && (depthwiseConv2dLayerParams.m_BiasEnabled == false) && (depthwiseConv2dLayerParams.m_StrideX == 1) && (depthwiseConv2dLayerParams.m_StrideY == 1) && (depthwiseConv2dLayerParams.m_DataLayout == DataLayout::NHWC); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimpleDepthwiseConv2d, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoDepthwiseConvolution2d())); auto checkPadFoldedIntoDepthwiseConv2d = [](const armnn::Layer* const layer)->bool { const auto depthwiseConv2dLayer = static_cast(layer); const auto depthwiseConv2dLayerParams = depthwiseConv2dLayer->GetParameters(); return IsLayerOfType(layer) && (layer->GetNameStr() == "folded-pad-into-depthwiseConv2d") && (depthwiseConv2dLayerParams.m_PadLeft == 2) && (depthwiseConv2dLayerParams.m_PadRight == 2) && (depthwiseConv2dLayerParams.m_PadTop == 2) && (depthwiseConv2dLayerParams.m_PadBottom == 2) && (depthwiseConv2dLayerParams.m_BiasEnabled == false) && (depthwiseConv2dLayerParams.m_StrideX == 1) && (depthwiseConv2dLayerParams.m_StrideY == 1) && (depthwiseConv2dLayerParams.m_DataLayout == DataLayout::NHWC); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkPadFoldedIntoDepthwiseConv2d, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(FoldPadLayerIntoPooling2dLayer) { Graph graph; const unsigned int inputShape[] = { 1, 2, 2, 3 }; const unsigned int paddedShape[] = { 1, 4, 4, 3 }; const unsigned int outputShape[] = { 1, 2, 2, 3 }; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); PadLayer* padLayer = graph.AddLayer(padDescriptor, "pad"); padLayer->GetOutputSlot().SetTensorInfo(paddedInfo); Pooling2dDescriptor pooling2dDescriptor; pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average; pooling2dDescriptor.m_PoolWidth = 3; pooling2dDescriptor.m_PoolHeight = 3; pooling2dDescriptor.m_StrideX = 1; pooling2dDescriptor.m_StrideY = 1; pooling2dDescriptor.m_DataLayout = DataLayout::NHWC; Pooling2dLayer* pool2dLayer = graph.AddLayer(pooling2dDescriptor, "pool2d"); pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); // Connect up layers - input -> pad -> pool2d -> output input->GetOutputSlot().Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot().Connect(pool2dLayer->GetInputSlot(0)); pool2dLayer->GetOutputSlot().Connect(output->GetInputSlot(0)); auto checkSimplePool2d = [&](const armnn::Layer* const layer) { const auto pool2dLayer = static_cast(layer); return IsLayerOfType(layer) && (layer->GetNameStr() == "pool2d") && (pool2dLayer->GetParameters() == pooling2dDescriptor); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); auto checkPadFoldedIntoPool2d = [&](const armnn::Layer* const layer) { if (!IsLayerOfType(layer) || (layer->GetNameStr() != "folded-pad-into-pool2d")) { return false; } const auto pool2dLayer = static_cast(layer); const Pooling2dDescriptor pool2dLayerParams = pool2dLayer->GetParameters(); Pooling2dDescriptor pool2dLayerParamsNoPad = pool2dLayerParams; pool2dLayerParamsNoPad.m_PadLeft = 0; pool2dLayerParamsNoPad.m_PadRight = 0; pool2dLayerParamsNoPad.m_PadTop = 0; pool2dLayerParamsNoPad.m_PadBottom = 0; // If we fold then PaddingMethod will be set to Ignore. The original will be Exclude. pool2dLayerParamsNoPad.m_PaddingMethod = PaddingMethod::Exclude; return (pool2dLayerParamsNoPad == pooling2dDescriptor) && (pool2dLayerParams.m_PadLeft == 1) && (pool2dLayerParams.m_PadRight == 1) && (pool2dLayerParams.m_PadTop == 1) && (pool2dLayerParams.m_PadBottom == 1) && (pool2dLayerParams.m_PaddingMethod == PaddingMethod::IgnoreValue); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkPadFoldedIntoPool2d, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(FoldPadLayerIntoPooling2d_PadWithMultipleOutputsShouldNotBeOptimized) { // In this test case we'll setup a pad layer with two outputs. One goes to a polling layers and the other // goes to an output layer. FoldPadLayerIntoPooling2d should not optimize this graph as it uses the // OptimizeForExclusiveConnection method. Graph graph; const unsigned int inputShape[] = { 1, 2, 2, 3 }; const unsigned int paddedShape[] = { 1, 4, 4, 3 }; const unsigned int outputShape[] = { 1, 2, 2, 3 }; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); PadLayer* padLayer = graph.AddLayer(padDescriptor, "pad"); padLayer->GetOutputSlot().SetTensorInfo(paddedInfo); Pooling2dDescriptor pooling2dDescriptor; pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average; pooling2dDescriptor.m_PoolWidth = 3; pooling2dDescriptor.m_PoolHeight = 3; pooling2dDescriptor.m_StrideX = 1; pooling2dDescriptor.m_StrideY = 1; pooling2dDescriptor.m_DataLayout = DataLayout::NHWC; Pooling2dLayer* pool2dLayer = graph.AddLayer(pooling2dDescriptor, "pool2d"); pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); // Connect up layers - input -> pad -> pool2d -> output input->GetOutputSlot().Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot().Connect(pool2dLayer->GetInputSlot(0)); pool2dLayer->GetOutputSlot().Connect(output->GetInputSlot(0)); // Add the alternative branch from the pas layer to an output layer. Layer* secondOutput = graph.AddLayer(1, "dummy output"); padLayer->GetOutputSlot().Connect(secondOutput->GetInputSlot(0)); auto checkSimplePool2d = [&](const armnn::Layer* const layer) { const auto pool2dLayer = static_cast(layer); return IsLayerOfType(layer) && (layer->GetNameStr() == "pool2d") && (pool2dLayer->GetParameters() == pooling2dDescriptor); }; // Initial sequence. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); // The network should not change. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(FoldPadLayerIntoPooling2dLayer_PoolingLayerWithExcludePaddingShouldNotTakeMorePadding) { // In this test setup input, Pad layer, Pooling layer that includes padding, output layer. The optimization // should not work as the pooling layer already includes and existing pad and specifies PaddingMethod::Exclude. Graph graph; const unsigned int inputShape[] = { 1, 2, 2, 3 }; const unsigned int paddedShape[] = { 1, 4, 4, 3 }; const unsigned int outputShape[] = { 1, 2, 2, 3 }; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); PadLayer* padLayer = graph.AddLayer(padDescriptor, "pad"); padLayer->GetOutputSlot().SetTensorInfo(paddedInfo); Pooling2dDescriptor pooling2dDescriptor; pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average; pooling2dDescriptor.m_PoolWidth = 3; pooling2dDescriptor.m_PoolHeight = 3; pooling2dDescriptor.m_StrideX = 1; pooling2dDescriptor.m_StrideY = 1; pooling2dDescriptor.m_DataLayout = DataLayout::NHWC; // Include a pad with the pooling layer. This should prevent the optimization working. pooling2dDescriptor.m_PadLeft = 1; pooling2dDescriptor.m_PadRight = 1; pooling2dDescriptor.m_PadTop = 1; pooling2dDescriptor.m_PadBottom = 1; pooling2dDescriptor.m_PaddingMethod = PaddingMethod::Exclude; Pooling2dLayer* pool2dLayer = graph.AddLayer(pooling2dDescriptor, "pool2d"); pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); // Connect up layers - input -> pad -> pool2d -> output input->GetOutputSlot().Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot().Connect(pool2dLayer->GetInputSlot(0)); pool2dLayer->GetOutputSlot().Connect(output->GetInputSlot(0)); auto checkSimplePool2d = [&](const armnn::Layer* const layer) { const auto pool2dLayer = static_cast(layer); return IsLayerOfType(layer) && (layer->GetNameStr() == "pool2d") && (pool2dLayer->GetParameters() == pooling2dDescriptor); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); // The optimization should not have modified the graph. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType)); } BOOST_AUTO_TEST_CASE(FoldPadLayerIntoPooling2dLayer_MaxPoolingLayerWithLargePadValueShouldNotBeFolded) { // In this test setup input, Pad layer with a large pad value, Max Pooling layer, output layer. The optimization // should not work as the pad value will modify the result of the max pooling layer. Graph graph; const unsigned int inputShape[] = { 1, 2, 2, 3 }; const unsigned int paddedShape[] = { 1, 4, 4, 3 }; const unsigned int outputShape[] = { 1, 2, 2, 3 }; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Layer* input = graph.AddLayer(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); // For Max pooling of a float a pad value of 0 is more than enough to stop the fold happening. // Set this to -std::numeric_limits::infinity() to make the fold happen. padDescriptor.m_PadValue = 0; PadLayer* padLayer = graph.AddLayer(padDescriptor, "pad"); padLayer->GetOutputSlot().SetTensorInfo(paddedInfo); Pooling2dDescriptor pooling2dDescriptor; pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max; pooling2dDescriptor.m_PoolWidth = 3; pooling2dDescriptor.m_PoolHeight = 3; pooling2dDescriptor.m_StrideX = 1; pooling2dDescriptor.m_StrideY = 1; pooling2dDescriptor.m_DataLayout = DataLayout::NHWC; Pooling2dLayer* pool2dLayer = graph.AddLayer(pooling2dDescriptor, "pool2d"); pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer(0, "output"); // Connect up layers - input -> pad -> pool2d -> output input->GetOutputSlot().Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot().Connect(pool2dLayer->GetInputSlot(0)); pool2dLayer->GetOutputSlot().Connect(output->GetInputSlot(0)); auto checkSimplePool2d = [&](const armnn::Layer* const layer) { const auto pool2dLayer = static_cast(layer); return IsLayerOfType(layer) && (layer->GetNameStr() == "pool2d") && (pool2dLayer->GetParameters() == pooling2dDescriptor); }; BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType)); armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); // The optimization should not have modified the graph. BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, checkSimplePool2d, &IsLayerOfType)); } #if defined(ARMNNREF_ENABLED) BOOST_AUTO_TEST_CASE(FoldPadLayerIntoPooling2dLayer_ExecuteInferenceWithAndWithoutOptimization) { // The idea of this test to run a simple pad+pool2d network twice. Once // with FoldPadLayerIntoPooling2dLayer enabled and a second time with it // avoided. The output tensors of each should match. const unsigned int inputShape[] = { 1, 4, 4, 2 }; const unsigned int paddedShape[] = { 1, 6, 6, 2 }; const unsigned int outputShape[] = { 1, 4, 4, 2 }; std::vector inputData({ 2.0f, 2.0f, 6.0f, 6.0f, 4.0f, 4.0f, 8.0f, 8.0f, 10.0f, 12.0f, 14.0f, 16.0f, 10.0f, 12.0f, 16.0f, 14.0f, 18.0f, 20.0f, 24.0f, 22.0f, 20.0f, 18.0f, 22.0f, 24.0f, 26.0f, 28.0f, 0.0f, 0.0f, 26.0f, 28.0f, 0.0f, 0.0f, }); try { // Create a network of input, pad, pooling 2D, output. INetworkPtr network = INetwork::Create(); IConnectableLayer* inputLayer = network->AddInputLayer(0); armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); inputLayer->GetOutputSlot(0).SetTensorInfo(inputInfo); PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); IConnectableLayer* padLayer = network->AddPadLayer(padDescriptor, "Pad"); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); padLayer->GetOutputSlot(0).SetTensorInfo(paddedInfo); Pooling2dDescriptor pooling2dDescriptor; pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average; pooling2dDescriptor.m_PoolWidth = 3; pooling2dDescriptor.m_PoolHeight = 3; pooling2dDescriptor.m_StrideX = 1; pooling2dDescriptor.m_StrideY = 1; pooling2dDescriptor.m_DataLayout = DataLayout::NHWC; IConnectableLayer* pool2dLayer = network->AddPooling2dLayer(pooling2dDescriptor, "Pool2D"); armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); pool2dLayer->GetOutputSlot(0).SetTensorInfo(outputInfo); IConnectableLayer* outputLayer = network->AddOutputLayer(0); // Connect layers inputLayer->GetOutputSlot(0).Connect(padLayer->GetInputSlot(0)); padLayer->GetOutputSlot(0).Connect(pool2dLayer->GetInputSlot(0)); pool2dLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); // Create ArmNN runtime IRuntimePtr run = IRuntime::Create(IRuntime::CreationOptions()); // default options // Optimise the network IOptimizedNetworkPtr optimizedNetwork = Optimize(*network, { Compute::CpuRef }, run->GetDeviceSpec()); // Load network into runtime NetworkId networkIdentifier; BOOST_TEST(run->LoadNetwork(networkIdentifier, std::move(optimizedNetwork)) == Status::Success); InputTensors inputTensors{ { 0, ConstTensor(run->GetInputTensorInfo(networkIdentifier, 0), inputData.data()) } }; // Set the initial values of the data to different values to the golden data just in case the inference fails. std::vector optimizedData(32, -std::numeric_limits::infinity()); armnn::OutputTensors outputTensors{ { 0, armnn::Tensor(outputInfo, optimizedData.data()) } }; // Execute network run->EnqueueWorkload(networkIdentifier, inputTensors, outputTensors); // Unload it. run->UnloadNetwork(networkIdentifier); // In this second case the pad will have two outputs, one connected to the pooling layer the second connected to // a second output layer. This will prevent the FoldPadLayerIntoPooling2dLayer optimization from working. // A previous test, FoldPadLayerIntoPooling2d_PadWithMultipleOutputsShouldNotBeOptimized, has proved that doing // this will avoid the optimization. IConnectableLayer* dummyOutputLayer = network->AddOutputLayer(1); padLayer->GetOutputSlot(0).Connect(dummyOutputLayer->GetInputSlot(0)); // Optimize and load and execute it a second time. optimizedNetwork = Optimize(*network, { Compute::CpuRef }, run->GetDeviceSpec()); BOOST_TEST(run->LoadNetwork(networkIdentifier, std::move(optimizedNetwork)) == Status::Success); std::vector goldenData(32, 0.0f); std::vector padOutputData(72, 0.0f); armnn::OutputTensors goldenTensors{ { 0, armnn::Tensor(outputInfo, goldenData.data()) }, { 1, armnn::Tensor(paddedInfo, padOutputData.data()) } }; run->EnqueueWorkload(networkIdentifier, inputTensors, goldenTensors); // Now we can compare goldenData against optimizedData. They should be the same. BOOST_TEST(std::equal(goldenData.begin(), goldenData.end(), optimizedData.begin())); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; ARMNN_ASSERT_MSG(false, e.what()); } } #endif class MockLayerSupport : public LayerSupportBase { public: bool IsInputSupported(const TensorInfo& /*input*/, Optional /*reasonIfUnsupported = EmptyOptional()*/) const override { return true; } bool IsOutputSupported(const TensorInfo& /*input*/, Optional /*reasonIfUnsupported = EmptyOptional()*/) const override { return true; } bool IsActivationSupported(const TensorInfo& /*input0*/, const TensorInfo& /*output*/, const ActivationDescriptor& /*descriptor*/, Optional /*reasonIfUnsupported = EmptyOptional()*/) const override { return true; } }; template class MockBackend : public IBackendInternal { public: MockBackend() = default; ~MockBackend() = default; static const BackendId& GetIdStatic() { return NamePolicy::GetIdStatic(); } const BackendId& GetId() const override { return GetIdStatic(); } IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override { return nullptr; }; IBackendInternal::IWorkloadFactoryPtr CreateWorkloadFactory(const IBackendInternal::IMemoryManagerSharedPtr&) const override { return nullptr; } IBackendInternal::IBackendContextPtr CreateBackendContext(const IRuntime::CreationOptions&) const override { return nullptr; } IBackendInternal::Optimizations GetOptimizations() const override { return {}; } IBackendInternal::ILayerSupportSharedPtr GetLayerSupport() const override { return std::make_shared(); } OptimizationViews OptimizeSubgraphView(const SubgraphView&) const override { return {}; }; }; BOOST_AUTO_TEST_CASE(BackendCapabilityTest) { BackendId backendId = "MockBackend"; // MockBackend does not support the NonConstWeights capability BOOST_CHECK(!armnn::IsCapabilitySupported(backendId, armnn::BackendCapability::NonConstWeights)); // MockBackend does not support the AsyncExecution capability BOOST_CHECK(!armnn::IsCapabilitySupported(backendId, armnn::BackendCapability::AsyncExecution)); } BOOST_AUTO_TEST_CASE(BackendHintTest) { class TestBackendAssignment : public LayerVisitorBase { public: void VisitInputLayer(const IConnectableLayer* layer, LayerBindingId id, const char* name = nullptr) override { IgnoreUnused(id, name); auto inputLayer = PolymorphicDowncast(layer); BOOST_TEST((inputLayer->GetBackendId() == "MockBackend")); } void VisitOutputLayer(const IConnectableLayer* layer, LayerBindingId id, const char* name = nullptr) override { IgnoreUnused(id, name); auto outputLayer = PolymorphicDowncast(layer); BOOST_TEST((outputLayer->GetBackendId() == "MockBackend")); } void VisitActivationLayer(const IConnectableLayer* layer, const ActivationDescriptor& activationDescriptor, const char* name = nullptr) override { IgnoreUnused(activationDescriptor, name); auto activation = PolymorphicDowncast(layer); BOOST_TEST((activation->GetBackendId() == "CustomBackend")); } }; struct CustomPolicy { static const BackendId& GetIdStatic() { static BackendId id = "CustomBackend"; return id; } }; struct MockPolicy { static const BackendId& GetIdStatic() { static BackendId id = "MockBackend"; return id; } }; auto& backendRegistry = BackendRegistryInstance(); backendRegistry.Register("MockBackend", []() { return std::make_unique>(); }); backendRegistry.Register("CustomBackend", []() { return std::make_unique>(); }); // Define the network auto network = INetwork::Create(); ActivationDescriptor desc; desc.m_Function = ActivationFunction::Linear; std::unique_ptr graph = std::make_unique(); auto input = graph->AddLayer(0, "input"); auto act = graph->AddLayer(desc, "activation"); auto output = graph->AddLayer(0, "output"); BackendId customBackendId("CustomBackend"); act->BackendSelectionHint(customBackendId); input->GetOutputSlot(0).Connect(act->GetInputSlot(0)); act->GetOutputSlot(0).Connect(output->GetInputSlot(0)); OptimizedNetworkImpl optNet(std::move(graph)); // Get the optimized graph Graph& optGraph = optNet.GetGraph(); std::vector prefs{ "MockBackend", "CustomBackend" }; BackendIdSet availableBackends = { "CustomBackend", "MockBackend" }; DeviceSpec spec(availableBackends); BackendSettings backendSettings(prefs, spec); // Assign an available backend to each layer Graph::Iterator firstLayer = optGraph.begin(); Graph::Iterator lastLayer = optGraph.end(); OptimizedNetworkImpl* optNetObjPtr = &optNet; OptimizationResult res = AssignBackends(optNetObjPtr, backendSettings, firstLayer, lastLayer, EmptyOptional()); BOOST_TEST(res.IsOk()); TestBackendAssignment visitor; for (auto it = firstLayer; it != lastLayer; ++it) { (*it)->Accept(visitor); } } // Tests that OptimizeForExclusiveConnections works, fusing when needed, using BatchNorm fusing as example BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsFuseTest) { using namespace armnn; // Define layers information Convolution2dDescriptor convolution2dDescriptor; convolution2dDescriptor.m_BiasEnabled = false; convolution2dDescriptor.m_DataLayout = DataLayout::NHWC; BatchNormalizationDescriptor batchNormDescriptor; batchNormDescriptor.m_DataLayout = DataLayout::NHWC; const unsigned int inputDimensionSizes[] = { 1, 4, 4, 3 }; // NHWCin const unsigned int weightsDimensionSizes[] = { 1, 2, 2, 3 }; // CoutHWCin const unsigned int outputDimensionSizes[] = { 1, 3, 3, 1 }; // NHWCout const unsigned int outputChannelSize[] = { outputDimensionSizes[3] }; // Cout TensorInfo inputInfo(4, inputDimensionSizes, DataType::Float32); TensorInfo outputInfo(4, outputDimensionSizes, DataType::Float32); std::vector weightsVector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; ConstTensor weights(TensorInfo(4, weightsDimensionSizes, DataType::Float32), weightsVector); std::vector betaVector = { 0.1f }; std::vector gammaVector = { 0.5f }; std::vector meanVector = { 0 }; std::vector varianceVector = { 1 }; ConstTensor beta(TensorInfo(1, outputChannelSize, DataType::Float32), betaVector); ConstTensor gamma(TensorInfo(1, outputChannelSize, DataType::Float32), gammaVector); ConstTensor mean(TensorInfo(1, outputChannelSize, DataType::Float32), meanVector); ConstTensor variance(TensorInfo(1, outputChannelSize, DataType::Float32), varianceVector); // Define the network Graph graph; auto input = graph.AddLayer(0, "input"); auto conv = graph.AddLayer(convolution2dDescriptor, "convolution"); auto batchNorm = graph.AddLayer(batchNormDescriptor, "batchNorm"); auto output = graph.AddLayer(0, "output"); // Set layer information input->GetOutputSlot().SetTensorInfo(inputInfo); conv->GetOutputSlot().SetTensorInfo(outputInfo); batchNorm->GetOutputSlot().SetTensorInfo(outputInfo); conv->m_Weight = std::make_unique(weights); batchNorm->m_Beta = std::make_unique(beta); batchNorm->m_Gamma = std::make_unique(gamma); batchNorm->m_Mean = std::make_unique(mean); batchNorm->m_Variance = std::make_unique(variance); if (convolution2dDescriptor.m_BiasEnabled) { std::vector biasVector = { 11 }; ConstTensor bias(TensorInfo(1, outputChannelSize, DataType::Float32), biasVector); conv->m_Bias = std::make_unique(bias); } // Connect layers input->GetOutputSlot(0).Connect(conv->GetInputSlot(0)); conv->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0)); batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0)); BOOST_CHECK(4 == graph.GetNumLayers()); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Optimize graph armnn::Optimizer::Pass(graph, MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32())); auto checkFusedConv2d = [](const armnn::Layer* const layer) -> bool { return IsLayerOfType(layer) && (layer->GetNameStr() == "fused-batchNorm-into-convolution"); }; BOOST_CHECK(3 == graph.GetNumLayers()); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, checkFusedConv2d, &IsLayerOfType)); } // Tests that OptimizeForExclusiveConnections works, not fusing when not needed, using BatchNorm fusing as example BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsWithoutFuseTest) { // Define the network Graph graph; Convolution2dDescriptor convolution2dDescriptor; BatchNormalizationDescriptor batchNormDescriptor; auto input = graph.AddLayer(0, "input"); auto conv = graph.AddLayer(convolution2dDescriptor, "convolution"); auto batchNorm = graph.AddLayer(batchNormDescriptor, "batchNorm"); auto output = graph.AddLayer(0, "output"); auto output2 = graph.AddLayer(1, "output2"); // Connect layers input->GetOutputSlot(0).Connect(conv->GetInputSlot(0)); conv->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0)); batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0)); conv->GetOutputSlot(0).Connect(output2->GetInputSlot(0)); BOOST_CHECK(5 == graph.GetNumLayers()); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); // Optimize graph armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32())); BOOST_CHECK(5 == graph.GetNumLayers()); BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType, &IsLayerOfType)); } BOOST_AUTO_TEST_SUITE_END()