ArmNN
 24.05
ArmComputeTensorUtils.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017-2024 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #include <armnn/Exceptions.hpp>
9 
10 #include "ArmComputeUtils.hpp"
11 #include <armnn/Descriptors.hpp>
12 
13 #include <fmt/format.h>
14 
15 namespace armnn
16 {
17 namespace armcomputetensorutils
18 {
19 
20 arm_compute::DataType GetArmComputeDataType(armnn::DataType dataType, bool multiScales)
21 {
22  switch(dataType)
23  {
25  return arm_compute::DataType::BFLOAT16;
27  return arm_compute::DataType::U8;
29  return arm_compute::DataType::F16;
31  return arm_compute::DataType::F32;
33  return arm_compute::DataType::QASYMM8_SIGNED;
35  return arm_compute::DataType::QASYMM8;
37  return arm_compute::DataType::QSYMM16;
39  return arm_compute::DataType::S64;
41  {
42  return multiScales ? arm_compute::DataType::QSYMM8_PER_CHANNEL : arm_compute::DataType::QSYMM8;
43  }
45  return arm_compute::DataType::S32;
46  default:
47  return arm_compute::DataType::UNKNOWN;
48  }
49 }
50 
51 armnn::DataType GetArmNNDataType(arm_compute::DataType dataType)
52 {
53  switch(dataType)
54  {
55  case arm_compute::DataType::BFLOAT16:
57  case arm_compute::DataType::U8:
59  case arm_compute::DataType::F16:
61  case arm_compute::DataType::F32:
63  case arm_compute::DataType::QASYMM8_SIGNED:
65  case arm_compute::DataType::QASYMM8:
67  case arm_compute::DataType::QSYMM16:
69  case arm_compute::DataType::S64:
71  case arm_compute::DataType::QSYMM8_PER_CHANNEL:
73  case arm_compute::DataType::QSYMM8:
75  case arm_compute::DataType::S32:
77  default:
78  throw InvalidArgumentException("Unknown arm_compute::DataType data type");
79  }
80 }
81 
82 arm_compute::Coordinates BuildArmComputeReductionCoordinates(size_t inputDimensions,
83  unsigned int originalInputRank,
84  const std::vector<unsigned int>& armnnAxes)
85 {
86  arm_compute::Coordinates outAclCoords;
87 
88  if (armnnAxes.empty())
89  {
90  // If no reduction axes were provided, then the input must be reduced along all dimensions.
91  // Since Compute Library does not accept an empty vector as the reduction dimensions, we then
92  // manually create a vector including all the input dimensions (in reversed order) as:
93  //
94  // { inputDimensions - 1, inputDimensions - 2, ..., 1, 0 }
95  //
96  outAclCoords.set_num_dimensions(inputDimensions);
97  std::generate(outAclCoords.begin(), outAclCoords.end(), [d = inputDimensions - 1] () mutable { return d--; });
98  }
99  else
100  {
101  // Create a vector of reduction dimensions (in reversed order) with the given reduction axes.
102  //
103  // Adjust the given reduction axes according to the original rank of the input tensor (before ACL applied any
104  // dimension correction).
105  // For example, if the input tensor originally had 4 dimensions, and one of the reduction axes was 2, then the
106  // new value for that reduction axis should be 1.
107  //
108  // Example:
109  // ArmNN input shape = { 1, 1, 3, 2 } -> ACL input shape = { 2, 3 }
110  // ArmNN reduction axis = { 2 } -> ACL reduction axis = { 1 }
111  // ArmNN reduction axis = { 3 } -> ACL reduction axis = { 0 }
112  //
113  // The transformation: ACL reduction axis index = original rank - ArmNN reduction axis index - 1
114  //
115  outAclCoords.set_num_dimensions(armnnAxes.size());
116  std::transform(armnnAxes.begin(), armnnAxes.end(),
117  outAclCoords.begin(),
118  [originalInputRank](unsigned int i){ return originalInputRank - i - 1; });
119  }
120 
121  return outAclCoords;
122 }
123 
124 arm_compute::TensorShape BuildArmComputeTensorShape(const armnn::TensorShape& tensorShape)
125 {
126  arm_compute::TensorShape shape;
127 
128  // armnn tensors are (batch, channels, height, width).
129  // arm_compute tensors are (width, height, channels, batch).
130  for (unsigned int i = 0; i < tensorShape.GetNumDimensions(); i++)
131  {
132  // Note that our dimensions are stored in the opposite order to ACL's.
133  shape.set(tensorShape.GetNumDimensions() - i - 1, tensorShape[i], false);
134 
135  // TensorShape::set() flattens leading ones, so that batch size 1 cannot happen.
136  // arm_compute tensors expect this.
137  }
138 
139  // prevent arm_compute issue where tensor is flattened to nothing
140  if (shape.num_dimensions() == 0)
141  {
142  shape.set_num_dimensions(1);
143  }
144 
145  return shape;
146 }
147 
148 std::vector<unsigned int> ReduceDimsForACL(const armnn::TensorShape tensorShape, unsigned int dimensions)
149 {
150  std::vector<unsigned int> newShape;
151 
152  unsigned int dimsToSkip = 0;
153 
154  if (tensorShape.GetNumDimensions() > dimensions)
155  {
156  dimsToSkip = tensorShape.GetNumDimensions() - dimensions;
157  }
158  unsigned int dimsSkipped = 0;
159  bool insertRemainder = false;
160 
161  for (unsigned int i = 0; i < tensorShape.GetNumDimensions(); ++i)
162  {
163  if (tensorShape[i] == 1 && dimsSkipped < dimsToSkip && !insertRemainder)
164  {
165  ++dimsSkipped;
166  continue;
167  }
168  newShape.insert(newShape.begin(), tensorShape[i]);
169  // Once we insert the first dimension we can't skip any more
170  insertRemainder = true;
171  }
172  return newShape;
173 }
174 
175 arm_compute::TensorShape BuildArmComputeTensorShape(const armnn::TensorShape& tensorShape, unsigned int dimensions)
176 {
177  arm_compute::TensorShape shape;
178  std::vector<unsigned int> strippedShape = ReduceDimsForACL(tensorShape, dimensions);
179 
180  for (unsigned int i = 0; i < strippedShape.size(); i++)
181  {
182  shape.set(i, strippedShape[i], false);
183  }
184 
185  // prevent arm_compute issue where tensor is flattened to nothing
186  if (shape.num_dimensions() == 0)
187  {
188  shape.set_num_dimensions(1);
189  }
190  return shape;
191 }
192 
193 // Utility function used to build a TensorInfo object, that can be used to initialise
194 // ARM Compute Tensor and CLTensor allocators.
195 // Note: this utility ignores the value of armnn::TensorInfo.IsConstant(). ACL tensors
196 // default to constant but Arm NN ones default to non constant. In the cases where
197 // we expect ACL to treat a tensor as constant that value must be set after this
198 // utility has been called.
199 arm_compute::TensorInfo BuildArmComputeTensorInfo(const armnn::TensorInfo& tensorInfo)
200 {
201  bool multiScales = tensorInfo.HasMultipleQuantizationScales();
202  const arm_compute::TensorShape aclTensorShape = BuildArmComputeTensorShape(tensorInfo.GetShape());
203  const arm_compute::DataType aclDataType = GetArmComputeDataType(tensorInfo.GetDataType(), multiScales);
204 
205  const arm_compute::QuantizationInfo aclQuantizationInfo = multiScales ?
206  arm_compute::QuantizationInfo(tensorInfo.GetQuantizationScales()) :
207  arm_compute::QuantizationInfo(tensorInfo.GetQuantizationScale(), tensorInfo.GetQuantizationOffset());
208 
209  return arm_compute::TensorInfo(aclTensorShape, 1, aclDataType, aclQuantizationInfo);
210 }
211 
212 arm_compute::TensorInfo BuildArmComputeTensorInfo(const armnn::TensorInfo& tensorInfo,
213  armnn::DataLayout dataLayout)
214 {
215  arm_compute::TensorInfo aclTensorInfo = BuildArmComputeTensorInfo(tensorInfo);
216  aclTensorInfo.set_data_layout(ConvertDataLayout(dataLayout));
217 
218  return aclTensorInfo;
219 }
220 
221 arm_compute::TensorInfo BuildArmComputeTensorInfo(const armnn::TensorInfo& tensorInfo, unsigned int dimensions)
222 {
223  bool multiScales = tensorInfo.HasMultipleQuantizationScales();
224  const arm_compute::TensorShape aclTensorShape = BuildArmComputeTensorShape(tensorInfo.GetShape(), dimensions);
225  const arm_compute::DataType aclDataType = GetArmComputeDataType(tensorInfo.GetDataType(), multiScales);
226 
227  const arm_compute::QuantizationInfo aclQuantizationInfo = multiScales ?
228  arm_compute::QuantizationInfo(tensorInfo.GetQuantizationScales()) :
229  arm_compute::QuantizationInfo(tensorInfo.GetQuantizationScale(), tensorInfo.GetQuantizationOffset());
230 
231  return arm_compute::TensorInfo(aclTensorShape, 1, aclDataType, aclQuantizationInfo);
232 }
233 arm_compute::TensorInfo BuildArmComputeTensorInfo(const armnn::TensorInfo& tensorInfo,
234  armnn::DataLayout dataLayout, unsigned int dimensions)
235 {
236  arm_compute::TensorInfo aclTensorInfo = BuildArmComputeTensorInfo(tensorInfo, dimensions);
237  aclTensorInfo.set_data_layout(ConvertDataLayout(dataLayout));
238 
239  return aclTensorInfo;
240 }
241 
242 
243 arm_compute::DataLayout ConvertDataLayout(armnn::DataLayout dataLayout)
244 {
245  switch(dataLayout)
246  {
247  case armnn::DataLayout::NHWC : return arm_compute::DataLayout::NHWC;
248 
249  case armnn::DataLayout::NCHW : return arm_compute::DataLayout::NCHW;
250 
251  case armnn::DataLayout::NDHWC : return arm_compute::DataLayout::NDHWC;
252 
253  case armnn::DataLayout::NCDHW : return arm_compute::DataLayout::NCDHW;
254 
255  default: throw InvalidArgumentException("Unknown armnn::DataLayout: [" +
256  std::to_string(static_cast<int>(dataLayout)) + "]");
257  }
258 }
259 
260 arm_compute::PoolingLayerInfo BuildArmComputePoolingLayerInfo(const Pooling2dDescriptor& descriptor,
261  bool fpMixedPrecision)
262 {
263  // Resolve ARM Compute layer parameters.
264  const arm_compute::PoolingType poolingType = ConvertPoolingAlgorithmToAclPoolingType(descriptor.m_PoolType);
265 
266  const arm_compute::DataLayout dataLayout = ConvertDataLayout(descriptor.m_DataLayout);
267 
268  bool isGlobalPooling = (descriptor.m_StrideX==0 && descriptor.m_StrideY==0);
269  //use specific constructor if global pooling
270  if(isGlobalPooling)
271  {
272  return arm_compute::PoolingLayerInfo(poolingType, dataLayout);
273  }
274 
275  const arm_compute::DimensionRoundingType rounding = ConvertOutputShapeRoundingToAclDimensionRoundingType(
276  descriptor.m_OutputShapeRounding);
277  const arm_compute::PadStrideInfo padStrideInfo(descriptor.m_StrideX,
278  descriptor.m_StrideY,
279  descriptor.m_PadLeft,
280  descriptor.m_PadRight,
281  descriptor.m_PadTop,
282  descriptor.m_PadBottom,
283  rounding);
284 
285  const bool excludePadding = (descriptor.m_PaddingMethod == PaddingMethod::Exclude);
286 
287  const arm_compute::Size2D poolSize(descriptor.m_PoolWidth, descriptor.m_PoolHeight);
288 
289  return arm_compute::PoolingLayerInfo(poolingType, poolSize, dataLayout, padStrideInfo, excludePadding,
290  fpMixedPrecision);
291 }
292 
293 arm_compute::Pooling3dLayerInfo BuildArmComputePooling3dLayerInfo(const Pooling3dDescriptor& descriptor,
294  bool fpMixedPrecision)
295 {
296  const arm_compute::PoolingType poolingType = ConvertPoolingAlgorithmToAclPoolingType(descriptor.m_PoolType);
297 
298  bool isGlobalPooling = (descriptor.m_StrideX==0 && descriptor.m_StrideY==0 && descriptor.m_StrideZ==0);
299  //use specific constructor if global pooling
300  if(isGlobalPooling)
301  {
302  return arm_compute::Pooling3dLayerInfo(poolingType);
303  }
304 
305  const arm_compute::Size3D poolSize(descriptor.m_PoolWidth, descriptor.m_PoolHeight, descriptor.m_PoolDepth);
306 
307  const arm_compute::Size3D stride(descriptor.m_StrideX,
308  descriptor.m_StrideY,
309  descriptor.m_StrideZ);
310 
311  const arm_compute::Padding3D padding(descriptor.m_PadLeft,
312  descriptor.m_PadRight,
313  descriptor.m_PadTop,
314  descriptor.m_PadBottom,
315  descriptor.m_PadFront,
316  descriptor.m_PadBack);
317 
318  const bool excludePadding = (descriptor.m_PaddingMethod == PaddingMethod::Exclude);
319 
320  const arm_compute::DimensionRoundingType rounding = ConvertOutputShapeRoundingToAclDimensionRoundingType(
321  descriptor.m_OutputShapeRounding);
322 
323  return arm_compute::Pooling3dLayerInfo(poolingType,
324  poolSize,
325  stride,
326  padding,
327  excludePadding,
328  fpMixedPrecision,
329  rounding);
330 }
331 
332 arm_compute::NormalizationLayerInfo BuildArmComputeNormalizationLayerInfo(const NormalizationDescriptor& descriptor)
333 {
334  const arm_compute::NormType normType =
335  ConvertNormalizationAlgorithmChannelToAclNormType(descriptor.m_NormChannelType);
336  return arm_compute::NormalizationLayerInfo(normType,
337  descriptor.m_NormSize,
338  descriptor.m_Alpha,
339  descriptor.m_Beta,
340  descriptor.m_K,
341  false);
342 }
343 
344 arm_compute::PermutationVector BuildArmComputePermutationVector(const armnn::PermutationVector& perm)
345 {
346  arm_compute::PermutationVector aclPerm;
347 
348  unsigned int start = 0;
349  while ((start < perm.GetSize()) && (start == perm[start]))
350  {
351  ++start;
352  }
353 
354  for (unsigned int i = start; i < perm.GetSize(); ++i)
355  {
356  aclPerm.set(i - start, perm[i] - start);
357  }
358  return aclPerm;
359 }
360 
361 arm_compute::PermutationVector BuildArmComputeTransposeVector(const armnn::PermutationVector& perm)
362 {
363  // As ArmNN indexes are left to right and ACL indexes are right to left,
364  // the permutation vector has to be reversed and then translated into ACL axis.
365  // i.e. {1, 0, 2, 3} --> {3, 2, 0, 1} --> {0, 1, 3, 2}
366 
367  // Below an example of how the ArmNN and ACL index format work:
368  // ArmNN Format:
369  // Input Shape {1, 10, 20, 30}
370  // Permutation Vector {1, 0, 2, 3}
371  // Output Shape {10, 1, 20, 30}
372  // dim "1" of input goes into index 0 of the output ([ 10, X, X, X])
373  // dim "0" of input goes into index 1 of the output ([ 10, 1, X, X ])
374  // dim "2" of input goes into index 2 of the output ([ 10, 1, 20, X ])
375  // dim "3" of input goes into index 3 of the output ([ 10, 1, 20, 30 ])
376  // ACL Format:
377  // Input Shape {30, 20, 10, 1}
378  // Permutation Vector {0, 1, 3, 2}
379  // Output Shape {30, 20, 1, 10}
380  // dim "0" of input goes into index 0 of the output ([ 30, X, X, X])
381  // dim "1" of input goes into index 1 of the output ([ 30, 20, X, X ])
382  // dim "3" of input goes into index 2 of the output ([ 30, 20, 1, X ])
383  // dim "2" of input goes into index 3 of the output ([ 30, 20, 1, 10 ])
384 
385  arm_compute::PermutationVector aclPerm;
386  auto rank = perm.GetSize();
387 
388  // Reverse the order. i.e. {1, 0, 2, 3} --> {3, 2, 0, 1}
389  std::vector<unsigned int> reversedPerm;
390  reversedPerm.reserve(rank);
391  for (unsigned int i = rank; i > 0; --i)
392  {
393  reversedPerm.push_back(perm[i-1]);
394  }
395 
396  // Translate from Arm NN axis to ACL axis. i.e. {3, 2, 0, 1} --> {0, 1, 3, 2}
397  for (unsigned int i = 0; i < rank; ++i)
398  {
399  auto aclAxis = rank - 1 - reversedPerm[i];
400  aclPerm.set(i, aclAxis);
401  }
402  return aclPerm;
403 }
404 
405 arm_compute::Size2D BuildArmComputeSize2D(const unsigned int width, const unsigned int height)
406 {
407  return arm_compute::Size2D(width, height);
408 }
409 
410 arm_compute::PixelValue GetPixelValue(const arm_compute::ITensorInfo* tensorInfo, float value)
411 {
412  switch (tensorInfo->data_type())
413  {
414  case arm_compute::DataType::F16:
415  {
416  arm_compute::PixelValue pixelValue = arm_compute::PixelValue(static_cast<Half>(value));
417  if (isinf(pixelValue.get<Half>())) {
418  throw InvalidArgumentException("Under/Overflow converting float value [" + std::to_string(value) +
419  "] to fp16: [" + std::to_string(pixelValue.get<Half>()) + "]");
420  }
421  return pixelValue;
422  }
423  case arm_compute::DataType::F32:
424  return arm_compute::PixelValue(value);
425  case arm_compute::DataType::QASYMM8:
426  return arm_compute::PixelValue(static_cast<uint8_t>(value));
427  case arm_compute::DataType::QSYMM16:
428  return arm_compute::PixelValue(static_cast<int16_t>(value));
429  case arm_compute::DataType::QSYMM8:
430  case arm_compute::DataType::QASYMM8_SIGNED:
431  case arm_compute::DataType::QSYMM8_PER_CHANNEL:
432  return arm_compute::PixelValue(static_cast<int8_t>(value));
433  case arm_compute::DataType::S32:
434  return arm_compute::PixelValue(static_cast<int32_t>(value));
435  default:
436  throw InvalidArgumentException("Unsupported DataType: [" +
437  std::to_string(static_cast<int>(tensorInfo->data_type())) + "]");
438  }
439 }
440 
441 unsigned int ComputeDepthwiseConv2dDepthMultiplier(armnn::DataLayout layout,
442  const arm_compute::TensorShape& weightsShape,
443  const arm_compute::TensorShape& inputShape)
444 {
445  unsigned int depthMultiplier;
446  if (layout == armnn::DataLayout::NHWC)
447  {
448  depthMultiplier = static_cast<uint32_t>(weightsShape[0]) / static_cast<uint32_t>(inputShape[0]);
449  }
450  else if (layout == armnn::DataLayout::NCHW)
451  {
452  depthMultiplier = static_cast<uint32_t>(weightsShape[2]) / static_cast<uint32_t>(inputShape[2]);
453  }
454  else
455  {
456  throw InvalidArgumentException(fmt::format("Unknown data layout for tensor conversion: {}",
457  GetDataLayoutName(layout)));
458  }
459  return depthMultiplier;
460 }
461 
462 arm_compute::ScatterInfo BuildArmComputeScatterInfo(const ScatterNdDescriptor& descriptor)
463 {
464  arm_compute::ScatterFunction scatterFunction;
465  switch(descriptor.m_Function)
466  {
468  scatterFunction = arm_compute::ScatterFunction::Update;
469  break;
471  scatterFunction = arm_compute::ScatterFunction::Add;
472  break;
474  scatterFunction = arm_compute::ScatterFunction::Sub;
475  break;
477  scatterFunction = arm_compute::ScatterFunction::Max;
478  break;
480  scatterFunction = arm_compute::ScatterFunction::Min;
481  break;
482  default: throw InvalidArgumentException("Unknown ArmNN::ScatterNd Function: [" +
483  std::to_string(static_cast<int>(descriptor.m_Function)) + "]");
484  }
485 
486  return arm_compute::ScatterInfo(scatterFunction, !descriptor.m_InputEnabled);
487 }
488 } // namespace armcomputetensorutils
489 } // namespace armnn
armnn::DataType::Boolean
@ Boolean
armnn::ScatterNdFunction::Min
@ Min
armnn::DataLayout::NCDHW
@ NCDHW
armnn::DataLayout
DataLayout
Definition: Types.hpp:62
Descriptors.hpp
armnn::ConvertOutputShapeRoundingToAclDimensionRoundingType
arm_compute::DimensionRoundingType ConvertOutputShapeRoundingToAclDimensionRoundingType(OutputShapeRounding rounding)
Definition: ArmComputeUtils.hpp:168
armnn::TensorInfo::GetQuantizationScales
std::vector< float > GetQuantizationScales() const
Definition: Tensor.cpp:451
armnn::ScatterNdFunction::Sub
@ Sub
armnn::DataLayout::NHWC
@ NHWC
armnn::TensorInfo
Definition: Tensor.hpp:152
armnn::DataType::Float32
@ Float32
armnn::GetDataLayoutName
constexpr const char * GetDataLayoutName(DataLayout dataLayout)
Definition: TypesUtils.hpp:253
armnn::DataType::QAsymmU8
@ QAsymmU8
armnn::DataType::QSymmS8
@ QSymmS8
armnn::Half
half_float::half Half
Definition: Half.hpp:22
armnn::Coordinates
std::array< unsigned int, MaxNumOfTensorDimensions > Coordinates
Definition: InternalTypes.hpp:15
armnn::DataType::QSymmS16
@ QSymmS16
armnn::DataType::BFloat16
@ BFloat16
armnn::DataLayout::NDHWC
@ NDHWC
armnn::TensorShape
Definition: Tensor.hpp:20
armnn::DataType::Float16
@ Float16
armnn::TensorShape::GetNumDimensions
unsigned int GetNumDimensions() const
Function that returns the tensor rank.
Definition: Tensor.cpp:174
armnn::PaddingMethod::Exclude
@ Exclude
The padding fields don't count and are ignored.
armnn::TensorInfo::HasMultipleQuantizationScales
bool HasMultipleQuantizationScales() const
Definition: Tensor.hpp:203
armnn::DataType
DataType
Definition: Types.hpp:48
ArmComputeUtils.hpp
armnn::PermutationVector
Definition: Types.hpp:314
armnn::ScatterNdFunction::Add
@ Add
armnn::TensorInfo::GetDataType
DataType GetDataType() const
Definition: Tensor.hpp:200
armnn::DataType::Signed32
@ Signed32
armnn::DataType::QAsymmS8
@ QAsymmS8
armnn::PermutationVector::GetSize
SizeType GetSize() const
Definition: Types.hpp:357
armnn::TensorInfo::GetShape
const TensorShape & GetShape() const
Definition: Tensor.hpp:193
armnn::ScatterNdFunction::Update
@ Update
armnnDeserializer::Pooling3dDescriptor
const armnnSerializer::Pooling3dDescriptor * Pooling3dDescriptor
Definition: Deserializer.hpp:22
armnn::ScatterNdFunction::Max
@ Max
Exceptions.hpp
armnn
Copyright (c) 2021 ARM Limited and Contributors.
Definition: 01_00_quick_start.dox:6
armnn::ConvertNormalizationAlgorithmChannelToAclNormType
arm_compute::NormType ConvertNormalizationAlgorithmChannelToAclNormType(NormalizationAlgorithmChannel channelType)
Definition: ArmComputeUtils.hpp:182
ArmComputeTensorUtils.hpp
armnn::DataType::Signed64
@ Signed64
armnnDeserializer::Pooling2dDescriptor
const armnnSerializer::Pooling2dDescriptor * Pooling2dDescriptor
Definition: Deserializer.hpp:21
armnn::DataLayout::NCHW
@ NCHW
armnn::ConvertPoolingAlgorithmToAclPoolingType
arm_compute::PoolingType ConvertPoolingAlgorithmToAclPoolingType(PoolingAlgorithm poolingAlgorithm)
Definition: ArmComputeUtils.hpp:155