ArmNN
 22.11
ConversionUtils.cpp
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 #include "ConversionUtils.hpp"
7 #include <armnnUtils/Permute.hpp>
8 
9 ///
10 /// Helper classes
11 ///
12 
13 namespace armnn_driver
14 {
15 
17  : m_OutputSlot(nullptr)
18  , m_Valid(false)
19 {}
20 
22  : m_OutputSlot(outputSlot)
23  , m_Valid(valid)
24  , m_TensorInfo(tensorInfo)
25 {}
26 
28 {
29  return m_Valid;
30 }
31 
33 {
35  if (m_OutputSlot)
36  {
37  m_OutputSlot->Connect(inputSlot);
38  }
39 }
40 
42 {
44  if (m_OutputSlot)
45  {
46  m_OutputSlot->Disconnect(inputSlot);
47  }
48 }
49 
51 {
52  return m_TensorInfo;
53 }
54 
56 {
57  if (m_OutputSlot)
58  {
59  armnn::TensorInfo weightInfo = weight.GetTensorInfo();
60  armnn::TensorInfo inputInfo = input.GetTensorInfo();
61  armnn::TensorInfo biasInfo = GetTensorInfo();
62 
63  SanitizeBiasQuantizationScale(biasInfo, weightInfo, inputInfo);
64 
65  m_TensorInfo = biasInfo;
66  m_OutputSlot->SetTensorInfo(biasInfo);
67  }
68 }
69 
71 {
72  return m_OutputSlot;
73 }
74 
76  : m_Optional(optional)
77 {}
78 
80  const void* valueStart,
81  uint32_t numBytes,
82  const armnn::PermutationVector& mappings)
83  : m_Optional(false)
84 {
85  armnn::IgnoreUnused(numBytes);
86  if (tensorInfo.GetNumBytes() != numBytes)
87  {
88  VLOG(DRIVER) << "The size of ConstTensor does not match its TensorInfo.";
89  }
90 
91  const bool needsSwizzling = (mappings.GetSize() > 0);
92  if (needsSwizzling)
93  {
94  m_SwizzledTensorData.resize(tensorInfo.GetNumBytes());
95  SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings);
96 
97  m_ConstTensor = armnn::ConstTensor(tensorInfo, m_SwizzledTensorData.data());
98  }
99  else
100  {
101  m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart);
102  }
103 }
104 
106 {
107  return m_ConstTensor.GetMemoryArea() != nullptr;
108 }
109 
111 {
112  return m_Optional;
113 }
114 
116 {
117  return m_ConstTensor;
118 }
119 
121 {
122  if (IsValid() && m_ConstTensor.GetNumElements() > 0)
123  {
124  return &m_ConstTensor;
125  }
126  // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided)
127  return nullptr;
128 }
129 
130 ///
131 /// Utility functions
132 ///
133 
134 bool IsWeightsValid(const Operation& operation,
135  uint32_t inputIndex,
136  const Model& model)
137 {
138  const Operand* operand = GetInputOperand(operation, inputIndex, model);
139  if (!operand)
140  {
141  Fail("%s: failed to get input operand %i", __func__, inputIndex);
142  return false;
143  }
144 
145  if (operand->lifetime != OperandLifeTime::CONSTANT_COPY
146  && operand->lifetime != OperandLifeTime::CONSTANT_REFERENCE
147  && operand->lifetime != OperandLifeTime::NO_VALUE)
148  {
149  return false;
150  }
151  return true;
152 }
153 
155  const Model& model,
156  const ConversionData& data,
157  const armnn::PermutationVector& dimensionMappings,
158  const armnn::TensorShape* overrideTensorShape,
159  bool optional,
160  const armnn::DataType* overrideDataType)
161 {
162  if (!IsOperandTypeSupportedForTensors(operand.type))
163  {
164  VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor" << operand.type;
165  return ConstTensorPin();
166  }
167 
168  if (!optional && !IsOperandConstant(operand))
169  {
170  VLOG(DRIVER) << __func__ << ": lifetime for input tensor: r" << operand.lifetime;
171  return ConstTensorPin();
172  }
173 
174  const void* const valueStart = GetOperandValueReadOnlyAddress(operand, model, data, optional);
175  if (!valueStart)
176  {
177  if (optional)
178  {
179  // optional tensor with no values is not really an error; return it as invalid, but marked as optional
180  return ConstTensorPin(true);
181  }
182  // mandatory tensor with no values
183  Fail("%s: failed to get operand address", __func__);
184  return ConstTensorPin();
185  }
186 
187  armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
188 
189  if (overrideTensorShape)
190  {
191  tensorInfo.SetShape(*overrideTensorShape);
192  }
193 
194  if (overrideDataType)
195  {
196  tensorInfo.SetDataType(*overrideDataType);
197  }
198 
199  // Make sure isConstant flag is set.
200  tensorInfo.SetConstant();
201  return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
202 }
203 
205  uint32_t inputIndex,
206  const Model& model,
207  ConversionData& data,
208  const armnn::PermutationVector& dimensionMappings,
209  const LayerInputHandle* inputHandle)
210 {
211 
212  const Operand* operand = GetInputOperand(operation, inputIndex, model);
213  if (!operand)
214  {
215  Fail("%s: failed to get input operand %i", __func__, inputIndex);
216  return LayerInputHandle();
217  }
218 
219  if (!IsOperandTypeSupportedForTensors(operand->type))
220  {
221  VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor: " << operand->type;
222  return LayerInputHandle();
223  }
224 
225  try
226  {
227  armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
228 
229  if (IsDynamicTensor(operandTensorInfo))
230  {
231  data.m_DynamicInputsEncountered = true;
232 
233  const uint32_t operandIndex = operation.inputs[inputIndex];
234 
235  // Check if the dynamic input tensors have been inferred by one of the previous layers
236  // If not we can't support them
237  if (data.m_OutputSlotForOperand.size() >= operandIndex && data.m_OutputSlotForOperand[operandIndex])
238  {
239  operandTensorInfo = data.m_OutputSlotForOperand[operandIndex]->GetTensorInfo();
240  }
241  else
242  {
243  Fail("%s: Type 2 dynamic input tensors are not supported", __func__);
244  return LayerInputHandle();
245  }
246  }
247 
248  switch (operand->lifetime)
249  {
250  case OperandLifeTime::SUBGRAPH_INPUT:
251  {
252  // NOTE: We must check whether we can support the input tensor on at least one
253  // of the provided backends; otherwise we cannot convert the operation
254  bool isInputSupported = false;
257  data.m_Backends,
258  isInputSupported,
259  operandTensorInfo);
260 
261  if (!isInputSupported)
262  {
263  Fail("%s: unsupported input tensor", __func__);
264  return LayerInputHandle();
265  }
266 
267  [[clang::fallthrough]]; // intentional fallthrough
268  }
269  case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
270  case OperandLifeTime::SUBGRAPH_OUTPUT:
271  {
272  // The tensor is either an operand internal to the model, or a model input.
273  // It can be associated with an ArmNN output slot for an existing layer.
274 
275  // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
276  const uint32_t operandIndex = operation.inputs[inputIndex];
277  return LayerInputHandle(true, data.m_OutputSlotForOperand[operandIndex], operandTensorInfo);
278  }
279  case OperandLifeTime::CONSTANT_COPY: // intentional fallthrough
280  case OperandLifeTime::POINTER:
281  case OperandLifeTime::CONSTANT_REFERENCE:
282  {
283  auto constantTensorDataType = operandTensorInfo.GetDataType();
284  // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
285  ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand,
286  model,
287  data,
288  dimensionMappings,
289  nullptr,
290  false,
291  &constantTensorDataType);
292  if (tensorPin.IsValid())
293  {
294  bool isSupported = false;
297  data.m_Backends,
298  isSupported,
299  tensorPin.GetConstTensor().GetInfo());
300  if (!isSupported)
301  {
302  return LayerInputHandle();
303  }
304 
305  armnn::IConnectableLayer* constantLayer =
306  data.m_Network->AddConstantLayer(tensorPin.GetConstTensor());
307  armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
308  armnn::TensorInfo constantTensorInfo = tensorPin.GetConstTensor().GetInfo();
309  outputSlot.SetTensorInfo(constantTensorInfo);
310 
311  return LayerInputHandle(true, &outputSlot, constantTensorInfo);
312  }
313  else
314  {
315  Fail("%s: invalid operand tensor", __func__);
316  return LayerInputHandle();
317  }
318  break;
319  }
320  default:
321  {
322  VLOG(DRIVER) << __func__ << ": unsupported lifetime for input tensor: " << operand->lifetime;
323  return LayerInputHandle();
324  }
325  }
326  }
328  {
329  VLOG(DRIVER) << __func__ << ": Operand type: " << e.m_type << " not supported in ArmnnDriver";
330  return LayerInputHandle();
331  }
332 }
333 
334 bool ConvertPaddings(const Operation& operation,
335  const Model& model,
336  ConversionData& data,
337  unsigned int rank,
338  armnn::PadDescriptor& padDescriptor)
339 {
340  const Operand* paddingsOperand = GetInputOperand(operation, 1, model);
341  if (!paddingsOperand)
342  {
343  return Fail("%s: Could not read paddings operand", __func__);
344  }
345 
346  armnn::TensorShape paddingsOperandShape = GetTensorShapeForOperand(*paddingsOperand);
347  if (paddingsOperandShape.GetNumDimensions() != 2 || paddingsOperandShape.GetNumElements() != rank * 2)
348  {
349  return Fail("%s: Operation has invalid paddings operand: expected shape [%d, 2]", __func__, rank);
350  }
351 
352  std::vector<int32_t> paddings;
353  if (!GetTensorInt32Values(*paddingsOperand, paddings, model, data))
354  {
355  return Fail("%s: Operation has invalid or unsupported paddings operand", __func__);
356  }
357 
358  // add padding for each dimension of input tensor.
359  for (unsigned int i = 0; i < paddings.size() - 1; i += 2)
360  {
361  int paddingBeforeInput = paddings[i];
362  int paddingAfterInput = paddings[i + 1];
363 
364  if (paddingBeforeInput < 0 || paddingAfterInput < 0)
365  {
366  return Fail("%s: Operation has invalid paddings operand, invalid padding values.", __func__);
367  }
368 
369  padDescriptor.m_PadList.emplace_back((unsigned int) paddingBeforeInput, (unsigned int) paddingAfterInput);
370  }
371 
372  return true;
373 }
374 
375 
376 bool ConvertPooling2d(const Operation& operation,
377  const char* operationName,
378  armnn::PoolingAlgorithm poolType,
379  const Model& model,
380  ConversionData& data)
381 {
382 
383  VLOG(DRIVER) << "Converter::ConvertL2Pool2d()";
384 
385  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
386  if (!input.IsValid())
387  {
388  return Fail("%s: Operation Could not read input 0", operationName);
389  }
390 
391  const Operand* output = GetOutputOperand(operation, 0, model);
392  if (!output)
393  {
394  return Fail("%s: Could not read output 0", __func__);
395  }
396 
397  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
398  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
399 
401  desc.m_PoolType = poolType;
404 
405  ActivationFn activation;
406 
407  auto inputSize = operation.inputs.size();
408 
409  if (inputSize >= 10)
410  {
411  // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
412  if (!GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft, model, data) ||
413  !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight, model, data) ||
414  !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop, model, data) ||
415  !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom, model, data) ||
416  !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX, model, data) ||
417  !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY, model, data) ||
418  !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth, model, data) ||
419  !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight, model, data) ||
420  !GetInputActivationFunction(operation, 9, activation, model, data))
421  {
422  return Fail("%s: Operation has invalid inputs", operationName);
423  }
424 
425  if (Is12OrLaterOperand(*output))
426  {
427  desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data);
428  }
429  }
430  else
431  {
432  // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
433  ::android::nn::PaddingScheme scheme;
434  if (!GetInputPaddingScheme(operation, 1, scheme, model, data) ||
435  !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX, model, data) ||
436  !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY, model, data) ||
437  !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth, model, data) ||
438  !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight, model, data) ||
439  !GetInputActivationFunction(operation, 6, activation, model, data))
440  {
441  return Fail("%s: Operation has invalid inputs", operationName);
442  }
443 
444  if (Is12OrLaterOperand(*output))
445  {
446  desc.m_DataLayout = OptionalDataLayout(operation, 7, model, data);
447  }
448 
449  const armnnUtils::DataLayoutIndexed dataLayout(desc.m_DataLayout);
450  const unsigned int inputWidth = inputInfo.GetShape()[dataLayout.GetWidthIndex()];
451  const unsigned int inputHeight = inputInfo.GetShape()[dataLayout.GetHeightIndex()];
452 
453  CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
454  CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
455  }
456 
457  bool isSupported = false;
458 
459  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
460  {
463  data.m_Backends,
464  isSupported,
465  inputInfo,
466  outputInfo,
467  desc);
468 
469  };
470 
471  if(IsDynamicTensor(outputInfo))
472  {
473  isSupported = AreDynamicTensorsSupported();
474  }
475  else
476  {
477  validateFunc(outputInfo, isSupported);
478  }
479 
480  if (!isSupported)
481  {
482  return false;
483  }
484 
485  armnn::IConnectableLayer* pooling2dLayer = data.m_Network->AddPooling2dLayer(desc);
486  if (!pooling2dLayer)
487  {
488  return Fail("%s: AddPooling2dLayer failed", __func__);
489  }
490 
491  input.Connect(pooling2dLayer->GetInputSlot(0));
492 
493  if (!isSupported)
494  {
495  return false;
496  }
497 
498  return SetupAndTrackLayerOutputSlot(operation, 0, *pooling2dLayer, model,
499  data, nullptr, validateFunc, activation);
500 }
501 
502 bool ConvertReduce(const Operation& operation,
503  const Model& model,
504  ConversionData& data,
505  armnn::ReduceOperation reduceOperation)
506 {
507  armnn::ReduceDescriptor descriptor;
508  descriptor.m_ReduceOperation = reduceOperation;
509 
510  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
511  if (!input.IsValid())
512  {
513  return Fail("%s: Operation has invalid inputs", __func__);
514  }
515  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
516 
517  const Operand* output = GetOutputOperand(operation, 0, model);
518  if (!output)
519  {
520  return Fail("%s: Could not read output 0", __func__);
521  }
522  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
523 
524  const Operand* axisOperand = GetInputOperand(operation, 1, model);
525  if (!axisOperand)
526  {
527  return Fail("%s: Could not read input 1", __func__);
528  }
529  std::vector<int32_t> axis;
530  if (!GetTensorInt32Values(*axisOperand, axis, model, data))
531  {
532  return Fail("%s: Input 1 has invalid values", __func__);
533  }
534 
535  // Convert the axis to unsigned int and remove duplicates.
536  unsigned int rank = inputInfo.GetNumDimensions();
537  std::set<unsigned int> uniqueAxis;
538  std::transform(axis.begin(), axis.end(),
539  std::inserter(uniqueAxis, uniqueAxis.begin()),
540  [rank](int i) -> unsigned int { return (i + rank) % rank; });
541  descriptor.m_vAxis.assign(uniqueAxis.begin(), uniqueAxis.end());
542 
543  // Get the "keep dims" flag.
544  if (!GetInputScalar(operation, 2, OperandType::BOOL, descriptor.m_KeepDims, model, data))
545  {
546  return Fail("%s: Could not read input 2", __func__);
547  }
548 
549  bool isSupported = false;
550  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
551  {
554  data.m_Backends,
555  isSupported,
556  inputInfo,
557  outputInfo,
558  descriptor);
559  };
560 
561  if(!IsDynamicTensor(outputInfo))
562  {
563  validateFunc(outputInfo, isSupported);
564  }
565  else
566  {
567  isSupported = AreDynamicTensorsSupported();
568  }
569 
570  if (!isSupported)
571  {
572  return false;
573  }
574 
575  armnn::IConnectableLayer* const layer = data.m_Network->AddReduceLayer(descriptor);
576  assert(layer != nullptr);
577  input.Connect(layer->GetInputSlot(0));
578 
579  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
580 }
581 
582 
583 bool ConvertToActivation(const Operation& operation,
584  const char* operationName,
585  const armnn::ActivationDescriptor& activationDesc,
586  const Model& model,
587  ConversionData& data)
588 {
589  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
590  if (!input.IsValid())
591  {
592  return Fail("%s: Input 0 is invalid", operationName);
593  }
594 
595  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
596  if (!outputOperand)
597  {
598  return false;
599  }
600 
601  const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
602 
603  bool isSupported = false;
604 
605  auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
606  {
609  data.m_Backends,
610  isSupported,
611  input.GetTensorInfo(),
612  outInfo,
613  activationDesc);
614  };
615 
616  if(IsDynamicTensor(outInfo))
617  {
618  isSupported = AreDynamicTensorsSupported();
619  }
620  else
621  {
622  validateFunc(outInfo, isSupported);
623  }
624 
625  if (!isSupported)
626  {
627  return false;
628  }
629 
630  armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(activationDesc);
631  ARMNN_ASSERT(layer != nullptr);
632  input.Connect(layer->GetInputSlot(0));
633 
634  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
635 }
636 
638  const Operation& operation,
639  const Model& model,
640  const ConversionData& data)
641 {
642  const Operand* weightsOperand = GetInputOperand(operation, operand_index, model);
643  if (!weightsOperand)
644  {
645  return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::INVALID_OPERAND };
646  }
647 
648  if (IsOperandConstant(*weightsOperand))
649  {
650  // Weights are already constant
651  return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::NOT_REQUIRED };
652  }
653 
654  const size_t weightsInputIndex = operation.inputs[operand_index];
655 
656  // The weights are a non const tensor, this indicates they might be the output of a dequantize op.
657  // Iterate over the nodes and find the previous operation which should be DEQUANTIZE
658  for (uint32_t operationIdx = 0; operationIdx < getMainModel(model).operations.size(); ++operationIdx)
659  {
660  // Search for the DEQUANTIZE op which has the operand with index equal to operandIndex
661  const auto& operationIt = getMainModel(model).operations[operationIdx];
662  if (operationIt.type != OperationType::DEQUANTIZE)
663  {
664  continue;
665  }
666 
667  size_t outOpIndex = weightsInputIndex + 1;
668  for (size_t i = 0; outOpIndex != weightsInputIndex && i < operationIt.outputs.size(); ++i)
669  {
670  outOpIndex = operationIt.outputs[i];
671  }
672 
673  if (outOpIndex != weightsInputIndex)
674  {
675  continue;
676  }
677 
678  const Operand* operand = GetInputOperand(operationIt, 0, model);
679  ARMNN_ASSERT(operand);
680 
681  if (!IsQSymm8(*operand))
682  {
683  // Only supporting dequantize from QSYMM8 to FLOAT
684  break;
685  }
686 
687  // Allocate a new buffer for the dequantized data and manually dequantize
688  const void* startValue = GetOperandValueReadOnlyAddress(*operand, model, data);
689  if (!startValue)
690  {
691  // Failed to get the operand address
692  break;
693  }
694 
695  const uint8_t* quantizedBuffer = reinterpret_cast<const uint8_t*>(startValue);
696  size_t dequantizedBufferLength = operand->location.length;
697  const float quantizationScale = operand->scale;
698 
699  auto dequantizedBuffer = std::make_unique<float[]>(dequantizedBufferLength + 1);
700  for (size_t i = 0; i < dequantizedBufferLength; ++i)
701  {
702  float* dstPtr = dequantizedBuffer.get();
703  ARMNN_ASSERT(dstPtr);
704  *dstPtr++ = quantizedBuffer[i] * quantizationScale;
705  }
706 
707  // Construct tensor info for dequantized ConstTensor
708  armnn::TensorInfo tensorInfo(operand->dimensions.size(),
709  operand->dimensions.data(),
711 
712  return { std::move(dequantizedBuffer), dequantizedBufferLength * sizeof(float),
713  std::move(tensorInfo),
715  }
716 
717  return { nullptr, 0, armnn::TensorInfo() , DequantizeStatus::NOT_REQUIRED};
718 }
719 
721  const Model& model,
722  const ConversionData& data,
723  size_t operandIndex,
724  bool optional)
725 {
726  DequantizeResult dequantized = DequantizeIfRequired(operandIndex,operation, model, data);
727 
728  DequantizeStatus status = std::get<3>(dequantized);
729  switch (status)
730  {
732  {
733  // return invalid const tensor pin
734  return ConstTensorPin();
735  }
737  {
739  operation, operandIndex, model, data, g_DontPermute, nullptr, optional);
740  }
742  default:
743  {
744  return ConstTensorPin(
745  std::get<2>(dequantized), std::get<0>(dequantized).get(), std::get<1>(dequantized), g_DontPermute);
746  }
747  }
748 }
749 
750 bool GetInputPaddingScheme(const Operation& operation,
751  uint32_t inputIndex,
752  PaddingScheme& outPaddingScheme,
753  const Model& model,
754  const ConversionData& data)
755 {
756  int32_t paddingSchemeAsInt;
757  if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt, model, data))
758  {
759  return Fail("%s: failed to get padding scheme input value", __func__);
760  }
761 
762  outPaddingScheme = static_cast<::android::nn::PaddingScheme>(paddingSchemeAsInt);
763  return true;
764 }
765 
766 const void* GetOperandValueReadOnlyAddress(const Operand& operand,
767  const Model& model,
768  const ConversionData& data,
769  bool optional)
770 {
771  const void* valueStart = nullptr;
772  switch (operand.lifetime)
773  {
774  case OperandLifeTime::CONSTANT_COPY:
775  {
776  valueStart = model.operandValues.data() + operand.location.offset;
777  break;
778  }
779  case OperandLifeTime::POINTER:
780  {
781  // Pointer specified in the model
782  valueStart = std::get<const void*>(operand.location.pointer);
783  break;
784  }
785  case OperandLifeTime::CONSTANT_REFERENCE:
786  {
787  // Constant specified via a Memory object
788  valueStart = GetMemoryFromPool(operand.location, data.m_MemPools);
789  break;
790  }
791  case OperandLifeTime::NO_VALUE:
792  {
793  // An optional input tensor with no values is not an error so should not register as a fail
794  if (optional)
795  {
796  valueStart = nullptr;
797  break;
798  }
799  [[fallthrough]];
800  }
801  default:
802  {
803  VLOG(DRIVER) << __func__ << ": unsupported/invalid operand lifetime:: " << operand.lifetime;
804  valueStart = nullptr;
805  }
806  }
807 
808  return valueStart;
809 }
810 
811 bool GetTensorInt32Values(const Operand& operand,
812  std::vector<int32_t>& outValues,
813  const Model& model,
814  const ConversionData& data)
815 {
816  if (operand.type != OperandType::TENSOR_INT32)
817  {
818  VLOG(DRIVER) << __func__ << ": invalid operand type: " << operand.type;
819  return false;
820  }
821 
822  const void* startAddress = GetOperandValueReadOnlyAddress(operand, model, data);
823  if (!startAddress)
824  {
825  VLOG(DRIVER) << __func__ << ": failed to get operand address " << operand.type;
826  return false;
827  }
828 
829  // Check number of bytes is sensible
830  const uint32_t numBytes = operand.location.length;
831  if (numBytes % sizeof(int32_t) != 0)
832  {
833  return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
834  __func__, numBytes, sizeof(int32_t));
835  }
836 
837  outValues.resize(numBytes / sizeof(int32_t));
838  memcpy(outValues.data(), startAddress, numBytes);
839  return true;
840 }
841 
843  uint32_t inputIndex,
844  const Model& model,
845  ConversionData& data)
846 {
847  const Operand* operand = GetInputOperand(operation, inputIndex, model);
848  if (!operand)
849  {
851  }
852 
853  if (!IsBool(*operand))
854  {
856  }
857 
858  const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
859  if (!valueAddress)
860  {
862  }
863 
864  if (*(static_cast<const bool*>(valueAddress)))
865  {
867  }
868  else
869  {
871  }
872 }
873 
875  ActivationFn activation,
876  armnn::IConnectableLayer* prevLayer,
877  ConversionData& data)
878 {
879  ARMNN_ASSERT(prevLayer->GetNumOutputSlots() == 1);
880 
881  prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
882 
883  armnn::IConnectableLayer* activationLayer = prevLayer;
884 
885  if (activation != ActivationFn::kActivationNone)
886  {
887  armnn::ActivationDescriptor activationDesc;
888  switch (activation)
889  {
890  case ActivationFn::kActivationRelu:
891  {
893  break;
894  }
895  case ActivationFn::kActivationRelu1:
896  {
898  activationDesc.m_A = 1.0f;
899  activationDesc.m_B = -1.0f;
900  break;
901  }
902  case ActivationFn::kActivationRelu6:
903  {
905  activationDesc.m_A = 6.0f;
906  break;
907  }
908  case ActivationFn::kActivationSigmoid:
909  {
911  break;
912  }
913  case ActivationFn::kActivationTanh:
914  {
916  activationDesc.m_A = 1.0f;
917  activationDesc.m_B = 1.0f;
918  break;
919  }
920  default:
921  {
922  Fail("%s: Invalid activation enum value %i", __func__, activation);
923  return nullptr;
924  }
925  }
926 
927  bool isSupported = false;
930  data.m_Backends,
931  isSupported,
932  prevLayer->GetOutputSlot(0).GetTensorInfo(),
933  tensorInfo,
934  activationDesc);
935  if (!isSupported)
936  {
937  return nullptr;
938  }
939 
940  activationLayer = data.m_Network->AddActivationLayer(activationDesc);
941 
942  prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0));
943  activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
944  }
945 
946  return activationLayer;
947 }
948 
950  uint32_t operationOutputIndex,
952  uint32_t layerOutputIndex,
953  const Model& model,
954  ConversionData& data,
955  const armnn::TensorInfo* overrideOutputInfo,
956  const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc,
957  const ActivationFn& activationFunction,
958  bool inferOutputShapes)
959 {
960  const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex, model);
961  if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots()))
962  {
963  return false;
964  }
965 
966  armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex);
967  if (overrideOutputInfo == nullptr)
968  {
969  outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
970  }
971  else
972  {
973  outputSlot.SetTensorInfo(*overrideOutputInfo);
974  }
975 
976  bool isSupported = false;
977  if (validateFunc && (IsDynamicTensor(outputSlot.GetTensorInfo()) || inferOutputShapes))
978  {
979  // Type one dynamic tensors require the previous layer's output shape for inference
980  for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex)
981  {
982  if(!layer.GetInputSlot(inputSlotIndex).GetConnection())
983  {
984  return false;
985  }
986  }
987  // IsTensorInfoSet will infer the dynamic output shape
988  outputSlot.IsTensorInfoSet();
989  // Once the shape is inferred we can validate it
990  validateFunc(outputSlot.GetTensorInfo(), isSupported);
991 
992  if(!isSupported)
993  {
994  for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex)
995  {
996  layer.GetInputSlot(inputSlotIndex).GetConnection()->Disconnect(layer.GetInputSlot(inputSlotIndex));
997  }
998  return false;
999  }
1000  }
1001 
1002  const uint32_t operandIndex = operation.outputs[operationOutputIndex];
1003 
1004  if (activationFunction != ActivationFn::kActivationNone)
1005  {
1006  const armnn::TensorInfo& activationOutputInfo = outputSlot.GetTensorInfo();
1007  armnn::IConnectableLayer* const endLayer = ProcessActivation(activationOutputInfo, activationFunction,
1008  &layer, data);
1009 
1010  if (!endLayer)
1011  {
1012  return Fail("%s: ProcessActivation failed", __func__);
1013  }
1014 
1015  armnn::IOutputSlot& activationOutputSlot = endLayer->GetOutputSlot(layerOutputIndex);
1016  data.m_OutputSlotForOperand[operandIndex] = &activationOutputSlot;
1017  }
1018  else
1019  {
1020  data.m_OutputSlotForOperand[operandIndex] = &outputSlot;
1021  }
1022 
1023  return true;
1024 }
1025 
1027 {
1028  VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize()";
1029  if (!ioutputSlot)
1030  {
1031  return false;
1032  }
1033  VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() ioutputSlot is valid.";
1034  // Find the connections and layers..
1035  armnn::IConnectableLayer& owningLayer = ioutputSlot->GetOwningIConnectableLayer();
1036  if (owningLayer.GetType() == armnn::LayerType::Dequantize)
1037  {
1038  VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() connected to Dequantize Layer.";
1039  armnn::IInputSlot& inputSlot = owningLayer.GetInputSlot(0);
1040  armnn::IOutputSlot* connection = inputSlot.GetConnection();
1041  if (connection)
1042  {
1043  VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() Dequantize Layer has a connection.";
1044  armnn::IConnectableLayer& connectedLayer =
1045  connection->GetOwningIConnectableLayer();
1046  if (connectedLayer.GetType() == armnn::LayerType::Constant)
1047  {
1048  VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() Dequantize Layer connected to Constant";
1049  return true;
1050  }
1051  }
1052  }
1053  return false;
1054 }
1055 
1056 } // 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
virtual unsigned int GetNumOutputSlots() const =0
Returns the number of connectable output slots.
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:68
bool IsQSymm8(const Operand &operand)
uint32_t m_PadBottom
Padding bottom value in the height dimension.
std::vector< armnn::IOutputSlot * > m_OutputSlotForOperand
virtual unsigned int GetNumInputSlots() const =0
Returns the number of connectable input slots.
DataLayout
Definition: Types.hpp:62
unsigned int GetWidthIndex() const
const TensorShape & GetShape() const
Definition: Tensor.hpp:191
uint32_t m_PadLeft
Padding left value in the width dimension.
const std::vector< armnn::BackendId > m_Backends
bool GetInputActivationFunction(const Operation &operation, uint32_t inputIndex, ActivationFn &outActivationFunction, const Model &model, const ConversionData &data)
uint32_t m_PoolWidth
Pooling width value.
bool ConvertReduce(const Operation &operation, const Model &model, ConversionData &data, armnn::ReduceOperation reduceOperation)
armnn::IOutputSlot * GetOutputSlot() const
::android::nn::Operation Operation
bool m_KeepDims
if true then output shape has no change.
void * GetMemoryFromPool(DataLocation location, const std::vector< android::nn::RunTimePoolInfo > &memPools)
Returns a pointer to a specific location in a pool`.
unsigned int GetNumBytes() const
Definition: Tensor.cpp:427
const armnn::ConstTensor * GetConstTensorPtr() const
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)
unsigned int GetNumElements() const
Definition: Tensor.hpp:303
void Connect(armnn::IInputSlot &inputSlot)
uint32_t m_PadTop
Padding top value in the height dimension.
MemoryType GetMemoryArea() const
Definition: Tensor.hpp:305
std::vector< std::pair< unsigned int, unsigned int > > m_PadList
Specifies the padding for input dimension.
ReduceOperation m_ReduceOperation
Specifies the reduction operation to execute.
void IgnoreUnused(Ts &&...)
SizeType GetSize() const
Definition: Types.hpp:338
PoolingAlgorithm
Definition: Types.hpp:136
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
unsigned int GetHeightIndex() const
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)
void SwizzleAndroidNn4dTensorToArmNn(armnn::TensorInfo &tensorInfo, const void *input, void *output, const armnn::PermutationVector &mappings)
Swizzles tensor data in input according to the dimension mappings.
uint32_t m_PoolHeight
Pooling height value.
bool AreDynamicTensorsSupported()
Checks for ArmNN support of dynamic tensors.
void Disconnect(armnn::IInputSlot &inputSlot)
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
uint32_t m_PadRight
Padding right value in the width dimension.
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)
Provides access to the appropriate indexes for Channels, Height and Width based on DataLayout...
DataType GetDataType() const
Definition: Tensor.hpp:198
A ReduceDescriptor for the REDUCE operators.
armnn::TensorInfo GetTensorInfoForOperand(const Operand &operand)
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
virtual void Disconnect(IInputSlot &slot)=0
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)
virtual const IConnectableLayer & GetOwningIConnectableLayer() const =0
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:36
const TensorInfo & GetInfo() const
Definition: Tensor.hpp:295
min(a, max(b, input)) ReLu1 & ReLu6.
void SetDataType(DataType type)
Definition: Tensor.hpp:199
ConstTensorPin(bool optional=false)
virtual LayerType GetType() const =0
Returns the armnn::LayerType of this layer.
bool GetTensorInt32Values(const Operand &operand, std::vector< int32_t > &outValues, const Model &model, const ConversionData &data)
bool IsDynamicTensor(const armnn::TensorInfo &tensorInfo)
Checks if a tensor info represents a dynamic tensor.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
std::vector< uint32_t > m_vAxis
The indices of the dimensions to reduce.
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 IsInputSupported(const BackendId &backend, const TensorInfo &input, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
float m_A
Alpha upper bound value used by the activation functions. (BoundedReLu, Linear, TanH, Elu).
Definition: Descriptors.hpp:61
LayerInputHandle ConvertToLayerInputHandle(const Operation &operation, uint32_t inputIndex, const Model &model, ConversionData &data, const armnn::PermutationVector &dimensionMappings, const LayerInputHandle *inputHandle)
::android::nn::Operand Operand
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)
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
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)
const armnn::ConstTensor & GetConstTensor() const
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
virtual bool IsTensorInfoSet() const =0
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 * GetConnection() const =0
armnn::IConnectableLayer * ProcessActivation(const armnn::TensorInfo &tensorInfo, ActivationFn activation, armnn::IConnectableLayer *prevLayer, ConversionData &data)
bool IsOperandConstant(const Operand &operand)
virtual const TensorInfo & GetTensorInfo() const =0
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)
bool IsReduceSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const ReduceDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
bool IsConstantSupported(const BackendId &backend, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
virtual int Connect(IInputSlot &destination)=0
bool IsPooling2dSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const Pooling2dDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
void SanitizeQuantizationScale(LayerInputHandle &weight, LayerInputHandle &input)
A Pooling2dDescriptor for the Pooling2dLayer.
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:195
Helper classes.
Definition: ArmnnDevice.cpp:37
float m_B
Beta lower bound value used by the activation functions. (BoundedReLu, Linear, TanH).
Definition: Descriptors.hpp:63
bool IsActivationSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const ActivationDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square, Elu).
Definition: Descriptors.hpp:59
An input connection slot for a layer.
Definition: INetwork.hpp:25
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.