ArmNN
 22.02
FuseBatchNormTests.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2020 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include "LayersFwd.hpp"
7 
8 #include <Network.hpp>
9 #include <ResolveType.hpp>
10 #include <armnn/INetwork.hpp>
11 #include <TestUtils.hpp>
12 
13 #include <doctest/doctest.h>
14 
15 using namespace armnn;
16 
17 TEST_SUITE("Optimizer")
18 {
19 namespace
20 {
21 
22 class Conv2dTest
23 {
24 public:
25  using ConvDescriptorType = armnn::Convolution2dDescriptor;
26  using ConvLayerType = armnn::Convolution2dLayer;
27 
28  static IConnectableLayer *AddConvolution(INetwork *network,
29  const Convolution2dDescriptor &descriptor,
30  const ConstTensor &weights,
31  const Optional<ConstTensor> &biases,
32  const char *name)
33  {
34  return network->AddConvolution2dLayer(descriptor, weights, biases, name);
35  }
36 };
37 
38 class DepthwiseConv2dTest
39 {
40 public:
41  using ConvDescriptorType = armnn::DepthwiseConvolution2dDescriptor;
42  using ConvLayerType = armnn::DepthwiseConvolution2dLayer;
43 
44  static IConnectableLayer *AddConvolution(INetwork *network,
45  const DepthwiseConvolution2dDescriptor &descriptor,
46  const ConstTensor &weights,
47  const Optional<ConstTensor> &biases,
48  const char *name)
49  {
50  return network->AddDepthwiseConvolution2dLayer(descriptor, weights, biases, name);
51  }
52 };
53 
54 template<typename T>
55 std::vector<T> GetVector(unsigned int size, float initial, float increment)
56 {
57  std::vector<float> typeVector(size, initial);
58  std::vector<T> vector(size);
59 
60  if (size > 1)
61  {
62  for (unsigned int i = 0; i < size; ++i)
63  {
64  vector[i] = T(initial + (increment * static_cast<float>(i)));
65  }
66  }
67  return vector;
68 }
69 
70 } // namespace
71 
72 template <typename Conv2dTest,
73  armnn::DataType ArmnnType,
74  typename ConvDescriptorType = typename Conv2dTest::ConvDescriptorType,
75  typename T = armnn::ResolveType<ArmnnType>>
76 INetworkPtr CreatNetwork(bool depthwise, bool preventFusing)
77 {
78  // Define layers information
79  ConvDescriptorType convolution2dDescriptor;
80  convolution2dDescriptor.m_BiasEnabled = false;
81  convolution2dDescriptor.m_DataLayout = DataLayout::NHWC;
82  convolution2dDescriptor.m_StrideX = 1;
83  convolution2dDescriptor.m_StrideY = 1;
84  BatchNormalizationDescriptor batchNormDescriptor;
85  batchNormDescriptor.m_DataLayout = DataLayout::NHWC;
86 
87  const unsigned int inputDimensionSizes[] = {1, 4, 4, 3}; // NHWCin
88  unsigned int weightsDimensionSizes[] = {4, 2, 2, 3}; // CoutHWCin
89  unsigned int outputDimensionSizes[] = {1, 3, 3, 4}; // NHWCout
90 
91  if (depthwise)
92  {
93  // [1, H, W, Cout]
94  weightsDimensionSizes[0] = 1;
95  weightsDimensionSizes[1] = 2;
96  weightsDimensionSizes[2] = 2;
97  weightsDimensionSizes[3] = 12;
98  outputDimensionSizes[3] = weightsDimensionSizes[3];
99  }
100  const unsigned int outputChannelSize[] = {outputDimensionSizes[3]}; // Cout
101 
102  TensorInfo inputInfo(4, inputDimensionSizes, ArmnnType);
103  TensorInfo outputInfo(4, outputDimensionSizes, ArmnnType);
104 
105  std::vector<int> weightsIntVector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
106  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
107  21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
108  31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42};
109  std::vector<T> weightsVector(begin(weightsIntVector), end(weightsIntVector));
110  TensorInfo weightsInfo(4, weightsDimensionSizes, ArmnnType, 0.0f, 0, true);
111  ConstTensor weights(weightsInfo, weightsVector);
112 
113  std::vector<T> biasVector = GetVector<T>(outputDimensionSizes[3], 3.3f, 0.1f);
114  TensorInfo biasInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true);
115  ConstTensor bias(biasInfo, biasVector);
116  Optional<ConstTensor> optionalBias = Optional<ConstTensor>(bias);
117 
118  std::vector<T> betaVector = GetVector<T>(outputDimensionSizes[3], 0.0f, 0.2f);
119  std::vector<T> gammaVector = GetVector<T>(outputDimensionSizes[3], 0.5f, 0.1f);
120  std::vector<T> meanVector = GetVector<T>(outputDimensionSizes[3], 0.1f, 0.1f);
121  std::vector<T> varianceVector = GetVector<T>(outputDimensionSizes[3], 1.0f, 0.1f);
122 
123  ConstTensor beta (TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), betaVector);
124  ConstTensor gamma (TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), gammaVector);
125  ConstTensor mean (TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), meanVector);
126  ConstTensor variance(TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), varianceVector);
127 
128  // Create a network
129  INetworkPtr network = INetwork::Create();
130 
131  IConnectableLayer* inputLayer = network->AddInputLayer(0);
132 
133  IConnectableLayer* convLayer = Conv2dTest::AddConvolution(network.get(),
134  convolution2dDescriptor,
135  weights,
136  optionalBias,
137  "convolution");
138 
139  IConnectableLayer* batchNormLayer = network->AddBatchNormalizationLayer(batchNormDescriptor,
140  mean,
141  variance,
142  beta,
143  gamma,
144  "batchNorm");
145 
146  IConnectableLayer* outputLayer = network->AddOutputLayer(0);
147  IConnectableLayer* output2Layer = nullptr;
148 
149  if (preventFusing)
150  {
151  output2Layer = network->AddOutputLayer(1);
152  }
153 
154  // Set layer information
155  inputLayer ->GetOutputSlot(0).SetTensorInfo(inputInfo);
156  convLayer ->GetOutputSlot(0).SetTensorInfo(outputInfo);
157  batchNormLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
158 
159  // Connect layers
160  inputLayer ->GetOutputSlot(0).Connect(convLayer->GetInputSlot(0));
161  convLayer ->GetOutputSlot(0).Connect(batchNormLayer->GetInputSlot(0));
162  batchNormLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
163 
164  if (preventFusing)
165  {
166  convLayer ->GetOutputSlot(0).Connect(output2Layer->GetInputSlot(0));
167  }
168 
169  return network;
170 }
171 
172 template <typename Conv2dTest,
173  armnn::DataType ArmnnType,
174  typename ConvDescriptorType = typename Conv2dTest::ConvDescriptorType,
175  typename ConvLayerType = typename Conv2dTest::ConvLayerType,
176  typename T = armnn::ResolveType<ArmnnType>>
177 void FuseBatchNormIntoConvTest(bool depthwise, float tolerance, armnn::Compute backendId)
178 {
179  // FIRST NETWORK: Fused
180  // Construct ArmNN network
181  INetworkPtr networkFused = CreatNetwork<Conv2dTest, ArmnnType>(depthwise, false);
182 
183  // Create ArmNN runtime
184  IRuntimePtr run = IRuntime::Create(IRuntime::CreationOptions()); // default options
185 
186  // Optimise ArmNN network
187  IOptimizedNetworkPtr optNetFused = Optimize(*networkFused, {backendId}, run->GetDeviceSpec());
188 
189  Graph& graphFused = GetGraphForTesting(optNetFused.get());
190 
191  auto checkFusedConv2d = [ ](const armnn::Layer* const layer) -> bool
192  {
193  return IsLayerOfType<ConvLayerType>(layer) &&
194  (layer->GetNameStr() == "fused-batchNorm-into-convolution");
195  };
196 
197  CHECK(3 == graphFused.GetNumLayers());
198  CHECK(CheckSequence(graphFused.cbegin(),
199  graphFused.cend(),
200  &IsLayerOfType<InputLayer>,
201  checkFusedConv2d,
202  &IsLayerOfType<OutputLayer>));
203 
204  // Load network into runtime
205  NetworkId networkIdentifier;
206  CHECK(run->LoadNetwork(networkIdentifier, std::move(optNetFused)) == Status::Success);
207 
208  //Creates structures for inputs and outputs.
209  std::vector<T> inputDataFused = GetVector<T>(48, 1.0f, 0.1f);
210 
211  std::vector<T> outputDataFused(36);
212 
213  if (depthwise)
214  {
215  outputDataFused.resize(108);
216  }
217 
218  TensorInfo inputTensorInfo = run->GetInputTensorInfo(networkIdentifier, 0);
219  inputTensorInfo.SetConstant(true);
220  InputTensors inputTensorsFused {
221  {0, ConstTensor(inputTensorInfo, inputDataFused.data())}};
222  OutputTensors outputTensorsFused{
223  {0, Tensor(run->GetOutputTensorInfo(networkIdentifier, 0), outputDataFused.data())}};
224 
225  // Execute network
226  run->EnqueueWorkload(networkIdentifier, inputTensorsFused, outputTensorsFused);
227 
228  // SECOND NETWORK: NotFused
229  // Construct ArmNN network
230  INetworkPtr networkNotFused = CreatNetwork<Conv2dTest, ArmnnType>(depthwise, true);
231 
232  // Create ArmNN runtime
233  IRuntimePtr runNotFused = IRuntime::Create(IRuntime::CreationOptions()); // default options
234 
235  // Optimise ArmNN network
236  IOptimizedNetworkPtr optNetNotFused = Optimize(*networkNotFused, {backendId}, runNotFused->GetDeviceSpec());
237 
238  Graph& graphNotFused = GetGraphForTesting(optNetNotFused.get());
239 
240  CHECK(5 == graphNotFused.GetNumLayers());
241  CHECK(CheckSequence(graphNotFused.cbegin(),
242  graphNotFused.cend(),
243  &IsLayerOfType<armnn::InputLayer>,
244  &IsLayerOfType<ConvLayerType>,
245  &IsLayerOfType<armnn::BatchNormalizationLayer>,
246  &IsLayerOfType<armnn::OutputLayer>,
247  &IsLayerOfType<armnn::OutputLayer>));
248 
249  // Load network into runtime
250  NetworkId networkIdentifierNotFused;
251  CHECK(runNotFused->LoadNetwork(networkIdentifierNotFused, std::move(optNetNotFused)) == Status::Success);
252 
253  //Creates structures for inputs and outputs.
254  std::vector<T> inputDataNotFused = GetVector<T>(48, 1.0f, 0.1f);
255 
256  std::vector<T> outputDataNotFused(36);
257  std::vector<T> outputData2NotFused(36);
258 
259  if (depthwise)
260  {
261  outputDataNotFused.resize(108);
262  outputData2NotFused.resize(108);
263  }
264 
265  TensorInfo inputTensorInfo2 = runNotFused->GetInputTensorInfo(networkIdentifierNotFused, 0);
266  inputTensorInfo2.SetConstant(true);
267  InputTensors inputTensorsNotFused{
268  {0, ConstTensor(inputTensorInfo2, inputDataNotFused.data())}};
269  OutputTensors outputTensorsNotFused{
270  {0, Tensor(runNotFused->GetOutputTensorInfo(networkIdentifierNotFused, 0), outputDataNotFused.data())},
271  {1, Tensor(runNotFused->GetOutputTensorInfo(networkIdentifierNotFused, 1), outputData2NotFused.data())}};
272 
273  // Execute network
274  runNotFused->EnqueueWorkload(networkIdentifierNotFused, inputTensorsNotFused, outputTensorsNotFused);
275 
276  // Check the output of the fused-convolution matches with the output of the batchNormm in the "NotFused" network
277  auto epsilon = T(tolerance);
278  for (unsigned int n = 0; n < outputDataFused.size(); ++n)
279  {
280  CHECK_EQ(outputDataFused[n], doctest::Approx(outputDataNotFused[n]).epsilon(epsilon));
281  }
282 }
283 
284 // This unit test needs the reference backend, it's not available if the reference backend is not built
285 #if defined(ARMNNREF_ENABLED)
286 TEST_CASE("FuseBatchNormIntoConv2DFloat32Test")
287 {
288  FuseBatchNormIntoConvTest<Conv2dTest, DataType::Float32>(false, 0.0001f, armnn::Compute::CpuRef);
289 }
290 
291 TEST_CASE("FuseBatchNormIntoConv2DFloat16Test")
292 {
293  FuseBatchNormIntoConvTest<Conv2dTest, DataType::Float16>(false, 0.1f, armnn::Compute::CpuRef);
294 }
295 
296 TEST_CASE("FuseBatchNormIntoDepthwiseConv2DFloat32Test")
297 {
298  FuseBatchNormIntoConvTest<DepthwiseConv2dTest, DataType::Float32>(true, 0.0001f,armnn::Compute::CpuRef);
299 }
300 
301 TEST_CASE("FuseBatchNormIntoDepthwiseConv2DFloat16Test")
302 {
303  FuseBatchNormIntoConvTest<DepthwiseConv2dTest, DataType::Float16>(true, 0.2f,armnn::Compute::CpuRef);
304 }
305 #endif
306 
307 }
TEST_SUITE("TestConstTensorLayerVisitor")
static IRuntimePtr Create(const CreationOptions &options)
Definition: Runtime.cpp:40
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:66
CPU Execution: Reference C++ kernels.
bool CheckSequence(const armnn::Graph::ConstIterator first, const armnn::Graph::ConstIterator last)
Definition: TestUtils.hpp:21
IConnectableLayer * AddDepthwiseConvolution2dLayer(const DepthwiseConvolution2dDescriptor &convolution2dDescriptor, const ConstTensor &weights, const Optional< ConstTensor > &biases, const char *name=nullptr)
Adds a 2D depthwise convolution layer to the network.
Definition: Network.cpp:130
This layer represents a depthwise convolution 2d operation.
A Convolution2dDescriptor for the Convolution2dLayer.
std::unique_ptr< IRuntime, void(*)(IRuntime *runtime)> IRuntimePtr
Definition: IRuntime.hpp:31
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
typename ResolveTypeImpl< DT >::Type ResolveType
Definition: ResolveType.hpp:79
Main network class which provides the interface for building up a neural network. ...
Definition: INetwork.hpp:249
std::vector< std::pair< LayerBindingId, class ConstTensor > > InputTensors
Definition: Tensor.hpp:392
IConnectableLayer * AddConvolution2dLayer(const Convolution2dDescriptor &convolution2dDescriptor, const ConstTensor &weights, const Optional< ConstTensor > &biases, const char *name=nullptr)
Adds a 2D convolution layer to the network.
Definition: Network.cpp:85
Copyright (c) 2021 ARM Limited and Contributors.
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
A tensor defined by a TensorInfo (shape and data type) and a mutable backing store.
Definition: Tensor.hpp:319
Compute
The Compute enum is now deprecated and it is now being replaced by BackendId.
Definition: BackendId.hpp:21
DataType
Definition: Types.hpp:35
IOptimizedNetworkPtr Optimize(const INetwork &network, const std::vector< BackendId > &backendPreferences, const IDeviceSpec &deviceSpec, const OptimizerOptions &options=OptimizerOptions(), Optional< std::vector< std::string > &> messages=EmptyOptional())
Create an optimized version of the network.
Definition: Network.cpp:1680
int NetworkId
Definition: IRuntime.hpp:25
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
std::vector< std::pair< LayerBindingId, class Tensor > > OutputTensors
Definition: Tensor.hpp:393
std::unique_ptr< IOptimizedNetwork, void(*)(IOptimizedNetwork *network)> IOptimizedNetworkPtr
Definition: INetwork.hpp:242
Graph & GetGraphForTesting(IOptimizedNetwork *optNet)
Definition: TestUtils.cpp:47
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
void SetConstant(const bool IsConstant=true)
Marks the data corresponding to this tensor info as constant.
Definition: Tensor.cpp:516
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
This layer represents a convolution 2d operation.
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:241
virtual int Connect(IInputSlot &destination)=0
static INetworkPtr Create(NetworkOptions networkOptions={})
Definition: Network.cpp:492
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
A BatchNormalizationDescriptor for the BatchNormalizationLayer.