ArmNN
 22.11
ConversionUtils.hpp
Go to the documentation of this file.
1 //
2 // Copyright © 2022 Arm Ltd and Contributors. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 
6 #pragma once
7 
8 #include "CanonicalUtils.hpp"
9 
10 #include <armnn/ArmNN.hpp>
11 #include <armnn/BackendHelper.hpp>
12 #include <armnn/utility/Assert.hpp>
15 
17 #include <armnnUtils/Transpose.hpp>
18 
19 #include <ActivationFunctor.h>
20 #include <CpuExecutor.h>
21 #include <OperationsUtils.h>
22 
24 
25 #include <log/log.h>
26 #include <vector>
27 
28 inline const android::nn::Model::Subgraph& getMainModel(const android::nn::Model& model) { return model.main; }
29 
30 namespace armnn_driver
31 {
32 
33 ///
34 /// Helper classes
35 ///
36 
37 #include <nnapi/OperandTypes.h>
38 #include <nnapi/Result.h>
39 #include <nnapi/TypeUtils.h>
40 #include <nnapi/Types.h>
41 #include <nnapi/Validation.h>
42 
45 using OperandLifeTime = ::android::nn::Operand::LifeTime;
50 
52 {
53  ConversionData(const std::vector<armnn::BackendId>& backends)
54  : m_Backends(backends)
55  , m_Network(nullptr, nullptr)
57  {}
58 
59  const std::vector<armnn::BackendId> m_Backends;
61  std::vector<armnn::IOutputSlot*> m_OutputSlotForOperand;
62  std::vector<::android::nn::RunTimePoolInfo> m_MemPools;
64 };
65 
67 {
68 public:
70  LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo);
71 
72  bool IsValid() const;
73 
74  void Connect(armnn::IInputSlot& inputSlot);
75 
76  void Disconnect(armnn::IInputSlot& inputSlot);
77 
78  const armnn::TensorInfo& GetTensorInfo() const;
79 
80  void SanitizeQuantizationScale(LayerInputHandle& weight, LayerInputHandle& input);
81 
82  armnn::IOutputSlot* GetOutputSlot() const;
83 
84 private:
85  armnn::IOutputSlot* m_OutputSlot;
86  bool m_Valid;
87  armnn::TensorInfo m_TensorInfo;
88 };
89 
91 {
92 public:
93  // Creates an invalid tensor pin (can be used to signal errors)
94  // The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid
95  ConstTensorPin(bool optional = false);
96 
97  // @param tensorInfo TensorInfo associated with the tensor.
98  // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
99  // the model being converted.
100  // @param numBytes Number of bytes for the tensor data.
101  ConstTensorPin(armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
102  const armnn::PermutationVector& mappings);
103 
104  ConstTensorPin(const ConstTensorPin& other) = delete;
105  ConstTensorPin(ConstTensorPin&& other) = default;
106 
107  bool IsValid() const;
108  bool IsOptional() const;
109 
110  const armnn::ConstTensor& GetConstTensor() const;
111  const armnn::ConstTensor* GetConstTensorPtr() const;
112 
113 private:
114  armnn::ConstTensor m_ConstTensor;
115 
116  // Owned memory for swizzled tensor data, only required if the tensor needed
117  // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
118  // the pools associated with the model being converted.
119  std::vector<uint8_t> m_SwizzledTensorData;
120 
121  // optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given
122  bool m_Optional;
123 };
124 
126 {
127  Success,
130 };
131 
132 } // namespace armnn_driver
133 
134 ///
135 /// Utility functions
136 ///
137 
138 namespace
139 {
140 using namespace armnn_driver;
141 
142 // Convenience function to log the reason for failing to convert a model.
143 // @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
144 template<class... Args>
145 static bool Fail(const char* formatStr, Args&&... args)
146 {
147  ALOGD(formatStr, std::forward<Args>(args)...);
148  return false;
149 }
150 
151 // Convenience macro to call an Is*Supported function and log caller name together with reason for lack of support.
152 // Called as: FORWARD_LAYER_SUPPORT_FUNC(__func__, Is*Supported, backends, a, b, c, d, e)
153 #define FORWARD_LAYER_SUPPORT_FUNC(funcName, func, backends, supported, ...) \
154 try \
155 { \
156  for (auto&& backendId : backends) \
157  { \
158  auto layerSupportObject = armnn::GetILayerSupportByBackendId(backendId); \
159  if (layerSupportObject.IsBackendRegistered()) \
160  { \
161  std::string reasonIfUnsupported; \
162  supported = \
163  layerSupportObject.func(__VA_ARGS__, armnn::Optional<std::string&>(reasonIfUnsupported)); \
164  if (supported) \
165  { \
166  break; \
167  } \
168  else \
169  { \
170  if (reasonIfUnsupported.size() > 0) \
171  { \
172  VLOG(DRIVER) << funcName << ": not supported by armnn: " << reasonIfUnsupported.c_str(); \
173  } \
174  else \
175  { \
176  VLOG(DRIVER) << funcName << ": not supported by armnn"; \
177  } \
178  } \
179  } \
180  else \
181  { \
182  VLOG(DRIVER) << funcName << ": backend not registered: " << backendId.Get().c_str(); \
183  } \
184  } \
185  if (!supported) \
186  { \
187  VLOG(DRIVER) << funcName << ": not supported by any specified backend"; \
188  } \
189 } \
190 catch (const armnn::InvalidArgumentException &e) \
191 { \
192  throw armnn::InvalidArgumentException(e, "Failed to check layer support", CHECK_LOCATION()); \
193 }
194 
195 inline armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
196 {
197  return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
198 }
199 
200 // Support within the 1.3 driver for specific tensor data types
201 inline bool IsOperandTypeSupportedForTensors(OperandType type)
202 {
203  return type == OperandType::BOOL ||
204  type == OperandType::TENSOR_BOOL8 ||
205  type == OperandType::TENSOR_FLOAT16 ||
206  type == OperandType::TENSOR_FLOAT32 ||
207  type == OperandType::TENSOR_QUANT8_ASYMM ||
208  type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
209  type == OperandType::TENSOR_QUANT8_SYMM ||
210  type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
211  type == OperandType::TENSOR_QUANT16_SYMM ||
212  type == OperandType::TENSOR_INT32;
213 }
214 
215 inline bool IsBool(Operand operand)
216 {
217  return operand.type == OperandType::BOOL;
218 }
219 
220 inline bool Is12OrLaterOperand(Operand)
221 {
222  return true;
223 }
224 
225 
226 template<typename LayerHandleType>
227 armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network,
228  LayerHandleType& inputLayer,
229  armnn::TensorInfo reshapeInfo)
230 {
231  armnn::ReshapeDescriptor reshapeDescriptor;
232  reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
233 
234  armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor);
235  ARMNN_ASSERT(reshapeLayer != nullptr);
236 
237  // Attach the input layer to the reshape layer
238  inputLayer.Connect(reshapeLayer->GetInputSlot(0));
239  reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo);
240 
241  return *reshapeLayer;
242 }
243 
244 
245  armnn::TensorShape FlattenFullyConnectedInput(const armnn::TensorShape& inputShape,
246  const armnn::TensorShape& weightsShape)
247 {
248  if (inputShape.GetNumDimensions() > 2U)
249  {
250  unsigned int totalInputElements = inputShape.GetNumElements();
251  unsigned int inputSize = weightsShape[1];
252 
253  unsigned int batchSize = totalInputElements / inputSize;
254 
255  if(totalInputElements % batchSize != 0)
256  {
257  throw std::runtime_error("Failed to deduce tensor shape");
258  }
259 
260  return armnn::TensorShape({batchSize, inputSize});
261  }
262  else
263  {
264  return inputShape;
265  }
266 }
267 
268 inline bool VerifyFullyConnectedShapes(const armnn::TensorShape& inputShape,
269  const armnn::TensorShape& weightsShape,
270  const armnn::TensorShape& outputShape,
271  bool transposeWeightMatrix)
272 {
273  unsigned int dimIdx = transposeWeightMatrix ? 0 : 1;
274  return (inputShape[0] == outputShape[0] && weightsShape[dimIdx] == outputShape[1]);
275 }
276 
277 bool BroadcastTensor(LayerInputHandle& input0,
278  LayerInputHandle& input1,
279  armnn::IConnectableLayer* startLayer,
280  ConversionData& data)
281 {
282  ARMNN_ASSERT(startLayer != nullptr);
283 
284  const armnn::TensorInfo& inputInfo0 = input0.GetTensorInfo();
285  const armnn::TensorInfo& inputInfo1 = input1.GetTensorInfo();
286 
287  unsigned int inputDimensions0 = inputInfo0.GetNumDimensions();
288  unsigned int inputDimensions1 = inputInfo1.GetNumDimensions();
289 
290  if (inputDimensions0 == inputDimensions1)
291  {
292  // The inputs have the same number of dimensions, simply connect them to the given layer as they are
293  input0.Connect(startLayer->GetInputSlot(0));
294  input1.Connect(startLayer->GetInputSlot(1));
295 
296  return true;
297  }
298 
299  // Since the number of dimensions do not match then we need to add degenerate dimensions
300  // to the "smaller" tensor using a reshape, while keeping the order of the inputs.
301 
302  unsigned int maxInputDimensions = std::max(inputDimensions0, inputDimensions1);
303  unsigned int sizeDifference = std::abs(armnn::numeric_cast<int>(inputDimensions0) -
304  armnn::numeric_cast<int>(inputDimensions1));
305 
306  bool input0IsSmaller = inputDimensions0 < inputDimensions1;
307  LayerInputHandle& smallInputHandle = input0IsSmaller ? input0 : input1;
308  const armnn::TensorInfo& smallInfo = smallInputHandle.GetTensorInfo();
309 
310  const armnn::TensorShape& smallShape = smallInfo.GetShape();
311  std::vector<unsigned int> reshapedDimensions(maxInputDimensions, 1);
312  for (unsigned int i = sizeDifference; i < maxInputDimensions; i++)
313  {
314  reshapedDimensions[i] = smallShape[i - sizeDifference];
315  }
316 
317  armnn::TensorInfo reshapedInfo = smallInfo;
318  reshapedInfo.SetShape(armnn::TensorShape{ armnn::numeric_cast<unsigned int>(reshapedDimensions.size()),
319  reshapedDimensions.data() });
320 
321  // RehsapeDescriptor that is ignored in the IsReshapeSupported function
322  armnn::ReshapeDescriptor reshapeDescriptor;
323 
324  bool isSupported = false;
327  data.m_Backends,
328  isSupported,
329  smallInfo,
330  reshapedInfo,
331  reshapeDescriptor);
332  if (!isSupported)
333  {
334  return false;
335  }
336 
337  ARMNN_ASSERT(data.m_Network != nullptr);
338  armnn::IConnectableLayer& reshapeLayer = AddReshapeLayer(*data.m_Network, smallInputHandle, reshapedInfo);
339 
340  if (input0IsSmaller)
341  {
342  // Input0 is the "smaller" tensor, connect the reshape layer as follows:
343  //
344  // Input0 Input1
345  // | |
346  // Reshape |
347  // \ /
348  // StartLayer
349 
350  reshapeLayer.GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
351  input1.Connect(startLayer->GetInputSlot(1));
352  }
353  else
354  {
355  // Input1 is the "smaller" tensor, connect the reshape layer as follows:
356  //
357  // Input0 Input1
358  // | |
359  // | Reshape
360  // \ /
361  // StartLayer
362 
363  input0.Connect(startLayer->GetInputSlot(0));
364  reshapeLayer.GetOutputSlot(0).Connect(startLayer->GetInputSlot(1));
365  }
366 
367  return true;
368 }
369 
370 void CalcPadding(uint32_t input,
371  uint32_t kernel,
372  uint32_t stride,
373  uint32_t& outPadHead,
374  uint32_t& outPadTail,
375  PaddingScheme scheme)
376 {
377  int32_t padHead;
378  int32_t padTail;
379  calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
380  outPadHead = armnn::numeric_cast<uint32_t>(padHead);
381  outPadTail = armnn::numeric_cast<uint32_t>(padTail);
382 }
383 
384 void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t dilation, uint32_t& outPadHead,
385  uint32_t& outPadTail, ::android::nn::PaddingScheme scheme)
386 {
387  int32_t padHead;
388  int32_t padTail;
389  calculateExplicitPadding(input, stride, dilation, kernel, scheme, &padHead, &padTail);
390  outPadHead = armnn::numeric_cast<uint32_t>(padHead);
391  outPadTail = armnn::numeric_cast<uint32_t>(padTail);
392 }
393 
394 inline void CalcPaddingTransposeConv(uint32_t output, uint32_t kernel, int32_t stride, int32_t& outPadHead,
395  int32_t& outPadTail, ::android::nn::PaddingScheme scheme)
396 {
397  calculateExplicitPaddingTransposeConv(output, stride, kernel, scheme, &outPadHead, &outPadTail);
398 }
399 
400 Shape GetOperandShape(const Operand& operand)
401 {
402  Shape shape;
403  shape.type = OperandType(operand.type);
404  shape.dimensions = operand.dimensions;
405  shape.scale = operand.scale;
406  shape.offset = operand.zeroPoint;
407  return shape;
408 }
409 
410 
411 // ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
412 // what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
413 // we accept some tolerance. We don't want ArmNN itself to accept these inconsistencies as it is up to the
414 // user (us, in this case) to ensure they match.
415 void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
416  const armnn::TensorInfo& weightInfo,
417  const armnn::TensorInfo& inputInfo)
418 {
419  if (weightInfo.HasPerAxisQuantization())
420  {
421  // NOTE: Bias scale is always set to 0 for per-axis quantization and
422  // it needs to be calculated: scale[i] = input_scale * weight_scale[i]
423  auto UpdateBiasScaleValue = [&inputInfo](float biasScale) -> float
424  {
425  return biasScale * inputInfo.GetQuantizationScale();
426  };
427 
428  std::vector<float> biasScales(weightInfo.GetQuantizationScales());
429  std::transform(biasScales.begin(), biasScales.end(), biasScales.begin(), UpdateBiasScaleValue);
430 
431  biasInfo.SetQuantizationScales(biasScales);
432  // bias is expected to be a 1d tensor, set qdim=0
433  biasInfo.SetQuantizationDim(0);
434 
435  VLOG(DRIVER) << "Bias quantization params have been updated for per-axis quantization";
436  }
437  else
438  {
439  const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
440  if (biasInfo.GetQuantizationScale() != expectedBiasScale)
441  {
442  if (armnnUtils::within_percentage_tolerance(biasInfo.GetQuantizationScale(), expectedBiasScale, 1.0f))
443  {
444  VLOG(DRIVER) << "Bias quantization scale has been modified to match input * weights";
445  biasInfo.SetQuantizationScale(expectedBiasScale);
446  }
447  }
448  }
449 }
450 
451 // 4D Tensor Permutations
452 const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U });
453 const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U });
454 const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U });
455 
456 // 3D Permutation Vectors
457 const armnn::PermutationVector RotateTensorLeft({ 1U, 2U, 0U });
458 const armnn::PermutationVector RotateTensorRight({ 2U, 0U, 1U });
459 
460 template<typename OSlot>
461 armnn::IConnectableLayer& AddTransposeLayer(armnn::INetwork& network, OSlot& input,
462  const armnn::PermutationVector& mappings)
463 {
464  // Add swizzle layer
465  armnn::IConnectableLayer* const layer = network.AddTransposeLayer(mappings);
466 
467  ARMNN_ASSERT(layer != nullptr);
468 
469  // Connect input to swizzle layer
470  input.Connect(layer->GetInputSlot(0));
471 
472  // Setup swizzled output
473  const armnn::TensorInfo outInfo = armnnUtils::TransposeTensorShape(input.GetTensorInfo(), mappings);
474  layer->GetOutputSlot(0).SetTensorInfo(outInfo);
475 
476  return *layer;
477 }
478 
479 bool ValidateConcatOutputShape(const std::vector<armnn::TensorShape> & inputShapes,
480  const armnn::TensorShape & outputShape,
481  uint32_t concatDim)
482 {
483  // Validate the output shape is correct given the input shapes (which have just been validated)
484  unsigned int numDimensions = inputShapes[0].GetNumDimensions();
485  if (outputShape.GetNumDimensions() != numDimensions)
486  {
487  return Fail("%s: Output shape has wrong number of dimensions", __func__);
488  }
489 
490  unsigned int outputSizeAlongConcatenatedDimension = 0;
491  for (unsigned int i = 0; i < inputShapes.size(); i++)
492  {
493  outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
494  }
495 
496  for (unsigned int i = 0; i < numDimensions; ++i)
497  {
498  if (i == concatDim)
499  {
500  if (outputShape[i] != outputSizeAlongConcatenatedDimension)
501  {
502  return Fail(
503  "%s: Invalid output shape for dimension %d (%d != %d)",
504  __func__,
505  i,
506  outputShape[i],
507  outputSizeAlongConcatenatedDimension);
508  }
509  }
510  else
511  {
512  if (outputShape[i] != inputShapes[0][i])
513  {
514  return Fail("%s: Invalid output shape", __func__);
515  }
516  }
517  }
518 
519  return true;
520 }
521 
522 inline bool RequiresReshape(armnn::TensorShape & inputShape)
523 {
524  return inputShape.GetNumDimensions() < 3;
525 }
526 
527 inline void SwizzleInputs(armnn::INetwork& network,
528  std::vector<LayerInputHandle>& inputs,
529  std::vector<armnn::TensorShape>& inputShapes,
530  const armnn::PermutationVector& mapping)
531 {
532  if (!mapping.IsEqual(IdentityPermutation4D))
533  {
534  size_t nInputs = inputs.size();
535  for (size_t i=0; i<nInputs; ++i)
536  {
537  // add swizzle layer
538  armnn::IConnectableLayer& swizzleLayer = AddTransposeLayer(network, inputs[i], mapping);
539  auto& outputSlot = swizzleLayer.GetOutputSlot(0);
540  auto& outputInfo = outputSlot.GetTensorInfo();
541  // replace inputs with the swizzled ones
542  inputs[i] = LayerInputHandle(true, &outputSlot, outputInfo);
543  inputShapes[i] = inputs[i].GetTensorInfo().GetShape();
544  }
545  }
546 }
547 
548 bool TransposeInputTensors(ConversionData& data,
549  std::vector<LayerInputHandle>& inputs,
550  std::vector<armnn::TensorShape>& inputShapes,
551  const armnn::PermutationVector& mapping)
552 {
553  // If we have a IdentityPermutation4D or IdentityPermutation3D then we are not permuting
554  if (!mapping.IsEqual(IdentityPermutation4D) && !mapping.IsEqual(IdentityPermutation3D))
555  {
556  armnn::TensorInfo outputTransposeInfo;
557  size_t nInputs = inputs.size();
558  for (size_t i=0; i<nInputs; ++i)
559  {
560  // check permute layer
561  armnn::TransposeDescriptor transposeDesc;
562  transposeDesc.m_DimMappings = mapping;
563  outputTransposeInfo = armnnUtils::TransposeTensorShape(inputs[i].GetTensorInfo(), mapping);
564 
565  bool isSupported = false;
567  IsTransposeSupported,
568  data.m_Backends,
569  isSupported,
570  inputs[i].GetTensorInfo(),
571  outputTransposeInfo,
572  transposeDesc);
573  if (!isSupported)
574  {
575  return false;
576  }
577 
578  }
579  SwizzleInputs(*data.m_Network, inputs, inputShapes, mapping);
580  }
581  return true;
582 }
583 
584 bool CreateConcatPermutationParameters(const unsigned int numberOfDimensions,
585  int32_t & concatDimension,
586  std::pair<armnn::PermutationVector, armnn::PermutationVector> & permutationPair)
587 {
588  bool needPermute = false;
589  ARMNN_ASSERT(numberOfDimensions >= 3);
590 
591  // ArmNN uses Compute Library subtensors to perform concatenation
592  // This only works when concatenating along dimension 0, 1 or 3 for a 4-D tensor,
593  // or along dimension 0 or 2 for a 3-D tensor.
594  if (numberOfDimensions == 4 && concatDimension == 2)
595  {
596  concatDimension = 1;
597  permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2);
598  needPermute = true;
599  }
600  else if (numberOfDimensions == 3 && concatDimension == 1)
601  {
602  concatDimension = 0;
603  permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight);
604  needPermute = true;
605  }
606  // If the tensor is 3-D and the concat dimension is 2 then we don't need to permute but we do need to change the
607  // permutation identity to only have 3 dimensions
608  else if (numberOfDimensions == 3 && concatDimension == 2)
609  {
610  permutationPair = std::make_pair(IdentityPermutation3D, IdentityPermutation3D);
611  }
612  return needPermute;
613 }
614 
615 } // anonymous namespace
616 
617 namespace armnn_driver
618 {
619 using namespace android::nn;
620 
621 //// Creates an ArmNN activation layer and connects it to the given layer, if the
622 //// passed in AndroidNN activation function requires so.
623 //// @return The end layer of the sequence of layers built for the given AndroidNN
624 //// activation function or nullptr if an error occurred (e.g. unsupported activation).
625 //// Note that the end layer matches the input layer if no activation is required
626 //// (the sequence of layers has length 1).
628  ActivationFn activation,
629  armnn::IConnectableLayer* prevLayer,
630  ConversionData& data);
631 
632 
633 inline const Operand* GetInputOperand(const Operation& operation,
634  uint32_t inputIndex,
635  const Model& model,
636  bool failOnIndexOutOfBounds = true)
637 {
638  if (inputIndex >= operation.inputs.size())
639  {
640  if (failOnIndexOutOfBounds)
641  {
642  Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
643  }
644  return nullptr;
645  }
646 
647  // Model should have been validated beforehand
648  ARMNN_ASSERT(operation.inputs[inputIndex] < getMainModel(model).operands.size());
649  return &getMainModel(model).operands[operation.inputs[inputIndex]];
650 }
651 
652 inline const Operand* GetOutputOperand(const Operation& operation,
653  uint32_t outputIndex,
654  const Model& model)
655 {
656  if (outputIndex >= operation.outputs.size())
657  {
658  Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
659  return nullptr;
660  }
661 
662  // Model should have been validated beforehand
663  ARMNN_ASSERT(operation.outputs[outputIndex] < getMainModel(model).operands.size());
664 
665  return &getMainModel(model).operands[operation.outputs[outputIndex]];
666 }
667 
668 const void* GetOperandValueReadOnlyAddress(const Operand& operand,
669  const Model& model,
670  const ConversionData& data,
671  bool optional = false);
672 
673 inline bool GetOperandType(const Operation& operation,
674  uint32_t inputIndex,
675  const Model& model,
676  OperandType& type)
677 {
678  const Operand* operand = GetInputOperand(operation, inputIndex, model);
679  if (!operand)
680  {
681  return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
682  }
683 
684  type = operand->type;
685  return true;
686 }
687 
688 inline bool IsOperandConstant(const Operand& operand)
689 {
690  OperandLifeTime lifetime = operand.lifetime;
691 
692  return lifetime == OperandLifeTime::CONSTANT_COPY ||
693  lifetime == OperandLifeTime::CONSTANT_REFERENCE ||
694  lifetime == OperandLifeTime::POINTER ||
695  lifetime == OperandLifeTime::NO_VALUE;
696 }
697 
698 bool IsWeightsValid(const Operation& operation, uint32_t inputIndex, const Model& model);
699 
701  const Model& model,
702  const ConversionData& data,
703  const armnn::PermutationVector& dimensionMappings = g_DontPermute,
704  const armnn::TensorShape* overrideTensorShape = nullptr,
705  bool optional = false,
706  const armnn::DataType* overrideDataType = nullptr);
707 
709  const Operation& operation,
710  uint32_t inputIndex,
711  const Model& model,
712  const ConversionData& data,
713  const armnn::PermutationVector& dimensionMappings = g_DontPermute,
714  const armnn::TensorShape* overrideTensorShape = nullptr,
715  bool optional = false)
716 {
717  const Operand* operand = GetInputOperand(operation, inputIndex, model);
718  if (!operand)
719  {
720  Fail("%s: failed to get input operand: index=%u", __func__, inputIndex);
721  return ConstTensorPin();
722  }
723  return ConvertOperandToConstTensorPin(*operand,
724  model,
725  data,
726  dimensionMappings,
727  overrideTensorShape,
728  optional);
729 }
730 
731 template <typename OutputType>
732 bool GetInputScalar(const Operation& operation,
733  uint32_t inputIndex,
734  OperandType type,
735  OutputType& outValue,
736  const Model& model,
737  const ConversionData& data,
738  bool optional = false)
739 {
740  const Operand* operand = GetInputOperand(operation, inputIndex, model);
741  if (!optional && !operand)
742  {
743  return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
744  }
745 
746  if (!optional && operand->type != type)
747  {
748  VLOG(DRIVER) << __func__ << ": unexpected operand type: " << operand->type << " should be: " << type;
749  return false;
750  }
751 
752  if (!optional && operand->location.length != sizeof(OutputType))
753  {
754  return Fail("%s: incorrect operand location length: %i (should be %i)",
755  __func__, operand->location.length, sizeof(OutputType));
756  }
757 
758  const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
759  if (!optional && !valueAddress)
760  {
761  return Fail("%s: failed to get address for operand", __func__);
762  }
763 
764  if(!optional)
765  {
766  outValue = *(static_cast<const OutputType*>(valueAddress));
767  }
768 
769  return true;
770 }
771 
772 inline bool GetInputInt32(const Operation& operation,
773  uint32_t inputIndex,
774  int32_t& outValue,
775  const Model& model,
776  const ConversionData& data)
777 {
778  return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue, model, data);
779 }
780 
781 inline bool GetInputFloat32(const Operation& operation,
782  uint32_t inputIndex,
783  float& outValue,
784  const Model& model,
785  const ConversionData& data)
786 {
787  return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue, model, data);
788 }
789 
790 inline bool GetInputActivationFunctionImpl(const Operation& operation,
791  uint32_t inputIndex,
792  OperandType type,
793  ActivationFn& outActivationFunction,
794  const Model& model,
795  const ConversionData& data)
796 {
797  if (type != OperandType::INT32 && type != OperandType::TENSOR_INT32)
798  {
799  VLOG(DRIVER) << __func__ << ": unexpected operand type: " << type
800  << " should be OperandType::INT32 or OperandType::TENSOR_INT32";
801  return false;
802  }
803 
804  int32_t activationFunctionAsInt;
805  if (!GetInputScalar(operation, inputIndex, type, activationFunctionAsInt, model, data))
806  {
807  return Fail("%s: failed to get activation input value", __func__);
808  }
809  outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
810  return true;
811 }
812 
813 inline bool GetInputActivationFunction(const Operation& operation,
814  uint32_t inputIndex,
815  ActivationFn& outActivationFunction,
816  const Model& model,
817  const ConversionData& data)
818 {
819  return GetInputActivationFunctionImpl(operation,
820  inputIndex,
821  OperandType::INT32,
822  outActivationFunction,
823  model,
824  data);
825 }
826 
827 inline bool GetInputActivationFunctionFromTensor(const Operation& operation,
828  uint32_t inputIndex,
829  ActivationFn& outActivationFunction,
830  const Model& model,
831  const ConversionData& data)
832 {
833  // This only accepts a 1-D tensor of size 1
834  return GetInputActivationFunctionImpl(operation,
835  inputIndex,
836  OperandType::INT32,
837  outActivationFunction,
838  model,
839  data);
840 }
841 
842 
843 inline bool GetOptionalInputActivation(const Operation& operation,
844  uint32_t inputIndex,
845  ActivationFn& activationFunction,
846  const Model& model,
847  const ConversionData& data)
848 {
849  if (operation.inputs.size() <= inputIndex)
850  {
851  activationFunction = ActivationFn::kActivationNone;
852  }
853  else
854  {
855  if (!GetInputActivationFunction(operation, inputIndex, activationFunction, model, data))
856  {
857  return Fail("%s: Operation has invalid inputs", __func__);
858  }
859  }
860  return true;
861 }
862 
863 template<typename ConvolutionDescriptor>
865  uint32_t dilationXIndex,
866  ConvolutionDescriptor& descriptor,
867  const Model& model,
868  const ConversionData& data)
869 {
870  bool success = true;
871  if (operation.inputs.size() >= dilationXIndex + 2)
872  {
873  success &= GetInputScalar(operation,
874  dilationXIndex,
875  OperandType::INT32,
876  descriptor.m_DilationX,
877  model,
878  data);
879  success &= GetInputScalar(operation,
880  dilationXIndex + 1,
881  OperandType::INT32,
882  descriptor.m_DilationY,
883  model,
884  data);
885  }
886 
887  return success;
888 }
889 
890 inline bool GetOptionalBool(const Operation& operation,
891  uint32_t inputIndex,
892  const Model& model,
893  const ConversionData& data)
894 {
895  const Operand* operand = GetInputOperand(operation, inputIndex, model);
896  if (!operand)
897  {
898  return false;
899  }
900 
901  if (!IsBool(*operand))
902  {
903  return false;
904  }
905 
906  const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
907  if (!valueAddress)
908  {
909  return false;
910  }
911 
912  return *(static_cast<const bool*>(valueAddress));
913 }
914 
915 bool GetTensorInt32Values(const Operand& operand,
916  std::vector<int32_t>& outValues,
917  const Model& model,
918  const ConversionData& data);
919 
920 bool GetInputPaddingScheme(const Operation& operation,
921  uint32_t inputIndex,
922  PaddingScheme& outPaddingScheme,
923  const Model& model,
924  const ConversionData& data);
925 
927  uint32_t inputIndex,
928  const Model& model,
929  ConversionData& data,
930  const armnn::PermutationVector& dimensionMappings = g_DontPermute,
931  const LayerInputHandle* inputHandle = nullptr);
932 
933 bool SetupAndTrackLayerOutputSlot(const Operation& operation,
934  uint32_t operationOutputIndex,
936  uint32_t layerOutputIndex,
937  const Model& model,
938  ConversionData& data,
939  const armnn::TensorInfo* overrideOutputInfo = nullptr,
940  const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc = nullptr,
941  const ActivationFn& activationFunction = ActivationFn::kActivationNone,
942  bool inferOutputShapes = false);
943 
945  uint32_t inputIndex,
946  const Model& model,
947  ConversionData& data);
948 
950  const Operation& operation,
951  uint32_t outputIndex,
953  const Model& model,
954  ConversionData& data,
955  const armnn::TensorInfo* overrideOutputInfo = nullptr,
956  const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc = nullptr,
957  const ActivationFn& activationFunction = ActivationFn::kActivationNone)
958 {
959  return SetupAndTrackLayerOutputSlot(operation,
960  outputIndex,
961  layer,
962  outputIndex,
963  model,
964  data,
965  overrideOutputInfo,
966  validateFunc,
967  activationFunction);
968 }
969 
970 bool ConvertToActivation(const Operation& operation,
971  const char* operationName,
972  const armnn::ActivationDescriptor& activationDesc,
973  const Model& model,
974  ConversionData& data);
975 
976 bool ConvertPaddings(const Operation& operation,
977  const Model& model,
978  ConversionData& data,
979  unsigned int rank,
980  armnn::PadDescriptor& padDescriptor);
981 bool ConvertReduce(const Operation& operation,
982  const Model& model,
983  ConversionData& data,
984  armnn::ReduceOperation reduceOperation);
985 
986 bool ConvertPooling2d(const Operation& operation,
987  const char* operationName,
988  armnn::PoolingAlgorithm poolType,
989  const Model& model,
990  ConversionData& data);
991 
992 inline bool IsQSymm8(const Operand& operand)
993 {
994  return operand.type == OperandType::TENSOR_QUANT8_SYMM;
995 }
996 
998 {
999  SUCCESS,
1000  NOT_REQUIRED,
1002 };
1003 
1004 using DequantizeResult = std::tuple<std::unique_ptr<float[]>, size_t, armnn::TensorInfo, DequantizeStatus>;
1005 
1006 DequantizeResult DequantizeIfRequired(size_t operand_index,
1007  const Operation& operation,
1008  const Model& model,
1009  const ConversionData& data);
1010 
1012  const Model& model,
1013  const ConversionData& data,
1014  size_t operandIndex,
1015  bool optional = false);
1016 
1017 bool IsConnectedToDequantize(armnn::IOutputSlot* ioutputSlot);
1018 
1019 } // namespace armnn_driver
unsigned int GetNumElements() const
Function that calculates the tensor elements by multiplying all dimension size which are Specified...
Definition: Tensor.cpp:181
const Operand * GetOutputOperand(const Operation &operation, uint32_t outputIndex, const Model &model)
std::vector<::android::nn::RunTimePoolInfo > m_MemPools
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:68
bool IsQSymm8(const Operand &operand)
std::vector< armnn::IOutputSlot * > m_OutputSlotForOperand
DataLayout
Definition: Types.hpp:62
const TensorShape & GetShape() const
Definition: Tensor.hpp:191
A ReshapeDescriptor for the ReshapeLayer.
::android::nn::ErrorStatus ErrorStatus
const std::vector< armnn::BackendId > m_Backends
bool GetInputActivationFunction(const Operation &operation, uint32_t inputIndex, ActivationFn &outActivationFunction, const Model &model, const ConversionData &data)
bool HasPerAxisQuantization() const
Definition: Tensor.cpp:446
bool ConvertReduce(const Operation &operation, const Model &model, ConversionData &data, armnn::ReduceOperation reduceOperation)
::android::nn::Operation Operation
bool GetOptionalBool(const Operation &operation, uint32_t inputIndex, const Model &model, const ConversionData &data)
ConstTensorPin ConvertOperandToConstTensorPin(const Operand &operand, const Model &model, const ConversionData &data, const armnn::PermutationVector &dimensionMappings, const armnn::TensorShape *overrideTensorShape, bool optional, const armnn::DataType *overrideDataType)
Main network class which provides the interface for building up a neural network. ...
Definition: INetwork.hpp:261
void Connect(armnn::IInputSlot &inputSlot)
bool GetOperandType(const Operation &operation, uint32_t inputIndex, const Model &model, OperandType &type)
PoolingAlgorithm
Definition: Types.hpp:136
bool GetOptionalInputActivation(const Operation &operation, uint32_t inputIndex, ActivationFn &activationFunction, const Model &model, const ConversionData &data)
std::vector< float > GetQuantizationScales() const
Definition: Tensor.cpp:451
::android::nn::OperandType OperandType
bool GetInputActivationFunctionImpl(const Operation &operation, uint32_t inputIndex, OperandType type, ActivationFn &outActivationFunction, const Model &model, const ConversionData &data)
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
void SetShape(const TensorShape &newShape)
Definition: Tensor.hpp:193
const armnn::PermutationVector g_DontPermute
const Operand * GetInputOperand(const Operation &operation, uint32_t inputIndex, const Model &model, bool failOnIndexOutOfBounds=true)
TensorShape m_TargetShape
Target shape value.
A PadDescriptor for the PadLayer.
bool ConvertPooling2d(const Operation &operation, const char *operationName, armnn::PoolingAlgorithm poolType, const Model &model, ConversionData &data)
ConstTensorPin ConvertOperationInputToConstTensorPin(const Operation &operation, uint32_t inputIndex, const Model &model, const ConversionData &data, const armnn::PermutationVector &dimensionMappings=g_DontPermute, const armnn::TensorShape *overrideTensorShape=nullptr, bool optional=false)
ReduceOperation
Definition: Types.hpp:143
::android::nn::Model Model
Helper classes.
DataType
Definition: Types.hpp:48
bool GetInputScalar(const Operation &operation, uint32_t inputIndex, OperandType type, OutputType &outValue, const Model &model, const ConversionData &data, bool optional=false)
An output connection slot for a layer.
Definition: INetwork.hpp:41
const armnn::TensorInfo & GetTensorInfo() const
armnn::DataLayout OptionalDataLayout(const Operation &operation, uint32_t inputIndex, const Model &model, ConversionData &data)
float GetQuantizationScale() const
Definition: Tensor.cpp:461
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
bool ConvertPaddings(const Operation &operation, const Model &model, ConversionData &data, unsigned int rank, armnn::PadDescriptor &padDescriptor)
const void * GetOperandValueReadOnlyAddress(const Operand &operand, const Model &model, const ConversionData &data, bool optional)
void SetQuantizationScale(float scale)
Definition: Tensor.cpp:473
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:36
bool GetTensorInt32Values(const Operand &operand, std::vector< int32_t > &outValues, const Model &model, const ConversionData &data)
bool GetInputActivationFunctionFromTensor(const Operation &operation, uint32_t inputIndex, ActivationFn &outActivationFunction, const Model &model, const ConversionData &data)
ConversionData(const std::vector< armnn::BackendId > &backends)
void SetQuantizationDim(const Optional< unsigned int > &quantizationDim)
Definition: Tensor.cpp:499
bool IsWeightsValid(const Operation &operation, uint32_t inputIndex, const Model &model)
Utility functions.
std::tuple< std::unique_ptr< float[]>, size_t, armnn::TensorInfo, DequantizeStatus > DequantizeResult
bool IsEqual(const PermutationVector &other) const
Definition: Types.hpp:347
LayerInputHandle ConvertToLayerInputHandle(const Operation &operation, uint32_t inputIndex, const Model &model, ConversionData &data, const armnn::PermutationVector &dimensionMappings, const LayerInputHandle *inputHandle)
::android::nn::Operand Operand
IConnectableLayer * AddReshapeLayer(const ReshapeDescriptor &reshapeDescriptor, const char *name=nullptr)
Adds a reshape layer to the network.
Definition: Network.cpp:274
bool SetupAndTrackLayerOutputSlot(const Operation &operation, uint32_t operationOutputIndex, armnn::IConnectableLayer &layer, uint32_t layerOutputIndex, const Model &model, ConversionData &data, const armnn::TensorInfo *overrideOutputInfo, const std::function< void(const armnn::TensorInfo &, bool &)> &validateFunc, const ActivationFn &activationFunction, bool inferOutputShapes)
const android::nn::Model::Subgraph & getMainModel(const android::nn::Model &model)
bool GetInputPaddingScheme(const Operation &operation, uint32_t inputIndex, PaddingScheme &outPaddingScheme, const Model &model, const ConversionData &data)
bool ConvertToActivation(const Operation &operation, const char *operationName, const armnn::ActivationDescriptor &activationDesc, const Model &model, ConversionData &data)
ConstTensorPin DequantizeAndMakeConstTensorPin(const Operation &operation, const Model &model, const ConversionData &data, size_t operandIndex, bool optional)
#define FORWARD_LAYER_SUPPORT_FUNC(funcName, func, backends, supported,...)
bool GetInputInt32(const Operation &operation, uint32_t inputIndex, int32_t &outValue, const Model &model, const ConversionData &data)
unsigned int GetNumDimensions() const
Function that returns the tensor rank.
Definition: Tensor.cpp:174
bool IsConnectedToDequantize(armnn::IOutputSlot *ioutputSlot)
::android::nn::Operand::LifeTime OperandLifeTime
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
armnn::IConnectableLayer * ProcessActivation(const armnn::TensorInfo &tensorInfo, ActivationFn activation, armnn::IConnectableLayer *prevLayer, ConversionData &data)
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
Definition: NumericCast.hpp:35
armnn::TensorInfo GetTensorInfo(unsigned int numberOfBatches, unsigned int numberOfChannels, unsigned int height, unsigned int width, const armnn::DataLayout dataLayout, const armnn::DataType dataType)
Definition: TensorUtils.cpp:38
bool IsOperandConstant(const Operand &operand)
A TransposeDescriptor for the TransposeLayer.
virtual const TensorInfo & GetTensorInfo() const =0
::android::nn::OperationType OperationType
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
DequantizeResult DequantizeIfRequired(size_t operand_index, const Operation &operation, const Model &model, const ConversionData &data)
void Connect(armnn::IConnectableLayer *from, armnn::IConnectableLayer *to, const armnn::TensorInfo &tensorInfo, unsigned int fromIndex, unsigned int toIndex)
Definition: TestUtils.cpp:14
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:253
armnn::TensorShape TransposeTensorShape(const armnn::TensorShape &srcShape, const armnn::PermutationVector &mappings)
Definition: Transpose.cpp:98
bool within_percentage_tolerance(float a, float b, float tolerancePercent=1.0f)
Compare two floats and return true if their values are within a specified tolerance of each other...
virtual int Connect(IInputSlot &destination)=0
bool GetOptionalConvolutionDilationParams(const Operation &operation, uint32_t dilationXIndex, ConvolutionDescriptor &descriptor, const Model &model, const ConversionData &data)
void SetQuantizationScales(const std::vector< float > &scales)
Definition: Tensor.cpp:456
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:195
Helper classes.
Definition: ArmnnDevice.cpp:37
PermutationVector m_DimMappings
Indicates how to translate tensor elements from a given source into the target destination, when source and target potentially have different memory layouts e.g.
An input connection slot for a layer.
Definition: INetwork.hpp:25
bool GetInputFloat32(const Operation &operation, uint32_t inputIndex, float &outValue, const Model &model, const ConversionData &data)
IConnectableLayer * AddTransposeLayer(const TransposeDescriptor &transposeDescriptor, const char *name=nullptr)
Adds a transpose layer to the network.
Definition: Network.cpp:379
bool IsReshapeSupported(const BackendId &backend, const TensorInfo &input, const ReshapeDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.