aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Lopez Recas <Diego.LopezRecas@arm.com>2021-03-19 12:40:16 +0000
committerColm Donelan <colm.donelan@arm.com>2021-04-21 14:51:17 +0000
commitfe95d7203ba7a6ac31e0656190ba56de6fcf4735 (patch)
tree4b127a99c97364d6b9062d93ece26ccafa60579d
parent7612bd6cc385dfbf54f831a6349f3a9363c6d0a2 (diff)
downloadarmnn-fe95d7203ba7a6ac31e0656190ba56de6fcf4735.tar.gz
Fold PAD into Pooling2d if possible
Some models would add a PAD layer before a pooling when they can't express their padding configuration as SAME or VALID. ArmNN can potentially merge the two merge the two because pooling layers are described with explicit padding. The merge is possible if the extra padding is neutral in the combined pooling operation. A merged operation can only fuse paddings in the dimensions that accept explicit padding in a pooling operation, i.e. the spatial dimensions. Signed-off-by: Diego Lopez Recas <diego.lopez.recas@gmail.com> Signed-off-by: Colm Donelan <Colm.Donelan@arm.com> Change-Id: Icd54718dcd9e797c923456b7fa6e0213e288e668
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/armnn/Network.cpp1
-rw-r--r--src/armnn/optimizations/All.hpp2
-rw-r--r--src/armnn/optimizations/FoldPadIntoConvolution2d.hpp93
-rw-r--r--src/armnn/optimizations/FoldPadIntoLayer2d.hpp204
-rw-r--r--src/armnn/test/OptimizerTests.cpp609
6 files changed, 694 insertions, 217 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c02db3d014..df34d28eaa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -461,7 +461,7 @@ list(APPEND armnn_sources
src/armnn/optimizations/ConvertConstants.hpp
src/armnn/optimizations/ConvertFp32NetworkToBf16.hpp
src/armnn/optimizations/ConvertFp32NetworkToFp16.hpp
- src/armnn/optimizations/FoldPadIntoConvolution2d.hpp
+ src/armnn/optimizations/FoldPadIntoLayer2d.hpp
src/armnn/optimizations/MovePermuteUp.hpp
src/armnn/optimizations/MoveTransposeUp.hpp
src/armnn/optimizations/Optimization.hpp
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()