ArmNN
 22.08
OptimizedNetworkTests.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include <CommonTestUtils.hpp>
7 
8 #include <Graph.hpp>
9 #include <Network.hpp>
10 
12 
13 #include <doctest/doctest.h>
14 
15 TEST_SUITE("OptimizedNetwork")
16 {
17 TEST_CASE("SerializeToDot")
18 {
19  // build up the structure of the network
21 
22  //Defines layers.
23  auto input = net->AddInputLayer(0);
24  auto add = net->AddAdditionLayer();
25  auto output = net->AddOutputLayer(0);
26 
27  // Connects layers.
28  input->GetOutputSlot(0).Connect(add->GetInputSlot(0));
29  input->GetOutputSlot(0).Connect(add->GetInputSlot(1));
30  add->GetOutputSlot(0).Connect(output->GetInputSlot(0));
31 
32  armnn::TensorShape shape({4});
34  input->GetOutputSlot(0).SetTensorInfo(info);
35  add->GetOutputSlot(0).SetTensorInfo(info);
36 
39 
40  std::vector<armnn::BackendId> backends = {armnn::Compute::CpuRef};
41  armnn::IOptimizedNetworkPtr optimizedNet = armnn::Optimize(*net, backends, runtime->GetDeviceSpec());
42 
43  std::ostringstream ss;
44  optimizedNet->SerializeToDot(ss);
45 
46  auto inputId = input->GetGuid();
47  auto addId = add->GetGuid();
48  auto outputId = output->GetGuid();
49 
50  std::stringstream expected;
51  expected <<
52  "digraph Optimized {\n"
53  " node [shape=\"record\"];\n"
54  " edge [fontsize=8 fontcolor=\"blue\" fontname=\"arial-bold\"];\n"
55  " " << inputId << " [label=\"{Input|Guid : " << inputId << "\\lLayerType : Input\\l"
56  "BackendID : CpuRef\\l}\"];\n"
57  " " << addId << " [label=\"{Addition|Guid : " << addId << "\\lLayerType : Addition\\l"
58  "BackendID : CpuRef\\l}\"];\n"
59  " " << outputId << " [label=\"{Output|Guid : " << outputId << "\\lLayerType : Output\\l"
60  "BackendID : CpuRef\\l}\"];\n"
61  " " << inputId << " -> " << addId << " [label=< [4] >];\n"
62  " " << inputId << " -> " << addId << " [label=< [4] >];\n"
63  " " << addId << " -> " << outputId << " [label=< [4] >];\n"
64  "}\n";
65 
66  CHECK(ss.str() == expected.str());
67 }
68 
69 TEST_CASE("OptimizeValidateDeviceNonSupportLayerNoFallback")
70 {
71  // build up the structure of the network
73 
74  armnn::IConnectableLayer* input = net->AddInputLayer(0);
75 
76  // This layer configuration isn't supported by CpuAcc and isn't allowed to fall back, so Optimize will return null.
78  armnn::IConnectableLayer* normalize = net->AddNormalizationLayer(descriptor);
79 
80  armnn::IConnectableLayer* output = net->AddOutputLayer(0);
81 
82  input->GetOutputSlot(0).Connect(normalize->GetInputSlot(0));
83  normalize->GetOutputSlot(0).Connect(output->GetInputSlot(0));
84 
87 
90 
91  std::vector<armnn::BackendId> backends = { armnn::Compute::CpuAcc };
92  std::vector<std::string> errMessages;
93 
94  try
95  {
96  Optimize(*net, backends, runtime->GetDeviceSpec(), armnn::OptimizerOptions(), errMessages);
97  FAIL("Should have thrown an exception.");
98  }
99  catch (const armnn::InvalidArgumentException&)
100  {
101  // Different exceptions are thrown on different backends
102  }
103  CHECK(errMessages.size() > 0);
104 }
105 
106 TEST_CASE("OptimizeValidateDeviceNonSupportLayerWithFallback")
107 {
108  // build up the structure of the network
110 
111  armnn::IConnectableLayer* input = net->AddInputLayer(0);
112 
113  // This layer configuration isn't supported by CpuAcc but it allows to fallback to CpuRef.
115  armnn::IConnectableLayer* normalize = net->AddNormalizationLayer(descriptor);
116 
117  armnn::IConnectableLayer* output = net->AddOutputLayer(0);
118 
119  input->GetOutputSlot(0).Connect(normalize->GetInputSlot(0));
120  normalize->GetOutputSlot(0).Connect(output->GetInputSlot(0));
121 
124 
127 
128  std::vector<armnn::BackendId> backends = { armnn::Compute::CpuAcc, armnn::Compute::CpuRef };
129  armnn::IOptimizedNetworkPtr optNet = armnn::Optimize(*net, backends, runtime->GetDeviceSpec());
130  REQUIRE(optNet);
131 
132  armnn::Graph& graph = GetGraphForTesting(optNet.get());
133  graph.AllocateDynamicBuffers();
134 
135  for (auto&& layer : graph)
136  {
137  // If NEON is enabled, Input and Output layers are supported by CpuAcc,
138  // the other layers are supported by CpuRef.
139  // If NEON is not enabled, all layers are supported by CpuRef.
140 #if defined(ARMCOMPUTENEON_ENABLED)
141  if (layer->GetType() == armnn::LayerType::Output)
142  {
143  CHECK(layer->GetBackendId() == armnn::Compute::CpuAcc);
144  }
145  else if (layer->GetType() == armnn::LayerType::Normalization)
146  {
147  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
148  }
149 #else
150  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
151 #endif
152  }
153 }
154 
155 TEST_CASE("OptimizeValidateWorkloadsUndefinedComputeDevice")
156 {
157  const armnn::TensorInfo desc({3, 5}, armnn::DataType::Float32);
158 
159  // build up the structure of the network
161 
164 
165  // in
166  // |
167  // nm
168  // / |
169  // ac |
170  // \ |
171  // ml
172  // |
173  // sm
174  // |
175  // ot
176  armnn::IConnectableLayer* layer = net->AddInputLayer(0, "in");
177  layer->GetOutputSlot(0).SetTensorInfo(desc);
178 
179  armnn::IConnectableLayer* const normLayer = net->AddNormalizationLayer(nmDesc, "nm");
180 
181  layer->GetOutputSlot(0).Connect(normLayer->GetInputSlot(0));
182  normLayer->GetOutputSlot(0).SetTensorInfo(desc);
183 
184  layer = net->AddActivationLayer(acDesc, "ac");
185 
186  normLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
187  layer->GetOutputSlot(0).SetTensorInfo(desc);
188 
189  armnn::IConnectableLayer* prevLayer = layer;
190  layer = net->AddMultiplicationLayer("ml");
191 
192  prevLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
193  normLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1));
194  layer->GetOutputSlot(0).SetTensorInfo(desc);
195 
196  prevLayer = layer;
197  armnn::SoftmaxDescriptor softmaxDescriptor;
198  layer = net->AddSoftmaxLayer(softmaxDescriptor, "sm");
199 
200  prevLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
201  layer->GetOutputSlot(0).SetTensorInfo(desc);
202 
203  prevLayer = layer;
204  layer = net->AddOutputLayer(0, "ot");
205 
206  prevLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
207 
210 
211  std::vector<armnn::BackendId> backends = { armnn::Compute::Undefined };
212  std::vector<std::string> errMessages;
213 
214  try
215  {
216  Optimize(*net, backends, runtime->GetDeviceSpec(), armnn::OptimizerOptions(), errMessages);
217  FAIL("Should have thrown an exception.");
218  }
219  catch (const armnn::InvalidArgumentException&)
220  {
221  // Different exceptions are thrown on different backends
222  }
223  CHECK(errMessages.size() > 0);
224 }
225 
226 TEST_CASE("OptimizeValidateWorkloadsUndefinedComputeDeviceWithFallback")
227 {
228  const armnn::TensorInfo desc({3, 5}, armnn::DataType::Float32);
229 
230  // build up the structure of the network
232 
235 
236  // in
237  // |
238  // nm
239  // / |
240  // ac |
241  // \ |
242  // ml
243  // |
244  // sm
245  // |
246  // ot
247  armnn::IConnectableLayer* layer = net->AddInputLayer(0, "in");
248  layer->GetOutputSlot(0).SetTensorInfo(desc);
249 
250  armnn::IConnectableLayer* const normLayer = net->AddNormalizationLayer(nmDesc, "nm");
251 
252  layer->GetOutputSlot(0).Connect(normLayer->GetInputSlot(0));
253  normLayer->GetOutputSlot(0).SetTensorInfo(desc);
254 
255  layer = net->AddActivationLayer(acDesc, "ac");
256 
257  normLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
258  layer->GetOutputSlot(0).SetTensorInfo(desc);
259 
260  armnn::IConnectableLayer* prevLayer = layer;
261  layer = net->AddMultiplicationLayer("ml");
262 
263  prevLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
264  normLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1));
265  layer->GetOutputSlot(0).SetTensorInfo(desc);
266 
267  prevLayer = layer;
268  armnn::SoftmaxDescriptor softmaxDescriptor;
269  layer = net->AddSoftmaxLayer(softmaxDescriptor, "sm");
270 
271  prevLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
272  layer->GetOutputSlot(0).SetTensorInfo(desc);
273 
274  prevLayer = layer;
275  layer = net->AddOutputLayer(0, "ot");
276 
277  prevLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
278 
281 
282  std::vector<armnn::BackendId> backends = { armnn::Compute::Undefined, armnn::Compute::CpuRef };
283 
284  armnn::IOptimizedNetworkPtr optNet = armnn::Optimize(*net, backends, runtime->GetDeviceSpec());
285  CHECK(optNet);
286 
287  armnn::Graph& graph = GetGraphForTesting(optNet.get());
288  graph.AllocateDynamicBuffers();
289 
290  // validate workloads
292  for (auto&& layer : graph)
293  {
294  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
295  CHECK_NOTHROW(
296  layer->CreateWorkload(fact));
297  }
298 }
299 
300 TEST_CASE("OptimizeValidateWorkloadsDuplicateComputeDeviceWithFallback")
301 {
302  // build up the structure of the network
304 
305  armnn::IConnectableLayer* input = net->AddInputLayer(0);
306 
307  // This layer configuration isn't supported by CpuAcc but it allows to fallback to CpuRef.
309  armnn::IConnectableLayer* normalize = net->AddNormalizationLayer(descriptor);
310 
311  armnn::IConnectableLayer* output = net->AddOutputLayer(0);
312 
313  input->GetOutputSlot(0).Connect(normalize->GetInputSlot(0));
314  normalize->GetOutputSlot(0).Connect(output->GetInputSlot(0));
315 
318 
321 
322  std::vector<armnn::BackendId> backends = { armnn::Compute::CpuAcc,
325 
326  armnn::IOptimizedNetworkPtr optNet = armnn::Optimize(*net, backends, runtime->GetDeviceSpec());
327  REQUIRE(optNet);
328 
329  armnn::Graph& graph = GetGraphForTesting(optNet.get());
330  graph.AllocateDynamicBuffers();
331 
332  for (auto&& layer : graph)
333  {
334  // If NEON is enabled, Input and Output layers are supported by CpuAcc,
335  // the other layers are supported by CpuRef.
336  // If only CL is enabled, Input and Output layers are supported by GpuAcc,
337  // the other layers are supported by CpuRef.
338  // If neither NEON, nor CL is enabled, all layers are supported by CpuRef.
339 #if defined(ARMCOMPUTENEON_ENABLED)
340  if (layer->GetType() == armnn::LayerType::Input)
341  {
342  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
343  }
344  else if (layer->GetType() == armnn::LayerType::Output)
345  {
346  CHECK(layer->GetBackendId() == armnn::Compute::CpuAcc);
347  }
348  else if (layer->GetType() == armnn::LayerType::Normalization)
349  {
350  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
351  }
352 #elif defined(ARMCOMPUTECL_ENABLED)
353  if (layer->GetType() == armnn::LayerType::Input)
354  {
355  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
356  }
357  else if (layer->GetType() == armnn::LayerType::Output)
358  {
359  CHECK(layer->GetBackendId() == armnn::Compute::GpuAcc);
360  }
361  else if (layer->GetType() == armnn::LayerType::Normalization)
362  {
363  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
364  }
365 #else
366  CHECK(layer->GetBackendId() == armnn::Compute::CpuRef);
367 #endif
368  }
369 }
370 
371 TEST_CASE("OptimizeNetworkCopy")
372 {
374  armnn::IRuntimePtr runtime = armnn::IRuntime::Create(options);
375  std::vector<armnn::NetworkId> networkIds;
376 
377  const std::string layerName("convolution2d");
378  const armnn::TensorInfo inputInfo ({ 1, 5, 5, 1 }, armnn::DataType::Float32);
379  const armnn::TensorInfo outputInfo({ 1, 2, 2, 1 }, armnn::DataType::Float32);
380 
381  const armnn::TensorInfo weightsInfo({ 1, 3, 3, 1 }, armnn::DataType::Float32, 0.0f, 0, true);
382  const armnn::TensorInfo biasesInfo ({ 1 }, armnn::DataType::Float32, 0.0f, 0, true);
383 
384  std::vector<float> weightsData = GenerateRandomData<float>(weightsInfo.GetNumElements());
385  armnn::ConstTensor weights(weightsInfo, weightsData);
386 
387  std::vector<float> biasesData = GenerateRandomData<float>(biasesInfo.GetNumElements());
388  armnn::ConstTensor biases(biasesInfo, biasesData);
389 
391  descriptor.m_PadLeft = 1;
392  descriptor.m_PadRight = 1;
393  descriptor.m_PadTop = 1;
394  descriptor.m_PadBottom = 1;
395  descriptor.m_StrideX = 2;
396  descriptor.m_StrideY = 2;
397  descriptor.m_DilationX = 2;
398  descriptor.m_DilationY = 2;
399  descriptor.m_BiasEnabled = true;
401 
403  armnn::IConnectableLayer* const inputLayer = network->AddInputLayer(0);
404 
405  armnn::IConnectableLayer* const convLayer = network->AddConvolution2dLayer(descriptor, layerName.c_str());
406  armnn::IConnectableLayer* const outputLayer = network->AddOutputLayer(0);
407  armnn::IConnectableLayer* weightsLayer = network->AddConstantLayer(weights);
408  armnn::IConnectableLayer* biasLayer = network->AddConstantLayer(biases);
409 
410  weightsLayer->GetOutputSlot(0).SetTensorInfo(weightsInfo);
411  weightsLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(1u));
412 
413  biasLayer->GetOutputSlot(0).SetTensorInfo(biasesInfo);
414  biasLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(2u));
415 
416  inputLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(0));
417  convLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0));
418 
419  inputLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
420  convLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
421 
422  std::vector<armnn::BackendId> preferredBackends { "CpuRef" };
423  armnn::ModelOptions modelOptions;
424  armnn::OptimizerOptions optimizerOptions(false, false, false, false, modelOptions, false);
425  std::vector<std::string> errorMessages;
426 
427  // optimize the network.
428  armnn::IOptimizedNetworkPtr optNet = Optimize(*network,
429  preferredBackends,
430  runtime->GetDeviceSpec(),
431  optimizerOptions,
433 
434  for (unsigned int i = 0; i < 2; ++i)
435  {
436  armnn::ModelOptions optimizedModelOptions;
437  auto copy = armnn::IOptimizedNetworkPtr(new armnn::IOptimizedNetwork(*optNet.get(), optimizedModelOptions),
439 
440  CHECK(copy);
441 
442  armnn::NetworkId netId;
443  std::string errorMessage;
444 
445  CHECK(armnn::Status::Success == runtime->LoadNetwork(netId, std::move(copy), errorMessage));
446 
447  // Record the networkID for the loaded network
448  networkIds.emplace_back(netId);
449  }
450  armnn::NetworkId optNetId;
451  std::string errorMessage;
452 
453  // Load the original optNet
454  CHECK(armnn::Status::Success == runtime->LoadNetwork(optNetId, std::move(optNet), errorMessage));
455 
456  std::vector<float> inputData = GenerateRandomData<float>(runtime->GetInputTensorInfo(optNetId, 0).GetNumElements());
457  std::vector<float> outputData(runtime->GetOutputTensorInfo(optNetId, 0).GetNumElements());
458 
459  armnn::TensorInfo inputTensorInfo = runtime->GetInputTensorInfo(optNetId, 0);
460  inputTensorInfo.SetConstant(true);
461  armnn::InputTensors inputTensors
462  {
463  {
464  0, armnn::ConstTensor(inputTensorInfo, inputData.data())
465  }
466  };
467  armnn::OutputTensors outputTensors
468  {
469  {
470  0, armnn::Tensor(runtime->GetOutputTensorInfo(optNetId, 0), outputData.data())
471  }
472  };
473  runtime->EnqueueWorkload(optNetId, inputTensors, outputTensors);
474  runtime->UnloadNetwork(optNetId);
475 
476  // Record the networkID for the loaded network
477  for (unsigned int i = 0; i < networkIds.size(); ++i)
478  {
479  armnn::NetworkId netId = networkIds[i];
480  std::vector<float> copyOutputData(runtime->GetOutputTensorInfo(netId, 0).GetNumElements());
481 
482  armnn::TensorInfo inputTensorInfo2 = runtime->GetInputTensorInfo(netId, 0);
483  inputTensorInfo2.SetConstant(true);
484  armnn::InputTensors copyInputTensors
485  {
486  {
487  0, armnn::ConstTensor(inputTensorInfo2, inputData.data())
488  }
489  };
490  armnn::OutputTensors copyOutputTensors
491  {
492  {
493  0, armnn::Tensor(runtime->GetOutputTensorInfo(netId, 0), copyOutputData.data())
494  }
495  };
496  runtime->EnqueueWorkload(netId, copyInputTensors, copyOutputTensors);
497  runtime->UnloadNetwork(netId);
498 
499  // Check results are identical to "original" version
500  for (unsigned int j = 0; j < outputData.size(); ++j)
501  {
502  CHECK(outputData[j] == copyOutputData[j]);
503  }
504  }
505 }
506 
507 }
uint32_t m_PadBottom
Padding bottom value in the height dimension.
bool m_BiasEnabled
Enable/disable bias.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
static IRuntimePtr Create(const CreationOptions &options)
Definition: Runtime.cpp:49
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:68
CPU Execution: Reference C++ kernels.
std::vector< BackendOptions > ModelOptions
A Convolution2dDescriptor for the Convolution2dLayer.
std::unique_ptr< IRuntime, void(*)(IRuntime *runtime)> IRuntimePtr
Definition: IRuntime.hpp:33
TEST_SUITE("OptimizedNetwork")
std::vector< std::pair< LayerBindingId, class ConstTensor > > InputTensors
Definition: Tensor.hpp:392
uint32_t m_PadRight
Padding right value in the width dimension.
uint32_t m_DilationY
Dilation along y axis.
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
uint32_t m_PadTop
Padding top value in the height dimension.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
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
GPU Execution: OpenCL: ArmCompute.
ArmNN performs an optimization on each model/network before it gets loaded for execution.
Definition: INetwork.hpp:127
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:36
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
Graph & GetGraphForTesting(IOptimizedNetwork *optNet)
Definition: TestUtils.cpp:49
uint32_t m_DilationX
Dilation along x axis.
CPU Execution: NEON: ArmCompute.
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.
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:238
virtual int Connect(IInputSlot &destination)=0
A NormalizationDescriptor for the NormalizationLayer.
Status AllocateDynamicBuffers()
Allocates memory for all tensors under output tensor handers of each layer.
Definition: Graph.cpp:181
static INetworkPtr Create(NetworkOptions networkOptions={})
Definition: Network.cpp:475
static void Destroy(IOptimizedNetwork *network)
Definition: Network.cpp:499
A SoftmaxDescriptor for the SoftmaxLayer.
uint32_t m_PadLeft
Padding left value in the width dimension.