diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/armnn/Network.cpp | 1 | ||||
-rw-r--r-- | src/armnn/optimizations/All.hpp | 2 | ||||
-rw-r--r-- | src/armnn/optimizations/FoldPadIntoConvolution2d.hpp | 93 | ||||
-rw-r--r-- | src/armnn/optimizations/FoldPadIntoLayer2d.hpp | 204 | ||||
-rw-r--r-- | src/armnn/test/OptimizerTests.cpp | 609 |
5 files changed, 693 insertions, 216 deletions
diff --git a/src/armnn/Network.cpp b/src/armnn/Network.cpp index 860048fecd..c99690e4a6 100644 --- a/src/armnn/Network.cpp +++ b/src/armnn/Network.cpp @@ -1563,6 +1563,7 @@ IOptimizedNetworkPtr Optimize(const INetwork& inNetwork, TransposeAsReshape(), OptimizeConsecutiveReshapes(), FoldPadIntoConvolution2d(), + FoldPadIntoPooling2d(), PermuteAndBatchToSpaceAsDepthToSpace(), TransposeAndBatchToSpaceAsDepthToSpace(), FuseBatchNormIntoConvolution2DFloat32(), diff --git a/src/armnn/optimizations/All.hpp b/src/armnn/optimizations/All.hpp index d042616ba4..5decc7c969 100644 --- a/src/armnn/optimizations/All.hpp +++ b/src/armnn/optimizations/All.hpp @@ -9,7 +9,7 @@ #include "ConvertConstants.hpp" #include "ConvertFp32NetworkToBf16.hpp" #include "ConvertFp32NetworkToFp16.hpp" -#include "FoldPadIntoConvolution2d.hpp" +#include "FoldPadIntoLayer2d.hpp" #include "FuseBatchNorm.hpp" #include "MovePermuteUp.hpp" #include "MoveTransposeUp.hpp" diff --git a/src/armnn/optimizations/FoldPadIntoConvolution2d.hpp b/src/armnn/optimizations/FoldPadIntoConvolution2d.hpp deleted file mode 100644 index 5def6dfdd2..0000000000 --- a/src/armnn/optimizations/FoldPadIntoConvolution2d.hpp +++ /dev/null @@ -1,93 +0,0 @@ -// -// Copyright © 2017 Arm Ltd. All rights reserved. -// SPDX-License-Identifier: MIT -// - -#pragma once - -#include "Optimization.hpp" - -#include <armnn/utility/PolymorphicDowncast.hpp> - -namespace armnn -{ -namespace optimizations -{ - -class FoldPadIntoConvolution2dImpl -{ -public: - - void Run(Graph& graph, InputSlot& connection) const - { - Layer& base = connection.GetConnectedOutputSlot()->GetOwningLayer(); - Layer& child = connection.GetOwningLayer(); - - ARMNN_ASSERT(base.GetType() == LayerType::Pad); - ARMNN_ASSERT(child.GetType() == LayerType::Convolution2d); - - PadLayer* padLayer = PolymorphicDowncast<PadLayer*>(&base); - Convolution2dLayer* convolution2dLayer = PolymorphicDowncast<Convolution2dLayer*>(&child); - - OutputSlot* parentOut = base.GetInputSlot(0).GetConnectedOutputSlot(); - - const std::string name = std::string("folded-") + base.GetName() + std::string("-into-") + child.GetName(); - Convolution2dDescriptor descriptor = convolution2dLayer->GetParameters(); - - auto padList = padLayer->GetParameters().m_PadList; - - armnn::DataLayout dataLayout = descriptor.m_DataLayout; - - // In Convolution2dDescriptor, padLeft and padRight are defined as paddings on width dimension - // whereas padTop and padBottom - paddings on height dimension, so setting these according to data layout - if(dataLayout == armnn::DataLayout::NHWC) - { - descriptor.m_PadLeft = padList[2].first; - descriptor.m_PadRight = padList[2].second; - descriptor.m_PadTop = padList[1].first; - descriptor.m_PadBottom = padList[1].second; - } - else - { - descriptor.m_PadLeft = padList[3].first; - descriptor.m_PadRight = padList[3].second; - descriptor.m_PadTop = padList[2].first; - descriptor.m_PadBottom = padList[2].second; - } - - auto& newConv2dLayer = *graph.InsertNewLayer<Convolution2dLayer>(base.GetInputSlot(0), - descriptor, - name.c_str()); - - // Copy weights and bias to the new convolution layer - ARMNN_ASSERT_MSG(convolution2dLayer->m_Weight != nullptr, - "FoldPadIntoConvolution2d: Weights data should not be null."); - newConv2dLayer.m_Weight = std::move(convolution2dLayer->m_Weight); - if (descriptor.m_BiasEnabled) - { - ARMNN_ASSERT_MSG(convolution2dLayer->m_Bias != nullptr, - "FoldPadIntoConvolution2d: Bias data should not be null if bias is enabled."); - newConv2dLayer.m_Bias = std::move(convolution2dLayer->m_Bias); - } - - // Reconnects with original parent. - newConv2dLayer.GetOutputSlot().MoveAllConnections(*parentOut); - // Parent is now the new convolution2d layer. - parentOut = &newConv2dLayer.GetOutputSlot(); - - // Moves connections in child output to parent layer. - // Child layer will be removed as it's left unconnected. - // Base layer will be removed if left unconnected. - child.GetOutputSlot().MoveAllConnections(*parentOut); - } -protected: - FoldPadIntoConvolution2dImpl() = default; - ~FoldPadIntoConvolution2dImpl() = default; -}; - -using FoldPadIntoConvolution2d = OptimizeForConnection<PadLayer, Convolution2dLayer, FoldPadIntoConvolution2dImpl>; - -} // namespace optimizations -} // namespace armnn - - diff --git a/src/armnn/optimizations/FoldPadIntoLayer2d.hpp b/src/armnn/optimizations/FoldPadIntoLayer2d.hpp new file mode 100644 index 0000000000..637f2b36d3 --- /dev/null +++ b/src/armnn/optimizations/FoldPadIntoLayer2d.hpp @@ -0,0 +1,204 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "Optimization.hpp" + +#include <QuantizeHelper.hpp> + +#include <armnn/utility/PolymorphicDowncast.hpp> +#include <armnnUtils/DataLayoutIndexed.hpp> + +namespace armnn +{ +namespace optimizations +{ +namespace pad_fold +{ +inline float GetZeroElement(const TensorInfo& tensorInfo) +{ + return static_cast<float>(tensorInfo.IsQuantized() ? tensorInfo.GetQuantizationOffset() : 0); +} + +inline float GetLowestElement(const TensorInfo& tensorInfo) +{ + constexpr float negativeInfinity = -std::numeric_limits<float>::infinity(); + const float scale = tensorInfo.GetQuantizationScale(); + const int32_t offset = tensorInfo.GetQuantizationOffset(); + + switch (tensorInfo.GetDataType()) + { + case DataType::Float16: + return armnnUtils::SelectiveQuantize<armnn::Half>(negativeInfinity, scale, offset); + case DataType::Float32: + return armnnUtils::SelectiveQuantize<float>(negativeInfinity, scale, offset); + case DataType::QAsymmU8: + return armnnUtils::SelectiveQuantize<uint8_t>(negativeInfinity, scale, offset); + case DataType::QSymmS16: + return armnnUtils::SelectiveQuantize<int16_t>(negativeInfinity, scale, offset); + case DataType::QSymmS8: + // Fall-through + case DataType::QAsymmS8: + return armnnUtils::SelectiveQuantize<int8_t>(negativeInfinity, scale, offset); + case DataType::BFloat16: + return armnnUtils::SelectiveQuantize<armnn::BFloat16>(negativeInfinity, scale, offset); + default: + { + ARMNN_ASSERT_MSG(false, "Unsupported DataType"); + return NAN; + } + } +} + +inline bool IsNeutralElement(const Convolution2dDescriptor&, const TensorInfo& tensorInfo, const float tensorValue) +{ + return tensorValue == GetZeroElement(tensorInfo); +} + +inline bool IsNeutralElement( + const Pooling2dDescriptor& descriptor, const TensorInfo& tensorInfo, const float tensorValue) +{ + return (descriptor.m_PoolType == PoolingAlgorithm::Max) + ? tensorValue <= GetLowestElement(tensorInfo) + : tensorValue == GetZeroElement(tensorInfo); +} + +template <typename Descriptor> +bool TryFoldPadIntoLayer2d( + const PadDescriptor& padDescriptor, Descriptor& layerDescriptor, const TensorInfo& tensorInfo) +{ + armnnUtils::DataLayoutIndexed layout = armnnUtils::DataLayoutIndexed(layerDescriptor.m_DataLayout); + constexpr unsigned int batchIndex = 0; + + constexpr auto noPad = std::make_pair(0U, 0U); + + if ((!IsNeutralElement(layerDescriptor, tensorInfo, padDescriptor.m_PadValue)) || + (padDescriptor.m_PadList[batchIndex] != noPad) || (padDescriptor.m_PadList[layout.GetChannelsIndex()] != noPad)) + { + return false; + } + + const auto& padList = padDescriptor.m_PadList; + + // In Convolution2dDescriptor/Pooling2dDescriptor, padLeft and padRight are defined as paddings + // on width dimension whereas padTop and padBottom - paddings on height dimension, so updating + // these according to data layout + layerDescriptor.m_PadLeft += padList[layout.GetWidthIndex()].first; + layerDescriptor.m_PadRight += padList[layout.GetWidthIndex()].second; + layerDescriptor.m_PadTop += padList[layout.GetHeightIndex()].first; + layerDescriptor.m_PadBottom += padList[layout.GetHeightIndex()].second; + + return true; +} + +inline bool TryFoldPadIntoLayer2d( + const PadDescriptor& padDescriptor, Pooling2dDescriptor& poolDescriptor, const TensorInfo& tensorInfo) +{ + const auto poolingPadValues = std::make_tuple(poolDescriptor.m_PadLeft, poolDescriptor.m_PadRight, + poolDescriptor.m_PadTop, poolDescriptor.m_PadBottom); + bool poolHasPadding = false; + if (poolingPadValues != std::make_tuple(0U, 0U, 0U, 0U)) + { + poolHasPadding = true; + } + + // We cannot fold Average or L2 pooling if there's is already padding and that padding method is Exclude. + if (poolDescriptor.m_PoolType != PoolingAlgorithm::Max) // PoolingAlgorithm::Average or PoolingAlgorithm::L2 + { + if ((poolHasPadding) && (poolDescriptor.m_PaddingMethod == PaddingMethod::Exclude)) + { + return false; + } + } + poolDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue; + + return TryFoldPadIntoLayer2d<Pooling2dDescriptor>(padDescriptor, poolDescriptor, tensorInfo); +} + +template <typename Layer2dT> +Layer2dT* FoldPadIntoLayer2dImpl(Graph& graph, InputSlot& connection) +{ + PadLayer& padLayer = *PolymorphicDowncast<PadLayer*>(&connection.GetConnectedOutputSlot()->GetOwningLayer()); + Layer2dT& layer2d = *PolymorphicDowncast<Layer2dT*>(&connection.GetOwningLayer()); + + const PadDescriptor& padDescriptor = padLayer.GetParameters(); + auto newLayer2dDescriptor = layer2d.GetParameters(); + + if (!TryFoldPadIntoLayer2d(padDescriptor, newLayer2dDescriptor, padLayer.GetOutputSlot().GetTensorInfo())) + { + return nullptr; + } + + // Save original parent output slot of the pad layer + OutputSlot& parentSlot = *padLayer.GetInputSlot(0).GetConnectedOutputSlot(); + + // Insert new layer2d layer between the pad layer an its parent layer. + const std::string name = std::string("folded-") + padLayer.GetName() + "-into-" + layer2d.GetName(); + auto& newLayer2d = *graph.InsertNewLayer<Layer2dT>(padLayer.GetInputSlot(0), newLayer2dDescriptor, name.c_str()); + + // Reconnect the pad layer with its original parent. + newLayer2d.GetOutputSlot().MoveAllConnections(parentSlot); + + // Moves connections in old layer2d layer output to new layer. + // Old layer2d layer will be removed as it's left unconnected. + // Pad layer will be removed if left unconnected. + layer2d.GetOutputSlot().MoveAllConnections(newLayer2d.GetOutputSlot()); + + return &newLayer2d; +} + +class FoldPadIntoConvolution2dImpl +{ +public: + void Run(Graph& graph, InputSlot& connection) const + { + const auto newConv2dLayer = FoldPadIntoLayer2dImpl<Convolution2dLayer>(graph, connection); + + if (newConv2dLayer != nullptr) + { + const auto conv2dLayer = PolymorphicDowncast<Convolution2dLayer*>(&connection.GetOwningLayer()); + // Copy weights and bias to the new convolution layer + ARMNN_ASSERT_MSG(conv2dLayer->m_Weight != nullptr, + "FoldPadIntoConvolution2d: Weights data should not be null."); + newConv2dLayer->m_Weight = std::move(conv2dLayer->m_Weight); + + if (conv2dLayer->GetParameters().m_BiasEnabled) + { + ARMNN_ASSERT_MSG(conv2dLayer->m_Bias != nullptr, + "FoldPadIntoConvolution2d: Bias data should not be null if bias is enabled."); + newConv2dLayer->m_Bias = std::move(conv2dLayer->m_Bias); + } + } + } + +protected: + FoldPadIntoConvolution2dImpl() = default; + ~FoldPadIntoConvolution2dImpl() = default; +}; + +class FoldPadIntoPooling2dImpl +{ +public: + void Run(Graph& graph, InputSlot& connection) const + { + FoldPadIntoLayer2dImpl<Pooling2dLayer>(graph, connection); + } + +protected: + FoldPadIntoPooling2dImpl() = default; + ~FoldPadIntoPooling2dImpl() = default; +}; +} // namespace pad_fold + +using FoldPadIntoConvolution2d = + OptimizeForExclusiveConnection<PadLayer, Convolution2dLayer, pad_fold::FoldPadIntoConvolution2dImpl>; +using FoldPadIntoPooling2d = + OptimizeForExclusiveConnection<PadLayer, Pooling2dLayer, pad_fold::FoldPadIntoPooling2dImpl>; + +} // namespace optimizations +} // namespace armnn + + diff --git a/src/armnn/test/OptimizerTests.cpp b/src/armnn/test/OptimizerTests.cpp index 95255c3a21..7e8a898293 100644 --- a/src/armnn/test/OptimizerTests.cpp +++ b/src/armnn/test/OptimizerTests.cpp @@ -15,8 +15,8 @@ #include <armnn/INetwork.hpp> #include <armnn/LayerVisitorBase.hpp> -#include <armnnUtils/FloatingPointConverter.hpp> #include <armnn/utility/PolymorphicDowncast.hpp> +#include <armnnUtils/FloatingPointConverter.hpp> #include <backendsCommon/CpuTensorHandle.hpp> #include <backendsCommon/IBackendInternal.hpp> @@ -138,7 +138,7 @@ void CreateLSTMLayerHelper(Graph &graph, bool CifgEnabled) Connect(layer, output, lstmTensorInfo3, 3, 0); } -} +} // namespace BOOST_AUTO_TEST_SUITE(Optimizer) using namespace armnn::optimizations; @@ -268,15 +268,15 @@ void CreateConvolution2dGraph(Graph &graph, const unsigned int* inputShape, Convolution2dDescriptor desc; desc.m_BiasEnabled = false; - desc.m_StrideX = 1; - desc.m_StrideY = 1; - desc.m_DataLayout = dataLayout; + desc.m_StrideX = 1; + desc.m_StrideY = 1; + desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer<InputLayer>(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); Convolution2dLayer* layer = graph.AddLayer<Convolution2dLayer>(desc, "conv2d"); - layer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights); + layer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer<OutputLayer>(0, "output"); @@ -318,15 +318,15 @@ void CreateDepthwiseConvolution2dGraph(Graph &graph, const unsigned int* inputSh DepthwiseConvolution2dDescriptor desc; desc.m_BiasEnabled = false; - desc.m_StrideX = 1; - desc.m_StrideY = 1; - desc.m_DataLayout = dataLayout; + desc.m_StrideX = 1; + desc.m_StrideY = 1; + desc.m_DataLayout = dataLayout; Layer* input = graph.AddLayer<InputLayer>(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); DepthwiseConvolution2dLayer* layer = graph.AddLayer<DepthwiseConvolution2dLayer>(desc, "depthwiseConv2d"); - layer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights); + layer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights); layer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer<OutputLayer>(0, "output"); @@ -363,15 +363,15 @@ void CreatePooling2dGraph(Graph& graph, const unsigned int* inputShape, const u armnn::TensorInfo outputInfo(4, outputShape, DataType::Float32); Pooling2dDescriptor desc; - desc.m_PoolType = armnn::PoolingAlgorithm::Average; + 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; + 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<InputLayer>(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); @@ -387,7 +387,7 @@ void CreatePooling2dGraph(Graph& graph, const unsigned int* inputShape, const u BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputs) { Graph graph; - const unsigned int inputShape[] = { 5, 3, 52, 60 }; + const unsigned int inputShape[] = { 5, 3, 52, 60 }; const unsigned int outputShape[] = { 5, 3, 11, 13 }; CreatePooling2dGraph(graph, inputShape, outputShape, DataLayout::NCHW); @@ -397,14 +397,16 @@ BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputs) BOOST_AUTO_TEST_CASE(Pooling2dValidateTensorShapesFromInputsNhwc) { Graph graph; - const unsigned int inputShape[] = { 5, 52, 60, 3 }; + 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, +void CreateResizeBilinearGraph(Graph& graph, + const unsigned int* inputShape, + const unsigned int* outputShape, DataLayout dataLayout = DataLayout::NCHW) { TensorInfo inputInfo(4, inputShape, DataType::Float32); @@ -430,7 +432,7 @@ void CreateResizeBilinearGraph(Graph& graph, const unsigned int* inputShape, con BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputs) { Graph graph; - const unsigned int inputShape[] = { 1, 2, 4, 5 }; + const unsigned int inputShape[] = { 1, 2, 4, 5 }; const unsigned int outputShape[] = { 1, 2, 3, 4 }; CreateResizeBilinearGraph(graph, inputShape, outputShape); @@ -440,14 +442,16 @@ BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputs) BOOST_AUTO_TEST_CASE(ResizeBilinearValidateTensorShapesFromInputsNhwc) { Graph graph; - const unsigned int inputShape[] = { 1, 4, 5, 2 }; + 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, +void CreateGatherGraph(Graph& graph, + const armnn::TensorInfo& paramsInfo, + const armnn::TensorInfo& indicesInfo, const armnn::TensorInfo& outputInfo) { Layer* input0 = graph.AddLayer<InputLayer>(0, "params"); @@ -540,10 +544,10 @@ BOOST_AUTO_TEST_CASE(DetectionPostProcessValidateTensorShapes) 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 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 }; + const unsigned int outputShape[] = { 1, 2, 1, 1 }; armnn::TensorInfo inputInfo(4, inputShape, DataType::Float32); armnn::TensorInfo paddedInfo(4, paddedShape, DataType::Float32); @@ -552,22 +556,22 @@ BOOST_AUTO_TEST_CASE(FoldPadLayerIntoConvolution2dLayer) Layer* input = graph.AddLayer<InputLayer>(0, "input"); input->GetOutputSlot().SetTensorInfo(inputInfo); - PadDescriptor padDescriptor({{ 0, 0 }, { 2, 2 }, { 2, 2 }, { 0, 0 }}); + PadDescriptor padDescriptor({ { 0, 0 }, { 2, 2 }, { 2, 2 }, { 0, 0 } }); PadLayer* padLayer = graph.AddLayer<PadLayer>(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; + convolution2dDescriptor.m_StrideX = 1; + convolution2dDescriptor.m_StrideY = 1; + convolution2dDescriptor.m_DataLayout = DataLayout::NHWC; std::vector<float> weightsVector(18); armnn::ConstTensor weights(armnn::TensorInfo(4, weightsShape, armnn::DataType::Float32), weightsVector); - Convolution2dLayer* conv2dLayer = graph.AddLayer<Convolution2dLayer>(convolution2dDescriptor,"conv2d"); - conv2dLayer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights); + Convolution2dLayer* conv2dLayer = graph.AddLayer<Convolution2dLayer>(convolution2dDescriptor, "conv2d"); + conv2dLayer->m_Weight = std::make_unique<armnn::ScopedCpuTensorHandle>(weights); conv2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); Layer* output = graph.AddLayer<OutputLayer>(0, "output"); @@ -577,24 +581,17 @@ BOOST_AUTO_TEST_CASE(FoldPadLayerIntoConvolution2dLayer) 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<const armnn::Convolution2dLayer*>(layer); + auto checkSimpleConv2d = [](const armnn::Layer* const layer) -> bool { + const auto conv2dLayer = static_cast<const armnn::Convolution2dLayer*>(layer); const auto conv2dLayerParams = conv2dLayer->GetParameters(); - return IsLayerOfType<armnn::Convolution2dLayer>(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); + return IsLayerOfType<armnn::Convolution2dLayer>(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(), + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<armnn::InputLayer>, &IsLayerOfType<armnn::PadLayer>, checkSimpleConv2d, @@ -602,30 +599,396 @@ BOOST_AUTO_TEST_CASE(FoldPadLayerIntoConvolution2dLayer) armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoConvolution2d())); - auto checkPadFoldedIntoConv2d = [ ](const armnn::Layer* const layer) -> bool - { - const auto conv2dLayer = static_cast<const armnn::Convolution2dLayer*>(layer); + auto checkPadFoldedIntoConv2d = [](const armnn::Layer* const layer) -> bool { + const auto conv2dLayer = static_cast<const armnn::Convolution2dLayer*>(layer); const auto conv2dLayerParams = conv2dLayer->GetParameters(); - return IsLayerOfType<armnn::Convolution2dLayer>(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); + return IsLayerOfType<armnn::Convolution2dLayer>(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(), + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<armnn::InputLayer>, checkPadFoldedIntoConv2d, &IsLayerOfType<armnn::OutputLayer>)); } -class MockLayerSupport : public LayerSupportBase { +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<InputLayer>(0, "input"); + input->GetOutputSlot().SetTensorInfo(inputInfo); + + PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); + + PadLayer* padLayer = graph.AddLayer<PadLayer>(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<Pooling2dLayer>(pooling2dDescriptor, "pool2d"); + pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); + + Layer* output = graph.AddLayer<OutputLayer>(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<const armnn::Pooling2dLayer*>(layer); + return IsLayerOfType<armnn::Pooling2dLayer>(layer) && (layer->GetNameStr() == "pool2d") && + (pool2dLayer->GetParameters() == pooling2dDescriptor); + }; + + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>)); + + armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); + + auto checkPadFoldedIntoPool2d = [&](const armnn::Layer* const layer) { + if (!IsLayerOfType<armnn::Pooling2dLayer>(layer) || (layer->GetNameStr() != "folded-pad-into-pool2d")) + { + return false; + } + + const auto pool2dLayer = static_cast<const armnn::Pooling2dLayer*>(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<armnn::InputLayer>, + checkPadFoldedIntoPool2d, + &IsLayerOfType<armnn::OutputLayer>)); +} + +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<InputLayer>(0, "input"); + input->GetOutputSlot().SetTensorInfo(inputInfo); + + PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); + + PadLayer* padLayer = graph.AddLayer<PadLayer>(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<Pooling2dLayer>(pooling2dDescriptor, "pool2d"); + pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); + + Layer* output = graph.AddLayer<OutputLayer>(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<OutputLayer>(1, "dummy output"); + padLayer->GetOutputSlot().Connect(secondOutput->GetInputSlot(0)); + + auto checkSimplePool2d = [&](const armnn::Layer* const layer) { + const auto pool2dLayer = static_cast<const armnn::Pooling2dLayer*>(layer); + return IsLayerOfType<armnn::Pooling2dLayer>(layer) && (layer->GetNameStr() == "pool2d") && + (pool2dLayer->GetParameters() == pooling2dDescriptor); + }; + + // Initial sequence. + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>, + &IsLayerOfType<armnn::OutputLayer>)); + + armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); + + // The network should not change. + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>, + &IsLayerOfType<armnn::OutputLayer>)); +} + +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<InputLayer>(0, "input"); + input->GetOutputSlot().SetTensorInfo(inputInfo); + + PadDescriptor padDescriptor({ { 0, 0 }, { 1, 1 }, { 1, 1 }, { 0, 0 } }); + + PadLayer* padLayer = graph.AddLayer<PadLayer>(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<Pooling2dLayer>(pooling2dDescriptor, "pool2d"); + pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); + + Layer* output = graph.AddLayer<OutputLayer>(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<const armnn::Pooling2dLayer*>(layer); + return IsLayerOfType<armnn::Pooling2dLayer>(layer) && (layer->GetNameStr() == "pool2d") && + (pool2dLayer->GetParameters() == pooling2dDescriptor); + }; + + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>)); + + armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); + + // The optimization should not have modified the graph. + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>)); +} + +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<InputLayer>(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<float>::infinity() to make the fold happen. + padDescriptor.m_PadValue = 0; + + PadLayer* padLayer = graph.AddLayer<PadLayer>(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<Pooling2dLayer>(pooling2dDescriptor, "pool2d"); + pool2dLayer->GetOutputSlot().SetTensorInfo(outputInfo); + + Layer* output = graph.AddLayer<OutputLayer>(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<const armnn::Pooling2dLayer*>(layer); + return IsLayerOfType<armnn::Pooling2dLayer>(layer) && (layer->GetNameStr() == "pool2d") && + (pool2dLayer->GetParameters() == pooling2dDescriptor); + }; + + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>)); + + armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FoldPadIntoPooling2d())); + + // The optimization should not have modified the graph. + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), + &IsLayerOfType<armnn::InputLayer>, + &IsLayerOfType<armnn::PadLayer>, + checkSimplePool2d, + &IsLayerOfType<armnn::OutputLayer>)); +} + +#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<float> 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<float> optimizedData(32, -std::numeric_limits<float>::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<float> goldenData(32, 0.0f); + std::vector<float> 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<std::string&> /*reasonIfUnsupported = EmptyOptional()*/) const override @@ -648,27 +1011,42 @@ public: } }; -template<typename NamePolicy> +template <typename NamePolicy> class MockBackend : public IBackendInternal { public: MockBackend() = default; ~MockBackend() = default; - static const BackendId& GetIdStatic() { return NamePolicy::GetIdStatic(); } - const BackendId& GetId() const override { return GetIdStatic(); } + static const BackendId& GetIdStatic() + { + return NamePolicy::GetIdStatic(); + } + const BackendId& GetId() const override + { + return GetIdStatic(); + } - IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override { return nullptr; }; + IBackendInternal::IMemoryManagerUniquePtr CreateMemoryManager() const override + { + return nullptr; + }; - IBackendInternal::IWorkloadFactoryPtr CreateWorkloadFactory( - const IBackendInternal::IMemoryManagerSharedPtr&) 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::Optimizations GetOptimizations() const override + { + return {}; + } IBackendInternal::ILayerSupportSharedPtr GetLayerSupport() const override { return std::make_shared<MockLayerSupport>(); @@ -682,7 +1060,7 @@ public: BOOST_AUTO_TEST_CASE(BackendCapabilityTest) { - BackendId backendId ="MockBackend"; + BackendId backendId = "MockBackend"; // MockBackend does not support the NonConstWeights capability BOOST_CHECK(!armnn::IsCapabilitySupported(backendId, armnn::BackendCapability::NonConstWeights)); @@ -695,18 +1073,14 @@ BOOST_AUTO_TEST_CASE(BackendHintTest) class TestBackendAssignment : public LayerVisitorBase<VisitorNoThrowPolicy> { public: - void VisitInputLayer(const IConnectableLayer* layer, - LayerBindingId id, - const char* name = nullptr) override + void VisitInputLayer(const IConnectableLayer* layer, LayerBindingId id, const char* name = nullptr) override { IgnoreUnused(id, name); auto inputLayer = PolymorphicDowncast<const InputLayer*>(layer); BOOST_TEST((inputLayer->GetBackendId() == "MockBackend")); } - void VisitOutputLayer(const IConnectableLayer* layer, - LayerBindingId id, - const char* name = nullptr) override + void VisitOutputLayer(const IConnectableLayer* layer, LayerBindingId id, const char* name = nullptr) override { IgnoreUnused(id, name); auto outputLayer = PolymorphicDowncast<const OutputLayer*>(layer); @@ -727,7 +1101,7 @@ BOOST_AUTO_TEST_CASE(BackendHintTest) { static const BackendId& GetIdStatic() { - static BackendId id="CustomBackend"; + static BackendId id = "CustomBackend"; return id; } }; @@ -736,20 +1110,16 @@ BOOST_AUTO_TEST_CASE(BackendHintTest) { static const BackendId& GetIdStatic() { - static BackendId id="MockBackend"; + static BackendId id = "MockBackend"; return id; } }; auto& backendRegistry = BackendRegistryInstance(); - backendRegistry.Register("MockBackend", [](){ - return std::make_unique<MockBackend<MockPolicy>>(); - }); + backendRegistry.Register("MockBackend", []() { return std::make_unique<MockBackend<MockPolicy>>(); }); - backendRegistry.Register("CustomBackend", [](){ - return std::make_unique<MockBackend<CustomPolicy>>(); - }); + backendRegistry.Register("CustomBackend", []() { return std::make_unique<MockBackend<CustomPolicy>>(); }); // Define the network auto network = INetwork::Create(); @@ -757,9 +1127,9 @@ BOOST_AUTO_TEST_CASE(BackendHintTest) desc.m_Function = ActivationFunction::Linear; std::unique_ptr<Graph> graph = std::make_unique<Graph>(); - auto input = graph->AddLayer<InputLayer>(0, "input"); - auto act = graph->AddLayer<ActivationLayer>(desc, "activation"); - auto output = graph->AddLayer<OutputLayer>(0, "output"); + auto input = graph->AddLayer<InputLayer>(0, "input"); + auto act = graph->AddLayer<ActivationLayer>(desc, "activation"); + auto output = graph->AddLayer<OutputLayer>(0, "output"); BackendId customBackendId("CustomBackend"); act->BackendSelectionHint(customBackendId); @@ -772,9 +1142,9 @@ BOOST_AUTO_TEST_CASE(BackendHintTest) // Get the optimized graph Graph& optGraph = optNet.GetGraph(); - std::vector<BackendId> prefs{"MockBackend", "CustomBackend"}; + std::vector<BackendId> prefs{ "MockBackend", "CustomBackend" }; - BackendIdSet availableBackends = {"CustomBackend", "MockBackend"}; + BackendIdSet availableBackends = { "CustomBackend", "MockBackend" }; DeviceSpec spec(availableBackends); BackendSettings backendSettings(prefs, spec); @@ -793,7 +1163,7 @@ BOOST_AUTO_TEST_CASE(BackendHintTest) BOOST_TEST(res.IsOk()); TestBackendAssignment visitor; - for (auto it =firstLayer; it != lastLayer; ++it) + for (auto it = firstLayer; it != lastLayer; ++it) { (*it)->Accept(visitor); } @@ -810,32 +1180,32 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsFuseTest) 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 + 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<float> weightsVector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; - ConstTensor weights(TensorInfo(4, weightsDimensionSizes, DataType::Float32), weightsVector); + std::vector<float> weightsVector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + ConstTensor weights(TensorInfo(4, weightsDimensionSizes, DataType::Float32), weightsVector); std::vector<float> betaVector = { 0.1f }; std::vector<float> gammaVector = { 0.5f }; std::vector<float> meanVector = { 0 }; std::vector<float> 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); + 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<InputLayer>(0, "input"); - auto conv = graph.AddLayer<Convolution2dLayer>(convolution2dDescriptor, "convolution"); - auto batchNorm = graph.AddLayer<BatchNormalizationLayer>(batchNormDescriptor, "batchNorm"); - auto output = graph.AddLayer<OutputLayer>(0, "output"); + auto input = graph.AddLayer<InputLayer>(0, "input"); + auto conv = graph.AddLayer<Convolution2dLayer>(convolution2dDescriptor, "convolution"); + auto batchNorm = graph.AddLayer<BatchNormalizationLayer>(batchNormDescriptor, "batchNorm"); + auto output = graph.AddLayer<OutputLayer>(0, "output"); // Set layer information input->GetOutputSlot().SetTensorInfo(inputInfo); @@ -848,9 +1218,9 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsFuseTest) batchNorm->m_Variance = std::make_unique<ScopedCpuTensorHandle>(variance); if (convolution2dDescriptor.m_BiasEnabled) { - std::vector<float> biasVector = {11}; - ConstTensor bias(TensorInfo(1, outputChannelSize, DataType::Float32), biasVector); - conv->m_Bias = std::make_unique<ScopedCpuTensorHandle>(bias); + std::vector<float> biasVector = { 11 }; + ConstTensor bias(TensorInfo(1, outputChannelSize, DataType::Float32), biasVector); + conv->m_Bias = std::make_unique<ScopedCpuTensorHandle>(bias); } // Connect layers @@ -859,8 +1229,7 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsFuseTest) batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0)); BOOST_CHECK(4 == graph.GetNumLayers()); - BOOST_TEST(CheckSequence(graph.cbegin(), - graph.cend(), + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<InputLayer>, &IsLayerOfType<Convolution2dLayer>, &IsLayerOfType<BatchNormalizationLayer>, @@ -869,15 +1238,13 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsFuseTest) // Optimize graph armnn::Optimizer::Pass(graph, MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32())); - auto checkFusedConv2d = [](const armnn::Layer* const layer)->bool - { + auto checkFusedConv2d = [](const armnn::Layer* const layer) -> bool { return IsLayerOfType<armnn::Convolution2dLayer>(layer) && - (layer->GetNameStr() == "fused-batchNorm-into-convolution"); + (layer->GetNameStr() == "fused-batchNorm-into-convolution"); }; BOOST_CHECK(3 == graph.GetNumLayers()); - BOOST_TEST(CheckSequence(graph.cbegin(), - graph.cend(), + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<InputLayer>, checkFusedConv2d, &IsLayerOfType<OutputLayer>)); @@ -887,8 +1254,8 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsFuseTest) BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsWithoutFuseTest) { // Define the network - Graph graph; - Convolution2dDescriptor convolution2dDescriptor; + Graph graph; + Convolution2dDescriptor convolution2dDescriptor; BatchNormalizationDescriptor batchNormDescriptor; auto input = graph.AddLayer<InputLayer>(0, "input"); @@ -904,8 +1271,7 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsWithoutFuseTest) conv->GetOutputSlot(0).Connect(output2->GetInputSlot(0)); BOOST_CHECK(5 == graph.GetNumLayers()); - BOOST_TEST(CheckSequence(graph.cbegin(), - graph.cend(), + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<armnn::InputLayer>, &IsLayerOfType<armnn::Convolution2dLayer>, &IsLayerOfType<armnn::BatchNormalizationLayer>, @@ -915,12 +1281,11 @@ BOOST_AUTO_TEST_CASE(OptimizeForExclusiveConnectionsWithoutFuseTest) armnn::Optimizer::Pass(graph, armnn::MakeOptimizations(FuseBatchNormIntoConvolution2DFloat32())); BOOST_CHECK(5 == graph.GetNumLayers()); - BOOST_TEST(CheckSequence(graph.cbegin(), - graph.cend(), + BOOST_TEST(CheckSequence(graph.cbegin(), graph.cend(), &IsLayerOfType<armnn::InputLayer>, &IsLayerOfType<armnn::Convolution2dLayer>, &IsLayerOfType<armnn::BatchNormalizationLayer>, &IsLayerOfType<armnn::OutputLayer>, &IsLayerOfType<armnn::OutputLayer>)); } -BOOST_AUTO_TEST_SUITE_END()
\ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() |