ArmNN
 22.08
FuseBatchNormTests.cpp File Reference
#include "LayersFwd.hpp"
#include <Network.hpp>
#include <ResolveType.hpp>
#include <armnn/INetwork.hpp>
#include <TestUtils.hpp>
#include <doctest/doctest.h>

Go to the source code of this file.

Functions

 TEST_SUITE ("Optimizer")
 

Function Documentation

◆ TEST_SUITE()

TEST_SUITE ( "Optimizer"  )

Definition at line 17 of file FuseBatchNormTests.cpp.

References INetwork::AddConstantLayer(), INetwork::AddConvolution2dLayer(), INetwork::AddDepthwiseConvolution2dLayer(), IOutputSlot::Connect(), INetwork::Create(), BaseTensor< MemoryType >::GetInfo(), IConnectableLayer::GetInputSlot(), IConnectableLayer::GetOutputSlot(), armnn::IgnoreUnused(), Convolution2dDescriptor::m_BiasEnabled, DepthwiseConvolution2dDescriptor::m_BiasEnabled, BatchNormalizationDescriptor::m_DataLayout, armnn::NHWC, IOutputSlot::SetTensorInfo(), and OptionalReferenceSwitch< std::is_reference< T >::value, T >::value().

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  IgnoreUnused(weights);
35  IgnoreUnused(biases);
36 
37  return network->AddConvolution2dLayer(descriptor, name);
38  }
39 
40  static std::vector<IConnectableLayer*> AddConstantLayers(INetwork *network,
41  const Convolution2dDescriptor &descriptor,
42  const ConstTensor &weights,
43  const Optional<ConstTensor> &biases)
44  {
45  auto weightsLayer = network->AddConstantLayer(weights, "Weights");
46  weightsLayer->GetOutputSlot(0).SetTensorInfo(weights.GetInfo());
47  std::vector<IConnectableLayer*> layers = {weightsLayer};
48 
49  if (descriptor.m_BiasEnabled)
50  {
51  auto biasLayer = network->AddConstantLayer(biases.value(), "Bias");
52  biasLayer->GetOutputSlot(0).SetTensorInfo(biases.value().GetInfo());
53  layers.emplace_back(biasLayer);
54  }
55 
56  return layers;
57  }
58 };
59 
60 class DepthwiseConv2dTest
61 {
62 public:
63  using ConvDescriptorType = armnn::DepthwiseConvolution2dDescriptor;
64  using ConvLayerType = armnn::DepthwiseConvolution2dLayer;
65 
66  static IConnectableLayer* AddConvolution(INetwork* network,
67  const DepthwiseConvolution2dDescriptor& descriptor,
68  const ConstTensor& weights,
69  const Optional<ConstTensor>& biases,
70  const char* name)
71  {
72  IgnoreUnused(weights);
73  IgnoreUnused(biases);
74 
75  return network->AddDepthwiseConvolution2dLayer(descriptor, name);
76  }
77 
78  static std::vector<IConnectableLayer*> AddConstantLayers(INetwork *network,
79  const DepthwiseConvolution2dDescriptor &descriptor,
80  const ConstTensor &weights,
81  const Optional<ConstTensor> &biases)
82  {
83  auto weightsLayer = network->AddConstantLayer(weights, "Weights");
84  weightsLayer->GetOutputSlot(0).SetTensorInfo(weights.GetInfo());
85  std::vector<IConnectableLayer*> layers = {weightsLayer};
86 
87  if (descriptor.m_BiasEnabled)
88  {
89  auto biasLayer = network->AddConstantLayer(biases.value(), "Bias");
90  biasLayer->GetOutputSlot(0).SetTensorInfo(biases.value().GetInfo());
91  layers.emplace_back(biasLayer);
92  }
93 
94  return layers;
95  }
96 };
97 
98 template<typename T>
99 std::vector<T> GetVector(unsigned int size, float initial, float increment)
100 {
101  std::vector<float> typeVector(size, initial);
102  std::vector<T> vector(size);
103 
104  if (size > 1)
105  {
106  for (unsigned int i = 0; i < size; ++i)
107  {
108  vector[i] = T(initial + (increment * static_cast<float>(i)));
109  }
110  }
111  return vector;
112 }
113 
114 } // namespace
115 
116 template <typename Conv2dTest,
117  armnn::DataType ArmnnType,
118  typename ConvDescriptorType = typename Conv2dTest::ConvDescriptorType,
119  typename T = armnn::ResolveType<ArmnnType>>
120 INetworkPtr CreateNetwork(bool depthwise, bool preventFusing)
121 {
122  // Define layers information
123  ConvDescriptorType convolution2dDescriptor;
124  convolution2dDescriptor.m_BiasEnabled = false;
125  convolution2dDescriptor.m_DataLayout = DataLayout::NHWC;
126  convolution2dDescriptor.m_StrideX = 1;
127  convolution2dDescriptor.m_StrideY = 1;
128  BatchNormalizationDescriptor batchNormDescriptor;
129  batchNormDescriptor.m_DataLayout = DataLayout::NHWC;
130 
131  const unsigned int inputDimensionSizes[] = {1, 4, 4, 3}; // NHWCin
132  unsigned int weightsDimensionSizes[] = {4, 2, 2, 3}; // CoutHWCin
133  unsigned int outputDimensionSizes[] = {1, 3, 3, 4}; // NHWCout
134 
135  if (depthwise)
136  {
137  // [1, H, W, Cout]
138  weightsDimensionSizes[0] = 1;
139  weightsDimensionSizes[1] = 2;
140  weightsDimensionSizes[2] = 2;
141  weightsDimensionSizes[3] = 12;
142  outputDimensionSizes[3] = weightsDimensionSizes[3];
143  }
144  const unsigned int outputChannelSize[] = {outputDimensionSizes[3]}; // Cout
145 
146  TensorInfo inputInfo(4, inputDimensionSizes, ArmnnType);
147  TensorInfo outputInfo(4, outputDimensionSizes, ArmnnType);
148 
149  std::vector<int> weightsIntVector = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
150  11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
151  21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
152  31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42};
153  std::vector<T> weightsVector(begin(weightsIntVector), end(weightsIntVector));
154  TensorInfo weightsInfo(4, weightsDimensionSizes, ArmnnType, 0.0f, 0, true);
155  ConstTensor weights(weightsInfo, weightsVector);
156 
157  std::vector<T> betaVector = GetVector<T>(outputDimensionSizes[3], 0.0f, 0.2f);
158  std::vector<T> gammaVector = GetVector<T>(outputDimensionSizes[3], 0.5f, 0.1f);
159  std::vector<T> meanVector = GetVector<T>(outputDimensionSizes[3], 0.1f, 0.1f);
160  std::vector<T> varianceVector = GetVector<T>(outputDimensionSizes[3], 1.0f, 0.1f);
161 
162  ConstTensor beta (TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), betaVector);
163  ConstTensor gamma (TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), gammaVector);
164  ConstTensor mean (TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), meanVector);
165  ConstTensor variance(TensorInfo(1, outputChannelSize, ArmnnType, 0.0f, 0, true), varianceVector);
166 
167  // Create a network
168  INetworkPtr network = INetwork::Create();
169 
170  IConnectableLayer* inputLayer = network->AddInputLayer(0);
171 
172  IConnectableLayer* convLayer = Conv2dTest::AddConvolution(network.get(),
173  convolution2dDescriptor,
174  weights,
176  "convolution");
177 
178  IConnectableLayer* batchNormLayer = network->AddBatchNormalizationLayer(batchNormDescriptor,
179  mean,
180  variance,
181  beta,
182  gamma,
183  "batchNorm");
184 
185  IConnectableLayer* outputLayer = network->AddOutputLayer(0);
186  IConnectableLayer* output2Layer = nullptr;
187 
188  if (preventFusing)
189  {
190  output2Layer = network->AddOutputLayer(1);
191  }
192 
193  std::vector<IConnectableLayer*> constantLayers = Conv2dTest::AddConstantLayers(network.get(),
194  convolution2dDescriptor,
195  weights,
197 
198  // Connect constant layers to receiverLayer.
199  for (unsigned int i = 0; i < constantLayers.size(); ++i)
200  {
201  constantLayers[i]->GetOutputSlot(0).Connect(convLayer->GetInputSlot(i + 1));
202  }
203 
204  // Set layer information
205  inputLayer ->GetOutputSlot(0).SetTensorInfo(inputInfo);
206  convLayer ->GetOutputSlot(0).SetTensorInfo(outputInfo);
207  batchNormLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
208 
209  // Connect layers
210  inputLayer ->GetOutputSlot(0).Connect(convLayer->GetInputSlot(0));
211  convLayer ->GetOutputSlot(0).Connect(batchNormLayer->GetInputSlot(0));
212  batchNormLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
213 
214  if (preventFusing)
215  {
216  convLayer ->GetOutputSlot(0).Connect(output2Layer->GetInputSlot(0));
217  }
218 
219  return network;
220 }
221 
222 template <typename Conv2dTest,
223  armnn::DataType ArmnnType,
224  typename ConvDescriptorType = typename Conv2dTest::ConvDescriptorType,
225  typename ConvLayerType = typename Conv2dTest::ConvLayerType,
226  typename T = armnn::ResolveType<ArmnnType>>
227 void FuseBatchNormIntoConvTest(bool depthwise, float tolerance, armnn::Compute backendId)
228 {
229  // FIRST NETWORK: Fused
230  // Construct ArmNN network
231  INetworkPtr networkFused = CreateNetwork<Conv2dTest, ArmnnType>(depthwise, false);
232 
233  // Create ArmNN runtime
234  IRuntimePtr run = IRuntime::Create(IRuntime::CreationOptions()); // default options
235 
236  // Optimise ArmNN network
237  IOptimizedNetworkPtr optNetFused = Optimize(*networkFused, {backendId}, run->GetDeviceSpec());
238 
239  Graph& graphFused = GetGraphForTesting(optNetFused.get());
240 
241  auto checkFusedConv2d = [ ](const armnn::Layer* const layer) -> bool
242  {
243  return IsLayerOfType<ConvLayerType>(layer) &&
244  (layer->GetNameStr() == "fused-batchNorm-into-convolution");
245  };
246 
247  CHECK(5 == graphFused.GetNumLayers());
248  CHECK(CheckSequence(graphFused.cbegin(),
249  graphFused.cend(),
250  &IsLayerOfType<InputLayer>,
251  &IsLayerOfType<ConstantLayer>,
252  &IsLayerOfType<ConstantLayer>,
253  checkFusedConv2d,
254  &IsLayerOfType<OutputLayer>));
255 
256  // Load network into runtime
257  NetworkId networkIdentifier;
258  CHECK(run->LoadNetwork(networkIdentifier, std::move(optNetFused)) == Status::Success);
259 
260  //Creates structures for inputs and outputs.
261  std::vector<T> inputDataFused = GetVector<T>(48, 1.0f, 0.1f);
262 
263  std::vector<T> outputDataFused(36);
264 
265  if (depthwise)
266  {
267  outputDataFused.resize(108);
268  }
269 
270  TensorInfo inputTensorInfo = run->GetInputTensorInfo(networkIdentifier, 0);
271  inputTensorInfo.SetConstant(true);
272  InputTensors inputTensorsFused {
273  {0, ConstTensor(inputTensorInfo, inputDataFused.data())}};
274  OutputTensors outputTensorsFused{
275  {0, Tensor(run->GetOutputTensorInfo(networkIdentifier, 0), outputDataFused.data())}};
276 
277  // Execute network
278  run->EnqueueWorkload(networkIdentifier, inputTensorsFused, outputTensorsFused);
279 
280  // SECOND NETWORK: NotFused
281  // Construct ArmNN network
282  INetworkPtr networkNotFused = CreateNetwork<Conv2dTest, ArmnnType>(depthwise, true);
283 
284  // Create ArmNN runtime
285  IRuntimePtr runNotFused = IRuntime::Create(IRuntime::CreationOptions()); // default options
286 
287  // Optimise ArmNN network
288  IOptimizedNetworkPtr optNetNotFused = Optimize(*networkNotFused, { backendId }, runNotFused->GetDeviceSpec());
289 
290  Graph& graphNotFused = GetGraphForTesting(optNetNotFused.get());
291 
292  CHECK(6 == graphNotFused.GetNumLayers());
293  CHECK(CheckSequence(graphNotFused.cbegin(),
294  graphNotFused.cend(),
295  &IsLayerOfType<armnn::InputLayer>,
296  &IsLayerOfType<armnn::ConstantLayer>,
297  &IsLayerOfType<ConvLayerType>,
298  &IsLayerOfType<armnn::BatchNormalizationLayer>,
299  &IsLayerOfType<armnn::OutputLayer>,
300  &IsLayerOfType<armnn::OutputLayer>));
301 
302  // Load network into runtime
303  NetworkId networkIdentifierNotFused;
304  CHECK(runNotFused->LoadNetwork(networkIdentifierNotFused, std::move(optNetNotFused)) == Status::Success);
305 
306  //Creates structures for inputs and outputs.
307  std::vector<T> inputDataNotFused = GetVector<T>(48, 1.0f, 0.1f);
308 
309  std::vector<T> outputDataNotFused(36);
310  std::vector<T> outputData2NotFused(36);
311 
312  if (depthwise)
313  {
314  outputDataNotFused.resize(108);
315  outputData2NotFused.resize(108);
316  }
317 
318  TensorInfo inputTensorInfo2 = runNotFused->GetInputTensorInfo(networkIdentifierNotFused, 0);
319  inputTensorInfo2.SetConstant(true);
320  InputTensors inputTensorsNotFused{
321  { 0, ConstTensor(inputTensorInfo2, inputDataNotFused.data()) } };
322  OutputTensors outputTensorsNotFused{
323  { 0, Tensor(runNotFused->GetOutputTensorInfo(networkIdentifierNotFused, 0), outputDataNotFused.data()) },
324  { 1, Tensor(runNotFused->GetOutputTensorInfo(networkIdentifierNotFused, 1), outputData2NotFused.data()) } };
325 
326  // Execute network
327  runNotFused->EnqueueWorkload(networkIdentifierNotFused, inputTensorsNotFused, outputTensorsNotFused);
328 
329  // Check the output of the fused-convolution matches with the output of the batchNormm in the "NotFused" network
330  auto epsilon = T(tolerance);
331  for (unsigned int n = 0; n < outputDataFused.size(); ++n)
332  {
333  CHECK_EQ(outputDataFused[n], doctest::Approx(outputDataNotFused[n]).epsilon(epsilon));
334  }
335 }
336 
337 // This unit test needs the reference backend, it's not available if the reference backend is not built
338 #if defined(ARMNNREF_ENABLED)
339 TEST_CASE("FuseBatchNormIntoConv2DFloat32Test")
340 {
341  FuseBatchNormIntoConvTest<Conv2dTest, DataType::Float32>(false, 0.0001f, armnn::Compute::CpuRef);
342 }
343 
344 TEST_CASE("FuseBatchNormIntoConv2DFloat16Test")
345 {
346  FuseBatchNormIntoConvTest<Conv2dTest, DataType::Float16>(false, 0.1f, armnn::Compute::CpuRef);
347 }
348 
349 TEST_CASE("FuseBatchNormIntoDepthwiseConv2DFloat32Test")
350 {
351  FuseBatchNormIntoConvTest<DepthwiseConv2dTest, DataType::Float32>(true, 0.0001f,armnn::Compute::CpuRef);
352 }
353 
354 TEST_CASE("FuseBatchNormIntoDepthwiseConv2DFloat16Test")
355 {
356  FuseBatchNormIntoConvTest<DepthwiseConv2dTest, DataType::Float16>(true, 0.2f,armnn::Compute::CpuRef);
357 }
358 #endif
359 
360 }
bool m_BiasEnabled
Enable/disable bias.
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:68
bool m_BiasEnabled
Enable/disable bias.
CPU Execution: Reference C++ kernels.
bool CheckSequence(const armnn::Graph::ConstIterator first, const armnn::Graph::ConstIterator last)
Definition: TestUtils.hpp:21
IConnectableLayer * AddConstantLayer(const ConstTensor &input, const char *name=nullptr)
Adds a layer with no inputs and a single output, which always corresponds to the passed in constant t...
Definition: Network.cpp:292
IConnectableLayer * AddDepthwiseConvolution2dLayer(const DepthwiseConvolution2dDescriptor &convolution2dDescriptor, const char *name=nullptr)
Adds a 2D depthwise convolution layer to the network.
Definition: Network.cpp:118
This layer represents a depthwise convolution 2d operation.
A Convolution2dDescriptor for the Convolution2dLayer.
std::unique_ptr< IRuntime, void(*)(IRuntime *runtime)> IRuntimePtr
Definition: IRuntime.hpp:33
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:246
std::vector< std::pair< LayerBindingId, class ConstTensor > > InputTensors
Definition: Tensor.hpp:392
void IgnoreUnused(Ts &&...)
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:48
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:1864
int NetworkId
Definition: IRuntime.hpp:27
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:239
const TensorInfo & GetInfo() const
Definition: Tensor.hpp:295
Graph & GetGraphForTesting(IOptimizedNetwork *optNet)
Definition: TestUtils.cpp:49
IConnectableLayer * AddConvolution2dLayer(const Convolution2dDescriptor &convolution2dDescriptor, const char *name=nullptr)
Adds a 2D convolution layer to the network.
Definition: Network.cpp:85
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:514
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:238
virtual int Connect(IInputSlot &destination)=0
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
A BatchNormalizationDescriptor for the BatchNormalizationLayer.