ArmNN
 22.05
DefaultAsyncExecuteTest.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include <armnn/Exceptions.hpp>
7 
10 
11 #include <doctest/doctest.h>
12 
13 #include <thread>
14 
15 using namespace armnn;
16 
17 
18 namespace
19 {
20 
21 TEST_SUITE("WorkloadAsyncExecuteTests")
22 {
23 
24 struct Workload0 : BaseWorkload<ElementwiseUnaryQueueDescriptor>
25 {
26  Workload0(const ElementwiseUnaryQueueDescriptor& descriptor, const WorkloadInfo& info)
27  : BaseWorkload(descriptor, info)
28  {
29  }
30 
32  {
33  }
34 
35  void Execute() const
36  {
37  int* inVals = static_cast<int*>(m_Data.m_Inputs[0][0].Map());
38  int* outVals = static_cast<int*>(m_Data.m_Outputs[0][0].Map());
39 
40  for (unsigned int i = 0;
41  i < m_Data.m_Inputs[0][0].GetShape().GetNumElements();
42  ++i)
43  {
44  outVals[i] = inVals[i] * outVals[i];
45  inVals[i] = outVals[i];
46  }
47  }
48 
49  void ExecuteAsync(WorkingMemDescriptor& desc)
50  {
51  int* inVals = static_cast<int*>(desc.m_Inputs[0][0].Map());
52  int* outVals = static_cast<int*>(desc.m_Outputs[0][0].Map());
53 
54  for (unsigned int i = 0;
55  i < desc.m_Inputs[0][0].GetShape().GetNumElements();
56  ++i)
57  {
58  outVals[i] = inVals[i] + outVals[i];
59  inVals[i] = outVals[i];
60  }
61  }
62 
63  QueueDescriptor* GetQueueDescriptor()
64  {
65  return &m_Data;
66  }
67 };
68 
69 struct Workload1 : BaseWorkload<ElementwiseUnaryQueueDescriptor>
70 {
71  Workload1(const ElementwiseUnaryQueueDescriptor& descriptor, const WorkloadInfo& info)
72  : BaseWorkload(descriptor, info)
73  {
74  }
75 
76  void Execute() const
77  {
78  int* inVals = static_cast<int*>(m_Data.m_Inputs[0][0].Map());
79  int* outVals = static_cast<int*>(m_Data.m_Outputs[0][0].Map());
80 
81  for (unsigned int i = 0;
82  i < m_Data.m_Inputs[0][0].GetShape().GetNumElements();
83  ++i)
84  {
85  outVals[i] = inVals[i] * outVals[i];
86  inVals[i] = outVals[i];
87  }
88  }
89 };
90 
91 void ValidateTensor(ITensorHandle* tensorHandle, int expectedValue)
92 {
93  int* actualOutput = static_cast<int*>(tensorHandle->Map());
94 
95  bool allValuesCorrect = true;
96  for (unsigned int i = 0;
97  i < tensorHandle->GetShape().GetNumElements();
98  ++i)
99  {
100  if (actualOutput[i] != expectedValue)
101  {
102  allValuesCorrect = false;
103  }
104  }
105 
106  CHECK(allValuesCorrect);
107 }
108 
109 template<typename Workload>
110 std::unique_ptr<Workload> CreateWorkload(TensorInfo info, ITensorHandle* inputTensor, ITensorHandle* outputTensor)
111 {
112  WorkloadInfo workloadInfo;
113  workloadInfo.m_InputTensorInfos = std::vector<TensorInfo>{info};
114  workloadInfo.m_OutputTensorInfos = std::vector<TensorInfo>{info};
115 
116  ElementwiseUnaryQueueDescriptor elementwiseUnaryQueueDescriptor;
117  elementwiseUnaryQueueDescriptor.m_Inputs = std::vector<ITensorHandle*>{inputTensor};
118  elementwiseUnaryQueueDescriptor.m_Outputs = std::vector<ITensorHandle*>{outputTensor};
119 
120  return std::make_unique<Workload>(elementwiseUnaryQueueDescriptor, workloadInfo);
121 }
122 
123 TEST_CASE("TestAsyncExecute")
124 {
125  TensorInfo info({5}, DataType::Signed32, 0.0, 0, true);
126 
127  int inVals[5]{2, 2, 2, 2, 2};
128  int outVals[5]{1, 1, 1, 1, 1};
129 
130  int expectedExecuteval = 2;
131  int expectedExecuteAsyncval = 3;
132 
133  ConstTensor constInputTensor(info, inVals);
134  ConstTensor constOutputTensor(info, outVals);
135 
136  ScopedTensorHandle syncInput0(constInputTensor);
137  ScopedTensorHandle syncOutput0(constOutputTensor);
138 
139  std::unique_ptr<Workload0> workload0 = CreateWorkload<Workload0>(info, &syncInput0, &syncOutput0);
140 
141  workload0.get()->Execute();
142 
143  ScopedTensorHandle asyncInput0(constInputTensor);
144  ScopedTensorHandle asyncOutput0(constOutputTensor);
145 
146  WorkingMemDescriptor workingMemDescriptor0;
147  workingMemDescriptor0.m_Inputs = std::vector<ITensorHandle*>{&asyncInput0};
148  workingMemDescriptor0.m_Outputs = std::vector<ITensorHandle*>{&asyncOutput0};
149 
150  workload0.get()->ExecuteAsync(workingMemDescriptor0);
151 
152  // Inputs are also changed by the execute/executeAsync calls to make sure there is no interference with them
153  ValidateTensor(workingMemDescriptor0.m_Outputs[0], expectedExecuteAsyncval);
154  ValidateTensor(workingMemDescriptor0.m_Inputs[0], expectedExecuteAsyncval);
155 
156  ValidateTensor(&workload0.get()->GetQueueDescriptor()->m_Outputs[0][0], expectedExecuteval);
157  ValidateTensor(&workload0.get()->GetQueueDescriptor()->m_Inputs[0][0], expectedExecuteval);
158 }
159 
160 TEST_CASE("TestDefaultAsyncExecute")
161 {
162  TensorInfo info({5}, DataType::Signed32, 0.0f, 0, true);
163 
164  std::vector<int> inVals{2, 2, 2, 2, 2};
165  std::vector<int> outVals{1, 1, 1, 1, 1};
166  std::vector<int> defaultVals{0, 0, 0, 0, 0};
167 
168  int expectedExecuteval = 2;
169 
170  ConstTensor constInputTensor(info, inVals);
171  ConstTensor constOutputTensor(info, outVals);
172  ConstTensor defaultTensor(info, &defaultVals);
173 
174  ScopedTensorHandle defaultInput = ScopedTensorHandle(defaultTensor);
175  ScopedTensorHandle defaultOutput = ScopedTensorHandle(defaultTensor);
176 
177  std::unique_ptr<Workload1> workload1 = CreateWorkload<Workload1>(info, &defaultInput, &defaultOutput);
178 
179  ScopedTensorHandle asyncInput(constInputTensor);
180  ScopedTensorHandle asyncOutput(constOutputTensor);
181 
182  WorkingMemDescriptor workingMemDescriptor;
183  workingMemDescriptor.m_Inputs = std::vector<ITensorHandle*>{&asyncInput};
184  workingMemDescriptor.m_Outputs = std::vector<ITensorHandle*>{&asyncOutput};
185 
186  workload1.get()->ExecuteAsync(workingMemDescriptor);
187 
188  // workload1 has no AsyncExecute implementation and so should use the default workload AsyncExecute
189  // implementation which will call workload1.Execute() in a thread safe manner
190  ValidateTensor(workingMemDescriptor.m_Outputs[0], expectedExecuteval);
191  ValidateTensor(workingMemDescriptor.m_Inputs[0], expectedExecuteval);
192 }
193 
194 TEST_CASE("TestDefaultAsyncExeuteWithThreads")
195 {
196  // Use a large vector so the threads have a chance to interact
197  unsigned int vecSize = 1000;
198  TensorInfo info({vecSize}, DataType::Signed32, 0.0f, 0, true);
199 
200  std::vector<int> inVals1(vecSize, 2);
201  std::vector<int> outVals1(vecSize, 1);
202  std::vector<int> inVals2(vecSize, 5);
203  std::vector<int> outVals2(vecSize, -1);
204 
205  std::vector<int> defaultVals(vecSize, 0);
206 
207  int expectedExecuteval1 = 4;
208  int expectedExecuteval2 = 25;
209  ConstTensor constInputTensor1(info, inVals1);
210  ConstTensor constOutputTensor1(info, outVals1);
211 
212  ConstTensor constInputTensor2(info, inVals2);
213  ConstTensor constOutputTensor2(info, outVals2);
214 
215  ConstTensor defaultTensor(info, defaultVals.data());
216 
217  ScopedTensorHandle defaultInput = ScopedTensorHandle(defaultTensor);
218  ScopedTensorHandle defaultOutput = ScopedTensorHandle(defaultTensor);
219  std::unique_ptr<Workload1> workload = CreateWorkload<Workload1>(info, &defaultInput, &defaultOutput);
220 
221  ScopedTensorHandle asyncInput1(constInputTensor1);
222  ScopedTensorHandle asyncOutput1(constOutputTensor1);
223 
224  WorkingMemDescriptor workingMemDescriptor1;
225  workingMemDescriptor1.m_Inputs = std::vector<ITensorHandle*>{&asyncInput1};
226  workingMemDescriptor1.m_Outputs = std::vector<ITensorHandle*>{&asyncOutput1};
227 
228 
229  ScopedTensorHandle asyncInput2(constInputTensor2);
230  ScopedTensorHandle asyncOutput2(constOutputTensor2);
231 
232  WorkingMemDescriptor workingMemDescriptor2;
233  workingMemDescriptor2.m_Inputs = std::vector<ITensorHandle*>{&asyncInput2};
234  workingMemDescriptor2.m_Outputs = std::vector<ITensorHandle*>{&asyncOutput2};
235 
236  std::thread thread1 = std::thread([&]()
237  {
238  workload.get()->ExecuteAsync(workingMemDescriptor1);
239  workload.get()->ExecuteAsync(workingMemDescriptor1);
240  });
241 
242  std::thread thread2 = std::thread([&]()
243  {
244  workload.get()->ExecuteAsync(workingMemDescriptor2);
245  workload.get()->ExecuteAsync(workingMemDescriptor2);
246  });
247 
248  thread1.join();
249  thread2.join();
250 
251  ValidateTensor(workingMemDescriptor1.m_Outputs[0], expectedExecuteval1);
252  ValidateTensor(workingMemDescriptor1.m_Inputs[0], expectedExecuteval1);
253 
254  ValidateTensor(workingMemDescriptor2.m_Outputs[0], expectedExecuteval2);
255  ValidateTensor(workingMemDescriptor2.m_Inputs[0], expectedExecuteval2);
256 }
257 
258 }
259 
260 }
TEST_SUITE("TestConstTensorLayerVisitor")
unsigned int GetNumElements() const
Function that calculates the tensor elements by multiplying all dimension size which are Specified...
Definition: Tensor.cpp:181
std::unique_ptr< armnn::IWorkload > CreateWorkload(const armnn::IWorkloadFactory &workloadFactory, const armnn::WorkloadInfo &info, const DescriptorType &descriptor)
Copyright (c) 2021 ARM Limited and Contributors.
std::vector< TensorInfo > m_InputTensorInfos
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
std::vector< TensorInfo > m_OutputTensorInfos
virtual TensorShape GetShape() const =0
Get the number of elements for each dimension ordered from slowest iterating dimension to fastest ite...
virtual const void * Map(bool blocking=true) const =0
Map the tensor data for access.
std::vector< ITensorHandle * > m_Outputs
Contains information about TensorInfos of a layer.
std::vector< ITensorHandle * > m_Inputs