aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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()