ArmNN
 22.11
Converter.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 "Converter.hpp"
7 #include <half/half.hpp>
9 
10 namespace armnn_driver
11 {
12 
13 using namespace android::nn;
14 using Half = half_float::half;
15 
16 namespace
17 {
18 
19 } // anonymouse namespace
20 
21 bool Converter::ConvertOperation(const Operation& operation, const Model& model, ConversionData& data)
22 {
23  switch (operation.type)
24  {
25  case OperationType::ABS:
26  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::Abs);
27  case OperationType::ADD:
28  return ConvertAdd(operation, model, data);
29  case OperationType::ARGMAX:
30  return ConvertArgMinMax(operation, model, data, ArgMinMaxFunction::Max);
31  case OperationType::ARGMIN:
32  return ConvertArgMinMax(operation, model, data, ArgMinMaxFunction::Min);
33  case OperationType::AVERAGE_POOL_2D:
34  return ConvertAveragePool2d(operation, model, data);
35  case OperationType::BATCH_MATMUL:
36  return ConvertBatchMatMul(operation, model, data);
37  case OperationType::BATCH_TO_SPACE_ND:
38  return ConvertBatchToSpaceNd(operation, model, data);
39  case OperationType::CAST:
40  return ConvertCast(operation, model, data);
41  case OperationType::CONCATENATION:
42  return ConvertConcatenation(operation, model, data);
43  case OperationType::CONV_2D:
44  return ConvertConv2d(operation, model, data);
45  case OperationType::DEPTH_TO_SPACE:
46  return ConvertDepthToSpace(operation, model, data);
47  case OperationType::DEPTHWISE_CONV_2D:
48  return ConvertDepthwiseConv2d(operation, model, data);
49  case OperationType::DEQUANTIZE:
50  return ConvertDequantize(operation, model, data);
51  case OperationType::DIV:
52  return ConvertDiv(operation, model, data);
53  case OperationType::ELU:
54  return ConvertElu(operation, model, data);
55  case OperationType::EQUAL:
56  return ConvertComparison(operation, model, data, ComparisonOperation::Equal);
57  case OperationType::EXP:
58  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::Exp);
59  case OperationType::EXPAND_DIMS:
60  return ConvertExpandDims(operation, model, data);
61  case OperationType::FILL:
62  return ConvertFill(operation, model, data);
63  case OperationType::FLOOR:
64  return ConvertFloor(operation, model, data);
65  case OperationType::FULLY_CONNECTED:
66  return ConvertFullyConnected(operation, model, data);
67  case OperationType::GATHER:
68  return ConvertGather(operation, model, data);
69  case OperationType::GREATER:
70  return ConvertComparison(operation, model, data, ComparisonOperation::Greater);
71  case OperationType::GREATER_EQUAL:
72  return ConvertComparison(operation, model, data, ComparisonOperation::GreaterOrEqual);
73  case OperationType::GROUPED_CONV_2D:
74  return ConvertGroupedConv2d(operation, model, data);
75  case OperationType::HARD_SWISH:
76  return ConvertHardSwish(operation, model, data);
77  case OperationType::INSTANCE_NORMALIZATION:
78  return ConvertInstanceNormalization(operation, model, data);
79  case OperationType::L2_NORMALIZATION:
80  return ConvertL2Normalization(operation, model, data);
81  case OperationType::L2_POOL_2D:
82  return ConvertL2Pool2d(operation, model, data);
83  case OperationType::LESS:
84  return ConvertComparison(operation, model, data, ComparisonOperation::Less);
85  case OperationType::LESS_EQUAL:
86  return ConvertComparison(operation, model, data, ComparisonOperation::LessOrEqual);
87  case OperationType::LOCAL_RESPONSE_NORMALIZATION:
88  return ConvertLocalResponseNormalization(operation, model, data);
89  case OperationType::LOG:
90  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::Log);
91  case OperationType::LOGICAL_AND:
92  return ConvertLogicalBinary(operation, model, data, LogicalBinaryOperation::LogicalAnd);
93  case OperationType::LOGICAL_NOT:
94  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::LogicalNot);
95  case OperationType::LOGICAL_OR:
96  return ConvertLogicalBinary(operation, model, data, LogicalBinaryOperation::LogicalOr);
97  case OperationType::LOGISTIC:
98  return ConvertLogistic(operation, model, data);
99  case OperationType::LOG_SOFTMAX:
100  return ConvertLogSoftmax(operation, model, data);
101  case OperationType::LSTM:
102  return ConvertLstm(operation, model, data);
103  case OperationType::MAX_POOL_2D:
104  return ConvertMaxPool2d(operation, model, data);
105  case OperationType::MAXIMUM:
106  return ConvertMaximum(operation, model, data);
107  case OperationType::MEAN:
108  return ConvertMean(operation, model, data);
109  case OperationType::MINIMUM:
110  return ConvertMinimum(operation, model, data);
111  case OperationType::MUL:
112  return ConvertMul(operation, model, data);
113  case OperationType::NEG:
114  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::Neg);
115  case OperationType::NOT_EQUAL:
116  return ConvertComparison(operation, model, data, ComparisonOperation::NotEqual);
117  case OperationType::PAD:
118  return ConvertPad(operation, model, data);
119  case OperationType::PAD_V2:
120  return ConvertPadV2(operation, model, data);
121  case OperationType::PRELU:
122  return ConvertPrelu(operation, model, data);
123  case OperationType::QUANTIZE:
124  return ConvertQuantize(operation, model, data);
125  case OperationType::QUANTIZED_LSTM:
126  return ConvertQuantizedLstm(operation, model, data);
127  case OperationType::QUANTIZED_16BIT_LSTM:
128  return ConvertQuantized16BitLstm(operation, model, data);
129  case OperationType::RANK:
130  return ConvertRank(operation, model, data);
131  case OperationType::REDUCE_MAX:
132  return ConvertReduce(operation, model, data, armnn::ReduceOperation::Max);
133  case OperationType::REDUCE_MIN:
134  return ConvertReduce(operation, model, data, armnn::ReduceOperation::Min);
135  case OperationType::REDUCE_SUM:
136  return ConvertReduce(operation, model, data, armnn::ReduceOperation::Sum);
137  case OperationType::RELU:
138  return ConvertReLu(operation, model, data);
139  case OperationType::RELU1:
140  return ConvertReLu1(operation, model, data);
141  case OperationType::RELU6:
142  return ConvertReLu6(operation, model, data);
143  case OperationType::RESHAPE:
144  return ConvertReshape(operation, model, data);
145  case OperationType::RESIZE_BILINEAR:
146  return ConvertResize(operation, model, data, ResizeMethod::Bilinear);
147  case OperationType::RESIZE_NEAREST_NEIGHBOR:
148  return ConvertResize(operation, model, data, ResizeMethod::NearestNeighbor);
149  case OperationType::RSQRT:
150  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::Rsqrt);
151  case OperationType::SIN:
152  return ConvertElementwiseUnary(operation, model, data, UnaryOperation::Sin);
153  case OperationType::SOFTMAX:
154  return ConvertSoftmax(operation, model, data);
155  case OperationType::SPACE_TO_BATCH_ND :
156  return ConvertSpaceToBatchNd(operation, model, data);
157  case OperationType::SPACE_TO_DEPTH:
158  return ConvertSpaceToDepth(operation, model, data);
159  case OperationType::SQRT:
160  return ConvertSqrt(operation, model, data);
161  case OperationType::SQUEEZE:
162  return ConvertSqueeze(operation, model, data);
163  case OperationType::STRIDED_SLICE:
164  return ConvertStridedSlice(operation, model, data);
165  case OperationType::SUB:
166  return ConvertSub(operation, model, data);
167  case OperationType::TRANSPOSE:
168  return ConvertTranspose(operation, model, data);
169  case OperationType::TRANSPOSE_CONV_2D:
170  return ConvertTransposeConv2d(operation, model, data);
171  case OperationType::TANH:
172  return ConvertTanH(operation, model, data);
173  default:
174  VLOG(DRIVER) << "Operation type: " << operation.type << "is not supported in ArmnnDriver";
175  return false;
176  }
177 }
178 
179 bool Converter::ConvertAdd(const Operation& operation, const Model& model, ConversionData& data)
180 {
181  VLOG(DRIVER) << "Converter::ConvertAdd()";
182  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
183  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
184 
185  if (!input0.IsValid() || !input1.IsValid())
186  {
187  return Fail("%s: Operation has invalid inputs", __func__);
188  }
189 
190  // The FuseActivation parameter is always the input index 2, and it should be optional
191  ActivationFn activationFunction;
192  if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
193  {
194  return Fail("%s: Operation has invalid inputs", __func__);
195  }
196 
197  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
198  if (!outputOperand)
199  {
200  return false;
201  }
202 
203  const armnn::TensorInfo& inputInfo0 = input0.GetTensorInfo();
204  const armnn::TensorInfo& inputInfo1 = input1.GetTensorInfo();
205 
206  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
207 
208  bool isSupported = false;
209  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
210  {
213  data.m_Backends,
214  isSupported,
215  inputInfo0,
216  inputInfo1,
217  outputInfo);
218  };
219 
220  if(!IsDynamicTensor(outputInfo))
221  {
222  validateFunc(outputInfo, isSupported);
223  }
224  else
225  {
226  isSupported = AreDynamicTensorsSupported();
227  }
228 
229  if (!isSupported)
230  {
231  return false;
232  }
233 
234  armnn::IConnectableLayer* const startLayer = data.m_Network->AddAdditionLayer();
235 
236  bool isReshapeSupported = BroadcastTensor(input0, input1, startLayer, data);
237  if (!isReshapeSupported)
238  {
239  return false;
240  }
241 
242  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
243  data, nullptr, validateFunc, activationFunction);
244 }
245 
246 bool Converter::ConvertArgMinMax(const Operation& operation,
247  const Model& model,
248  ConversionData& data,
249  armnn::ArgMinMaxFunction argMinMaxFunction)
250 {
251  VLOG(DRIVER) << "Converter::ConvertArgMinMax()";
252  VLOG(DRIVER) << "argMinMaxFunction = " << GetArgMinMaxFunctionAsCString(argMinMaxFunction);
253 
254  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
255 
256  if (!input0.IsValid())
257  {
258  return Fail("%s: Operation has invalid inputs", __func__);
259  }
260 
261  int32_t axis;
262  if (!GetInputScalar(operation, 1, OperandType::INT32, axis, model, data))
263  {
264  return Fail("%s: Operation has invalid inputs. Failed to read axis.", __func__);
265  }
266 
267  const armnn::TensorInfo& inputInfo = input0.GetTensorInfo();
268  int rank = static_cast<int>(inputInfo.GetNumDimensions());
269 
270  if (((axis < -rank) && (axis < 0)) || ((axis >= rank) && (axis > 0)))
271  {
272  // Square bracket denotes inclusive n while parenthesis denotes exclusive n
273  // E.g. Rank 4 tensor can have axis in range [-4, 3)
274  // -1 == 3, -2 == 2, -3 == 1, -4 == 0
275  return Fail("%s: Axis must be in range [-n, n)", __func__);
276  }
277 
278  const Operand* output = GetOutputOperand(operation, 0, model);
279  if (!output)
280  {
281  return Fail("%s: Could not read output 0", __func__);
282  }
283 
284  const armnn::TensorInfo& inputInfo0 = input0.GetTensorInfo();
285 
286  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
287 
288  armnn::ArgMinMaxDescriptor descriptor;
289  descriptor.m_Function = argMinMaxFunction;
290  descriptor.m_Axis = axis;
291 
292  bool isSupported = false;
293 
294  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
295  {
297  IsArgMinMaxSupported,
298  data.m_Backends,
299  isSupported,
300  inputInfo0,
301  outputInfo,
302  descriptor);
303  };
304 
305  if(IsDynamicTensor(outputInfo))
306  {
307  isSupported = AreDynamicTensorsSupported();
308  }
309  else
310  {
311  validateFunc(outputInfo, isSupported);
312  }
313 
314  if (!isSupported)
315  {
316  return false;
317  }
318 
319  armnn::IConnectableLayer* layer = data.m_Network->AddArgMinMaxLayer(descriptor);
320  assert(layer != nullptr);
321 
322  input0.Connect(layer->GetInputSlot(0));
323 
324  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
325 }
326 
327 bool Converter::ConvertAveragePool2d(const Operation& operation, const Model& model, ConversionData& data)
328 {
329  VLOG(DRIVER) << "Converter::ConvertAveragePool2d()";
330  return ConvertPooling2d(operation, __func__, PoolingAlgorithm::Average, model, data);
331 }
332 
333 bool Converter::ConvertBatchMatMul(const Operation& operation, const Model& model, ConversionData& data)
334 {
335  VLOG(DRIVER) << "Converter::ConvertBatchMatMul()";
336  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
337  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
338 
339  if (!input0.IsValid() || !input1.IsValid())
340  {
341  return Fail("%s: Operation has invalid inputs", __func__);
342  }
343 
344  const armnn::TensorInfo& inputInfo0 = input0.GetTensorInfo();
345  const armnn::TensorInfo& inputInfo1 = input1.GetTensorInfo();
346 
347  unsigned int rankInput0 = inputInfo0.GetNumDimensions();
348  if (rankInput0 > 4 || rankInput0 < 2)
349  {
350  Fail("%s: Only inputs with rank at least 2 and up to 4 are supported", __func__);
351  }
352 
353  unsigned int rankInput1 = inputInfo1.GetNumDimensions();
354  if (rankInput1 > 4 || rankInput1 < 2)
355  {
356  Fail("%s: Only inputs with rank at least 2 and up to 4 are supported", __func__);
357  }
358 
359  // Determine data type of input tensor 0
360  OperandType input0Type;
361  if (!GetOperandType(operation, 0, model, input0Type))
362  {
363  return Fail("%s: Operation has invalid inputs", __func__);
364  }
365 
366  // Determine data type of input tensor 0
367  OperandType input1Type;
368  if (!GetOperandType(operation, 0, model, input1Type))
369  {
370  return Fail("%s: Operation has invalid inputs", __func__);
371  }
372 
373  if (input0Type != input1Type)
374  {
375  return Fail("%s: Operation has invalid inputs (Inputs must have same OperandCode)", __func__);
376  }
377 
378  const Operand* output = GetOutputOperand(operation, 0, model);
379  if (!output)
380  {
381  return Fail("%s: Could not read output 0", __func__);
382  }
383 
384  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
385 
386  armnn::BatchMatMulDescriptor batchMatMulDesc;
387 
388  // Inputs 2 and 3 are adjoint in Android NeuralNetworks, but they perform transpose.
389  // This is why we are linking them with transpose parameters in the descriptor
390  batchMatMulDesc.m_TransposeX = GetOptionalBool(operation, 2, model, data);
391  batchMatMulDesc.m_TransposeY = GetOptionalBool(operation, 3, model, data);
392 
393  bool isSupported = false;
394  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
395  {
397  IsBatchMatMulSupported,
398  data.m_Backends,
399  isSupported,
400  inputInfo0,
401  inputInfo1,
402  outputInfo,
403  batchMatMulDesc);
404  };
405 
406  if(!IsDynamicTensor(outputInfo))
407  {
408  validateFunc(outputInfo, isSupported);
409  }
410  else
411  {
412  isSupported = AreDynamicTensorsSupported();
413  }
414 
415 
416  if (!isSupported)
417  {
418  return false;
419  }
420 
421  armnn::IConnectableLayer* const layer = data.m_Network->AddBatchMatMulLayer(batchMatMulDesc);
422  assert(layer != nullptr);
423  input0.Connect(layer->GetInputSlot(0));
424  input1.Connect(layer->GetInputSlot(1));
425 
426  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
427 }
428 
429 bool Converter::ConvertBatchToSpaceNd(const Operation& operation, const Model& model, ConversionData& data)
430 {
431  VLOG(DRIVER) << "Converter::ConvertBatchToSpaceNd()";
432  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
433  if (!input.IsValid())
434  {
435  return Fail("%s: Operation has invalid inputs", __func__);
436  }
437 
438  const Operand* output = GetOutputOperand(operation, 0, model);
439  if (!output)
440  {
441  return Fail("%s: Could not read output 0", __func__);
442  }
443 
444  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
445 
446  const Operand* blockOperand = GetInputOperand(operation, 1, model);
447  if (!blockOperand)
448  {
449  return Fail("%s: Could not read input 1", __func__);
450  }
451 
452  // Convert the block operand to int32
453  std::vector<int32_t> block;
454  if (!GetTensorInt32Values(*blockOperand, block, model, data))
455  {
456  return Fail("%s: Input 1 has invalid values", __func__);
457  }
458 
459  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
460 
461  unsigned int rank = inputInfo.GetNumDimensions();
462  if (rank != 4)
463  {
464  Fail("%s: Only inputs with rank equal to 4 are supported", __func__);
465  }
466 
467  if (std::any_of(block.cbegin(), block.cend(), [](int32_t i){ return i < 1; }))
468  {
469  return Fail("%s: Block sizes for each spatial dimension of the input tensor must be"
470  " greater than or equal to 1", __func__);
471  }
472 
473  armnn::BatchToSpaceNdDescriptor batchToSpaceNdDesc;
474  batchToSpaceNdDesc.m_BlockShape.assign(block.cbegin(), block.cend());
475  batchToSpaceNdDesc.m_DataLayout = armnn::DataLayout::NHWC;
476 
477  if (Is12OrLaterOperand(*output))
478  {
479  batchToSpaceNdDesc.m_DataLayout = OptionalDataLayout(operation, 2, model, data);
480  }
481  // Setting crops to 0,0 0,0 as it is not supported in Android NN API
482  batchToSpaceNdDesc.m_Crops = {{0, 0}, {0, 0}};
483 
484  bool isSupported = false;
485  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
486  {
489  data.m_Backends,
490  isSupported,
491  inputInfo,
492  outputInfo,
493  batchToSpaceNdDesc);
494  };
495 
496  if(!IsDynamicTensor(outputInfo))
497  {
498  validateFunc(outputInfo, isSupported);
499  }
500  else
501  {
502  isSupported = AreDynamicTensorsSupported();
503  }
504 
505 
506  if (!isSupported)
507  {
508  return false;
509  }
510 
511  armnn::IConnectableLayer* const layer = data.m_Network->AddBatchToSpaceNdLayer(batchToSpaceNdDesc);
512  assert(layer != nullptr);
513  input.Connect(layer->GetInputSlot(0));
514 
515  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
516 }
517 
518 bool Converter::ConvertCast(const Operation& operation, const Model& model, ConversionData& data)
519 {
520  VLOG(DRIVER) << "Converter::ConvertCast()";
521 
522  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
523 
524  if (!input.IsValid())
525  {
526  return Fail("%s: Operation has invalid inputs", __func__);
527  }
528 
529  const Operand* output = GetOutputOperand(operation, 0, model);
530  if (!output)
531  {
532  return Fail("%s: Could not read output 0", __func__);
533  }
534 
535  const TensorInfo& inputInfo = input.GetTensorInfo();
536  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
537 
538  bool isSupported = false;
539 
540  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
541  {
543  IsCastSupported,
544  data.m_Backends,
545  isSupported,
546  inputInfo,
547  outputInfo);
548  };
549 
550  if(!IsDynamicTensor(outputInfo))
551  {
552  validateFunc(outputInfo, isSupported);
553  }
554  else
555  {
556  isSupported = AreDynamicTensorsSupported();
557  }
558 
559  if (!isSupported)
560  {
561  return false;
562  }
563 
564  IConnectableLayer* layer = data.m_Network->AddCastLayer();
565  assert(layer != nullptr);
566  input.Connect(layer->GetInputSlot(0));
567 
568  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
569 }
570 
571 bool Converter::ConvertComparison(const Operation& operation,
572  const Model& model,
573  ConversionData& data,
574  ComparisonOperation comparisonOperation)
575 {
576  VLOG(DRIVER) << "Converter::ConvertComparison()";
577  VLOG(DRIVER) << "comparisonOperation = " << GetComparisonOperationAsCString(comparisonOperation);
578 
579  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
580  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
581 
582  if (!(input0.IsValid() && input1.IsValid()))
583  {
584  return Fail("%s: Operation has invalid inputs", __func__);
585  }
586 
587  const Operand* output = GetOutputOperand(operation, 0, model);
588  if (!output)
589  {
590  return Fail("%s: Could not read output 0", __func__);
591  }
592 
593  const TensorInfo& inputInfo0 = input0.GetTensorInfo();
594  const TensorInfo& inputInfo1 = input1.GetTensorInfo();
595  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
596 
597  ComparisonDescriptor descriptor(comparisonOperation);
598 
599  bool isSupported = false;
600  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
601  {
603  IsComparisonSupported,
604  data.m_Backends,
605  isSupported,
606  inputInfo0,
607  inputInfo1,
608  outputInfo,
609  descriptor);
610  };
611 
612  if(!IsDynamicTensor(outputInfo))
613  {
614  validateFunc(outputInfo, isSupported);
615  }
616  else
617  {
618  isSupported = AreDynamicTensorsSupported();
619  }
620 
621  if (!isSupported)
622  {
623  return false;
624  }
625 
626  IConnectableLayer* layer = data.m_Network->AddComparisonLayer(descriptor);
627  assert(layer != nullptr);
628 
629  bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
630  if (!isReshapeSupported)
631  {
632  return false;
633  }
634 
635  if(IsDynamicTensor(outputInfo))
636  {
637  input0.Connect(layer->GetInputSlot(0));
638  input1.Connect(layer->GetInputSlot(1));
639  }
640 
641  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
642 }
643 
644 
645 bool Converter::ConvertConcatenation(const Operation& operation, const Model& model, ConversionData& data)
646 {
647  VLOG(DRIVER) << "Converter::ConvertConcatenation()";
648 
649  // The first N (0..N-1) inputs are tensors. The Nth input is the concatenation axis.
650  if (operation.inputs.size() <= 1)
651  {
652  return Fail("%s: Operation has insufficient arguments", __func__);
653  }
654 
655  // Get inputs and outputs
656  const std::size_t numInputTensors = operation.inputs.size() - 1;
657 
658  int32_t concatDim;
659  if (!GetInputScalar(operation, numInputTensors, OperandType::INT32, concatDim, model, data))
660  {
661  return Fail("%s: Operation has invalid inputs", __func__);
662  }
663 
664  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
665  if (!outputOperand)
666  {
667  return Fail("%s: Operation has no outputs", __func__);
668  }
669 
670  armnn::TensorInfo outputInfo = GetTensorInfoForOperand(*outputOperand);
671  armnn::TensorShape outputShape = outputInfo.GetShape();
672  const bool isDynamicTensor = IsDynamicTensor(outputInfo);
673  //
674  // handle negative concat dims along the lines of tensorflow as described here:
675  // https://www.tensorflow.org/api_docs/python/tf/concat
676  // "negative axis refers to axis + rank(values)-th dimension"
677  //
678  if (concatDim < 0)
679  {
680  concatDim += outputShape.GetNumDimensions();
681  }
682 
683  if (concatDim >= static_cast<int32_t>(outputShape.GetNumDimensions()) || concatDim < 0)
684  {
685  return Fail("%s: Operation has invalid concat axis: %d", __func__, concatDim);
686  }
687 
688  std::vector<LayerInputHandle> inputHandles;
689  std::vector<armnn::TensorShape> inputShapes;
690 
691  inputHandles.reserve(numInputTensors);
692  inputShapes.reserve(numInputTensors);
693 
694  bool inputsHaveBeenReshaped = false;
695  unsigned int tensorDimensionsAdded = 0;
696  for (uint32_t i = 0; i < numInputTensors; ++i)
697  {
698  const Operand* operand = GetInputOperand(operation, i, model);
699  if (!operand)
700  {
701  return Fail("%s: Operation has invalid inputs", __func__);
702  }
703 
704  LayerInputHandle operandInputHandle = ConvertToLayerInputHandle(operation, i, model, data);
705  if (!operandInputHandle.IsValid())
706  {
707  return Fail("%s: Operation has invalid inputs", __func__);
708  }
709 
710  armnn::TensorShape operandShape = GetTensorShapeForOperand(*operand);
711  if (operandShape.GetNumDimensions() == 0)
712  {
713  return Fail("%s: Operands with rank 0 are not supported", __func__);
714  }
715 
716  if (RequiresReshape(operandShape))
717  {
718  inputsHaveBeenReshaped = true;
719 
720  armnn::TensorInfo reshapeInfo = operandInputHandle.GetTensorInfo();
721 
722  // Expand the tensor to three dimensions
723  if (operandShape.GetNumDimensions() == 2)
724  {
725  reshapeInfo.SetShape(armnn::TensorShape({1, operandShape[0], operandShape[1]}));
726  tensorDimensionsAdded = 1;
727  }
728  else
729  {
730  reshapeInfo.SetShape(armnn::TensorShape({1, 1, operandShape[0]}));
731  tensorDimensionsAdded = 2;
732  }
733 
734  armnn::ReshapeDescriptor reshapeDescriptor;
735  reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
736 
737  bool isSupported = false;
740  data.m_Backends,
741  isSupported,
742  operandInputHandle.GetTensorInfo(),
743  reshapeInfo,
744  reshapeDescriptor);
745 
746  if (!isSupported)
747  {
748  return false;
749  }
750  armnn::IConnectableLayer& newReshape = AddReshapeLayer(*data.m_Network, operandInputHandle, reshapeInfo);
751 
752  // Point to the reshape operation rather then the input operation
753  operandShape = reshapeInfo.GetShape();
754  operandInputHandle = LayerInputHandle(true, &newReshape.GetOutputSlot(0), reshapeInfo);
755  }
756 
757  inputShapes.emplace_back(operandShape);
758  inputHandles.emplace_back(operandInputHandle);
759 
760  if (!inputHandles.back().IsValid())
761  {
762  return Fail("%s: Operation has invalid inputs", __func__);
763  }
764  }
765 
766  ARMNN_ASSERT(inputShapes.size() == inputHandles.size());
767 
768  if (inputsHaveBeenReshaped)
769  {
770  // Adjust the concatenation dimension by the amount of dimensions added (if any)
771  concatDim += tensorDimensionsAdded;
772 
773  // Add extra dimensions to the output shape to reflect the addition of the reshape layers
774  if (tensorDimensionsAdded == 1)
775  {
776  if (IsDynamicTensor(outputInfo))
777  {
778  outputShape = armnn::TensorShape({1, 0, 0}, {true, false, false});
779  }
780  else
781  {
782  outputShape = armnn::TensorShape({1, outputShape[0], outputShape[1]});
783  }
784  }
785  else if (tensorDimensionsAdded == 2)
786  {
787  if (IsDynamicTensor(outputInfo))
788  {
789  outputShape = armnn::TensorShape({1, 1, 0}, {true, true, false});
790  }
791  else
792  {
793  outputShape = armnn::TensorShape({1, 1, outputShape[0]});
794  }
795  }
796  }
797 
798  // Check if permutations is required and get the pair of permutations required for the concatenation.
799  // Permutation is required when the concat dimension is 2 for a 4D tensor or 1 for a 3D tensor.
800  std::pair<armnn::PermutationVector, armnn::PermutationVector> permutationPair =
801  std::make_pair(IdentityPermutation4D, IdentityPermutation4D);
802  bool needPermute = CreateConcatPermutationParameters(inputShapes[0].GetNumDimensions(),
803  concatDim,
804  permutationPair);
805 
806  // Only relevant to static tensors as dynamic output tensors will be transposed as a result of inferring from input
807  if (!isDynamicTensor)
808  {
809  if (needPermute)
810  {
811  outputShape = armnnUtils::TransposeTensorShape(outputShape, permutationPair.first);
812  }
813 
814  outputInfo.SetShape(outputShape);
815  }
816  // this is no-op for identity swizzles, otherwise it replaces both
817  // the handles and shapes with the swizzled layer output handles and shapes
818  if (!TransposeInputTensors(data, inputHandles, inputShapes, permutationPair.first))
819  {
820  return false;
821  }
822 
823  // Create an armnn concat layer descriptor - this will also perform validation on the input shapes
824  armnn::OriginsDescriptor concatDescriptor;
825 
826  try
827  {
828  // The concat descriptor is always created across the only supported concat dimension
829  // which is 0, 1 or 3 for a 4-D tensor, or 0 or 2 for a 3-D tensor.
830  concatDescriptor = armnn::CreateDescriptorForConcatenation(inputShapes.begin(),
831  inputShapes.end(),
832  concatDim);
833  } catch (std::exception& error)
834  {
835  return Fail("%s: Error preparing concat descriptor. %s", __func__, error.what());
836  }
837 
838  // Validate the output shape is correct given the input shapes based on the
839  // only valid concat dimension which is 0, 1 or 3 for a 4-D tensor, or 0 or 2 for a 3-D tensor.
840  if (!isDynamicTensor)
841  {
842  if (!ValidateConcatOutputShape(inputShapes, outputShape, concatDim))
843  {
844  return Fail("%s: Error validating the output shape for concat", __func__);
845  }
846  }
847 
848  std::vector<const armnn::TensorInfo*> inputTensorInfos;
849  std::transform(inputHandles.begin(), inputHandles.end(), std::back_inserter(inputTensorInfos),
850  [](const LayerInputHandle& h)->const armnn::TensorInfo*{ return &h.GetTensorInfo(); });
851 
852  bool isSupported = false;
853  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported){
854  FORWARD_LAYER_SUPPORT_FUNC(__func__, IsConcatSupported, data.m_Backends, isSupported, inputTensorInfos,
855  outputInfo, concatDescriptor);
856  };
857 
858  if (!isDynamicTensor)
859  {
860  validateFunc(outputInfo, isSupported);
861  }
862  else
863  {
864  isSupported = AreDynamicTensorsSupported();
865  }
866 
867  if (!isSupported)
868  {
869  return false;
870  }
871 
872  armnn::IConnectableLayer* layer = data.m_Network->AddConcatLayer(concatDescriptor);
873  assert(layer != nullptr);
874  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
875  // Connect inputs to the layer
876  const int numInputSlots = layer->GetNumInputSlots();
877  assert(static_cast<std::size_t>(numInputSlots) == inputHandles.size());
878  for (int i = 0; i < numInputSlots; ++i)
879  {
880  // connect the input directly to the merge (concat) layer
881  inputHandles[static_cast<unsigned int>(i)].Connect(layer->GetInputSlot(i));
882  }
883 
884  // Transpose the output shape
885  auto transposeOutputShape = [&](){
886  armnn::TransposeDescriptor transposeDesc;
887  transposeDesc.m_DimMappings = permutationPair.second;
888  armnn::TensorInfo inputTransposeInfo = layer->GetOutputSlot(0).GetTensorInfo();
889  armnn::TensorInfo outputTransposeInfo = armnnUtils::TransposeTensorShape(inputTransposeInfo,
890  permutationPair.second);
891  isSupported = false;
893  IsTransposeSupported,
894  data.m_Backends,
895  isSupported,
896  inputTransposeInfo,
897  outputTransposeInfo,
898  transposeDesc);
899  if (!isSupported)
900  {
901  return false;
902  }
903  // Add permutation layer and connect the output to it, the permutation becomes the output layer
904  armnn::IConnectableLayer& deswizzleLayer = AddTransposeLayer(*data.m_Network, layer->GetOutputSlot(0),
905  permutationPair.second);
906  layer = &deswizzleLayer;
907 
908  return true;
909  };
910 
911  if (needPermute && !isDynamicTensor)
912  {
913  transposeOutputShape();
914  }
915 
916  if (inputsHaveBeenReshaped)
917  {
918  if (isDynamicTensor)
919  {
920  // Infer the output shapes of concat if outputs are type 1 dynamic
922  if (!ValidateConcatOutputShape(inputShapes,
923  layer->GetOutputSlot(0).GetTensorInfo().GetShape(),
924  concatDim))
925  {
926  return Fail("%s: Error validating the output shape for concat", __func__);
927  }
928  transposeOutputShape();
929  }
930 
931  armnn::TensorInfo afterConcatInfo = layer->GetOutputSlot(0).GetTensorInfo();
932  // Undo the reshape knowing the amount of dimensions added
933  if (tensorDimensionsAdded == 1)
934  {
935  afterConcatInfo.SetShape(
936  armnn::TensorShape({afterConcatInfo.GetShape()[1], afterConcatInfo.GetShape()[2]}));
937  }
938  else if (tensorDimensionsAdded == 2)
939  {
940  afterConcatInfo.SetShape(armnn::TensorShape({afterConcatInfo.GetShape()[2]}));
941  }
942 
943  armnn::ReshapeDescriptor reshapeDescriptor;
944  reshapeDescriptor.m_TargetShape = afterConcatInfo.GetShape();
945  armnn::TensorInfo concatInfo = layer->GetOutputSlot(0).GetTensorInfo();
946 
947  isSupported = false;
948  auto validateReshapeFunc = [&](const armnn::TensorInfo& afterConcatInfo, bool& isSupported){
951  data.m_Backends,
952  isSupported,
953  concatInfo,
954  afterConcatInfo,
955  reshapeDescriptor);
956  };
957 
958  if (!IsDynamicTensor(afterConcatInfo))
959  {
960  validateReshapeFunc(afterConcatInfo, isSupported);
961  }
962  else
963  {
964  isSupported = AreDynamicTensorsSupported();
965  }
966 
967  if (!isSupported)
968  {
969  return false;
970  }
971  layer = &AddReshapeLayer(*data.m_Network, layer->GetOutputSlot(0), afterConcatInfo);
972  return SetupAndTrackLayerOutputSlot(operation,
973  0,
974  *layer,
975  model,
976  data,
977  nullptr,
978  validateReshapeFunc);
979  }
980 
981  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
982 }
983 
984 bool Converter::ConvertConv2d(const Operation& operation, const Model& model, ConversionData& data)
985 {
986  VLOG(DRIVER) << "Converter::ConvertConv2d()";
987 
988  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
989  if (!input.IsValid())
990  {
991  return Fail("%s: Operation has invalid inputs", __func__);
992  }
993 
994  const Operand* output = GetOutputOperand(operation, 0, model);
995  if (!output)
996  {
997  return Fail("%s: Could not read output 0", __func__);
998  }
999 
1000  const TensorInfo& inputInfo = input.GetTensorInfo();
1001  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1002 
1004  desc.m_DataLayout = DataLayout::NHWC;
1005 
1006  // Determine whether padding is implicit or explicit
1007  bool implicitPadding = operation.inputs.size() == 7
1008  || (operation.inputs.size() >= 8
1009  && GetInputOperand(operation, 7, model)->type == OperandType::BOOL);
1010 
1011  if (implicitPadding)
1012  {
1013  desc.m_DataLayout = OptionalDataLayout(operation, 7, model, data);
1014  }
1015  else if (operation.inputs.size() >= 10)
1016  {
1017  desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data);
1018  }
1019 
1020  const PermutationVector OHWIToOIHW = {0, 2, 3, 1};
1021 
1022  // ArmNN does not currently support non-fixed weights or bias
1023  // The NNAPI filter is always OHWI [depth_out, filter_height, filter_width, depth_in] but ArmNN expects the
1024  // filter's height and width indices to match the input's height and width indices so we permute it to OIHW if
1025  // the DataLayout is NCHW
1026 
1027  if (!IsWeightsValid(operation, 1, model) && desc.m_DataLayout == DataLayout::NCHW)
1028  {
1029  return Fail("%s: Operation has unsupported weights OperandLifeTime", __func__);
1030  }
1031 
1032  LayerInputHandle weightsInput = (desc.m_DataLayout == DataLayout::NCHW)
1033  ? ConvertToLayerInputHandle(operation, 1, model, data, OHWIToOIHW, &input)
1034  : ConvertToLayerInputHandle(operation, 1, model, data, g_DontPermute, &input);
1035 
1036  if (!weightsInput.IsValid())
1037  {
1038  return Fail("%s: Operation has invalid inputs", __func__);
1039  }
1040 
1041  LayerInputHandle biasInput = ConvertToLayerInputHandle(operation, 2, model, data, g_DontPermute, &input); // 1D
1042  if (!biasInput.IsValid())
1043  {
1044  return Fail("%s: Operation has invalid inputs", __func__);
1045  }
1046 
1047  biasInput.SanitizeQuantizationScale(weightsInput, input);
1048  armnn::TensorInfo weightsInfo = weightsInput.GetTensorInfo();
1049  armnn::TensorInfo biasInfo = biasInput.GetTensorInfo();
1050 
1051  ActivationFn activation;
1052  if (implicitPadding)
1053  {
1054  ::android::nn::PaddingScheme paddingScheme;
1055  if (!GetInputPaddingScheme(operation, 3, paddingScheme, model, data)
1056  || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX, model, data)
1057  || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY, model, data)
1058  || !GetInputActivationFunction(operation, 6, activation, model, data)
1059  || !GetOptionalConvolutionDilationParams(operation, 8, desc, model, data))
1060  {
1061  return Fail("%s: Operation has invalid inputs (implicit padding)", __func__);
1062  }
1063 
1064  armnnUtils::DataLayoutIndexed dataLayoutIndexed(desc.m_DataLayout);
1065  unsigned int widthIndex = dataLayoutIndexed.GetWidthIndex();
1066  unsigned int heightIndex = dataLayoutIndexed.GetHeightIndex();
1067  const uint32_t kernelX = weightsInfo.GetShape()[widthIndex];
1068  const uint32_t kernelY = weightsInfo.GetShape()[heightIndex];
1069  const uint32_t inputX = inputInfo.GetShape()[widthIndex];
1070  const uint32_t inputY = inputInfo.GetShape()[heightIndex];
1071 
1072  CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_DilationX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1073  CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_DilationY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1074 
1075  }
1076  else if (operation.inputs.size() >= 10)
1077  {
1078  // explicit padding
1079  if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data)
1080  || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data)
1081  || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data)
1082  || !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data)
1083  || !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data)
1084  || !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data)
1085  || !GetInputActivationFunction(operation, 9, activation, model, data)
1086  || !GetOptionalConvolutionDilationParams(operation, 11, desc, model, data))
1087  {
1088  return Fail("%s: Operation has invalid inputs (explicit padding)", __func__);
1089  }
1090  }
1091  else
1092  {
1093  return Fail("%s: Unsupported number of operation inputs", __func__);
1094  }
1095 
1096  desc.m_BiasEnabled = true;
1097  Optional<TensorInfo> biases(biasInfo);
1098 
1099  bool requiresValidation = true;
1100  const Operand* weightsOperand = GetInputOperand(operation, 1, model);
1101  const Operand* biasOperand = GetInputOperand(operation, 2, model);
1102  if (IsConnectedToDequantize(weightsInput.GetOutputSlot())
1103  || IsConnectedToDequantize(biasInput.GetOutputSlot()))
1104  {
1105  // Do not require validation for now. There will be an optimization step
1106  // [ConvertConstDequantisationLayersToConstLayers] will convert layers to Constant layers
1107  // then at the end of the optimization there will be layer supported validation.
1108  requiresValidation = false;
1109  VLOG(DRIVER) << "Converter::ConvertConv2d(): Weights and Biases are as INPUTS.";
1110  }
1111 
1112  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported) {
1113  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1115  data.m_Backends,
1116  isSupported,
1117  inputInfo,
1118  outputInfo,
1119  desc,
1120  weightsInfo,
1121  biases);
1122  };
1123 
1124  if (requiresValidation)
1125  {
1126  VLOG(DRIVER) << "Converter::ConvertConv2d(): Requires Validation!";
1127  bool isSupported = false;
1128  if (!IsDynamicTensor(outputInfo))
1129  {
1130  validateFunc(outputInfo, isSupported);
1131  }
1132  else
1133  {
1134  isSupported = AreDynamicTensorsSupported();
1135  }
1136 
1137  if (!isSupported)
1138  {
1139  return false;
1140  }
1141  }
1142 
1143  armnn::IConnectableLayer* startLayer = data.m_Network->AddConvolution2dLayer(desc);
1144 
1145  if (!startLayer)
1146  {
1147  return Fail("%s: AddConvolution2dLayer failed", __func__);
1148  }
1149 
1150  input.Connect(startLayer->GetInputSlot(0));
1151  weightsInput.Connect(startLayer->GetInputSlot(1));
1152  biasInput.Connect(startLayer->GetInputSlot(2));
1153 
1154  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model, data, nullptr, validateFunc, activation);
1155 }
1156 
1157 bool Converter::ConvertDepthToSpace(const Operation& operation, const Model& model, ConversionData& data)
1158 {
1159  VLOG(DRIVER) << "Converter::ConvertDepthToSpace()";
1160 
1161  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1162  if (!input.IsValid() )
1163  {
1164  return Fail("%s: Operation has invalid inputs", __func__);
1165  }
1166 
1167  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1168  unsigned int rank = inputInfo.GetNumDimensions();
1169  if (rank != 4)
1170  {
1171  return Fail("%s: Only inputs with rank 4 are supported", __func__);
1172  }
1173 
1174  const Operand* output = GetOutputOperand(operation, 0, model);
1175  if (!output)
1176  {
1177  return Fail("%s: Could not read output 0", __func__);
1178  }
1179 
1180  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1181 
1182  armnn::DepthToSpaceDescriptor descriptor;
1183 
1184  GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_BlockSize, model, data);
1185  if (descriptor.m_BlockSize <= 1)
1186  {
1187  return Fail("%s: Block size must be at least 1 in all dimensions");
1188  }
1189 
1191  if (Is12OrLaterOperand(*output))
1192  {
1193  descriptor.m_DataLayout = OptionalDataLayout(operation, 2, model, data);
1194  }
1195 
1196  bool isSupported = false;
1197  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1198  {
1199  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1200  IsDepthToSpaceSupported,
1201  data.m_Backends,
1202  isSupported,
1203  inputInfo,
1204  outputInfo,
1205  descriptor);
1206  };
1207 
1208  if(!IsDynamicTensor(outputInfo))
1209  {
1210  validateFunc(outputInfo, isSupported);
1211  }
1212  else
1213  {
1214  isSupported = AreDynamicTensorsSupported();
1215  }
1216 
1217  if (!isSupported)
1218  {
1219  return false;
1220  }
1221 
1222  armnn::IConnectableLayer* const layer = data.m_Network->AddDepthToSpaceLayer(descriptor);
1223  assert(layer != nullptr);
1224  input.Connect(layer->GetInputSlot(0));
1225 
1226  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
1227 }
1228 
1229 bool Converter::ConvertDepthwiseConv2d(const Operation& operation, const Model& model, ConversionData& data)
1230 {
1231  VLOG(DRIVER) << "Converter::ConvertDepthwiseConv2d()";
1232 
1233  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1234 
1235  if (!input.IsValid())
1236  {
1237  return Fail("%s: Operation has invalid inputs", __func__);
1238  }
1239 
1240  const Operand* output = GetOutputOperand(operation, 0, model);
1241 
1242  if (!output)
1243  {
1244  return Fail("%s: Could not read output 0", __func__);
1245  }
1246 
1247  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1248  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1249 
1250  // ArmNN does not currently support non-fixed weights or bias
1251  // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
1252  const Operand* weightsOperand = GetInputOperand(operation, 1, model);
1253 
1254  if (!weightsOperand)
1255  {
1256  return Fail("%s: Could not read weights", __func__);
1257  }
1258  // Basic sanity check on the weights shape.
1259  // ANEURALNETWORKS_DEPTHWISE_CONV_2D specifies a 4-D tensor, of shape
1260  // [1, filter_height, filter_width, depth_out]
1261  if (weightsOperand->dimensions[0] != 1)
1262  {
1263  return Fail("%s: Filter operand dimension 0 is invalid, should be 1", __func__);
1264  }
1265 
1268 
1269  // Determine whether padding is implicit or explicit
1270  bool implicitPadding = operation.inputs.size() == 8
1271  || (operation.inputs.size() >= 9
1272  && GetInputOperand(operation, 8, model)->type == OperandType::BOOL);
1273 
1274  // Look ahead to find the optional DataLayout, if present
1275  const uint32_t dataLayoutFlagIndex = implicitPadding ? 8 : 11;
1276  desc.m_DataLayout = OptionalDataLayout(operation, dataLayoutFlagIndex, model, data);
1277 
1278  armnnUtils::DataLayoutIndexed dataLayoutIndexed(desc.m_DataLayout);
1279  unsigned int widthIndex = dataLayoutIndexed.GetWidthIndex();
1280  unsigned int heightIndex = dataLayoutIndexed.GetHeightIndex();
1281 
1282  LayerInputHandle weightsInput = ConvertToLayerInputHandle(operation, 1, model, data, g_DontPermute, &input);
1283  if (!weightsInput.IsValid())
1284  {
1285  return Fail("%s: Operation has invalid inputs", __func__);
1286  }
1287 
1288  const Operand* biasOperand = GetInputOperand(operation, 2, model);
1289  if (!biasOperand)
1290  {
1291  return Fail("%s: Could not read bias", __func__);
1292  }
1293 
1294  LayerInputHandle biasInput = ConvertToLayerInputHandle(operation, 2, model, data, g_DontPermute, &input); // 1D
1295  if (!biasInput.IsValid())
1296  {
1297  return Fail("%s: Operation has invalid inputs", __func__);
1298  }
1299 
1300  biasInput.SanitizeQuantizationScale(weightsInput, input);
1301  armnn::TensorInfo weightsInfo = weightsInput.GetTensorInfo();
1302  armnn::TensorInfo biasInfo = biasInput.GetTensorInfo();
1303 
1304  ActivationFn activation;
1305  if (implicitPadding)
1306  {
1307  ::android::nn::PaddingScheme paddingScheme;
1308  if (!GetInputPaddingScheme(operation, 3, paddingScheme, model, data)
1309  || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX, model, data)
1310  || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY, model, data)
1311  || !GetInputActivationFunction(operation, 7, activation, model, data)
1312  || !GetOptionalConvolutionDilationParams(operation, 9, desc, model, data))
1313  {
1314  return Fail("%s: Operation has invalid inputs (implicit padding)", __func__);
1315  }
1316 
1317  const uint32_t kernelX = weightsInfo.GetShape()[2];
1318  const uint32_t kernelY = weightsInfo.GetShape()[1];
1319  const uint32_t inputX = inputInfo.GetShape()[widthIndex];
1320  const uint32_t inputY = inputInfo.GetShape()[heightIndex];
1321 
1322  CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_DilationX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1323  CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_DilationY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1324  }
1325  else if (operation.inputs.size() >= 11)
1326  {
1327  // explicit padding
1328  if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data)
1329  || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data)
1330  || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data)
1331  || !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data)
1332  || !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data)
1333  || !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data)
1334  || !GetInputActivationFunction(operation, 10, activation, model, data)
1335  || !GetOptionalConvolutionDilationParams(operation, 12, desc, model, data))
1336  {
1337  return Fail("%s: Operation has invalid inputs (explicit padding)", __func__);
1338  }
1339  }
1340  else
1341  {
1342  return Fail("%s: Unsupported number of operation inputs", __func__);
1343  }
1344 
1345  desc.m_BiasEnabled = true;
1346  Optional<TensorInfo> biases(biasInfo);
1347 
1348  bool requiresValidation = true;
1349  if (IsConnectedToDequantize(weightsInput.GetOutputSlot()) || IsConnectedToDequantize(biasInput.GetOutputSlot()))
1350  {
1351  // Do not require validation for now. There will be an optimization step
1352  // [ConvertConstDequantisationLayersToConstLayers] will convert layers to Constant layers
1353  // then at the end of the optimization there will be layer supported validation.
1354  requiresValidation = false;
1355  VLOG(DRIVER) << "Converter::ConvertDepthwiseConv2d(): Weights and Biases are as INPUTS.";
1356  }
1357 
1358  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported) {
1359  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1361  data.m_Backends,
1362  isSupported,
1363  inputInfo,
1364  outputInfo,
1365  desc,
1366  weightsInfo,
1367  biases);
1368  };
1369 
1370  if (requiresValidation)
1371  {
1372  VLOG(DRIVER) << "Converter::ConvertDepthwiseConv2d(): Requires Validation!";
1373  bool isSupported = false;
1374  if (!IsDynamicTensor(outputInfo))
1375  {
1376  validateFunc(outputInfo, isSupported);
1377  }
1378  else
1379  {
1380  isSupported = AreDynamicTensorsSupported();
1381  }
1382 
1383  if (!isSupported)
1384  {
1385  return false;
1386  }
1387  }
1388 
1389  armnn::IConnectableLayer* startLayer = data.m_Network->AddDepthwiseConvolution2dLayer(desc);
1390 
1391  if (!startLayer)
1392  {
1393  return Fail("%s: AddDepthwiseConvolution2dLayer failed", __func__);
1394  }
1395 
1396  input.Connect(startLayer->GetInputSlot(0));
1397 
1398  // Connect weights and bias inputs
1399  weightsInput.Connect(startLayer->GetInputSlot(1));
1400  biasInput.Connect(startLayer->GetInputSlot(2));
1401 
1402  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model, data, nullptr, validateFunc, activation);
1403 }
1404 
1405 bool Converter::ConvertDequantize(const Operation& operation, const Model& model, ConversionData& data)
1406 {
1407  VLOG(DRIVER) << "Converter::ConvertDequantize()";
1408 
1409  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1410  if (!input.IsValid())
1411  {
1412  return Fail("%s: Operation has invalid input", __func__);
1413  }
1414 
1415  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1416  const armnn::Optional<unsigned int>& quantizationDim = inputInfo.GetQuantizationDim();
1417  if (quantizationDim.has_value() && quantizationDim.value() != 0)
1418  {
1419  return Fail("%s: Operation has quantization dimension different than 0", __func__);
1420  }
1421 
1422  const Operand* const outputOperand = GetOutputOperand(operation, 0, model);
1423  if (!outputOperand)
1424  {
1425  return Fail("%s: Operation has invalid outputs", __func__);
1426  }
1427 
1428  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
1429 
1430  bool isSupported = false;
1431  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1432  {
1433  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1435  data.m_Backends,
1436  isSupported,
1437  inputInfo,
1438  outputInfo);
1439  };
1440 
1441  if(IsDynamicTensor(outputInfo))
1442  {
1443  isSupported = AreDynamicTensorsSupported();
1444  }
1445  else
1446  {
1447  validateFunc(outputInfo, isSupported);
1448  }
1449 
1450  if (!isSupported)
1451  {
1452  return false;
1453  }
1454 
1455  armnn::IConnectableLayer* const layer = data.m_Network->AddDequantizeLayer();
1456  assert(layer != nullptr);
1457  input.Connect(layer->GetInputSlot(0));
1458 
1459  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
1460 }
1461 
1462 bool Converter::ConvertDiv(const Operation& operation, const Model& model, ConversionData& data)
1463 {
1464  VLOG(DRIVER) << "Converter::ConvertDiv()";
1465 
1466  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
1467  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
1468 
1469  if (!input0.IsValid() || !input1.IsValid())
1470  {
1471  return Fail("%s: Operation has invalid inputs", __func__);
1472  }
1473 
1474  // The FuseActivation parameter is always the input index 2
1475  // and it should be optional
1476  ActivationFn activationFunction;
1477  if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
1478  {
1479  return Fail("%s: Operation has invalid inputs", __func__);
1480  }
1481 
1482  const Operand* output = GetOutputOperand(operation, 0, model);
1483  if (!output)
1484  {
1485  return Fail("%s: Could not read output 0", __func__);
1486  }
1487 
1488  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1489 
1490  bool isSupported = false;
1491  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1492  {
1493  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1495  data.m_Backends,
1496  isSupported,
1497  input0.GetTensorInfo(),
1498  input1.GetTensorInfo(),
1499  outputInfo);
1500  };
1501 
1502  if(!IsDynamicTensor(outputInfo))
1503  {
1504  validateFunc(outputInfo, isSupported);
1505  }
1506  else
1507  {
1508  isSupported = AreDynamicTensorsSupported();
1509  }
1510 
1511  if (!isSupported)
1512  {
1513  return false;
1514  }
1515 
1516  armnn::IConnectableLayer* const startLayer = data.m_Network->AddDivisionLayer();
1517 
1518  bool isReshapeSupported = BroadcastTensor(input0, input1, startLayer, data);
1519  if (!isReshapeSupported)
1520  {
1521  return false;
1522  }
1523 
1524  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
1525  data, nullptr, validateFunc, activationFunction);
1526 }
1527 
1528 bool Converter::ConvertElementwiseUnary(const Operation& operation,
1529  const Model& model,
1530  ConversionData& data,
1531  UnaryOperation unaryOperation)
1532 {
1533  VLOG(DRIVER) << "Converter::ConvertElementwiseUnary()";
1534  VLOG(DRIVER) << "unaryOperation = " << GetUnaryOperationAsCString(unaryOperation);
1535 
1536  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1537 
1538  if (!input.IsValid())
1539  {
1540  return Fail("%s: Operation has invalid input", __func__);
1541  }
1542 
1543  const Operand* output = GetOutputOperand(operation, 0, model);
1544  if (!output)
1545  {
1546  return Fail("%s: Could not read output 0", __func__);
1547  }
1548 
1549  const TensorInfo& inputInfo = input.GetTensorInfo();
1550  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1551 
1552  ElementwiseUnaryDescriptor descriptor(unaryOperation);
1553 
1554  bool isSupported = false;
1555 
1556  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1557  {
1558  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1559  IsElementwiseUnarySupported,
1560  data.m_Backends,
1561  isSupported,
1562  inputInfo,
1563  outputInfo,
1564  descriptor);
1565  };
1566 
1567  if(!IsDynamicTensor(outputInfo))
1568  {
1569  validateFunc(outputInfo, isSupported);
1570  }
1571  else
1572  {
1573  isSupported = AreDynamicTensorsSupported();
1574  }
1575 
1576  if (!isSupported)
1577  {
1578  return false;
1579  }
1580 
1581  IConnectableLayer* layer = data.m_Network->AddElementwiseUnaryLayer(descriptor);
1582  assert(layer != nullptr);
1583  input.Connect(layer->GetInputSlot(0));
1584 
1585  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
1586 }
1587 
1588 bool Converter::ConvertElu(const Operation& operation, const Model& model, ConversionData& data)
1589 {
1590  VLOG(DRIVER) << "Converter::ConvertElu()";
1591 
1592  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
1593  if (!input0.IsValid())
1594  {
1595  return Fail("%s: Operation has invalid inputs", __func__);
1596  }
1597 
1598  // Determine data type of input tensor
1599  OperandType inputType;
1600  if (!GetOperandType(operation, 0, model, inputType))
1601  {
1602  return Fail("%s: Operation has invalid inputs", __func__);
1603  }
1604 
1605  ActivationDescriptor desc;
1606  desc.m_Function = ActivationFunction::Elu;
1607 
1608  // Read alpha
1609  if (inputType == OperandType::TENSOR_FLOAT16)
1610  {
1611  Half alpha;
1612 
1613  if (!GetInputScalar(operation, 1, OperandType::FLOAT16, alpha, model, data))
1614  {
1615  return Fail("%s: Operation has invalid inputs (FLOAT16)", __func__);
1616  }
1617 
1618  desc.m_A = static_cast<float>(alpha);
1619  }
1620  else if (inputType == OperandType::TENSOR_FLOAT32)
1621  {
1622  if (!GetInputScalar(operation, 1, OperandType::FLOAT32, desc.m_A, model, data))
1623  {
1624  return Fail("%s: Operation has invalid inputs (FLOAT32)", __func__);
1625  }
1626  }
1627  else
1628  {
1629  return Fail("%s: Unsupported input tensor type: %d", __func__, inputType);
1630  }
1631 
1632  return ::ConvertToActivation(operation, __func__, desc, model, data);
1633 }
1634 
1635 bool Converter::ConvertExpandDims(const Operation& operation, const Model& model, ConversionData& data)
1636 {
1637  VLOG(DRIVER) << "Converter::ConvertExpandDims()";
1638 
1639  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1640 
1641  if (!input.IsValid())
1642  {
1643  return Fail("%s: Operation has invalid input", __func__);
1644  }
1645 
1646  const Operand* output = GetOutputOperand(operation, 0, model);
1647  if (!output)
1648  {
1649  return Fail("%s: Operation has invalid output", __func__);
1650  }
1651 
1652  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1653 
1654  int32_t axis;
1655  if (!GetInputScalar(operation, 1, OperandType::INT32, axis, model, data))
1656  {
1657  return Fail("%s: failed to get axis input value", __func__);
1658  }
1659 
1660  TensorShape targetShape;
1661 
1662  try
1663  {
1664  targetShape = armnnUtils::ExpandDims(input.GetTensorInfo().GetShape(), axis);
1665  }
1666  catch (const std::exception& e)
1667  {
1668  return Fail("%s: %s", __func__, e.what());
1669  }
1670 
1671  ReshapeDescriptor reshapeDescriptor;
1672  reshapeDescriptor.m_TargetShape = targetShape;
1673 
1674  bool isSupported = false;
1675  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1676  {
1677  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1679  data.m_Backends,
1680  isSupported,
1681  input.GetTensorInfo(),
1682  outputInfo,
1683  reshapeDescriptor);
1684  };
1685 
1686  if(!IsDynamicTensor(outputInfo))
1687  {
1688  if (targetShape != outputInfo.GetShape())
1689  {
1690  return Fail("%s: Shape of the output operand does not match the resolved expanded shape", __func__);
1691  }
1692  validateFunc(outputInfo, isSupported);
1693  }
1694  else
1695  {
1696  isSupported = AreDynamicTensorsSupported();
1697  }
1698 
1699  if (!isSupported)
1700  {
1701  return false;
1702  }
1703 
1704  IConnectableLayer* layer = data.m_Network->AddReshapeLayer(reshapeDescriptor);
1705  assert(layer != nullptr);
1706  input.Connect(layer->GetInputSlot(0));
1707 
1708  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
1709 }
1710 
1711 bool Converter::ConvertFill(const Operation& operation, const Model& model, ConversionData& data)
1712 {
1713  VLOG(DRIVER) << "Converter::ConvertFill()";
1714  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1715  if (!input.IsValid())
1716  {
1717  return Fail("%s: Operation has invalid inputs", __func__);
1718  }
1719 
1720  const Operand* output = GetOutputOperand(operation, 0, model);
1721  if (!output)
1722  {
1723  return Fail("%s: Could not read output", __func__);
1724  }
1725 
1726  const TensorInfo& inputInfo = input.GetTensorInfo();
1727  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1728  if (IsDynamicTensor(outputInfo))
1729  {
1730  return Fail("%s: Dynamic output tensors are not supported", __func__);
1731  }
1732 
1733  // Determine data type of output tensor
1734  OperandType outputType = output->type;
1735  FillDescriptor descriptor;
1736  // Read the scalar fill value
1737  if (outputType == OperandType::TENSOR_FLOAT16)
1738  {
1739  Half value;
1740 
1741  if (!GetInputScalar(operation, 1, OperandType::FLOAT16, value, model, data))
1742  {
1743  return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
1744  }
1745 
1746  descriptor.m_Value = static_cast<float>(value);
1747  }
1748  else if (outputType == OperandType::TENSOR_FLOAT32)
1749  {
1750  if (!GetInputScalar(operation, 1, OperandType::FLOAT32, descriptor.m_Value, model, data))
1751  {
1752  return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
1753  }
1754  }
1755  else if (outputType == OperandType::TENSOR_INT32)
1756  {
1757  int32_t value;
1758 
1759  if (!GetInputScalar(operation, 1, OperandType::INT32, value, model, data))
1760  {
1761  return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
1762  }
1763 
1764  descriptor.m_Value = static_cast<float>(value);
1765  }
1766  else
1767  {
1768  return Fail("%s: Unsupported input tensor type: %d", __func__, outputType);
1769  }
1770 
1771  bool isSupported = false;
1772  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1773  IsFillSupported,
1774  data.m_Backends,
1775  isSupported,
1776  inputInfo,
1777  outputInfo,
1778  descriptor);
1779  if (!isSupported)
1780  {
1781  return false;
1782  }
1783 
1784  IConnectableLayer* const layer = data.m_Network->AddFillLayer(descriptor);
1785  assert(layer != nullptr);
1786  input.Connect(layer->GetInputSlot(0));
1787 
1788  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
1789 }
1790 
1791 bool Converter::ConvertFloor(const Operation& operation, const Model& model, ConversionData& data)
1792 {
1793  VLOG(DRIVER) << "Converter::ConvertFloor()";
1794  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1795  if (!input.IsValid())
1796  {
1797  return Fail("%s: Operation has invalid inputs", __func__);
1798  }
1799 
1800  const Operand* const outputOperand = GetOutputOperand(operation, 0, model);
1801  if (!outputOperand)
1802  {
1803  return Fail("%s: Operation has invalid outputs", __func__);
1804  }
1805 
1806  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
1807 
1808  bool isSupported = false;
1809  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1810  {
1811  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1813  data.m_Backends,
1814  isSupported,
1815  input.GetTensorInfo(),
1816  outputInfo);
1817  };
1818 
1819  if(!IsDynamicTensor(outputInfo))
1820  {
1821  validateFunc(outputInfo, isSupported);
1822  }
1823  else
1824  {
1825  isSupported = AreDynamicTensorsSupported();
1826  }
1827 
1828  if (!isSupported)
1829  {
1830  return false;
1831  }
1832 
1833  armnn::IConnectableLayer* layer = data.m_Network->AddFloorLayer();
1834  assert(layer != nullptr);
1835  input.Connect(layer->GetInputSlot(0));
1836 
1837  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
1838 }
1839 
1840 bool Converter::ConvertFullyConnected(const Operation& operation, const Model& model, ConversionData& data)
1841 {
1842  VLOG(DRIVER) << "Converter::ConvertFullyConnected()";
1843  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1844  if (!input.IsValid())
1845  {
1846  return Fail("%s: Operation has invalid inputs", __func__);
1847  }
1848 
1849  const Operand* output = GetOutputOperand(operation, 0, model);
1850  if (!output)
1851  {
1852  return Fail("%s: Could not read output 0", __func__);
1853  }
1854 
1855  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1856  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1857 
1858  LayerInputHandle weightsInput = LayerInputHandle();
1859  const Operand* weightsOperand = GetInputOperand(operation, 1, model);
1860  if (!weightsOperand)
1861  {
1862  return Fail("%s: Could not read weights", __func__);
1863  }
1864 
1865  // If weights are constant a separate constant layer will be created to store data.
1866  // Otherwise handle non const weights as inputs.
1867  weightsInput = ConvertToLayerInputHandle(operation, 1, model, data);
1868  if (!weightsInput.IsValid())
1869  {
1870  return Fail("%s: Operation has invalid inputs", __func__);
1871  }
1872 
1873  LayerInputHandle biasInput = LayerInputHandle();
1874  const Operand* biasOperand = GetInputOperand(operation, 2, model);
1875  if (!biasOperand)
1876  {
1877  return Fail("%s: Could not read bias", __func__);
1878  }
1879 
1880  // If bias are constant a separate constant layer will be created to store data.
1881  // Otherwise handle non const bias as inputs.
1882  biasInput = ConvertToLayerInputHandle(operation, 2, model, data); // 1D
1883  if (!biasInput.IsValid())
1884  {
1885  return Fail("%s: Operation has invalid inputs", __func__);
1886  }
1887 
1888  armnn::TensorInfo weightsInfo = weightsInput.GetTensorInfo();
1889  armnn::TensorInfo reshapedInfo = inputInfo;
1890  try
1891  {
1892  reshapedInfo.SetShape(FlattenFullyConnectedInput(inputInfo.GetShape(), weightsInfo.GetShape()));
1893  }
1894  catch (const std::exception& e)
1895  {
1896  return Fail("%s: %s", __func__, e.what());
1897  }
1898 
1899  // Ensuring that the bias value is within 1% of the weights input (small float differences can exist)
1900  armnn::TensorInfo biasInfo = biasInput.GetTensorInfo();
1901  SanitizeBiasQuantizationScale(biasInfo, weightsInfo, reshapedInfo);
1902 
1903  ActivationFn activationFunction;
1904  if (!GetInputActivationFunction(operation, 3, activationFunction, model, data))
1905  {
1906  return Fail("%s: Operation has invalid inputs", __func__);
1907  }
1908 
1910  desc.m_TransposeWeightMatrix = true;
1911  desc.m_BiasEnabled = true;
1912  desc.m_ConstantWeights = IsOperandConstant(*weightsOperand);
1913 
1914  bool isSupported = false;
1915  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
1916  {
1917  if (!VerifyFullyConnectedShapes(reshapedInfo.GetShape(),
1918  weightsInfo.GetShape(),
1919  outputInfo.GetShape(),
1921  {
1922  isSupported = false;
1923  Fail("%s: Expected outputShape does not match actual outputShape", __func__);
1924  return;
1925  }
1926 
1927  FORWARD_LAYER_SUPPORT_FUNC(__func__,
1929  data.m_Backends,
1930  isSupported,
1931  reshapedInfo,
1932  outputInfo,
1933  weightsInfo,
1934  biasInfo,
1935  desc);
1936  };
1937 
1938  if(!IsDynamicTensor(outputInfo))
1939  {
1940  validateFunc(outputInfo, isSupported);
1941  }
1942  else
1943  {
1944  isSupported = AreDynamicTensorsSupported();
1945  }
1946 
1947  if (!isSupported)
1948  {
1949  return false;
1950  }
1951 
1952  // Add FullyConnected layer. Weights and bias will be connected as constant layers or non const inputs.
1953  armnn::IConnectableLayer* startLayer = data.m_Network->AddFullyConnectedLayer(desc);
1954 
1955  if (inputInfo.GetNumDimensions() > 2U)
1956  {
1957  armnn::ReshapeDescriptor reshapeDescriptor;
1958  reshapeDescriptor.m_TargetShape = reshapedInfo.GetShape();
1959 
1960  armnn::IConnectableLayer* reshapeLayer = data.m_Network->AddReshapeLayer(reshapeDescriptor);
1961  assert(reshapeLayer != nullptr);
1962  input.Connect(reshapeLayer->GetInputSlot(0));
1963  reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
1964  reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
1965  }
1966  else
1967  {
1968  input.Connect(startLayer->GetInputSlot(0));
1969  }
1970 
1971  // Connect weights and bias inputs
1972  weightsInput.Connect(startLayer->GetInputSlot(1));
1973  biasInput.Connect(startLayer->GetInputSlot(2));
1974 
1975  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
1976  data, nullptr, validateFunc, activationFunction);
1977 }
1978 
1979 bool Converter::ConvertGather(const Operation& operation, const Model& model, ConversionData& data)
1980 {
1981  VLOG(DRIVER) << "Converter::ConvertGather()";
1982 
1983  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1984  if (!input.IsValid())
1985  {
1986  return Fail("%s: Operation has invalid input", __func__);
1987  }
1988  auto inputDimensions = input.GetTensorInfo().GetNumDimensions();
1989 
1990  LayerInputHandle indices = ConvertToLayerInputHandle(operation, 2, model, data);
1991  if (!indices.IsValid())
1992  {
1993  return Fail("%s: Operation has invalid indices", __func__);
1994  }
1995  auto indicesDimensions = indices.GetTensorInfo().GetNumDimensions();
1996 
1997  const Operand* output = GetOutputOperand(operation, 0, model);
1998  if (!output)
1999  {
2000  return Fail("%s: Operation has invalid output", __func__);
2001  }
2002  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2003  auto outputDimensions = outputInfo.GetNumDimensions();
2004  if (outputDimensions != inputDimensions + indicesDimensions - 1)
2005  {
2006  return Fail("%s: Operation has invalid output dimensions: %d. Output must be an (%d + %d - 1)-D tensor",
2007  __func__, outputDimensions, inputDimensions, indicesDimensions);
2008  }
2009 
2010  int32_t axis;
2011  if (!GetInputScalar(operation, 1, OperandType::INT32, axis, model, data))
2012  {
2013  return Fail("%s: Operation has invalid or unsupported axis operand", __func__);
2014  }
2015  if (((axis < -inputDimensions) && (axis < 0)) || ((axis >= inputDimensions) && (axis > 0)))
2016  {
2017  return Fail("%s: Operation has invalid axis: %d. It is out of bounds [-%d, %d))", __func__, axis,
2018  inputDimensions, inputDimensions);
2019  }
2020 
2021  GatherDescriptor desc;
2022  desc.m_Axis = axis;
2023 
2024  bool isSupported = false;
2025  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2026  {
2027  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2028  IsGatherSupported,
2029  data.m_Backends,
2030  isSupported,
2031  input.GetTensorInfo(),
2032  indices.GetTensorInfo(),
2033  outputInfo,
2034  desc);
2035  };
2036 
2037  if(!IsDynamicTensor(outputInfo))
2038  {
2039  validateFunc(outputInfo, isSupported);
2040  }
2041  else
2042  {
2043  isSupported = AreDynamicTensorsSupported();
2044  }
2045 
2046  if (!isSupported)
2047  {
2048  return false;
2049  }
2050 
2051  IConnectableLayer* layer = data.m_Network->AddGatherLayer(desc);
2052  assert(layer != nullptr);
2053  input.Connect(layer->GetInputSlot(0));
2054  indices.Connect(layer->GetInputSlot(1));
2055 
2056  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2057 }
2058 
2059 bool Converter::ConvertGroupedConv2d(const Operation& operation, const Model& model, ConversionData& data)
2060 {
2061  VLOG(DRIVER) << "Converter::ConvertGroupedConv2d()";
2062  //
2063  // Parse data
2064  //
2065  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2066  if (!input.IsValid())
2067  {
2068  return Fail("%s: Operation has invalid inputs", __func__);
2069  }
2070  const TensorInfo& inputInfo = input.GetTensorInfo();
2071 
2072  const Operand* output = GetOutputOperand(operation, 0, model);
2073  if (!output)
2074  {
2075  return Fail("%s: Could not read output 0", __func__);
2076  }
2077  TensorInfo outputInfo = GetTensorInfoForOperand(*output);
2078 
2079  // Look ahead to determine data layout
2080  DataLayout dataLayout = DataLayout::NHWC;
2081  if (operation.inputs.size() == 12)
2082  {
2083  dataLayout = OptionalDataLayout(operation, 11, model, data);
2084  }
2085  else
2086  {
2087  dataLayout = OptionalDataLayout(operation, 8, model, data);
2088  }
2089 
2090  // NOTE:
2091  // NNAPI weights are always OHWI, i.e. [depth_out, filter_height, filter_width, depth_group],
2092  // but Arm NN expects the filter's height and width indices to match the input's height and
2093  // width indices so when the DataLayout is NCHW, we need to permute the weights to OIHW
2094  const PermutationVector ohwiToOihw = { 0u, 2u, 3u, 1u };
2095  const ConstTensorPin weightsPin = (dataLayout == DataLayout::NCHW) ?
2097  model, data, ohwiToOihw) :
2098  ConvertOperationInputToConstTensorPin(operation, 1, model, data);
2099  const ConstTensorPin biasesPin =
2100  ConvertOperationInputToConstTensorPin(operation, 2, model, data);
2101  if (!weightsPin.IsValid() || !biasesPin.IsValid())
2102  {
2103  return Fail("%s: Operation has invalid inputs", __func__);
2104  }
2105 
2106  ConstTensor weights = weightsPin.GetConstTensor();
2107  ConstTensor biases = biasesPin.GetConstTensor();
2108  SanitizeBiasQuantizationScale(biases.GetInfo(), weights.GetInfo(), inputInfo);
2109 
2110  const TensorShape& inputShape = inputInfo.GetShape();
2111  const TensorShape& outputShape = outputInfo.GetShape();
2112  const TensorShape& weightsShape = weights.GetShape();
2113  const TensorShape& biasesShape = biases.GetShape();
2114 
2115  armnnUtils::DataLayoutIndexed dataLayoutIndexed(dataLayout);
2116  const unsigned int channelsIndex = dataLayoutIndexed.GetChannelsIndex();
2117  const unsigned int heightIndex = dataLayoutIndexed.GetHeightIndex();
2118  const unsigned int widthIndex = dataLayoutIndexed.GetWidthIndex();
2119 
2121  desc.m_DataLayout = dataLayout;
2122  desc.m_BiasEnabled = true;
2123 
2124  int numGroups;
2125  ActivationFn activation;
2126 
2127  if (operation.inputs.size() == 12)
2128  {
2129  if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data) ||
2130  !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data) ||
2131  !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data) ||
2132  !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data) ||
2133  !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data) ||
2134  !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data) ||
2135  !GetInputScalar(operation, 9, OperandType::INT32, numGroups, model, data) ||
2136  !GetInputActivationFunction(operation, 10, activation, model, data))
2137  {
2138  return Fail("%s: Operation has invalid inputs (explicit padding)", __func__);
2139  }
2140 
2141  }
2142  else if (operation.inputs.size() == 9)
2143  {
2144  ::android::nn::PaddingScheme paddingScheme;
2145  if (!GetInputPaddingScheme(operation, 3, paddingScheme, model, data) ||
2146  !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX, model, data) ||
2147  !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY, model, data) ||
2148  !GetInputScalar(operation, 6, OperandType::INT32, numGroups, model, data) ||
2149  !GetInputActivationFunction(operation, 7, activation, model, data))
2150  {
2151  return Fail("%s: Operation has invalid inputs (implicit padding)", __func__);
2152  }
2153 
2154  const uint32_t inputX = inputInfo.GetShape()[widthIndex];
2155  const uint32_t inputY = inputInfo.GetShape()[heightIndex];
2156 
2157  const uint32_t kernelX = weightsShape[widthIndex];
2158  const uint32_t kernelY = weightsShape[heightIndex];
2159 
2160  CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
2161  CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
2162  }
2163  else
2164  {
2165  return Fail("%s: Unsupported number of operation inputs", __func__);
2166  }
2167 
2168  // Equivalent to outputShape[channelsIndex], but we can't know the outputShape in the case of dynamic tensors
2169  const unsigned int outputChannels = weightsShape[0];
2170 
2171  const unsigned int channelsPerGroup = weightsShape[channelsIndex];
2172  const unsigned int channelMultiplier = outputChannels / numGroups;
2173 
2174  //
2175  // Validate all relevant inputs
2176  //
2177  if (numGroups <= 0)
2178  {
2179  return Fail("%s: Number of groups must be greater than 0. Got: %d", __func__, numGroups);
2180  }
2181 
2182  if (outputChannels % numGroups != 0u)
2183  {
2184  return Fail("%s: Output channels must be divisible by the number of groups", __func__);
2185  }
2186 
2187  //
2188  // Set up Splitter layer
2189  //
2190  unsigned int splitterDimSizes[4] = { inputShape[0], inputShape[1], inputShape[2], inputShape[3] };
2191  splitterDimSizes[channelsIndex] /= numGroups; // split in depth
2192 
2193  TensorInfo splitterOutputInfo(4,
2194  splitterDimSizes,
2195  inputInfo.GetDataType(),
2196  inputInfo.GetQuantizationScale(),
2197  inputInfo.GetQuantizationOffset());
2198 
2199  std::vector<std::reference_wrapper<TensorInfo>> splitterOutputInfos(numGroups, std::ref(splitterOutputInfo));
2200 
2201  ViewsDescriptor splitterDesc(numGroups);
2202  for (unsigned int group = 0u; group < numGroups; ++group)
2203  {
2204  splitterDesc.SetViewOriginCoord(group, channelsIndex, splitterDimSizes[channelsIndex] * group);
2205  for (unsigned int dimIdx = 0u; dimIdx < 4u; dimIdx++)
2206  {
2207  splitterDesc.SetViewSize(group, dimIdx, splitterDimSizes[dimIdx]);
2208  }
2209  }
2210 
2211  bool isSupported = false;
2212  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2214  data.m_Backends,
2215  isSupported,
2216  inputInfo,
2217  splitterOutputInfos,
2218  splitterDesc);
2219  if (!isSupported)
2220  {
2221  return false;
2222  }
2223 
2224  IConnectableLayer* splitterLayer = data.m_Network->AddSplitterLayer(splitterDesc);
2225  if (!splitterLayer)
2226  {
2227  return Fail("%s: Failed to add SplitterLayer", __func__);
2228  }
2229 
2230  input.Connect(splitterLayer->GetInputSlot(0));
2231  for (unsigned int group = 0u; group < splitterLayer->GetNumOutputSlots(); ++group)
2232  {
2233  splitterLayer->GetOutputSlot(group).SetTensorInfo(splitterOutputInfo);
2234  }
2235 
2236  //
2237  // Set up Convolution2d layers for each group
2238  //
2239 
2240  // Set up group tensor shapes
2241  TensorShape groupInputShape(inputShape);
2242  groupInputShape[channelsIndex] = channelsPerGroup;
2243 
2244  TensorShape groupWeightsShape(weightsShape);
2245  groupWeightsShape[0] /= channelMultiplier * numGroups;
2246 
2247  TensorShape groupBiasesShape({ 1 });
2248 
2249  // Set up group tensor infos
2250  TensorInfo groupInputInfo(inputInfo);
2251  groupInputInfo.SetShape(groupInputShape);
2252 
2253  const TensorInfo& weightsInfo = weights.GetInfo();
2254  TensorInfo groupWeightsInfo(weightsInfo);
2255  groupWeightsInfo.SetShape(groupWeightsShape);
2256 
2257  const TensorInfo& biasesInfo = biases.GetInfo();
2258  TensorInfo groupBiasesInfo(biasesInfo);
2259  groupBiasesInfo.SetShape(groupBiasesShape);
2260 
2261  TensorInfo groupOutputInfo(outputInfo);
2262 
2263  TensorShape groupOutputShape(outputShape);
2264  const bool isDynamic = IsDynamicTensor(outputInfo);
2265  if (!isDynamic)
2266  {
2267  groupOutputShape[channelsIndex] = 1;
2268  }
2269  groupOutputInfo.SetShape(groupOutputShape);
2270 
2271  const unsigned int weightsDataTypeSize = GetDataTypeSize(groupWeightsInfo.GetDataType());
2272  const unsigned int biasesDataTypeSize = GetDataTypeSize(groupBiasesInfo.GetDataType());
2273 
2274  std::vector<IConnectableLayer*> convLayers(numGroups * channelMultiplier, nullptr);
2275  for (unsigned int group = 0u; group < numGroups; ++group)
2276  {
2277  for (unsigned int m = 0u; m < channelMultiplier; ++m)
2278  {
2279  auto index = group * channelMultiplier + m;
2280 
2281  const unsigned int weightsDataOffset = groupWeightsShape.GetNumElements() * index * weightsDataTypeSize;
2282  const unsigned int biasesDataOffset = groupBiasesShape.GetNumElements() * index * biasesDataTypeSize;
2283 
2284  if (weightsInfo.HasPerAxisQuantization())
2285  {
2286  // Extract per-axis quantization scales for group weights
2287  const std::vector<float>& weightsQuantScales = weightsInfo.GetQuantizationScales();
2288  groupWeightsInfo.SetQuantizationScales(
2289  std::vector<float>(weightsQuantScales.begin() + index,
2290  weightsQuantScales.begin() + index + groupWeightsShape[0]));
2291 
2292  // Extract per-axis quantization scales for group biases
2293  const std::vector<float>& biasesQuantScales = biasesInfo.GetQuantizationScales();
2294  groupBiasesInfo.SetQuantizationScales(
2295  std::vector<float>(biasesQuantScales.begin() + index,
2296  biasesQuantScales.begin() + index + groupWeightsShape[0]));
2297  }
2298 
2299  // Extract weights and biases data for current group convolution
2300  ConstTensor groupWeights(groupWeightsInfo,
2301  static_cast<const void *>(reinterpret_cast<const char *>(weights.GetMemoryArea()) +
2302  weightsDataOffset));
2303  ConstTensor groupBiases(groupBiasesInfo,
2304  static_cast<const void *>(reinterpret_cast<const char *>(biases.GetMemoryArea()) +
2305  biasesDataOffset));
2306 
2307  isSupported = false;
2308  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2309  {
2310  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2312  data.m_Backends,
2313  isSupported,
2314  groupInputInfo,
2315  outputInfo,
2316  desc,
2317  groupWeightsInfo,
2318  Optional<TensorInfo>(groupBiasesInfo));
2319  };
2320 
2321  if(!isDynamic)
2322  {
2323  validateFunc(groupOutputInfo, isSupported);
2324  }
2325  else
2326  {
2327  isSupported = AreDynamicTensorsSupported();
2328  }
2329 
2330  if (!isSupported)
2331  {
2332  return false;
2333  }
2334 
2335  IConnectableLayer* weightsLayer = data.m_Network->AddConstantLayer(groupWeights);
2336  IConnectableLayer* biasLayer = data.m_Network->AddConstantLayer(groupBiases);
2337  IConnectableLayer* convLayer = data.m_Network->AddConvolution2dLayer(desc);
2338 
2339  if (!convLayer)
2340  {
2341  return Fail("%s: AddConvolution2dLayer failed", __func__);
2342  }
2343 
2344  splitterLayer->GetOutputSlot(group).Connect(convLayer->GetInputSlot(0));
2345  weightsLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(1));
2346  biasLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(2));
2347 
2348  weightsLayer->GetOutputSlot(0).SetTensorInfo(groupWeightsInfo);
2349  biasLayer->GetOutputSlot(0).SetTensorInfo(groupBiasesInfo);
2350  convLayer->GetOutputSlot(0).SetTensorInfo(groupOutputInfo);
2351 
2352  if(isDynamic)
2353  {
2354  convLayer->GetOutputSlot(0).IsTensorInfoSet();
2355 
2356  validateFunc(convLayer->GetOutputSlot(0).GetTensorInfo(), isSupported);
2357 
2358  outputInfo = convLayer->GetOutputSlot(0).GetTensorInfo();
2359 
2360  if (!isSupported)
2361  {
2362  return false;
2363  }
2364  }
2365 
2366  convLayers[index] = convLayer;
2367  }
2368  }
2369 
2370  //
2371  // Set up Concat layer
2372  //
2373  ConcatDescriptor concatDescriptor;
2374  // Equivalent to outputShape[channelsIndex], but we can't know the outputShape in the case of dynamic tensors
2375  concatDescriptor = ConcatDescriptor(weightsShape[0]);
2376  for (unsigned int group = 0u; group < numGroups; ++group)
2377  {
2378  for (unsigned int m = 0u; m < channelMultiplier; ++m)
2379  {
2380  auto index = group * channelMultiplier + m;
2381  concatDescriptor.SetViewOriginCoord(index, channelsIndex, index);
2382  concatDescriptor.SetConcatAxis(channelsIndex);
2383  }
2384  }
2385 
2386  isSupported = false;
2387  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2389  data.m_Backends,
2390  isSupported,
2391  std::vector<const TensorInfo*>(numGroups * channelMultiplier, &groupOutputInfo),
2392  outputInfo,
2393  concatDescriptor);
2394 
2395  if (!isSupported)
2396  {
2397  return false;
2398  }
2399 
2400  IConnectableLayer* concatLayer = data.m_Network->AddConcatLayer(concatDescriptor);
2401  if (!concatLayer)
2402  {
2403  return Fail("%s: AddConcatLayer failed", __func__);
2404  }
2405 
2406  for (unsigned int group = 0u; group < numGroups; ++group)
2407  {
2408  for (unsigned int m = 0u; m < channelMultiplier; ++m)
2409  {
2410  auto index = group * channelMultiplier + m;
2411  convLayers[index]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(index));
2412  }
2413  }
2414  concatLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2415 
2416  return SetupAndTrackLayerOutputSlot(operation, 0, *concatLayer, model,
2417  data, nullptr, nullptr, activation);
2418 }
2419 
2420 bool Converter::ConvertHardSwish(const Operation& operation, const Model& model, ConversionData& data)
2421 {
2422  VLOG(DRIVER) << "Converter::ConvertHardSwish()";
2423  ActivationDescriptor desc;
2424  desc.m_Function = ActivationFunction::HardSwish;
2425 
2426  return ::ConvertToActivation(operation, __func__, desc, model, data);
2427 }
2428 
2429 bool Converter::ConvertInstanceNormalization(const Operation& operation, const Model& model, ConversionData& data)
2430 {
2431  VLOG(DRIVER) << "Converter::ConvertInstanceNormalization()";
2432 
2433  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2434  if (!input.IsValid())
2435  {
2436  return Fail("%s: Operation has an invalid input 0", __func__);
2437  }
2438 
2439  const Operand* output = GetOutputOperand(operation, 0, model);
2440  if (!output)
2441  {
2442  return Fail("%s: Operation has an invalid output", __func__);
2443  }
2444 
2445  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2446 
2447  // Determine data type of input tensor
2448  OperandType inputType;
2449  if (!GetOperandType(operation, 0, model, inputType))
2450  {
2451  return Fail("%s: Operation has invalid inputs", __func__);
2452  }
2453 
2455 
2456  // Read gamma, beta & epsilon
2457  if (inputType == OperandType::TENSOR_FLOAT16)
2458  {
2459  Half fp16Gamma;
2460  Half fp16Beta;
2461  Half fp16Epsilon;
2462 
2463  if (!GetInputScalar(operation, 1, OperandType::FLOAT16, fp16Gamma, model, data) ||
2464  !GetInputScalar(operation, 2, OperandType::FLOAT16, fp16Beta, model, data) ||
2465  !GetInputScalar(operation, 3, OperandType::FLOAT16, fp16Epsilon, model, data))
2466  {
2467  return Fail("%s: Operation has invalid inputs (FLOAT16)", __func__);
2468  }
2469 
2470  desc.m_Gamma = static_cast<float>(fp16Gamma);
2471  desc.m_Beta = static_cast<float>(fp16Beta);
2472  desc.m_Eps = static_cast<float>(fp16Epsilon);
2473  }
2474  else if (inputType == OperandType::TENSOR_FLOAT32)
2475  {
2476  if (!GetInputScalar(operation, 1, OperandType::FLOAT32, desc.m_Gamma, model, data) ||
2477  !GetInputScalar(operation, 2, OperandType::FLOAT32, desc.m_Beta, model, data) ||
2478  !GetInputScalar(operation, 3, OperandType::FLOAT32, desc.m_Eps, model, data))
2479  {
2480  return Fail("%s: Operation has invalid inputs (FLOAT32)", __func__);
2481  }
2482  }
2483  else
2484  {
2485  return Fail("%s: Unsupported input tensor type: %d", __func__, inputType);
2486  }
2487 
2488  desc.m_DataLayout = OptionalDataLayout(operation, 4, model, data);
2489 
2490  bool isSupported = false;
2491  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2492  {
2493  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2494  IsInstanceNormalizationSupported,
2495  data.m_Backends,
2496  isSupported,
2497  input.GetTensorInfo(),
2498  outputInfo,
2499  desc);
2500  };
2501 
2502  if(IsDynamicTensor(outputInfo))
2503  {
2504  isSupported = AreDynamicTensorsSupported();
2505  }
2506  else
2507  {
2508  validateFunc(outputInfo, isSupported);
2509  }
2510 
2511  if (!isSupported)
2512  {
2513  return false;
2514  }
2515 
2516  IConnectableLayer* layer = data.m_Network->AddInstanceNormalizationLayer(desc);
2517  input.Connect(layer->GetInputSlot(0));
2518 
2519  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2520 }
2521 
2522 bool Converter::ConvertL2Normalization(const Operation& operation, const Model& model, ConversionData& data)
2523 {
2524  VLOG(DRIVER) << "Converter::ConvertL2Normalization()";
2525 
2526  if (operation.inputs.size() != 1)
2527  {
2528  return Fail("%s: Optional inputs are not supported", __func__);
2529  }
2530 
2531  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2532  if (!input.IsValid())
2533  {
2534  return Fail("%s: Operation has invalid inputs", __func__);
2535  }
2536 
2537  const Operand* output = GetOutputOperand(operation, 0, model);
2538  if (!output)
2539  {
2540  return Fail("%s: Could not read output 0", __func__);
2541  }
2542 
2543  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
2544  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2545 
2546  if (outputInfo.GetNumDimensions() != 4u)
2547  {
2548  return Fail("%s: Tensor Rank other than 4 is not supported", __func__);
2549  }
2550 
2553 
2554  bool isSupported = false;
2555  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2556  {
2557  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2559  data.m_Backends,
2560  isSupported,
2561  inputInfo,
2562  outputInfo,
2563  desc);
2564  };
2565 
2566  if(!IsDynamicTensor(outputInfo))
2567  {
2568  validateFunc(outputInfo, isSupported);
2569  }
2570  else
2571  {
2572  isSupported = AreDynamicTensorsSupported();
2573  }
2574 
2575  if (!isSupported)
2576  {
2577  return false;
2578  }
2579 
2580  armnn::IConnectableLayer* layer = data.m_Network->AddL2NormalizationLayer(desc);
2581  assert(layer != nullptr);
2582  input.Connect(layer->GetInputSlot(0));
2583 
2584  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2585 }
2586 
2587 bool Converter::ConvertL2Pool2d(const Operation& operation, const Model& model, ConversionData& data)
2588 {
2589  VLOG(DRIVER) << "Converter::ConvertL2Pool2d()";
2590  return ConvertPooling2d(operation, __func__, PoolingAlgorithm::L2, model, data);
2591 }
2592 
2593 bool Converter::ConvertLocalResponseNormalization(const Operation& operation,
2594  const Model& model,
2595  ConversionData& data)
2596 {
2597  VLOG(DRIVER) << "Converter::ConvertLocalResponseNormalization()";
2598 
2599  if (operation.inputs.size() != 5)
2600  {
2601  return Fail("%s: Optional inputs are not supported", __func__);
2602  }
2603 
2604  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2605  if (!input.IsValid())
2606  {
2607  return Fail("%s: Operation has invalid inputs", __func__);
2608  }
2609 
2610  const Operand* output = GetOutputOperand(operation, 0, model);
2611  if (!output)
2612  {
2613  return Fail("%s: Could not read output 0", __func__);
2614  }
2615 
2616  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
2617  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2618 
2619  if (outputInfo.GetNumDimensions() != 4u)
2620  {
2621  return Fail("%s: Tensor Rank other than 4 is not supported", __func__);
2622  }
2623 
2624  armnn::NormalizationDescriptor descriptor;
2628 
2629  if (!input.IsValid() ||
2630  !GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_NormSize, model, data) ||
2631  !GetInputFloat32(operation, 2, descriptor.m_K, model, data) ||
2632  !GetInputFloat32(operation, 3, descriptor.m_Alpha, model, data) ||
2633  !GetInputFloat32(operation, 4, descriptor.m_Beta, model, data))
2634  {
2635  return Fail("%s: Operation has invalid inputs", __func__);
2636  }
2637 
2638  // ArmNN expects normSize to be the full size of the normalization
2639  // window rather than the radius as in AndroidNN.
2640  descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize);
2641 
2642  bool isSupported = false;
2643  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2644  {
2645  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2647  data.m_Backends,
2648  isSupported,
2649  inputInfo,
2650  outputInfo,
2651  descriptor);
2652  };
2653 
2654  if(!IsDynamicTensor(outputInfo))
2655  {
2656  validateFunc(outputInfo, isSupported);
2657  }
2658  else
2659  {
2660  isSupported = AreDynamicTensorsSupported();
2661  }
2662 
2663  if (!isSupported)
2664  {
2665  return false;
2666  }
2667 
2668 
2669  armnn::IConnectableLayer* layer = data.m_Network->AddNormalizationLayer(descriptor);
2670  assert(layer != nullptr);
2671  input.Connect(layer->GetInputSlot(0));
2672 
2673  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2674 }
2675 
2676 bool Converter::ConvertLogicalBinary(const Operation& operation,
2677  const Model& model,
2678  ConversionData& data,
2679  armnn::LogicalBinaryOperation logicalOperation)
2680 {
2681  VLOG(DRIVER) << "Converter::ConvertLogicalBinary()";
2682  VLOG(DRIVER) << "ConvertLogicalBinary()";
2683  VLOG(DRIVER) << "logicalOperation = " << GetLogicalBinaryOperationAsCString(logicalOperation);
2684 
2685  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
2686  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
2687 
2688  if (!(input0.IsValid() && input1.IsValid()))
2689  {
2690  return Fail("%s: Operation has invalid inputs", __func__);
2691  }
2692 
2693  const Operand* output = GetOutputOperand(operation, 0, model);
2694  if (!output)
2695  {
2696  return Fail("%s: Could not read output 0", __func__);
2697  }
2698 
2699  const TensorInfo& inputInfo0 = input0.GetTensorInfo();
2700  const TensorInfo& inputInfo1 = input1.GetTensorInfo();
2701  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2702 
2703  LogicalBinaryDescriptor descriptor(logicalOperation);
2704 
2705  bool isSupported = false;
2706 
2707  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2708  {
2709  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2710  IsLogicalBinarySupported,
2711  data.m_Backends,
2712  isSupported,
2713  inputInfo0,
2714  inputInfo1,
2715  outputInfo,
2716  descriptor);
2717  };
2718 
2719  if(!IsDynamicTensor(outputInfo))
2720  {
2721  validateFunc(outputInfo, isSupported);
2722  }
2723  else
2724  {
2725  isSupported = AreDynamicTensorsSupported();
2726  }
2727 
2728  if (!isSupported)
2729  {
2730  return false;
2731  }
2732 
2733  IConnectableLayer* layer = data.m_Network->AddLogicalBinaryLayer(descriptor);
2734  assert(layer != nullptr);
2735 
2736  bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
2737  if (!isReshapeSupported)
2738  {
2739  return false;
2740  }
2741 
2742  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2743 }
2744 
2745 bool Converter::ConvertLogistic(const Operation& operation, const Model& model, ConversionData& data)
2746 {
2747  VLOG(DRIVER) << "Converter::ConvertLogistic()";
2750 
2751  return ConvertToActivation(operation, __func__, desc, model, data);
2752 }
2753 
2754 bool Converter::ConvertLogSoftmax(const Operation& operation, const Model& model, ConversionData& data)
2755 {
2756  VLOG(DRIVER) << "Converter::ConvertLogSoftmax()";
2757 
2758  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2759  if (!input.IsValid())
2760  {
2761  return Fail("%s: Failed to read input 0", __func__);
2762  }
2763 
2764  const Operand* output = GetOutputOperand(operation, 0, model);
2765  if (!output)
2766  {
2767  return Fail("%s: Failed to read output", __func__);
2768  }
2769 
2770  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2771 
2772  // Determine data type of input tensor
2773  OperandType inputType;
2774  if (!GetOperandType(operation, 0, model, inputType))
2775  {
2776  return Fail("%s: Operation has invalid inputs", __func__);
2777  }
2778 
2779  LogSoftmaxDescriptor descriptor;
2780 
2781  // Read beta
2782  if (inputType == OperandType::TENSOR_FLOAT16)
2783  {
2784  Half fp16Beta;
2785  if (!GetInputScalar(operation, 1, OperandType::FLOAT16, fp16Beta, model, data))
2786  {
2787  return Fail("%s: Failed to read input 1 (FLOAT16)", __func__);
2788  }
2789 
2790  descriptor.m_Beta = static_cast<float>(fp16Beta);
2791  }
2792  else if (inputType == OperandType::TENSOR_FLOAT32)
2793  {
2794  if (!GetInputScalar(operation, 1, OperandType::FLOAT32, descriptor.m_Beta, model, data))
2795  {
2796  return Fail("%s: Failed to read input 1 (FLOAT32)", __func__);
2797  }
2798  }
2799  else
2800  {
2801  return Fail("%s: Unsupported input tensor type: %d", __func__, inputType);
2802  }
2803 
2804  // Read axis
2805  if (!GetInputInt32(operation, 2, descriptor.m_Axis, model, data))
2806  {
2807  return Fail("%s: Failed to read input 2", __func__);
2808  }
2809 
2810  bool isSupported = false;
2811  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2812  {
2813  FORWARD_LAYER_SUPPORT_FUNC(__func__,
2814  IsLogSoftmaxSupported,
2815  data.m_Backends,
2816  isSupported,
2817  input.GetTensorInfo(),
2818  outputInfo,
2819  descriptor);
2820  };
2821 
2822  if(IsDynamicTensor(outputInfo))
2823  {
2824  isSupported = AreDynamicTensorsSupported();
2825  }
2826  else
2827  {
2828  validateFunc(outputInfo, isSupported);
2829  }
2830 
2831  if (!isSupported)
2832  {
2833  return false;
2834  }
2835 
2836  IConnectableLayer* layer = data.m_Network->AddLogSoftmaxLayer(descriptor);
2837  if (!layer)
2838  {
2839  return Fail("%s: AddLogSoftmaxLayer() returned nullptr", __func__);
2840  }
2841 
2842  input.Connect(layer->GetInputSlot(0));
2843 
2844  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2845 }
2846 
2847 bool Converter::ConvertLstm(const Operation& operation, const Model& model, ConversionData& data)
2848 {
2849  VLOG(DRIVER) << "Converter::ConvertLstm()";
2850 
2851  // Inputs:
2852  // 00: The input: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, input_size], where
2853  // “batch_size” corresponds to the batching dimension, and “input_size” is the size of the input.
2854  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2855  if (!input.IsValid())
2856  {
2857  return Fail("%s: Could not read input 0: input", __func__);
2858  }
2859  // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
2860  LayerInputHandle outputStateIn = ConvertToLayerInputHandle(operation, 18, model, data);
2861  if (!outputStateIn.IsValid())
2862  {
2863  return Fail("%s: Could not read input 18: outputStateIn", __func__);
2864  }
2865  // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
2866  LayerInputHandle cellStateIn = ConvertToLayerInputHandle(operation, 19, model, data);
2867  if (!cellStateIn.IsValid())
2868  {
2869  return Fail("%s: Could not read input 19: cellStateIn", __func__);
2870  }
2871 
2872  // Get the mandatory input tensors:
2873  // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2874  // [num_units, input_size].
2875  const ConstTensorPin inputToForgetWeightsPin =
2876  (DequantizeAndMakeConstTensorPin(operation, model, data, 2));
2877  // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2878  // [num_units, input_size].
2879  const ConstTensorPin inputToCellWeightsPin =
2880  (DequantizeAndMakeConstTensorPin(operation, model, data, 3));
2881  // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2882  // [num_units, input_size].
2883  const ConstTensorPin inputToOutputWeightsPin =
2884  (DequantizeAndMakeConstTensorPin(operation, model, data, 4));
2885  // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2886  // [num_units, output_size].
2887  const ConstTensorPin recurrentToForgetWeightsPin =
2888  (DequantizeAndMakeConstTensorPin(operation, model, data, 6));
2889  // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2890  // [num_units, output_size].
2891  const ConstTensorPin recurrentToCellWeightsPin =
2892  (DequantizeAndMakeConstTensorPin(operation, model, data, 7));
2893  // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2894  // [num_units, output_size].
2895  const ConstTensorPin recurrentToOutputWeightsPin =
2896  (DequantizeAndMakeConstTensorPin(operation, model, data, 8));
2897  // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2898  const ConstTensorPin forgetGateBiasPin =
2899  ConvertOperationInputToConstTensorPin(operation, 13, model, data);
2900  // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2901  const ConstTensorPin cellBiasPin =
2902  ConvertOperationInputToConstTensorPin(operation, 14, model, data);
2903  // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2904  const ConstTensorPin outputGateBiasPin =
2905  ConvertOperationInputToConstTensorPin(operation, 15, model, data);
2906 
2907  if (!inputToForgetWeightsPin.IsValid() ||
2908  !inputToCellWeightsPin.IsValid() ||
2909  !inputToOutputWeightsPin.IsValid() ||
2910  !recurrentToForgetWeightsPin.IsValid() ||
2911  !recurrentToCellWeightsPin.IsValid() ||
2912  !recurrentToOutputWeightsPin.IsValid() ||
2913  !forgetGateBiasPin.IsValid() ||
2914  !cellBiasPin.IsValid() ||
2915  !outputGateBiasPin.IsValid())
2916  {
2917  return Fail("%s: Operation has invalid tensor inputs", __func__);
2918  }
2919 
2920  // Get the optional input tensors:
2921  // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2922  // [num_units, input_size], where “num_units” corresponds to the number of cell units.
2923  const ConstTensorPin inputToInputWeightsPin =
2924  (DequantizeAndMakeConstTensorPin(operation, model, data, 1, true));
2925  // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2926  // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
2927  // “num_units”), or the second dimension of the “projection_weights”, if defined.
2928  const ConstTensorPin recurrentToInputWeightsPin =
2929  (DequantizeAndMakeConstTensorPin(operation, model, data, 5, true));
2930  // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2931  const ConstTensorPin cellToInputWeightsPin =
2932  (DequantizeAndMakeConstTensorPin(operation, model, data, 9, true));
2933  // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2934  const ConstTensorPin cellToForgetWeightsPin =
2935  (DequantizeAndMakeConstTensorPin(operation, model, data, 10, true));
2936  // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2937  const ConstTensorPin cellToOutputWeightsPin =
2938  (DequantizeAndMakeConstTensorPin(operation, model, data, 11, true));
2939  // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2940  const ConstTensorPin inputGateBiasPin =
2942  12,
2943  model,
2944  data,
2945  g_DontPermute,
2946  nullptr,
2947  true);
2948 
2949  // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2950  // [output_size, num_units].
2951  const ConstTensorPin projectionWeightsPin =
2952  (DequantizeAndMakeConstTensorPin(operation, model, data, 16, true));
2953  // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [output_size].
2954  const ConstTensorPin projectionBiasPin =
2956  17,
2957  model,
2958  data,
2959  g_DontPermute,
2960  nullptr,
2961  true);
2962 
2963  if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional()) ||
2964  (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional()) ||
2965  (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional()) ||
2966  (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional()) ||
2967  (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional()) ||
2968  (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional()) ||
2969  (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional()) ||
2970  (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
2971  {
2972  return Fail("%s: Operation has invalid tensor inputs", __func__);
2973  }
2974 
2975  // Get the mandatory input scalars (actually 1-D tensors of size 1):
2976  // 20: The activation function: A value indicating the activation function:
2977  // 0: None; 1: Relu; 3: Relu6; 4: Tanh; 6: Sigmoid.
2978  // 21: The clipping threshold: for the cell state, such that values are bound within [-cell_clip, cell_clip].
2979  // If set to 0.0 then clipping is disabled.
2980  // 22: The clipping threshold: for the output from the projection layer, such that values are bound within
2981  // [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
2982  ActivationFn activation = ActivationFn::kActivationNone;
2983  float cellClip;
2984  float projClip;
2985  if (!GetInputActivationFunctionFromTensor(operation, 20, activation, model, data) ||
2986  !GetInputScalar(operation, 21, OperandType::FLOAT32, cellClip, model, data) ||
2987  !GetInputScalar(operation, 22, OperandType::FLOAT32, projClip, model, data))
2988  {
2989  return Fail("%s: Operation has invalid scalar inputs", __func__);
2990  }
2991 
2992  // Get the normalization tensors
2993  // 23: The input layer normalization weights. A 1-D tensor of shape [num_units].
2994  // Used to rescale normalized inputs to activation at input gate.
2995  const ConstTensorPin inputLayerNormWeightsPin
2996  (DequantizeAndMakeConstTensorPin(operation, model, data, 23, true));
2997 
2998  // 24: The forget layer normalization weights. A 1-D tensor of shape [num_units].
2999  // Used to rescale normalized inputs to activation at forget gate.
3000  const ConstTensorPin forgetLayerNormWeightsPin =
3002  24,
3003  model,
3004  data,
3005  g_DontPermute,
3006  nullptr,
3007  true);
3008 
3009  // 25: The cell layer normalization weights. A 1-D tensor of shape [num_units].
3010  // Used to rescale normalized inputs to activation at cell gate.
3011  const ConstTensorPin cellLayerNormWeightsPin =
3013  25,
3014  model,
3015  data,
3016  g_DontPermute,
3017  nullptr,
3018  true);
3019 
3020  // 26: The output layer normalization weights. A 1-D tensor of shape [num_units].
3021  // Used to rescale normalized inputs to activation at output gate.
3022  const ConstTensorPin outputLayerNormWeightsPin =
3024  26,
3025  model,
3026  data,
3027  g_DontPermute,
3028  nullptr,
3029  true);
3030 
3031  // Outputs:
3032  // 00: The scratch buffer: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units * 4]
3033  // with CIFG, or [batch_size, num_units * 3] without CIFG.
3034  const Operand* scratchBuffer = GetOutputOperand(operation, 0, model);
3035  if (!scratchBuffer)
3036  {
3037  return Fail("%s: Could not read output 0: scratchBuffer", __func__);
3038  }
3039  // 01: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
3040  const Operand* outputStateOut = GetOutputOperand(operation, 1, model);
3041  if (!outputStateOut)
3042  {
3043  return Fail("%s: Could not read output 1: outputStateOut", __func__);
3044  }
3045  // 02: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
3046  const Operand* cellStateOut = GetOutputOperand(operation, 2, model);
3047  if (!cellStateOut)
3048  {
3049  return Fail("%s: Could not read output 2: cellStateOut", __func__);
3050  }
3051  // 03: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. This is
3052  // effectively the same as the current “output state (out)” value.
3053  const Operand* output = GetOutputOperand(operation, 3, model);
3054  if (!output)
3055  {
3056  return Fail("%s: Could not read output 3: output", __func__);
3057  }
3058 
3059  // set the params structure for the AddLstmLayer call
3060  LstmInputParams params;
3061  params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
3062  params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
3063  params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
3064  params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
3065  params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
3066  params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
3067  params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
3068  params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
3069  params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
3070  params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
3071  params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
3072  params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
3073  params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
3074  params.m_CellBias = cellBiasPin.GetConstTensorPtr();
3075  params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
3076  params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
3077  params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
3078  params.m_InputLayerNormWeights = inputLayerNormWeightsPin.GetConstTensorPtr();
3079  params.m_ForgetLayerNormWeights = forgetLayerNormWeightsPin.GetConstTensorPtr();
3080  params.m_CellLayerNormWeights = cellLayerNormWeightsPin.GetConstTensorPtr();
3081  params.m_OutputLayerNormWeights = outputLayerNormWeightsPin.GetConstTensorPtr();
3082 
3083  // set the layer descriptor
3084  LstmDescriptor desc;
3085  desc.m_ActivationFunc = activation;
3086  desc.m_ClippingThresCell = cellClip;
3087  desc.m_ClippingThresProj = projClip;
3088  desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
3089  params.m_RecurrentToInputWeights == nullptr ||
3090  params.m_InputGateBias == nullptr);
3091  desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
3092  params.m_CellToOutputWeights != nullptr);
3093  desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
3094  desc.m_LayerNormEnabled = (params.m_InputLayerNormWeights != nullptr ||
3095  params.m_ForgetLayerNormWeights != nullptr ||
3096  params.m_CellLayerNormWeights != nullptr ||
3097  params.m_OutputLayerNormWeights != nullptr);
3098 
3099  // validate the optional input groups
3100  if (desc.m_CifgEnabled &&
3101  (params.m_InputToInputWeights != nullptr ||
3102  params.m_RecurrentToInputWeights != nullptr ||
3103  params.m_InputGateBias != nullptr))
3104  {
3105  return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
3106  " and input gate bias must be provided", __func__);
3107  }
3108 
3109  if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
3110  {
3111  return Fail("%s: projection bias should not be provided without projection weights", __func__);
3112  }
3113 
3114  if (desc.m_PeepholeEnabled &&
3115  (params.m_CellToForgetWeights == nullptr ||
3116  params.m_CellToOutputWeights == nullptr ||
3117  (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
3118  {
3119  return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
3120  " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
3121  }
3122 
3123  if (desc.m_LayerNormEnabled &&
3124  (params.m_ForgetLayerNormWeights == nullptr ||
3125  params.m_CellLayerNormWeights == nullptr ||
3126  params.m_OutputLayerNormWeights == nullptr ||
3127  (!desc.m_CifgEnabled && params.m_InputLayerNormWeights == nullptr)))
3128  {
3129  return Fail("%s: All, or none, of forget-norm weights, cell-norm weights and output-norm weights must be"
3130  " provided and, if CIFG is not enabled, input-norm weights must also be provided", __func__);
3131  }
3132 
3133  // Check if the layer is supported
3134  // Inputs
3135  const TensorInfo& inputInfo = input.GetTensorInfo();
3136  const TensorInfo& outputStateInInfo = outputStateIn.GetTensorInfo();
3137  const TensorInfo& cellStateInInfo = cellStateIn.GetTensorInfo();
3138 
3139  // Outputs
3140  const TensorInfo& scratchBufferInfo = GetTensorInfoForOperand(*scratchBuffer);
3141  const TensorInfo& outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
3142  const TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
3143  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3144 
3145  // Basic parameters
3146  LstmInputParamsInfo paramsInfo;
3147  paramsInfo.m_InputToForgetWeights = &(params.m_InputToForgetWeights->GetInfo());
3148  paramsInfo.m_InputToCellWeights = &(params.m_InputToCellWeights->GetInfo());
3149  paramsInfo.m_InputToOutputWeights = &(params.m_InputToOutputWeights->GetInfo());
3151  paramsInfo.m_RecurrentToCellWeights = &(params.m_RecurrentToCellWeights->GetInfo());
3153  paramsInfo.m_ForgetGateBias = &(params.m_ForgetGateBias->GetInfo());
3154  paramsInfo.m_CellBias = &(params.m_CellBias->GetInfo());
3155  paramsInfo.m_OutputGateBias = &(params.m_OutputGateBias->GetInfo());
3156 
3157  // Optional parameters
3158  if (!desc.m_CifgEnabled)
3159  {
3160  paramsInfo.m_InputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
3162  if (params.m_CellToInputWeights != nullptr)
3163  {
3164  paramsInfo.m_CellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
3165  }
3166  paramsInfo.m_InputGateBias = &(params.m_InputGateBias->GetInfo());
3167  }
3168 
3169  if (desc.m_ProjectionEnabled)
3170  {
3171  paramsInfo.m_ProjectionWeights = &(params.m_ProjectionWeights->GetInfo());
3172  if (params.m_ProjectionBias != nullptr)
3173  {
3174  paramsInfo.m_ProjectionBias = &(params.m_ProjectionBias->GetInfo());
3175  }
3176  }
3177 
3178  if (desc.m_PeepholeEnabled)
3179  {
3180  paramsInfo.m_CellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
3181  paramsInfo.m_CellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
3182  }
3183 
3184  if (desc.m_LayerNormEnabled)
3185  {
3186  if(!desc.m_CifgEnabled)
3187  {
3188  paramsInfo.m_InputLayerNormWeights = &(params.m_InputLayerNormWeights->GetInfo());
3189  }
3190  paramsInfo.m_ForgetLayerNormWeights = &(params.m_ForgetLayerNormWeights->GetInfo());
3191  paramsInfo.m_CellLayerNormWeights = &(params.m_CellLayerNormWeights->GetInfo());
3192  paramsInfo.m_OutputLayerNormWeights = &(params.m_OutputLayerNormWeights->GetInfo());
3193  }
3194 
3195  bool isSupported = false;
3196  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3197  {
3198  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3200  data.m_Backends,
3201  isSupported,
3202  inputInfo,
3203  outputStateInInfo,
3204  cellStateInInfo,
3205  scratchBufferInfo,
3206  outputStateOutInfo,
3207  cellStateOutInfo,
3208  outputInfo,
3209  desc,
3210  paramsInfo);
3211  };
3212 
3213  bool isDynamic = false;
3214  if (!IsDynamicTensor(outputStateOutInfo) &&
3215  !IsDynamicTensor(scratchBufferInfo) &&
3216  !IsDynamicTensor(cellStateOutInfo) &&
3217  !IsDynamicTensor(outputInfo))
3218  {
3219  validateFunc(outputInfo, isSupported);
3220  }
3221  else
3222  {
3223  isDynamic = true;
3224  isSupported = AreDynamicTensorsSupported();
3225  }
3226 
3227  if (!isSupported)
3228  {
3229  return false;
3230  }
3231 
3232  // Add the layer
3233  IConnectableLayer* layer = data.m_Network->AddLstmLayer(desc, params, "Lstm");
3234 
3235  input.Connect(layer->GetInputSlot(0));
3236  outputStateIn.Connect(layer->GetInputSlot(1));
3237  cellStateIn.Connect(layer->GetInputSlot(2));
3238 
3239  if (!isDynamic)
3240  {
3241  return (
3242  SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
3243  SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
3244  SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data) &&
3245  SetupAndTrackLayerOutputSlot(operation, 3, *layer, 3, model, data));
3246  }
3247  else
3248  {
3249  return (
3250  SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
3251  SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
3252  SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data) &&
3254  operation, 3, *layer, 3, model, data, nullptr, validateFunc, ActivationFn::kActivationNone, true));
3255  }
3256 
3257 }
3258 
3259 bool Converter::ConvertMaxPool2d(const Operation& operation, const Model& model, ConversionData& data)
3260 {
3261  VLOG(DRIVER) << "Converter::ConvertMaxPool2d()";
3262  return ConvertPooling2d(operation, __func__, PoolingAlgorithm::Max, model, data);
3263 }
3264 
3265 bool Converter::ConvertMaximum(const Operation& operation, const Model& model, ConversionData& data)
3266 {
3267  VLOG(DRIVER) << "Converter::ConvertMaximum()";
3268 
3269  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
3270  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
3271 
3272  if (!input0.IsValid() || !input1.IsValid())
3273  {
3274  return Fail("%s: Operation has invalid inputs", __func__);
3275  }
3276 
3277  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
3278  if (!outputOperand)
3279  {
3280  return Fail("%s: Could not read output", __func__);
3281  }
3282 
3283  const TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
3284 
3285  bool isSupported = false;
3286  auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
3287  {
3288  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3290  data.m_Backends,
3291  isSupported,
3292  input0.GetTensorInfo(),
3293  input1.GetTensorInfo(),
3294  outInfo);
3295  };
3296 
3297  if(IsDynamicTensor(outInfo))
3298  {
3299  isSupported = AreDynamicTensorsSupported();
3300  }
3301  else
3302  {
3303  validateFunc(outInfo, isSupported);
3304  }
3305 
3306  if (!isSupported)
3307  {
3308  return false;
3309  }
3310 
3311  IConnectableLayer* layer = data.m_Network->AddMaximumLayer();
3312  assert(layer != nullptr);
3313  bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
3314  if (!isReshapeSupported)
3315  {
3316  return false;
3317  }
3318 
3319  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3320 }
3321 
3322 bool Converter::ConvertMean(const Operation& operation, const Model& model, ConversionData& data)
3323 {
3324  VLOG(DRIVER) << "Converter::ConvertMean()";
3325 
3326  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3327  if (!input.IsValid())
3328  {
3329  return Fail("%s: Operation has invalid inputs", __func__);
3330  }
3331 
3332  const Operand* output = GetOutputOperand(operation, 0, model);
3333  if (!output)
3334  {
3335  return Fail("%s: Could not read output 0", __func__);
3336  }
3337 
3338  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3339 
3340  const Operand* axisOperand = GetInputOperand(operation, 1, model);
3341  if (!axisOperand)
3342  {
3343  return Fail("%s: Could not read input 1", __func__);
3344  }
3345 
3346  std::vector<int32_t> axis;
3347  if (!GetTensorInt32Values(*axisOperand, axis, model, data))
3348  {
3349  return Fail("%s: Input 1 has invalid values", __func__);
3350  }
3351 
3352  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
3353 
3354  // Convert the axis to unsigned int and remove duplicates.
3355  unsigned int rank = inputInfo.GetNumDimensions();
3356  std::set<unsigned int> uniqueAxis;
3357  std::transform(axis.begin(), axis.end(),
3358  std::inserter(uniqueAxis, uniqueAxis.begin()),
3359  [rank](int i) -> unsigned int { return (i + rank) % rank; });
3360 
3361  // Get the "keep dims" flag.
3362  int32_t keepDims = 0;
3363  if (!GetInputInt32(operation, 2, keepDims, model, data))
3364  {
3365  return Fail("%s: Could not read input 2", __func__);
3366  }
3367 
3368  armnn::MeanDescriptor descriptor;
3369  descriptor.m_Axis.assign(uniqueAxis.begin(), uniqueAxis.end());
3370  descriptor.m_KeepDims = keepDims > 0;
3371 
3372  bool isSupported = false;
3373  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3374  {
3375  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3377  data.m_Backends,
3378  isSupported,
3379  inputInfo,
3380  outputInfo,
3381  descriptor);
3382  };
3383 
3384  if(!IsDynamicTensor(outputInfo))
3385  {
3386  validateFunc(outputInfo, isSupported);
3387  }
3388  else
3389  {
3390  isSupported = AreDynamicTensorsSupported();
3391  }
3392 
3393  if (!isSupported)
3394  {
3395  return false;
3396  }
3397 
3398  armnn::IConnectableLayer* const layer = data.m_Network->AddMeanLayer(descriptor);
3399  assert(layer != nullptr);
3400  input.Connect(layer->GetInputSlot(0));
3401 
3402  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3403 }
3404 
3405 bool Converter::ConvertMinimum(const Operation& operation, const Model& model, ConversionData& data)
3406 {
3407  VLOG(DRIVER) << "Converter::ConvertMinimum()";
3408 
3409  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
3410  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
3411 
3412  if (!input0.IsValid() || !input1.IsValid())
3413  {
3414  return Fail("%s: Operation has invalid inputs", __func__);
3415  }
3416 
3417  const Operand* output = GetOutputOperand(operation, 0, model);
3418  if (!output)
3419  {
3420  return Fail("%s: Could not read output 0", __func__);
3421  }
3422 
3423  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3424 
3425  bool isSupported = false;
3426  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3427  {
3428  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3430  data.m_Backends,
3431  isSupported,
3432  input0.GetTensorInfo(),
3433  input1.GetTensorInfo(),
3434  outputInfo);
3435  };
3436 
3437  if(IsDynamicTensor(outputInfo))
3438  {
3439  isSupported = AreDynamicTensorsSupported();
3440  }
3441  else
3442  {
3443  validateFunc(outputInfo, isSupported);
3444  }
3445 
3446  if (!isSupported)
3447  {
3448  return false;
3449  }
3450 
3451  IConnectableLayer* const layer = data.m_Network->AddMinimumLayer();
3452  assert(layer != nullptr);
3453  bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
3454  if (!isReshapeSupported)
3455  {
3456  return false;
3457  }
3458 
3459  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3460 }
3461 
3462 bool Converter::ConvertMul(const Operation& operation, const Model& model, ConversionData& data)
3463 {
3464  VLOG(DRIVER) << "Converter::ConvertMul()";
3465 
3466  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
3467  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
3468 
3469  if (!input0.IsValid() || !input1.IsValid())
3470  {
3471  return Fail("%s: Operation has invalid inputs", __func__);
3472  }
3473 
3474  // The FuseActivation parameter is always the input index 2
3475  // and it should be optional
3476  ActivationFn activationFunction;
3477  if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
3478  {
3479  return Fail("%s: Operation has invalid inputs", __func__);
3480  }
3481 
3482  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
3483 
3484  if (outputOperand == nullptr)
3485  {
3486  return false;
3487  }
3488 
3489  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
3490 
3491  bool isSupported = false;
3492  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3493  {
3494  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3496  data.m_Backends,
3497  isSupported,
3498  input0.GetTensorInfo(),
3499  input1.GetTensorInfo(),
3500  outputInfo);
3501  };
3502 
3503  if(!IsDynamicTensor(outputInfo))
3504  {
3505  validateFunc(outputInfo, isSupported);
3506  }
3507  else
3508  {
3509  isSupported = AreDynamicTensorsSupported();
3510  }
3511 
3512  if (!isSupported)
3513  {
3514  return false;
3515  }
3516 
3517  armnn::IConnectableLayer* const startLayer = data.m_Network->AddMultiplicationLayer();
3518 
3519  bool isReshapeSupported = BroadcastTensor(input0, input1, startLayer, data);
3520  if (!isReshapeSupported)
3521  {
3522  return false;
3523  }
3524 
3525  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
3526  data, nullptr, validateFunc, activationFunction);
3527 }
3528 
3529 bool Converter::ConvertPad(const Operation& operation, const Model& model, ConversionData& data)
3530 {
3531  VLOG(DRIVER) << "Converter::ConvertPad()";
3532 
3533  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3534  if (!input.IsValid())
3535  {
3536  return Fail("%s: Operation has invalid inputs", __func__);
3537  }
3538 
3539  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
3540  unsigned int rank = inputInfo.GetNumDimensions();
3541 
3542  armnn::PadDescriptor descriptor;
3543  if (!ConvertPaddings(operation, model, data, rank, descriptor))
3544  {
3545  return Fail("%s: Could not convert paddings", __func__);
3546  }
3547 
3548  // For a ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED tensor,
3549  // the scale and zeroPoint must be the same as input0
3550  // Before Android Q, the pad value for ANEURALNETWORKS_TENSOR_QUANT8_ASYMM was undefined. Since Android Q the pad
3551  // value must be "logical zero" we set it to be equal to the QuantizationOffset so effectively it ends up as
3552  // (QuantizationOffset - QuantizationOffset) * scale = 0.
3553  if (inputInfo.GetDataType() == armnn::DataType::QAsymmU8 || inputInfo.GetDataType() == armnn::DataType::QAsymmS8)
3554  {
3555  descriptor.m_PadValue = inputInfo.GetQuantizationOffset();
3556  }
3557 
3558  const Operand* output = GetOutputOperand(operation, 0, model);
3559  if (!output)
3560  {
3561  return Fail("%s: Could not read output", __func__);
3562  }
3563 
3564  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3565 
3566  bool isSupported = false;
3567  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3568  {
3569  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3571  data.m_Backends,
3572  isSupported,
3573  inputInfo,
3574  outputInfo,
3575  descriptor);
3576  };
3577 
3578  if(!IsDynamicTensor(outputInfo))
3579  {
3580  validateFunc(outputInfo, isSupported);
3581  }
3582  else
3583  {
3584  isSupported = AreDynamicTensorsSupported();
3585  }
3586 
3587  if (!isSupported)
3588  {
3589  return false;
3590  }
3591 
3592  armnn::IConnectableLayer* const layer = data.m_Network->AddPadLayer(descriptor);
3593  assert(layer != nullptr);
3594  input.Connect(layer->GetInputSlot(0));
3595 
3596  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3597 }
3598 
3599 bool Converter::ConvertPadV2(const Operation& operation, const Model& model, ConversionData& data)
3600 {
3601  VLOG(DRIVER) << "Converter::ConvertPadV2()";
3602 
3603  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3604  if (!input.IsValid())
3605  {
3606  return Fail("%s: Could not read input 0", __func__);
3607  }
3608 
3609  const Operand* output = GetOutputOperand(operation, 0, model);
3610  if (!output)
3611  {
3612  return Fail("%s: Could not read output", __func__);
3613  }
3614 
3615  const TensorInfo& inputInfo = input.GetTensorInfo();
3616  unsigned int rank = inputInfo.GetNumDimensions();
3617 
3618  PadDescriptor descriptor;
3619  if (!ConvertPaddings(operation, model, data, rank, descriptor))
3620  {
3621  return Fail("%s: Could not convert paddings", __func__);
3622  }
3623 
3624  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3625 
3626  // Determine type of padding value
3627  OperandType operandType0;
3628  OperandType operandType2;
3629 
3630  if (!GetOperandType(operation, 0, model, operandType0) ||
3631  !GetOperandType(operation, 2, model, operandType2))
3632  {
3633  return Fail("%s: Operation has invalid inputs", __func__);
3634  }
3635 
3636  // Read value to use for padding
3637  if (operandType0 == OperandType::TENSOR_FLOAT16 && operandType2 == OperandType::FLOAT16)
3638  {
3639  Half f16PadValue;
3640  if (!GetInputScalar(operation, 2, operandType2, f16PadValue, model, data))
3641  {
3642  return Fail("%s: Could not read input 2 (FLOAT16)", __func__);
3643  }
3644 
3645  descriptor.m_PadValue = f16PadValue;
3646  }
3647  else if (operandType0 == OperandType::TENSOR_FLOAT32 && operandType2 == OperandType::FLOAT32)
3648  {
3649  if (!GetInputFloat32(operation, 2, descriptor.m_PadValue, model, data))
3650  {
3651  return Fail("%s: Could not read input 2 (FLOAT32)", __func__);
3652  }
3653  }
3654  else if (isQuantizedOperand(operandType0) && operandType2 == OperandType::INT32)
3655  {
3656  int32_t intPadValue = 0;
3657  if (!GetInputInt32(operation, 2, intPadValue, model, data))
3658  {
3659  return Fail("%s: Could not read input 2 (INT32)", __func__);
3660  }
3661  descriptor.m_PadValue = intPadValue;
3662  }
3663  else
3664  {
3665  return Fail("%s: Operation has invalid inputs: type mismatch", __func__);
3666  }
3667 
3668  bool isSupported = false;
3669  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3670  {
3671  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3673  data.m_Backends,
3674  isSupported,
3675  inputInfo,
3676  outputInfo,
3677  descriptor);
3678  };
3679 
3680  if(IsDynamicTensor(outputInfo))
3681  {
3682  isSupported = AreDynamicTensorsSupported();
3683  }
3684  else
3685  {
3686  validateFunc(outputInfo, isSupported);
3687  }
3688 
3689  if (!isSupported)
3690  {
3691  return false;
3692  }
3693 
3694  IConnectableLayer* const layer = data.m_Network->AddPadLayer(descriptor);
3695  assert(layer != nullptr);
3696  input.Connect(layer->GetInputSlot(0));
3697 
3698  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3699 }
3700 
3701 bool Converter::ConvertPrelu(const Operation& operation, const Model& model, ConversionData& data)
3702 {
3703  VLOG(DRIVER) << "Converter::ConvertPrelu()";
3704 
3705  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3706  LayerInputHandle alpha = ConvertToLayerInputHandle(operation, 1, model, data);
3707 
3708  if (!input.IsValid() || !alpha.IsValid())
3709  {
3710  return Fail("%s: Operation has invalid inputs", __func__);
3711  }
3712 
3713  const Operand* output = GetOutputOperand(operation, 0, model);
3714 
3715  if (!output)
3716  {
3717  return Fail("%s: Could not read output", __func__);
3718  }
3719 
3720  const TensorInfo& inputInfo = input.GetTensorInfo();
3721  const TensorInfo& alphaInfo = alpha.GetTensorInfo();
3722  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3723 
3724  bool isSupported = false;
3725  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3726  {
3727  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3729  data.m_Backends,
3730  isSupported,
3731  inputInfo,
3732  alphaInfo,
3733  outputInfo);
3734  };
3735 
3736  if(IsDynamicTensor(outputInfo))
3737  {
3738  isSupported = AreDynamicTensorsSupported();
3739  }
3740  else
3741  {
3742  validateFunc(outputInfo, isSupported);
3743  }
3744 
3745  if (!isSupported)
3746  {
3747  return false;
3748  }
3749 
3750  IConnectableLayer* const layer = data.m_Network->AddPreluLayer();
3751 
3752  if (!layer)
3753  {
3754  return Fail("%s: AddPreluLayer failed", __func__);
3755  }
3756 
3757  bool isReshapeSupported = BroadcastTensor(input, alpha, layer, data);
3758  if (!isReshapeSupported)
3759  {
3760  return false;
3761  }
3762 
3763  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3764 }
3765 
3766 bool Converter::ConvertQuantize(const Operation& operation, const Model& model, ConversionData& data)
3767 {
3768  VLOG(DRIVER) << "Converter::ConvertQuantize()";
3769 
3770  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3771  if (!input.IsValid())
3772  {
3773  return Fail("%s: Operation has invalid input", __func__);
3774  }
3775 
3776  const Operand* const outputOperand = GetOutputOperand(operation, 0, model);
3777  if (!outputOperand)
3778  {
3779  return Fail("%s: Operation has invalid outputs", __func__);
3780  }
3781 
3782  const TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
3783 
3784  bool isSupported = false;
3785  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3786  {
3787  FORWARD_LAYER_SUPPORT_FUNC(__func__,
3788  IsQuantizeSupported,
3789  data.m_Backends,
3790  isSupported,
3791  input.GetTensorInfo(),
3792  outputInfo);
3793  };
3794 
3795  if(IsDynamicTensor(outputInfo))
3796  {
3797  isSupported = AreDynamicTensorsSupported();
3798  }
3799  else
3800  {
3801  validateFunc(outputInfo, isSupported);
3802  }
3803 
3804  if (!isSupported)
3805  {
3806  return false;
3807  }
3808 
3809  IConnectableLayer* const layer = data.m_Network->AddQuantizeLayer();
3810  assert(layer != nullptr);
3811  input.Connect(layer->GetInputSlot(0));
3812 
3813  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3814 }
3815 
3816 bool Converter::ConvertQuantizedLstm(const Operation& operation, const Model& model, ConversionData& data)
3817 {
3818  VLOG(DRIVER) << "Converter::ConvertQuantizedLstm()";
3819 
3820  VLOG(DRIVER) << "ConvertQuantizedLstm()";
3821 
3822  //Inputs:
3823  // 0: The input: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape [numBatches, inputSize]
3824  // specifying the input to the LSTM cell. Tensor is quantized with a fixed quantization range of -1, 127/128.
3825  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3826  if (!input.IsValid())
3827  {
3828  return Fail("%s: Could not read input 0: input", __func__);
3829  }
3830 
3831  // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, of shape [batch_size, output_size].
3832  LayerInputHandle outputStatePrevTimeStep = ConvertToLayerInputHandle(operation, 18, model, data);
3833  if (!outputStatePrevTimeStep.IsValid())
3834  {
3835  return Fail("%s: Could not read input 18: outputStatePrevTimeStep", __func__);
3836  }
3837 
3838  // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape [batch_size, num_units].
3839  LayerInputHandle cellStatePrevTimeStep = ConvertToLayerInputHandle(operation, 19, model, data);
3840  if (!cellStatePrevTimeStep.IsValid())
3841  {
3842  return Fail("%s: Could not read input 19: cellStatePrevTimeStep", __func__);
3843  }
3844 
3845  // Get the mandatory input tensors:
3846 
3847  // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3848  // [num_units, input_size].
3849  const ConstTensorPin inputToForgetWeightsPin =
3850  ConvertOperationInputToConstTensorPin(operation, 2, model, data);
3851 
3852  // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3853  // [num_units, input_size].
3854  const ConstTensorPin inputToCellWeightsPin =
3855  ConvertOperationInputToConstTensorPin(operation, 3, model, data);
3856 
3857  // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3858  // [num_units, input_size].
3859  const ConstTensorPin inputToOutputWeightsPin =
3860  ConvertOperationInputToConstTensorPin(operation, 4, model, data);
3861 
3862  // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3863  // [num_units, output_size].
3864  const ConstTensorPin recurrentToForgetWeightsPin =
3865  ConvertOperationInputToConstTensorPin(operation, 6, model, data);
3866 
3867  // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3868  // [num_units, output_size].
3869  const ConstTensorPin recurrentToCellWeightsPin =
3870  ConvertOperationInputToConstTensorPin(operation, 7, model, data);
3871 
3872  // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3873  // [num_units, output_size].
3874  const ConstTensorPin recurrentToOutputWeightsPin =
3875  ConvertOperationInputToConstTensorPin(operation, 8, model, data);
3876 
3877  // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3878  const ConstTensorPin forgetGateBiasPin =
3879  ConvertOperationInputToConstTensorPin(operation, 13, model, data);
3880 
3881  // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3882  const ConstTensorPin cellBiasPin =
3883  ConvertOperationInputToConstTensorPin(operation, 14, model, data);
3884 
3885  // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3886  const ConstTensorPin outputGateBiasPin =
3887  ConvertOperationInputToConstTensorPin(operation, 15, model, data);
3888 
3889  if (!inputToForgetWeightsPin.IsValid() ||
3890  !inputToCellWeightsPin.IsValid() ||
3891  !inputToOutputWeightsPin.IsValid() ||
3892  !recurrentToForgetWeightsPin.IsValid() ||
3893  !recurrentToCellWeightsPin.IsValid() ||
3894  !recurrentToOutputWeightsPin.IsValid() ||
3895  !forgetGateBiasPin.IsValid() ||
3896  !cellBiasPin.IsValid() ||
3897  !outputGateBiasPin.IsValid())
3898  {
3899  return Fail("%s: Operation has invalid tensor inputs", __func__);
3900  }
3901 
3902  // Get the optional input tensors:
3903 
3904  // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3905  // [num_units, input_size], where “num_units” corresponds to the number of cell units.
3906  const ConstTensorPin inputToInputWeightsPin =
3908  1,
3909  model,
3910  data,
3911  g_DontPermute,
3912  nullptr,
3913  true);
3914 
3915  // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3916  // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
3917  // “num_units”), or the second dimension of the “projection_weights”, if defined.
3918  const ConstTensorPin recurrentToInputWeightsPin =
3920  5,
3921  model,
3922  data,
3923  g_DontPermute,
3924  nullptr,
3925  true);
3926 
3927  // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape
3928  // [num_units].
3929  const ConstTensorPin cellToInputWeightsPin =
3931  9,
3932  model,
3933  data,
3934  g_DontPermute,
3935  nullptr,
3936  true);
3937 
3938  // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape
3939  // [num_units].
3940  const ConstTensorPin cellToForgetWeightsPin =
3942  10,
3943  model,
3944  data,
3945  g_DontPermute,
3946  nullptr,
3947  true);
3948 
3949  // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape
3950  // [num_units].
3951  const ConstTensorPin cellToOutputWeightsPin =
3953  11,
3954  model,
3955  data,
3956  g_DontPermute,
3957  nullptr,
3958  true);
3959 
3960  // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3961  const ConstTensorPin inputGateBiasPin =
3963  12,
3964  model,
3965  data,
3966  g_DontPermute,
3967  nullptr,
3968  true);
3969 
3970  // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3971  // [output_size, num_units].
3972  const ConstTensorPin projectionWeightsPin =
3974  16,
3975  model,
3976  data,
3977  g_DontPermute,
3978  nullptr,
3979  true);
3980 
3981  // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [output_size].
3982  const ConstTensorPin projectionBiasPin =
3984  17,
3985  model,
3986  data,
3987  g_DontPermute,
3988  nullptr,
3989  true);
3990 
3991  if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional())
3992  || (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional())
3993  || (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional())
3994  || (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional())
3995  || (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional())
3996  || (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional())
3997  || (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional())
3998  || (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
3999  {
4000  return Fail("%s: Operation has invalid tensor inputs", __func__);
4001  }
4002 
4003 
4004  // Get the optional normalization tensors
4005 
4006  // 20: The input layer normalization weights. A 1-D tensor of shape [num_units] ANEURALNETWORKS_TENSOR_QUANT16_SYMM.
4007  // Used to rescale normalized inputs to activation at input gate.
4008  const ConstTensorPin inputLayerNormWeightsPin =
4010  20,
4011  model,
4012  data,
4013  g_DontPermute,
4014  nullptr,
4015  true);
4016 
4017  // 21: The forget layer normalization weights. A 1-D tensor of shape [num_units] ANEURALNETWORKS_TENSOR_QUANT16_SYMM
4018  // Used to rescale normalized inputs to activation at forget gate.
4019  const ConstTensorPin forgetLayerNormWeightsPin =
4021  21,
4022  model,
4023  data,
4024  g_DontPermute,
4025  nullptr,
4026  true);
4027 
4028  // 22: The cell layer normalization weights. A 1-D tensor of shape [num_units] ANEURALNETWORKS_TENSOR_QUANT16_SYMM.
4029  // Used to rescale normalized inputs to activation at cell gate.
4030  const ConstTensorPin cellLayerNormWeightsPin =
4032  22,
4033  model,
4034  data,
4035  g_DontPermute,
4036  nullptr,
4037  true);
4038 
4039  // 23: The output layer normalization weights. A 1-D tensor of shape [num_units].
4040  // Used to rescale normalized inputs to activation at output gate.
4041  const ConstTensorPin outputLayerNormWeightsPin =
4043  23,
4044  model,
4045  data,
4046  g_DontPermute,
4047  nullptr,
4048  true);
4049 
4050  if ((!inputLayerNormWeightsPin.IsValid() && !inputLayerNormWeightsPin.IsOptional())
4051  || (!forgetLayerNormWeightsPin.IsValid() && !forgetLayerNormWeightsPin.IsOptional())
4052  || (!cellLayerNormWeightsPin.IsValid() && !cellLayerNormWeightsPin.IsOptional())
4053  || (!outputLayerNormWeightsPin.IsValid() && !outputLayerNormWeightsPin.IsOptional()))
4054  {
4055  return Fail("%s: Operation has invalid tensor inputs", __func__);
4056  }
4057 
4058  // Get the optional input scalars:
4059  // 24: The cell clip: If provided the cell state is clipped by this value prior to the cell output activation.
4060  // 25: The projection clip: If provided and projection is enabled, this is used for clipping the projected values.
4061 
4062  // Get the mandatory input scalars:
4063  // 26: The scale of the intermediate result of matmul, i.e. input to layer normalization, at input gate.
4064  // 27: The scale of the intermediate result of matmul, i.e. input to layer normalization, at forget gate.
4065  // 28: The scale of the intermediate result of matmul, i.e. input to layer normalization, at cell gate.
4066  // 29: The scale of the intermediate result of matmul, i.e. input to layer normalization, at output gate.
4067  // 30: The zero point of the hidden state, i.e. input to projection.
4068  // 31: The scale of the hidden state, i.e. input to projection.
4069  float cellClip, projClip, matMulInputGate, matMulForgetGate, matMulCellGate, matMulOutputGate, projInputScale;
4070  int projInputZeroPoint;
4071 
4072  if (!GetInputScalar(operation, 24, OperandType::FLOAT32, cellClip, model, data, true) ||
4073  !GetInputScalar(operation, 25, OperandType::FLOAT32, projClip, model, data, true) ||
4074  !GetInputScalar(operation, 26, OperandType::FLOAT32, matMulInputGate, model, data) ||
4075  !GetInputScalar(operation, 27, OperandType::FLOAT32, matMulForgetGate, model, data) ||
4076  !GetInputScalar(operation, 28, OperandType::FLOAT32, matMulCellGate, model, data) ||
4077  !GetInputScalar(operation, 29, OperandType::FLOAT32, matMulOutputGate, model, data) ||
4078  !GetInputScalar(operation, 30, OperandType::INT32, projInputZeroPoint, model, data) ||
4079  !GetInputScalar(operation, 31, OperandType::FLOAT32, projInputScale, model, data))
4080  {
4081  return Fail("%s: Operation has invalid scalar inputs", __func__);
4082  }
4083 
4084  // Outputs:
4085  // 0: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, of shape [batch_size,
4086  // output_size].
4087  const Operand* outputStateOut = GetOutputOperand(operation, 0, model);
4088  if (!outputStateOut)
4089  {
4090  return Fail("%s: Could not read output 0: outputStateOut", __func__);
4091  }
4092 
4093  // 1: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape [batch_size, num_units].
4094  const Operand* cellStateOut = GetOutputOperand(operation, 1, model);
4095  if (!cellStateOut)
4096  {
4097  return Fail("%s: Could not read output 1: cellStateOut", __func__);
4098  }
4099 
4100  // 2: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, of shape [batch_size, output_size].
4101  // This is effectively the same as the current “output state (out)” value.
4102  const Operand* output = GetOutputOperand(operation, 2, model);
4103  if (!output)
4104  {
4105  return Fail("%s: Could not read output 2: output", __func__);
4106  }
4107 
4108  // set the params structure for the AddLstmLayer call
4109  LstmInputParams params;
4110  params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
4111  params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
4112  params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
4113  params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
4114  params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
4115  params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
4116  params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
4117  params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
4118  params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
4119  params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
4120  params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
4121  params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
4122  params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
4123  params.m_CellBias = cellBiasPin.GetConstTensorPtr();
4124  params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
4125  params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
4126  params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
4127  params.m_InputLayerNormWeights = inputLayerNormWeightsPin.GetConstTensorPtr();
4128  params.m_ForgetLayerNormWeights = forgetLayerNormWeightsPin.GetConstTensorPtr();
4129  params.m_CellLayerNormWeights = cellLayerNormWeightsPin.GetConstTensorPtr();
4130  params.m_OutputLayerNormWeights = outputLayerNormWeightsPin.GetConstTensorPtr();
4131 
4132  // set the layer descriptor
4133  QLstmDescriptor desc;
4134  desc.m_CellClip = cellClip;
4135  desc.m_ProjectionClip = projClip;
4136  desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
4137  params.m_RecurrentToInputWeights == nullptr ||
4138  params.m_InputGateBias == nullptr);
4139  desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
4140  params.m_CellToOutputWeights != nullptr);
4141  desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
4142  desc.m_LayerNormEnabled = (params.m_InputLayerNormWeights != nullptr ||
4143  params.m_ForgetLayerNormWeights != nullptr ||
4144  params.m_CellLayerNormWeights != nullptr ||
4145  params.m_OutputLayerNormWeights != nullptr);
4146  desc.m_InputIntermediateScale = matMulInputGate;
4147  desc.m_ForgetIntermediateScale = matMulForgetGate;
4148  desc.m_CellIntermediateScale = matMulCellGate;
4149  desc.m_OutputIntermediateScale = matMulOutputGate;
4150  desc.m_HiddenStateScale = projInputScale;
4151  desc.m_HiddenStateZeroPoint = projInputZeroPoint;
4152 
4153  // validate the optional input groups
4154  if (desc.m_CifgEnabled &&
4155  (params.m_InputToInputWeights != nullptr ||
4156  params.m_RecurrentToInputWeights != nullptr ||
4157  params.m_InputGateBias != nullptr))
4158  {
4159  return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
4160  " and input gate bias must be provided", __func__);
4161  }
4162 
4163  if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
4164  {
4165  return Fail("%s: projection bias should not be provided without projection weights", __func__);
4166  }
4167 
4168  if (desc.m_PeepholeEnabled &&
4169  (params.m_CellToForgetWeights == nullptr ||
4170  params.m_CellToOutputWeights == nullptr ||
4171  (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
4172  {
4173  return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
4174  " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
4175  }
4176 
4177  if (desc.m_LayerNormEnabled &&
4178  (params.m_ForgetLayerNormWeights == nullptr ||
4179  params.m_CellLayerNormWeights == nullptr ||
4180  params.m_OutputLayerNormWeights == nullptr ||
4181  (!desc.m_CifgEnabled && params.m_InputLayerNormWeights == nullptr)))
4182  {
4183  return Fail("%s: All, or none, of forget-norm weights, cell-norm weights and output-norm weights must be"
4184  " provided and, if CIFG is not enabled, input-norm weights must also be provided", __func__);
4185  }
4186 
4187  // Basic parameters
4188  LstmInputParamsInfo paramsInfo;
4189  paramsInfo.m_InputToForgetWeights = &(params.m_InputToForgetWeights->GetInfo());
4190  paramsInfo.m_InputToCellWeights = &(params.m_InputToCellWeights->GetInfo());
4191  paramsInfo.m_InputToOutputWeights = &(params.m_InputToOutputWeights->GetInfo());
4193  paramsInfo.m_RecurrentToCellWeights = &(params.m_RecurrentToCellWeights->GetInfo());
4195  paramsInfo.m_ForgetGateBias = &(params.m_ForgetGateBias->GetInfo());
4196  paramsInfo.m_CellBias = &(params.m_CellBias->GetInfo());
4197  paramsInfo.m_OutputGateBias = &(params.m_OutputGateBias->GetInfo());
4198 
4199  // Inputs
4200  const TensorInfo& inputInfo = input.GetTensorInfo();
4201  const TensorInfo& outputStatePrevTimeStepInfo = outputStatePrevTimeStep.GetTensorInfo();
4202  const TensorInfo& cellStatePrevTimeStepInfo = cellStatePrevTimeStep.GetTensorInfo();
4203 
4204  // Outputs
4205  TensorInfo outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
4206  TensorInfo outputInfo = GetTensorInfoForOperand(*output);
4207  const TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
4208 
4209  // Optional parameters
4210  if (!desc.m_CifgEnabled)
4211  {
4212  paramsInfo.m_InputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
4214  if (desc.m_PeepholeEnabled)
4215  {
4216  paramsInfo.m_CellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
4217  }
4218  paramsInfo.m_InputGateBias = &(params.m_InputGateBias->GetInfo());
4219  }
4220 
4221 
4222  if (desc.m_ProjectionEnabled)
4223  {
4224  paramsInfo.m_ProjectionWeights = &(params.m_ProjectionWeights->GetInfo());
4225  if (params.m_ProjectionBias != nullptr)
4226  {
4227  paramsInfo.m_ProjectionBias = &(params.m_ProjectionBias->GetInfo());
4228  }
4229  }
4230  else
4231  {
4232  // If Projection is disabled, override non-const outputs to change the quant info with hidden params, then
4233  // create a new const TensorInfo based on this
4234  outputStateOutInfo.SetQuantizationScale(projInputScale);
4235  outputStateOutInfo.SetQuantizationOffset(projInputZeroPoint);
4236  outputInfo.SetQuantizationScale(projInputScale);
4237  outputInfo.SetQuantizationOffset(projInputZeroPoint);
4238  }
4239 
4240  const TensorInfo constOutputStateOutInfo(outputStateOutInfo);
4241  const TensorInfo constOutputInfo(outputInfo);
4242 
4243  if (desc.m_PeepholeEnabled)
4244  {
4245  paramsInfo.m_CellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
4246  paramsInfo.m_CellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
4247  }
4248 
4249  if (desc.m_LayerNormEnabled)
4250  {
4251  if(!desc.m_CifgEnabled)
4252  {
4253  paramsInfo.m_InputLayerNormWeights = &(params.m_InputLayerNormWeights->GetInfo());
4254  }
4255  paramsInfo.m_ForgetLayerNormWeights = &(params.m_ForgetLayerNormWeights->GetInfo());
4256  paramsInfo.m_CellLayerNormWeights = &(params.m_CellLayerNormWeights->GetInfo());
4257  paramsInfo.m_OutputLayerNormWeights = &(params.m_OutputLayerNormWeights->GetInfo());
4258  }
4259 
4260  // Check if the layer is supported
4261  bool isSupported = false;
4262  auto validateFunc = [&](const armnn::TensorInfo& cellStateOutInfo, bool& isSupported)
4263  {
4264  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4265  IsQLstmSupported,
4266  data.m_Backends,
4267  isSupported,
4268  inputInfo,
4269  outputStatePrevTimeStepInfo,
4270  cellStatePrevTimeStepInfo,
4271  constOutputStateOutInfo,
4272  cellStateOutInfo,
4273  constOutputInfo,
4274  desc,
4275  paramsInfo);
4276  };
4277 
4278  bool isDynamic = false;
4279  if (!IsDynamicTensor(constOutputStateOutInfo) &&
4280  !IsDynamicTensor(cellStateOutInfo) &&
4281  !IsDynamicTensor(constOutputInfo))
4282  {
4283  validateFunc(outputInfo, isSupported);
4284  }
4285  else
4286  {
4287  isDynamic = true;
4288  isSupported = AreDynamicTensorsSupported();
4289  }
4290 
4291  if (!isSupported)
4292  {
4293  return false;
4294  }
4295 
4296  // Add the layer
4297  IConnectableLayer* layer = data.m_Network->AddQLstmLayer(desc, params, "QLstm");
4298 
4299  input.Connect(layer->GetInputSlot(0));
4300  outputStatePrevTimeStep.Connect(layer->GetInputSlot(1));
4301  cellStatePrevTimeStep.Connect(layer->GetInputSlot(2));
4302 
4303  if (!isDynamic)
4304  {
4306  operation, 0, *layer, 0, model, data, &constOutputStateOutInfo) &&
4307  SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
4308  SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data, &constOutputInfo));
4309  }
4310  else
4311  {
4313  operation, 0, *layer, 0, model, data, &constOutputStateOutInfo) &&
4315  operation, 1, *layer, 1, model, data, nullptr, validateFunc,
4316  ActivationFn::kActivationNone, true) &&
4317  SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data, &constOutputInfo));
4318  }
4319 }
4320 
4321 bool Converter::ConvertQuantized16BitLstm(const Operation& operation, const Model& model, ConversionData& data)
4322 {
4323  VLOG(DRIVER) << "Converter::ConvertQuantized16BitLstm()";
4324  VLOG(DRIVER) << "Policy::ConvertQuantized16BitLstm()";
4325 
4326  //Inputs:
4327  // 0: The input: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape [numBatches, inputSize]
4328  // specifying the input to the LSTM cell. Tensor is quantized with a fixed quantization range of -1, 127/128.
4329  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4330  if (!input.IsValid())
4331  {
4332  return Fail("%s: Could not read input 0: input", __func__);
4333  }
4334 
4335  //13: The previous cell state: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT16_SYMM and shape
4336  // [numBatches, outputSize] specifying the cell state from the previous time step of the LSTM cell.
4337  // It is quantized using a quantization range of -2^4, 2^4 * 32767/32768.
4338  LayerInputHandle previousCellStateIn = ConvertToLayerInputHandle(operation, 13, model, data);
4339  if (!previousCellStateIn.IsValid())
4340  {
4341  return Fail("%s: Could not read input 13: previousCellStateIn", __func__);
4342  }
4343 
4344  // 14: The previous output state: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4345  // [numBathes, outputSize] specifying the output of the LSTM cell from previous time-step. Tensor
4346  // is quantized with a fixed quantization range of -1, 127/128.
4347  LayerInputHandle previousOutputIn = ConvertToLayerInputHandle(operation, 14, model, data);
4348  if (!previousOutputIn.IsValid())
4349  {
4350  return Fail("%s: Could not read input 14: previousOutputIn", __func__);
4351  }
4352 
4353  // Get the input tensors:
4354  // 1: The input-to-input weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4355  // [outputSize, inputSize] specifying input-to-input part of weights for fully-connected layer inside the
4356  // LSTM cell. Quantization zero point and scale must be the same across all the weights.
4357  const ConstTensorPin inputToInputWeightsPin =
4358  ConvertOperationInputToConstTensorPin(operation, 1, model, data);
4359 
4360  // 2: The input-to-forget weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4361  // [outputSize, inputSize] specifying input-to-forget part of weights for fully-connected layer inside the
4362  // LSTM cell. Quantization zero point and scale must be the same across all the weights.
4363  const ConstTensorPin inputToForgetWeightsPin =
4364  ConvertOperationInputToConstTensorPin(operation, 2, model, data);
4365 
4366  // 3: The input-to-cell weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4367  // [outputSize, inputSize] specifying input-to-cell part of weights for fully-connected layer inside the
4368  // LSTM cell. Quantization zero point and scale must be the same across all the weights.
4369  const ConstTensorPin inputToCellWeightsPin =
4370  ConvertOperationInputToConstTensorPin(operation, 3, model, data);
4371 
4372  // 4: The input-to-output weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4373  // [outputSize, inputSize] specifying input-to-output part of weights for fully-connected layer inside the
4374  // LSTM cell. Quantization zero point and scale must be the same across all the weights.
4375  const ConstTensorPin inputToOutputWeightsPin =
4376  ConvertOperationInputToConstTensorPin(operation, 4, model, data);
4377 
4378  // 5: The recurrent-to-input weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4379  // [outputSize, outputSize] specifying recurrent-to-input part of weights for fully-connected layer inside
4380  // the LSTM cell. Quantization zero point and scale must be the same across all the weights.
4381  const ConstTensorPin recurrentToInputWeightsPin =
4382  ConvertOperationInputToConstTensorPin(operation, 5, model, data);
4383 
4384  // 6: The recurrent-to-forget weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4385  // [outputSize, outputSize] specifying recurrent-to-forget part of weights for fully-connected layer inside
4386  // the LSTM cell. Quantization zero point and scale must be the same across all the weights.
4387  const ConstTensorPin recurrentToForgetWeightsPin =
4388  ConvertOperationInputToConstTensorPin(operation, 6, model, data);
4389 
4390  // 7: The recurrent-to-cell weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4391  // [outputSize, outputSize] specifying recurrent-to-cell part of weights for fully-connected layer inside
4392  // the LSTM cell. Quantization zero point and scale must be the same across all the weights.
4393  const ConstTensorPin recurrentToCellWeightsPin =
4394  ConvertOperationInputToConstTensorPin(operation, 7, model, data);
4395 
4396  // 8: The recurrent-to-output weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4397  // [outputSize, outputSize] specifying recurrent-to-output part of weights for fully-connected layer inside
4398  // the LSTM cell. Quantization zero point and scale must be the same across all the weights.
4399  const ConstTensorPin recurrentToOutputWeightsPin =
4400  ConvertOperationInputToConstTensorPin(operation, 8, model, data);
4401 
4402  // 9: The input gate bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying the
4403  // bias for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product
4404  // of input and weights scales and zeroPoint equal to 0.
4405  const ConstTensorPin inputGateBiasPin =
4406  ConvertOperationInputToConstTensorPin(operation, 9, model, data);
4407 
4408  // 10: The forget gate bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying
4409  // the bias for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product
4410  // of input and weights scales and zeroPoint equal to 0.
4411  const ConstTensorPin forgetGateBiasPin =
4412  ConvertOperationInputToConstTensorPin(operation, 10, model, data);
4413 
4414  // 11:The cell bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying the bias
4415  // for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product of input
4416  // and weights scales and zeroPoint equal to 0.
4417  const ConstTensorPin cellBiasPin =
4418  ConvertOperationInputToConstTensorPin(operation, 11, model, data);
4419 
4420  // 12:The output gate bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying
4421  // the bias for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product
4422  // of input and weights scales and zeroPoint equal to 0.
4423  const ConstTensorPin outputGateBiasPin =
4424  ConvertOperationInputToConstTensorPin(operation, 12, model, data);
4425 
4426  if (!inputToInputWeightsPin.IsValid() ||
4427  !inputToForgetWeightsPin.IsValid() ||
4428  !inputToCellWeightsPin.IsValid() ||
4429  !inputToOutputWeightsPin.IsValid() ||
4430  !recurrentToInputWeightsPin.IsValid() ||
4431  !recurrentToForgetWeightsPin.IsValid() ||
4432  !recurrentToCellWeightsPin.IsValid() ||
4433  !recurrentToOutputWeightsPin.IsValid() ||
4434  !inputGateBiasPin.IsValid() ||
4435  !forgetGateBiasPin.IsValid() ||
4436  !cellBiasPin.IsValid() ||
4437  !outputGateBiasPin.IsValid())
4438  {
4439  return Fail("%s: Operation has invalid tensor inputs", __func__);
4440  }
4441 
4442  // Outputs:
4443  // 0: The cell state: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT16_SYMM and shape [numBatches, outputSize]
4444  // which contains a cell state from the current time step. Tensor is quantized using a quantization range
4445  // of -2^4, 2^4 * 32767/32768.
4446  const Operand* cellStateOut = GetOutputOperand(operation, 0, model);
4447  if (!cellStateOut)
4448  {
4449  return Fail("%s: Could not read output 0: cellStateOut", __func__);
4450  }
4451 
4452  // 1: The output: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape [numBathes, outputSize] which
4453  // contains the output value. Tensor is quantized with a fixed quantization range of -1, 127/128.
4454  const Operand* output = GetOutputOperand(operation, 1, model);
4455  if (!output)
4456  {
4457  return Fail("%s: Could not read output 1: output", __func__);
4458  }
4459 
4460  // Inputs
4461  const TensorInfo& inputInfo = input.GetTensorInfo();
4462  const TensorInfo& previousCellStateInInfo = previousCellStateIn.GetTensorInfo();
4463  const TensorInfo& previousOutputInInfo = previousOutputIn.GetTensorInfo();
4464 
4465  // Outputs
4466  const TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
4467  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
4468 
4469  // Dynamic tensors currently not supported
4470  if (IsDynamicTensor(cellStateOutInfo) || IsDynamicTensor(outputInfo))
4471  {
4472  return Fail("%s: Dynamic output tensors are not supported", __func__);
4473  }
4474 
4475  QuantizedLstmInputParams params;
4476 
4477  params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
4478  params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
4479  params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
4480  params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
4481  params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
4482  params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
4483  params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
4484  params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
4485  params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
4486  params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
4487  params.m_CellBias = cellBiasPin.GetConstTensorPtr();
4488  params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
4489 
4490  QuantizedLstmInputParamsInfo paramsInfo;
4491  paramsInfo.m_InputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
4492  paramsInfo.m_InputToForgetWeights = &(params.m_InputToForgetWeights->GetInfo());
4493  paramsInfo.m_InputToCellWeights = &(params.m_InputToCellWeights->GetInfo());
4494  paramsInfo.m_InputToOutputWeights = &(params.m_InputToOutputWeights->GetInfo());
4497  paramsInfo.m_RecurrentToCellWeights = &(params.m_RecurrentToCellWeights->GetInfo());
4499  paramsInfo.m_InputGateBias = &(params.m_InputGateBias->GetInfo());
4500  paramsInfo.m_ForgetGateBias = &(params.m_ForgetGateBias->GetInfo());
4501  paramsInfo.m_CellBias = &(params.m_CellBias->GetInfo());
4502  paramsInfo.m_OutputGateBias = &(params.m_OutputGateBias->GetInfo());
4503 
4504  bool isSupported = false;
4505  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4506  {
4507  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4509  data.m_Backends,
4510  isSupported,
4511  inputInfo,
4512  previousCellStateInInfo,
4513  previousOutputInInfo,
4514  cellStateOutInfo,
4515  outputInfo,
4516  paramsInfo);
4517  };
4518 
4519  bool isDynamic = false;
4520  if (!IsDynamicTensor(cellStateOutInfo) &&
4521  !IsDynamicTensor(outputInfo))
4522  {
4523  validateFunc(outputInfo, isSupported);
4524  }
4525  else
4526  {
4527  isDynamic = true;
4528  isSupported = AreDynamicTensorsSupported();
4529  }
4530 
4531  if (!isSupported)
4532  {
4533  return false;
4534  }
4535 
4536  IConnectableLayer* const layer = data.m_Network->AddQuantizedLstmLayer(params, "QuantizedLstm");
4537  input.Connect(layer->GetInputSlot(0));
4538  previousCellStateIn.Connect(layer->GetInputSlot(1));
4539  previousOutputIn.Connect(layer->GetInputSlot(2));
4540 
4541  if (!isDynamic)
4542  {
4543  return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
4544  SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data));
4545  }
4546  else
4547  {
4548  return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
4550  operation, 1, *layer, 1, model, data, nullptr, validateFunc, ActivationFn::kActivationNone, true));
4551  }
4552 
4553 }
4554 
4555 bool Converter::ConvertRank(const Operation& operation, const Model& model, ConversionData& data)
4556 {
4557  VLOG(DRIVER) << "Converter::ConvertRank()";
4558 
4559  const Operand* inputOperand = GetInputOperand(operation, 0, model);
4560  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4561 
4562  if (inputOperand == nullptr || outputOperand == nullptr)
4563  {
4564  return Fail("%s: Operation has invalid inputs", __func__);
4565  }
4566 
4567  const Shape inputOperandShape = GetOperandShape(*inputOperand);
4568  const Shape outputOperandShape = GetOperandShape(*outputOperand);
4569 
4570  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4571  if (!input.IsValid())
4572  {
4573  return Fail("%s: Could not read input 0", __func__);
4574  }
4575 
4576  armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
4577  if (IsDynamicTensor(outInfo))
4578  {
4579  return Fail("%s: Dynamic output tensors are not supported", __func__);
4580  }
4581 
4582  bool isSupported = false;
4583  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4584  IsRankSupported,
4585  data.m_Backends,
4586  isSupported,
4587  input.GetTensorInfo(),
4588  outInfo);
4589  if (!isSupported)
4590  {
4591  return false;
4592  }
4593 
4594  armnn::IConnectableLayer* layer = data.m_Network->AddRankLayer();
4595  assert(layer != nullptr);
4596  input.Connect(layer->GetInputSlot(0));
4597 
4598  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, &outInfo);
4599 }
4600 
4601 bool Converter::ConvertReLu(const Operation& operation, const Model& model, ConversionData& data)
4602 {
4603  VLOG(DRIVER) << "Converter::ConvertReLu()";
4606 
4607 
4608  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4609  if (!input.IsValid())
4610  {
4611  return Fail("%s: Input 0 is invalid", "operationName");
4612  }
4613 
4614  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4615  if (!outputOperand)
4616  {
4617  return false;
4618  }
4619 
4620  const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
4621 
4622  bool isSupported = false;
4623 
4624  auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
4625  {
4626  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4628  data.m_Backends,
4629  isSupported,
4630  input.GetTensorInfo(),
4631  outInfo,
4632  desc);
4633  };
4634 
4635  if(IsDynamicTensor(outInfo))
4636  {
4637  isSupported = AreDynamicTensorsSupported();
4638  }
4639  else
4640  {
4641  validateFunc(outInfo, isSupported);
4642  }
4643 
4644  if (!isSupported)
4645  {
4646  return false;
4647  }
4648 
4649  armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(desc);
4650  ARMNN_ASSERT(layer != nullptr);
4651  input.Connect(layer->GetInputSlot(0));
4652 
4653  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4654 }
4655 
4656 bool Converter::ConvertReLu1(const Operation& operation, const Model& model, ConversionData& data)
4657 {
4658  VLOG(DRIVER) << "Converter::ConvertReLu1()";
4661  desc.m_A = 1.0f;
4662  desc.m_B = -1.0f;
4663 
4664  return ConvertToActivation(operation, __func__, desc, model, data);
4665 }
4666 
4667 bool Converter::ConvertReLu6(const Operation& operation, const Model& model, ConversionData& data)
4668 {
4669  VLOG(DRIVER) << "Converter::ConvertReLu6()";
4672  desc.m_A = 6.0f;
4673 
4674  return ConvertToActivation(operation, __func__, desc, model, data);
4675 }
4676 
4677 bool Converter::ConvertReshape(const Operation& operation, const Model& model, ConversionData& data)
4678 {
4679  VLOG(DRIVER) << "Converter::ConvertReshape()";
4680 
4681  const Operand* inputOperand = GetInputOperand(operation, 0, model);
4682  const Operand* requestedShapeOperand = GetInputOperand(operation, 1, model);
4683  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4684 
4685  if (inputOperand == nullptr
4686  || requestedShapeOperand == nullptr
4687  || outputOperand == nullptr)
4688  {
4689  return Fail("%s: Operation has invalid inputs", __func__);
4690  }
4691 
4692  if (requestedShapeOperand->dimensions.size() != 1)
4693  {
4694  return Fail("%s: Input 1 expected to be one-dimensional (found %i dimensions)",
4695  __func__, requestedShapeOperand->dimensions.size());
4696  }
4697 
4698  std::vector<int32_t> targetDimensions;
4699  if (!GetTensorInt32Values(*requestedShapeOperand, targetDimensions, model, data))
4700  {
4701  return Fail("%s: Could not read values of input 1", __func__);
4702  }
4703 
4704  const Shape inputOperandShape = GetOperandShape(*inputOperand);
4705 
4706  Shape requestedShape;
4707  // targetDimensions may contain special values (e.g. -1). reshapePrepare() is an AndroidNN provided utility
4708  // function that resolves these values into a fully specified tensor shape.
4709  if (!reshapePrepare(inputOperandShape, targetDimensions.data(), targetDimensions.size(), &requestedShape))
4710  {
4711  return Fail("%s: Failed to resolve the requested shape", __func__);
4712  }
4713 
4714  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4715  if (!input.IsValid())
4716  {
4717  return Fail("%s: Could not read input 0", __func__);
4718  }
4719 
4720  armnn::ReshapeDescriptor reshapeDescriptor;
4721  reshapeDescriptor.m_TargetShape = armnn::TensorShape(requestedShape.dimensions.size(),
4722  requestedShape.dimensions.data());
4723 
4724  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
4725 
4726  bool isSupported = false;
4727  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4728  {
4729  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4731  data.m_Backends,
4732  isSupported,
4733  input.GetTensorInfo(),
4734  outputInfo,
4735  reshapeDescriptor);
4736  };
4737 
4738  if(!IsDynamicTensor(outputInfo))
4739  {
4740  validateFunc(outputInfo, isSupported);
4741  }
4742  else
4743  {
4744  isSupported = AreDynamicTensorsSupported();
4745  }
4746 
4747  if (!isSupported)
4748  {
4749  return false;
4750  }
4751 
4752  armnn::IConnectableLayer* layer = data.m_Network->AddReshapeLayer(reshapeDescriptor);
4753  assert(layer != nullptr);
4754  input.Connect(layer->GetInputSlot(0));
4755 
4756  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4757 }
4758 
4759 bool Converter::ConvertResize(const Operation& operation,
4760  const Model& model,
4761  ConversionData& data,
4762  ResizeMethod resizeMethod)
4763 {
4764  VLOG(DRIVER) << "Converter::ConvertResize()";
4765  VLOG(DRIVER) << "resizeMethod = " << GetResizeMethodAsCString(resizeMethod);
4766 
4767  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4768  if (!input.IsValid())
4769  {
4770  return Fail("%s: Could not read input 0", __func__);
4771  }
4772 
4773  const Operand* output = GetOutputOperand(operation, 0, model);
4774  if (!output)
4775  {
4776  return Fail("%s: Could not read output 0", __func__);
4777  }
4778 
4779  const TensorInfo& inputInfo = input.GetTensorInfo();
4780  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
4781 
4782  ResizeDescriptor descriptor;
4783  descriptor.m_Method = resizeMethod;
4784  descriptor.m_DataLayout = OptionalDataLayout(operation, 3, model, data);
4785 
4786  OperandType operandType1;
4787  OperandType operandType2;
4788 
4789  if (!GetOperandType(operation, 1, model, operandType1) ||
4790  !GetOperandType(operation, 2, model, operandType2))
4791  {
4792  return Fail("%s: Operation has invalid inputs", __func__);
4793  }
4794 
4795  if (operandType1 != operandType2)
4796  {
4797  return Fail("%s: Operation has invalid inputs. Type of input 1 and 2 should be the same", __func__);
4798  }
4799 
4800  if (operandType1 == OperandType::INT32)
4801  {
4802  // Case 1: resizing by shape
4803  int32_t targetWidth = 0;
4804  int32_t targetHeight = 0;
4805 
4806  if (!GetInputInt32(operation, 1, targetWidth, model, data) ||
4807  !GetInputInt32(operation, 2, targetHeight, model, data))
4808  {
4809  return Fail("%s: Operation has invalid inputs for resizing by shape", __func__);
4810  }
4811 
4812  if (targetWidth < 0 || targetHeight < 0)
4813  {
4814  return Fail("%s: Operation has invalid inputs for resizing by shape. "
4815  "Target width/height cannot be < 0", __func__);
4816  }
4817 
4818  descriptor.m_TargetWidth = static_cast<uint32_t>(targetWidth);
4819  descriptor.m_TargetHeight = static_cast<uint32_t>(targetHeight);
4820  }
4821  else if (operandType1 == OperandType::FLOAT32)
4822  {
4823  // Case 2: resizing by scale
4824  float widthScale = 1.0f;
4825  float heightScale = 1.0f;
4826 
4827  if (!GetInputFloat32(operation, 1, widthScale, model, data) ||
4828  !GetInputFloat32(operation, 2, heightScale, model, data))
4829  {
4830  return Fail("%s: Operation has invalid inputs for resizing by scale", __func__);
4831  }
4832 
4833  const TensorShape& inputShape = inputInfo.GetShape();
4834  armnnUtils::DataLayoutIndexed dataLayoutIndexed(descriptor.m_DataLayout);
4835 
4836  float width = inputShape[dataLayoutIndexed.GetWidthIndex()];
4837  float height = inputShape[dataLayoutIndexed.GetHeightIndex()];
4838 
4839  descriptor.m_TargetWidth = std::floor(width * widthScale);
4840  descriptor.m_TargetHeight = std::floor(height * heightScale);
4841  }
4842  else if (operandType1 == OperandType::FLOAT16)
4843  {
4844  Half widthScale;
4845  Half heightScale;
4846 
4847  if (!GetInputScalar(operation, 1, OperandType::FLOAT16, widthScale, model, data) ||
4848  !GetInputScalar(operation, 2, OperandType::FLOAT16, heightScale, model, data))
4849  {
4850  return Fail("%s: Operation has invalid inputs for resizing by scale", __func__);
4851  }
4852 
4853  const TensorShape& inputShape = inputInfo.GetShape();
4854  armnnUtils::DataLayoutIndexed dataLayoutIndexed(descriptor.m_DataLayout);
4855 
4856  Half width = static_cast<Half>(inputShape[dataLayoutIndexed.GetWidthIndex()]);
4857  Half height = static_cast<Half>(inputShape[dataLayoutIndexed.GetHeightIndex()]);
4858 
4859  descriptor.m_TargetWidth = std::floor(width * widthScale);
4860  descriptor.m_TargetHeight = std::floor(height * heightScale);
4861  }
4862  else
4863  {
4864  return Fail("%s: Operand has invalid data type for resizing by scale", __func__);
4865  }
4866 
4867  descriptor.m_AlignCorners = GetOptionalBool(operation, 4, model, data);
4868  descriptor.m_HalfPixelCenters = GetOptionalBool(operation, 5, model, data);
4869 
4870  bool isSupported = false;
4871  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4872  {
4873  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4875  data.m_Backends,
4876  isSupported,
4877  inputInfo,
4878  outputInfo,
4879  descriptor);
4880  };
4881 
4882  if(IsDynamicTensor(outputInfo))
4883  {
4884  isSupported = AreDynamicTensorsSupported();
4885  }
4886  else
4887  {
4888  validateFunc(outputInfo, isSupported);
4889  }
4890 
4891  if (!isSupported)
4892  {
4893  return false;
4894  }
4895 
4896  IConnectableLayer* layer = data.m_Network->AddResizeLayer(descriptor);
4897  assert(layer != nullptr);
4898  input.Connect(layer->GetInputSlot(0));
4899 
4900  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4901 }
4902 
4903 bool Converter::ConvertSpaceToBatchNd(const Operation& operation, const Model& model, ConversionData& data)
4904 {
4905  VLOG(DRIVER) << "Converter::ConvertSpaceToBatchNd()";
4906 
4907  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4908  if(!input.IsValid())
4909  {
4910  return Fail("%s: Operation has invalid inputs", __func__);
4911  }
4912 
4913  const armnn::TensorInfo &inputInfo = input.GetTensorInfo();
4914  unsigned int rank = inputInfo.GetNumDimensions();
4915  unsigned int spatialDim = rank - 2;
4916 
4917  if(rank != 4)
4918  {
4919  Fail("%s: Only inputs with rank 4 are supported", __func__);
4920  }
4921 
4922  const Operand *output = GetOutputOperand(operation, 0, model);
4923  if(!output)
4924  {
4925  return Fail("%s: Could not read output 0", __func__);
4926  }
4927 
4928  const armnn::TensorInfo &outputInfo = GetTensorInfoForOperand(*output);
4929 
4930  const Operand *blockShapeOperand = GetInputOperand(operation, 1, model);
4931  const Operand *paddingsOperand = GetInputOperand(operation, 2, model);
4932 
4933  armnn::TensorShape blockShapeOperandShape = GetTensorShapeForOperand(*blockShapeOperand);
4934  if(blockShapeOperandShape.GetNumDimensions() != 1 || blockShapeOperandShape.GetNumElements() != spatialDim)
4935  {
4936  return Fail("%s: Operation has invalid block shape operand: expected shape [%d]", __func__, spatialDim);
4937  }
4938 
4939  std::vector<int32_t> blockShape;
4940  if(!GetTensorInt32Values(*blockShapeOperand, blockShape, model, data))
4941  {
4942  return Fail("%s: Operation has an invalid or unsupported block size operand", __func__);
4943  }
4944  if(std::any_of(blockShape.cbegin(), blockShape.cend(), [](int32_t i)
4945  { return i < 1; }))
4946  {
4947  return Fail("%s: Block shape must be at least 1 in all dimensions.", __func__);
4948  }
4949 
4950  armnn::TensorShape paddingsOperandShape = GetTensorShapeForOperand(*paddingsOperand);
4951  if(paddingsOperandShape.GetNumDimensions() != 2 || paddingsOperandShape.GetNumElements() != 2 * spatialDim)
4952  {
4953  return Fail("%s: Operation has invalid paddings operand: expected shape [%d, 2]", __func__, spatialDim);
4954  }
4955 
4956  std::vector<std::pair<unsigned int, unsigned int>> paddingList;
4957  std::vector<int32_t> paddings;
4958  if(!GetTensorInt32Values(*paddingsOperand, paddings, model, data))
4959  {
4960  return Fail("%s: Operation has an invalid or unsupported paddings operand", __func__);
4961  }
4962  for (unsigned int i = 0; i < paddings.size() - 1; i += 2)
4963  {
4964  int paddingBeforeInput = paddings[i];
4965  int paddingAfterInput = paddings[i + 1];
4966  if(paddingBeforeInput < 0 || paddingAfterInput < 0)
4967  {
4968  return Fail("%s: Operation has invalid paddings operand, invalid padding values.", __func__);
4969  }
4970 
4971  paddingList.emplace_back((unsigned int) paddingBeforeInput, (unsigned int) paddingAfterInput);
4972  }
4973 
4976  descriptor.m_BlockShape.assign(blockShape.cbegin(), blockShape.cend());
4977  descriptor.m_PadList.assign(paddingList.cbegin(), paddingList.cend());
4978 
4979  if(Is12OrLaterOperand(*output))
4980  {
4981  descriptor.m_DataLayout = OptionalDataLayout(operation, 3, model, data);
4982  }
4983 
4984  bool isSupported = false;
4985  auto validateFunc = [&](const armnn::TensorInfo &outputInfo, bool &isSupported)
4986  {
4987  FORWARD_LAYER_SUPPORT_FUNC(__func__,
4989  data.m_Backends,
4990  isSupported,
4991  inputInfo,
4992  outputInfo,
4993  descriptor);
4994  };
4995 
4996  if(IsDynamicTensor(outputInfo))
4997  {
4998  isSupported = AreDynamicTensorsSupported();
4999  } else
5000  {
5001  validateFunc(outputInfo, isSupported);
5002  }
5003 
5004  if(!isSupported)
5005  {
5006  return false;
5007  }
5008 
5009  armnn::IConnectableLayer *const layer = data.m_Network->AddSpaceToBatchNdLayer(descriptor);
5010  assert(layer != nullptr);
5011  input.Connect(layer->GetInputSlot(0));
5012 
5013  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5014 }
5015 
5016 bool Converter::ConvertSpaceToDepth(const Operation& operation, const Model& model, ConversionData& data)
5017 {
5018  VLOG(DRIVER) << "Converter::ConvertSpaceToDepth()";
5019 
5020  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5021  if (!input.IsValid() )
5022  {
5023  return Fail("%s: Operation has invalid inputs", __func__);
5024  }
5025 
5026  const TensorInfo& inputInfo = input.GetTensorInfo();
5027  unsigned int rank = inputInfo.GetNumDimensions();
5028  if (rank != 4)
5029  {
5030  return Fail("%s: Only inputs with rank 4 are supported", __func__);
5031  }
5032 
5033  const Operand* output = GetOutputOperand(operation, 0, model);
5034  if (!output)
5035  {
5036  return Fail("%s: Could not read output 0", __func__);
5037  }
5038 
5039  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5040 
5042 
5043  GetInputScalar(operation, 1, OperandType::INT32, desc.m_BlockSize, model, data);
5044 
5045  if (desc.m_BlockSize <= 1)
5046  {
5047  return Fail("%s: Block size must be at least 1 in all dimensions");
5048  }
5049 
5050  desc.m_DataLayout = OptionalDataLayout(operation, 2, model, data);
5051 
5052  bool isSupported = false;
5053  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5054  {
5055  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5057  data.m_Backends,
5058  isSupported,
5059  inputInfo,
5060  outputInfo,
5061  desc);
5062  };
5063 
5064  if(IsDynamicTensor(outputInfo))
5065  {
5066  isSupported = AreDynamicTensorsSupported();
5067  }
5068  else
5069  {
5070  validateFunc(outputInfo, isSupported);
5071  }
5072 
5073  if (!isSupported)
5074  {
5075  return false;
5076  }
5077 
5078  IConnectableLayer* const layer = data.m_Network->AddSpaceToDepthLayer(desc);
5079  assert(layer != nullptr);
5080  input.Connect(layer->GetInputSlot(0));
5081 
5082  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5083 }
5084 
5085 bool Converter::ConvertSoftmax(const Operation& operation, const Model& model, ConversionData& data)
5086 {
5087  VLOG(DRIVER) << "Converter::ConvertSoftmax()";
5088 
5089  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5090  if (!input.IsValid())
5091  {
5092  return Fail("%s: Operation has invalid inputs", __func__);
5093  }
5094 
5095  const Operand* outputOperand = GetOutputOperand(operation, 0, model);
5096  if (!outputOperand)
5097  {
5098  return Fail("%s: Operation has no outputs", __func__);
5099  }
5100 
5101  const TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
5102 
5103  SoftmaxDescriptor desc;
5104  OperandType outputType = outputOperand->type;
5105 
5106  // Read beta value
5107  if (outputType == OperandType::TENSOR_FLOAT16)
5108  {
5109  Half value;
5110 
5111  if (!GetInputScalar(operation, 1, OperandType::FLOAT16, value, model, data))
5112  {
5113  return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
5114  }
5115 
5116  desc.m_Beta = static_cast<float>(value);
5117  }
5118  else
5119  {
5120  if (!GetInputFloat32(operation, 1, desc.m_Beta, model, data))
5121  {
5122  return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
5123  }
5124  }
5125 
5126  if (operation.inputs.size() > 2 && !GetInputScalar(operation,
5127  2,
5128  OperandType::INT32,
5129  desc.m_Axis,
5130  model,
5131  data))
5132  {
5133  return Fail("%s: Operation has invalid inputs", __func__);
5134  }
5135 
5136  bool isSupported = false;
5137  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5138  {
5139  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5141  data.m_Backends,
5142  isSupported,
5143  input.GetTensorInfo(),
5144  outputInfo,
5145  desc);
5146  };
5147 
5148  if(IsDynamicTensor(outputInfo))
5149  {
5150  isSupported = AreDynamicTensorsSupported();
5151  }
5152  else
5153  {
5154  validateFunc(outputInfo, isSupported);
5155  }
5156 
5157  if (!isSupported)
5158  {
5159  return false;
5160  }
5161 
5162  IConnectableLayer* layer = data.m_Network->AddSoftmaxLayer(desc);
5163  assert(layer != nullptr);
5164  input.Connect(layer->GetInputSlot(0));
5165 
5166  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5167 }
5168 
5169 bool Converter::ConvertSub(const Operation& operation, const Model& model, ConversionData& data)
5170 {
5171  VLOG(DRIVER) << "Converter::ConvertSub()";
5172 
5173  LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
5174  LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
5175 
5176  if (!input0.IsValid() || !input1.IsValid())
5177  {
5178  return Fail("%s: Operation has invalid inputs", __func__);
5179  }
5180 
5181  // The FuseActivation parameter is always the input index 2
5182  // and it should be optional
5183  ActivationFn activationFunction;
5184  if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
5185  {
5186  return Fail("%s: Operation has invalid inputs", __func__);
5187  }
5188 
5189  const Operand* output = GetOutputOperand(operation, 0, model);
5190  if (!output)
5191  {
5192  return Fail("%s: Could not read output 0", __func__);
5193  }
5194 
5195  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5196 
5197  bool isSupported = false;
5198  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5199  {
5200  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5202  data.m_Backends,
5203  isSupported,
5204  input0.GetTensorInfo(),
5205  input1.GetTensorInfo(),
5206  outputInfo);
5207  };
5208 
5209  if(IsDynamicTensor(outputInfo))
5210  {
5211  isSupported = AreDynamicTensorsSupported();
5212  }
5213  else
5214  {
5215  validateFunc(outputInfo, isSupported);
5216  }
5217 
5218  if (!isSupported)
5219  {
5220  return false;
5221  }
5222 
5223  armnn::IConnectableLayer* const startLayer = data.m_Network->AddSubtractionLayer();
5224 
5225  bool isReshapeSupported = BroadcastTensor(input0, input1, startLayer, data);
5226  if (!isReshapeSupported)
5227  {
5228  return false;
5229  }
5230  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
5231  data, nullptr, validateFunc, activationFunction);
5232 }
5233 
5234 bool Converter::ConvertTanH(const Operation& operation, const Model& model, ConversionData& data)
5235 {
5236  VLOG(DRIVER) << "Converter::ConvertTanH()";
5237 
5240  desc.m_A = 1.0f; // android nn does not support tanH parameters
5241  desc.m_B = 1.0f; // set to 1.0f for unity scaling
5242 
5243  return ConvertToActivation(operation, __func__, desc, model, data);
5244 }
5245 
5246 bool Converter::ConvertTransposeConv2d(const Operation& operation, const Model& model, ConversionData& data)
5247 {
5248  VLOG(DRIVER) << "Converter::ConvertTransposeConv2d()";
5249 
5250  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5251 
5252  if (!input.IsValid())
5253  {
5254  return Fail("%s: Operation has invalid inputs", __func__);
5255  }
5256 
5257  const Operand* output = GetOutputOperand(operation, 0, model);
5258 
5259  if (!output)
5260  {
5261  return Fail("%s: Could not read output 0", __func__);
5262  }
5263 
5264  const TensorInfo& inputInfo = input.GetTensorInfo();
5265  const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5266 
5267  // ArmNN does not currently support non-fixed weights or bias
5268  // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
5269  const Operand* weightsOperand = GetInputOperand(operation, 1, model);
5270 
5271  if (weightsOperand == nullptr)
5272  {
5273  return Fail("%s: Operand is invalid", __func__);
5274  }
5276  desc.m_DataLayout = DataLayout::NHWC;
5277 
5278  // Determine whether padding is implicit or explicit
5279  bool implicitPadding = operation.inputs.size() == 9;
5280 
5281  if (implicitPadding )
5282  {
5283  desc.m_DataLayout = OptionalDataLayout(operation, 8, model, data);
5284  }
5285  else
5286  {
5287  desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data);
5288  }
5289 
5290  armnnUtils::DataLayoutIndexed dataLayoutIndexed(desc.m_DataLayout);
5291  unsigned int widthIndex = dataLayoutIndexed.GetWidthIndex();
5292  unsigned int heightIndex = dataLayoutIndexed.GetHeightIndex();
5293 
5294  const PermutationVector OHWIToOIHW = {0, 2, 3, 1};
5295 
5296  // The shape of the weight is [depth_out, filter_height, filter_width, depth_in].
5297  // We have to permute it to OIHW if the data layout is NCHW.
5298  const ConstTensorPin weightsPin = (desc.m_DataLayout == DataLayout::NCHW) ?
5300  model, data, OHWIToOIHW) :
5301  ConvertOperationInputToConstTensorPin(operation, 1, model, data);
5302 
5303  // Bias is a 1D tensor
5304  const ConstTensorPin biasPin =
5305  ConvertOperationInputToConstTensorPin(operation, 2, model, data);
5306 
5307  if (!weightsPin.IsValid())
5308  {
5309  return Fail("%s: Operation has invalid weights", __func__);
5310  }
5311 
5312  if (!biasPin.IsValid())
5313  {
5314  return Fail("%s: Operation has invalid biases", __func__);
5315  }
5316 
5317  ConstTensor weights = weightsPin.GetConstTensor();
5318  ConstTensor bias = biasPin.GetConstTensor();
5319  SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), inputInfo);
5320 
5321  ActivationFn activation;
5322 
5323  if (implicitPadding)
5324  {
5325  int32_t strideX{0};
5326  int32_t strideY{0};
5327  int32_t padLeft{0};
5328  int32_t padRight{0};
5329  int32_t padTop{0};
5330  int32_t padBottom{0};
5331 
5332  ::android::nn::PaddingScheme paddingScheme;
5333  if (!GetInputPaddingScheme(operation, 4, paddingScheme, model, data) ||
5334  !GetInputScalar(operation, 5, OperandType::INT32, strideX, model, data) ||
5335  !GetInputScalar(operation, 6, OperandType::INT32, strideY, model, data) ||
5336  !GetInputActivationFunction(operation, 7, activation, model, data))
5337  {
5338  return Fail("%s: Operation has invalid inputs (implicit padding)", __func__);
5339  }
5340 
5341  const uint32_t kernelX = weights.GetShape()[widthIndex];
5342  const uint32_t kernelY = weights.GetShape()[heightIndex];
5343 
5344  // If output shape has been specified as a parameter then extract it and make it available.
5345  const Operand* outputShapeOperand = GetInputOperand(operation, 3, model, false);
5346  std::vector<int32_t> outputShape;
5347  if ((outputShapeOperand) && (GetTensorInt32Values(*outputShapeOperand, outputShape, model, data)))
5348  {
5349  // Change from signed to unsigned int to store in TransposeConvolution2dDescriptor.
5350  for (int dimension : outputShape)
5351  {
5352  desc.m_OutputShape.push_back(static_cast<unsigned int>(dimension));
5353  }
5354  desc.m_OutputShapeEnabled = true;
5355  }
5356 
5357  uint32_t outputX;
5358  uint32_t outputY;
5359 
5360  if (IsDynamicTensor(outputInfo))
5361  {
5362  if (outputShape.size() == 0)
5363  {
5364  return Fail("%s: Padding sizes cannot be inferred", __func__);
5365  }
5366 
5367  outputX = outputShape[widthIndex];
5368  outputY = outputShape[heightIndex];
5369  }
5370  else
5371  {
5372  outputX = outputInfo.GetShape()[widthIndex];
5373  outputY = outputInfo.GetShape()[heightIndex];
5374  }
5375 
5376  CalcPaddingTransposeConv(outputX, kernelX, strideX, padLeft, padRight, paddingScheme);
5377  CalcPaddingTransposeConv(outputY, kernelY, strideY, padTop, padBottom, paddingScheme);
5378 
5379  // NOTE: The Android NN API allows for negative padding values in TransposeConv2d,
5380  // but Arm NN only supports values >= 0
5381  if (padLeft < 0 || padRight < 0 || padTop < 0 || padBottom < 0)
5382  {
5383  return Fail("%s: Negative padding values are not supported", __func__);
5384  }
5385 
5386  desc.m_StrideX = armnn::numeric_cast<uint32_t>(strideX);
5387  desc.m_StrideY = armnn::numeric_cast<uint32_t>(strideY);
5388  desc.m_PadLeft = armnn::numeric_cast<uint32_t>(padLeft);
5389  desc.m_PadRight = armnn::numeric_cast<uint32_t>(padRight);
5390  desc.m_PadTop = armnn::numeric_cast<uint32_t>(padTop);
5391  desc.m_PadBottom = armnn::numeric_cast<uint32_t>(padBottom);
5392  }
5393  else if (operation.inputs.size() == 11)
5394  {
5395  // explicit padding
5396  if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data) ||
5397  !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data) ||
5398  !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data) ||
5399  !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data) ||
5400  !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data) ||
5401  !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data) ||
5402  !GetInputActivationFunction(operation, 9, activation, model, data))
5403  {
5404  return Fail("%s: Operation has invalid inputs (explicit padding)", __func__);
5405  }
5406  }
5407  else
5408  {
5409  return Fail("%s: Unsupported number of operation inputs", __func__);
5410  }
5411 
5412  desc.m_BiasEnabled = true;
5413  Optional<TensorInfo> biases(bias.GetInfo());
5414 
5415  bool isSupported = false;
5416  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5417  {
5418  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5420  data.m_Backends,
5421  isSupported,
5422  inputInfo,
5423  outputInfo,
5424  desc,
5425  weights.GetInfo(),
5426  biases);
5427  };
5428 
5429  if(IsDynamicTensor(outputInfo))
5430  {
5431  isSupported = AreDynamicTensorsSupported();
5432  }
5433  else
5434  {
5435  validateFunc(outputInfo, isSupported);
5436  }
5437  if (!isSupported)
5438  {
5439  return false;
5440  }
5441 
5442  IConnectableLayer* startLayer =
5443  data.m_Network->AddTransposeConvolution2dLayer(desc, weights, Optional<ConstTensor>(bias));
5444  if (!startLayer)
5445  {
5446  return Fail("%s: AddTransposeConvolution2dLayer failed", __func__);
5447  }
5448 
5449  input.Connect(startLayer->GetInputSlot(0));
5450 
5451  return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
5452  data, nullptr, validateFunc, activation);
5453 }
5454 
5455 bool Converter::ConvertSqrt(const Operation& operation, const Model& model, ConversionData& data)
5456 {
5457  VLOG(DRIVER) << "Converter::ConvertSqrt()";
5458  ActivationDescriptor desc;
5459  desc.m_Function = ActivationFunction::Sqrt;
5460 
5461  return ::ConvertToActivation(operation, __func__, desc, model, data);
5462 }
5463 
5464 bool Converter::ConvertSqueeze(const Operation& operation, const Model& model, ConversionData& data)
5465 {
5466  VLOG(DRIVER) << "Converter::ConvertSqueeze()";
5467 
5468  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5469  if (!input.IsValid())
5470  {
5471  return Fail("%s: Operation has invalid inputs", __func__);
5472  }
5473 
5474  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
5475  unsigned int rank = inputInfo.GetNumDimensions();
5476  if (rank > 4)
5477  {
5478  Fail("%s: Inputs with rank greater than 4 are not supported", __func__);
5479  }
5480 
5481  const Operand* output = GetOutputOperand(operation, 0, model);
5482  if (!output)
5483  {
5484  return Fail("%s: Could not read output 0", __func__);
5485  }
5486 
5488  {
5489  return Fail("%s: Dynamic output tensors are not supported", __func__);
5490  }
5491 
5492  // NOTE: Axis is an optional parameter to SQUEEZE, therefore we do not want to generate a failure
5493  // if the operand index is out of bounds.
5494  const Operand* axisOperand = GetInputOperand(operation, 1, model, false);
5495 
5496  const uint32_t dimensionSequence[] = { 0, 1, 2, 3 };
5497 
5498  std::vector<int32_t> axis;
5499  if (!axisOperand)
5500  {
5501  axis.assign(dimensionSequence,
5502  dimensionSequence + rank);
5503  }
5504  else if (!GetTensorInt32Values(*axisOperand, axis, model, data))
5505  {
5506  return Fail("%s: Operation has an invalid or unsupported axis operand", __func__);
5507  }
5508 
5509  std::vector<uint32_t> outputDims;
5510  for (unsigned int i = 0; i < rank; i++)
5511  {
5512  bool skipSqueeze = (std::find(axis.begin(), axis.end(), i) == axis.end());
5513  auto currentDimension = inputInfo.GetShape()[i];
5514  if (skipSqueeze || currentDimension != 1)
5515  {
5516  outputDims.push_back(currentDimension);
5517  }
5518  }
5519 
5520  armnn::TensorShape outShape = armnn::TensorShape(outputDims.size(), outputDims.data());
5521 
5522  armnn::TensorInfo outputInfo = inputInfo;
5523  outputInfo.SetShape(outShape);
5524 
5525  armnn::ReshapeDescriptor reshapeDesc;
5526  reshapeDesc.m_TargetShape = outputInfo.GetShape();
5527 
5528  bool isSupported = false;
5529  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5531  data.m_Backends,
5532  isSupported,
5533  inputInfo,
5534  outputInfo,
5535  reshapeDesc);
5536 
5537  if (!isSupported)
5538  {
5539  return false;
5540  }
5541 
5542  armnn::IConnectableLayer* const layer = data.m_Network->AddReshapeLayer(reshapeDesc);
5543  assert(layer != nullptr);
5544  input.Connect(layer->GetInputSlot(0));
5545 
5546  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
5547 }
5548 
5549 bool Converter::ConvertStridedSlice(const Operation& operation, const Model& model, ConversionData& data)
5550 {
5551  VLOG(DRIVER) << "Converter::ConvertStridedSlice()";
5552 
5553  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5554  if (!input.IsValid())
5555  {
5556  return Fail("%s: Operation has invalid inputs", __func__);
5557  }
5558 
5559  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
5560  unsigned int rank = inputInfo.GetNumDimensions();
5561  if (rank > 4)
5562  {
5563  Fail("%s: Inputs with rank greater than 4 are not supported", __func__);
5564  }
5565 
5566  const Operand* output = GetOutputOperand(operation, 0, model);
5567  if (!output)
5568  {
5569  return Fail("%s: Could not read output 0", __func__);
5570  }
5571 
5572  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5573 
5574  const Operand* beginOperand = GetInputOperand(operation, 1, model);
5575  const Operand* endOperand = GetInputOperand(operation, 2, model);
5576  const Operand* stridesOperand = GetInputOperand(operation, 3, model);
5577 
5578  std::vector<int32_t> beginValues;
5579  std::vector<int32_t> endValues;
5580  std::vector<int32_t> stridesValues;
5581 
5582  // The length of the beginOperand, endOperand and stridesOperand must be of a rank(input)
5583  auto ValidateInputOperands = [&] (const Operand& operand, std::vector<int32_t>& operandValues)
5584  {
5585  if (!GetTensorInt32Values(operand, operandValues, model, data))
5586  {
5587  return false;
5588  }
5589 
5590  if (operandValues.size() != rank)
5591  {
5592  return false;
5593  }
5594 
5595  return true;
5596  };
5597 
5598  if (!ValidateInputOperands(*beginOperand, beginValues)
5599  || !ValidateInputOperands(*endOperand, endValues)
5600  || !ValidateInputOperands(*stridesOperand, stridesValues))
5601  {
5602  return Fail("%s: Operation has invalid input operand", __func__);
5603  }
5604 
5605  // Stride cannot have value '0'
5606  if (std::any_of(stridesValues.cbegin(), stridesValues.cend(), [](int32_t i){ return i == 0; }))
5607  {
5608  return Fail("%s: Stride must be non-zero value.", __func__);
5609  }
5610 
5611  armnn::StridedSliceDescriptor descriptor;
5612  descriptor.m_Begin.assign(beginValues.cbegin(), beginValues.cend());
5613  descriptor.m_End.assign(endValues.cbegin(), endValues.cend());
5614  descriptor.m_Stride.assign(stridesValues.cbegin(), stridesValues.cend());
5616 
5617  // Get the "begin_mask", "end_mask", and "shrink_axis_mask" flags
5618  if (!GetInputInt32(operation, 4, descriptor.m_BeginMask, model, data) ||
5619  !GetInputInt32(operation, 5, descriptor.m_EndMask, model, data) ||
5620  !GetInputInt32(operation, 6, descriptor.m_ShrinkAxisMask, model, data))
5621  {
5622  return Fail("%s: Operation has invalid inputs", __func__);
5623  }
5624 
5625  bool isSupported = false;
5626  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5627  {
5628  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5630  data.m_Backends,
5631  isSupported,
5632  inputInfo,
5633  outputInfo,
5634  descriptor);
5635  };
5636 
5637  if(IsDynamicTensor(outputInfo))
5638  {
5639  isSupported = AreDynamicTensorsSupported();
5640  }
5641  else
5642  {
5643  validateFunc(outputInfo, isSupported);
5644  }
5645 
5646  if (!isSupported)
5647  {
5648  return false;
5649  }
5650 
5651  // Check if slice can fit in a inferred output
5652  armnn::TensorShape inputShape = inputInfo.GetShape();
5653  for (unsigned int i = 0; i < inputShape.GetNumDimensions(); i++)
5654  {
5655  int stride = descriptor.m_Stride[i];
5656 
5657  if (descriptor.m_ShrinkAxisMask & (1 << i))
5658  {
5659  // If the difference between the start point and the end point of the slice on an axis being shrunk
5660  // is greater than 1 then throw an error as the output will not be large enough to hold the slice
5661  if (((descriptor.m_Begin[i] - descriptor.m_End[i]) > 1)
5662  || ((descriptor.m_Begin[i] - descriptor.m_End[i]) < -1))
5663  {
5664  return Fail("%s: StridedSlice: Output will not be large enough to hold the slice", __func__);
5665  }
5666 
5667  if(stride < 0)
5668  {
5669  return Fail("%s: StridedSlice: Stride can not be negative while ShrinkAxisMask is set.", __func__);
5670  }
5671  }
5672  }
5673 
5674  armnn::IConnectableLayer* const layer = data.m_Network->AddStridedSliceLayer(descriptor);
5675  assert(layer != nullptr);
5676  input.Connect(layer->GetInputSlot(0));
5677 
5678  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5679 }
5680 
5681 bool Converter::ConvertTranspose(const Operation& operation, const Model& model, ConversionData& data)
5682 {
5683  VLOG(DRIVER) << "Converter::ConvertTranspose()";
5684 
5685  LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5686  if (!input.IsValid())
5687  {
5688  return Fail("%s: Operation has invalid inputs", __func__);
5689  }
5690 
5691  const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
5692  unsigned int rank = inputInfo.GetNumDimensions();
5693  if (rank > 4)
5694  {
5695  Fail("%s: Inputs with rank greater than 4 are not supported", __func__);
5696  }
5697 
5698  // NOTE: Axis is an optional parameter to TRANSPOSE, therefore we do not want to generate a failure
5699  // if the operand index is out of bounds.
5700  const Operand* permOperand = GetInputOperand(operation, 1, model, false);
5701 
5702  std::vector<int32_t> perm(rank);
5703  if (!permOperand || (permOperand->lifetime == OperandLifeTime::NO_VALUE))
5704  {
5705  for (unsigned int i = rank; i > 0; i--)
5706  {
5707  perm[rank - i] = armnn::numeric_cast<int> (i - 1);
5708  }
5709  }
5710  else if (!GetTensorInt32Values(*permOperand, perm, model, data))
5711  {
5712  return Fail("%s: Operation has an invalid or unsupported permutation operand", __func__);
5713  }
5714 
5715  std::vector<uint32_t> outputDims(perm.begin(), perm.begin() + rank);
5716 
5717  armnn::TransposeDescriptor transposeDesc;
5718  transposeDesc.m_DimMappings = armnn::PermutationVector(outputDims.data(), outputDims.size());
5719 
5720  const Operand* output = GetOutputOperand(operation, 0, model);
5721  if (!output)
5722  {
5723  return Fail("%s: Could not read output 0", __func__);
5724  }
5725 
5726  const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5727 
5728  bool isSupported = false;
5729  auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5730  {
5731  FORWARD_LAYER_SUPPORT_FUNC(__func__,
5732  IsTransposeSupported,
5733  data.m_Backends,
5734  isSupported,
5735  inputInfo,
5736  outputInfo,
5737  transposeDesc);
5738  };
5739 
5740  if(IsDynamicTensor(outputInfo))
5741  {
5742  isSupported = AreDynamicTensorsSupported();
5743  }
5744  else
5745  {
5746  validateFunc(outputInfo, isSupported);
5747  }
5748 
5749  if (!isSupported)
5750  {
5751  return false;
5752  }
5753 
5754  armnn::IConnectableLayer* const layer = data.m_Network->AddTransposeLayer(transposeDesc);
5755  assert(layer != nullptr);
5756  input.Connect(layer->GetInputSlot(0));
5757 
5758  return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5759 }
5760 
5761 } // 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)
uint32_t m_PadBottom
Padding bottom value in the height dimension.
bool m_BiasEnabled
Enable/disable bias.
virtual unsigned int GetNumOutputSlots() const =0
Returns the number of connectable output slots.
bool m_ProjectionEnabled
Enable/disable the projection layer.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
const TensorInfo * m_InputLayerNormWeights
Definition: LstmParams.hpp:106
bool IsSoftmaxSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const SoftmaxDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const ConstTensor * m_ProjectionWeights
Definition: LstmParams.hpp:55
bool IsConcatSupported(const BackendId &backend, const std::vector< const TensorInfo *> inputs, const TensorInfo &output, const OriginsDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const TensorInfo * m_OutputGateBias
Definition: LstmParams.hpp:103
A ViewsDescriptor for the SplitterLayer.
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:68
const ConstTensor * m_CellBias
Definition: LstmParams.hpp:53
bool m_BiasEnabled
Enable/disable bias.
bool IsQuantizedLstmSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &previousCellStateIn, const TensorInfo &previousOutputIn, const TensorInfo &cellStateOut, const TensorInfo &output, const QuantizedLstmInputParamsInfo &paramsInfo, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
virtual unsigned int GetNumInputSlots() const =0
Returns the number of connectable input slots.
DataLayout
Definition: Types.hpp:62
unsigned int GetWidthIndex() const
float m_K
Kappa value used for the across channel normalization equation.
int m_Axis
Scalar, defaulted to the last index (-1), specifying the dimension the activation will be performed o...
A TransposeConvolution2dDescriptor for the TransposeConvolution2dLayer.
const TensorShape & GetShape() const
Definition: Tensor.hpp:191
const ConstTensor * m_RecurrentToOutputWeights
uint32_t m_PadBottom
Padding bottom value in the height dimension.
const TensorInfo * m_ProjectionWeights
Definition: LstmParams.hpp:104
::android::nn::Operand Operand
Definition: Converter.hpp:25
float m_ClippingThresProj
Clipping threshold value for the projection.
int32_t m_ShrinkAxisMask
Shrink axis mask value. If set, the nth specification shrinks the dimensionality by 1...
A ReshapeDescriptor for the ReshapeLayer.
std::vector< int > m_Begin
Begin values for the input that will be sliced.
constexpr const char * GetResizeMethodAsCString(ResizeMethod method)
Definition: TypesUtils.hpp:254
const ConstTensor * m_CellToOutputWeights
Definition: LstmParams.hpp:50
const std::vector< armnn::BackendId > m_Backends
bool IsLstmSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &outputStateIn, const TensorInfo &cellStateIn, const TensorInfo &scratchBuffer, const TensorInfo &outputStateOut, const TensorInfo &cellStateOut, const TensorInfo &output, const LstmDescriptor &descriptor, const LstmInputParamsInfo &paramsInfo, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const ConstTensor * m_RecurrentToForgetWeights
float m_PadValue
Optional value to use for padding, defaults to 0.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
bool GetInputActivationFunction(const Operation &operation, uint32_t inputIndex, ActivationFn &outActivationFunction, const Model &model, const ConversionData &data)
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
A ComparisonDescriptor for the ComparisonLayer.
Definition: Descriptors.hpp:89
bool m_TransposeWeightMatrix
Enable/disable transpose weight matrix.
bool HasPerAxisQuantization() const
Definition: Tensor.cpp:446
bool m_PeepholeEnabled
Enable/disable peephole.
bool ConvertReduce(const Operation &operation, const Model &model, ConversionData &data, armnn::ReduceOperation reduceOperation)
A Convolution2dDescriptor for the Convolution2dLayer.
float m_Alpha
Alpha value for the normalization equation.
armnn::IOutputSlot * GetOutputSlot() const
static bool ConvertOperation(const Operation &operation, const Model &model, ConversionData &data)
Definition: Converter.cpp:21
uint32_t m_PadLeft
Padding left value in the width dimension.
float m_HiddenStateScale
Hidden State quantization scale.
bool m_BiasEnabled
Enable/disable bias.
const ConstTensor * m_CellToInputWeights
Definition: LstmParams.hpp:48
bool m_TransposeX
Transpose the slices of each input tensor Transpose and Adjoint can not both be set to true for the s...
std::vector< unsigned int > m_OutputShape
Optional< unsigned int > GetQuantizationDim() const
Definition: Tensor.cpp:494
bool IsDequantizeSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
bool GetOptionalBool(const Operation &operation, uint32_t inputIndex, const Model &model, const ConversionData &data)
const TensorShape & GetShape() const
Definition: Tensor.hpp:297
float m_OutputIntermediateScale
Output intermediate quantization scale.
ResizeMethod m_Method
The Interpolation method to use (Bilinear, NearestNeighbor).
float m_Gamma
Gamma, the scale scalar value applied for the normalized tensor. Defaults to 1.0. ...
bool IsMaximumSupported(const BackendId &backend, const TensorInfo &input0, const TensorInfo &input1, const TensorInfo &output, char *reasonIfUnSupported=nullptr, size_t reasonIfUnSupportedMaxLength=0)
Deprecated in favor of IBackend and ILayerSupport interfaces.
float m_Beta
Exponentiation value.
bool IsL2NormalizationSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const L2NormalizationDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
bool IsStridedSliceSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const StridedSliceDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const armnn::ConstTensor * GetConstTensorPtr() const
const TensorInfo * m_ForgetLayerNormWeights
Definition: LstmParams.hpp:107
const ConstTensor * m_InputGateBias
Definition: LstmParams.hpp:51
ArgMinMaxFunction m_Function
Specify if the function is to find Min or Max.
Definition: Descriptors.hpp:81
bool m_OutputShapeEnabled
Output shape if it has been specified.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
bool IsTransposeConvolution2dSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const TransposeConvolution2dDescriptor &descriptor, const TensorInfo &weights, const Optional< TensorInfo > &biases, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
bool IsDepthwiseConvolutionSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const DepthwiseConvolution2dDescriptor &descriptor, const TensorInfo &weights, const Optional< TensorInfo > &biases, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
void Connect(armnn::IInputSlot &inputSlot)
const ConstTensor * m_RecurrentToCellWeights
Definition: LstmParams.hpp:46
A LogicalBinaryDescriptor for the LogicalBinaryLayer.
uint32_t m_PadRight
Padding right value in the width dimension.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
MemoryType GetMemoryArea() const
Definition: Tensor.hpp:305
const ConstTensor * m_ForgetLayerNormWeights
Definition: LstmParams.hpp:58
const ConstTensor * m_CellToForgetWeights
Definition: LstmParams.hpp:49
bool GetOperandType(const Operation &operation, uint32_t inputIndex, const Model &model, OperandType &type)
uint32_t m_PadBottom
Padding bottom value in the height dimension.
int32_t m_BeginMask
Begin mask value.
const TensorInfo * m_ForgetGateBias
Definition: LstmParams.hpp:101
const TensorInfo * m_OutputLayerNormWeights
Definition: LstmParams.hpp:109
uint32_t m_DilationY
Dilation along y axis.
int32_t m_EndMask
End mask value.
A SpaceToDepthDescriptor for the SpaceToDepthLayer.
bool GetOptionalInputActivation(const Operation &operation, uint32_t inputIndex, ActivationFn &activationFunction, const Model &model, const ConversionData &data)
bool IsResizeSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const ResizeDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
std::vector< std::pair< unsigned int, unsigned int > > m_PadList
Specifies the padding values for the input dimension: heightPad{top, bottom} widthPad{left, right}.
std::vector< float > GetQuantizationScales() const
Definition: Tensor.cpp:451
uint32_t m_DilationY
Dilation factor value for height dimension.
A BatchToSpaceNdDescriptor for the BatchToSpaceNdLayer.
bool IsSpaceToBatchNdSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const SpaceToBatchNdDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
LogicalBinaryOperation
Definition: Types.hpp:118
const TensorInfo * m_RecurrentToCellWeights
Definition: LstmParams.hpp:95
const ConstTensor * m_OutputGateBias
Definition: LstmParams.hpp:54
unsigned int GetHeightIndex() const
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
NormalizationAlgorithmMethod m_NormMethodType
Normalization method algorithm to use (LocalBrightness, LocalContrast).
void SetShape(const TensorShape &newShape)
Definition: Tensor.hpp:193
const armnn::PermutationVector g_DontPermute
A ResizeDescriptor for the ResizeLayer.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
const TensorInfo * m_InputToCellWeights
Definition: LstmParams.hpp:91
std::vector< unsigned int > m_Axis
Values for the dimensions to reduce.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
bool IsSubtractionSupported(const BackendId &backend, const TensorInfo &input0, const TensorInfo &input1, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const Operand * GetInputOperand(const Operation &operation, uint32_t inputIndex, const Model &model, bool failOnIndexOutOfBounds=true)
constexpr char const * GetUnaryOperationAsCString(UnaryOperation operation)
Definition: TypesUtils.hpp:71
TensorShape m_TargetShape
Target shape value.
bool IsFullyConnectedSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const TensorInfo &weights, const TensorInfo &biases, const FullyConnectedDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
constexpr char const * GetLogicalBinaryOperationAsCString(LogicalBinaryOperation operation)
Definition: TypesUtils.hpp:87
const TensorInfo * m_RecurrentToInputWeights
Definition: LstmParams.hpp:93
const TensorInfo * m_RecurrentToOutputWeights
Definition: LstmParams.hpp:96
bool AreDynamicTensorsSupported()
Checks for ArmNN support of dynamic tensors.
uint32_t m_PadTop
Padding top value in the height dimension.
A PadDescriptor for the PadLayer.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
constexpr char const * GetArgMinMaxFunctionAsCString(ArgMinMaxFunction function)
Definition: TypesUtils.hpp:47
const ConstTensor * m_InputLayerNormWeights
Definition: LstmParams.hpp:57
bool ConvertPooling2d(const Operation &operation, const char *operationName, armnn::PoolingAlgorithm poolType, const Model &model, ConversionData &data)
ComparisonOperation
Definition: Types.hpp:108
const TensorInfo * m_CellToOutputWeights
Definition: LstmParams.hpp:99
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)
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
bool m_LayerNormEnabled
Enable/disable layer normalization.
const ConstTensor * m_RecurrentToOutputWeights
Definition: LstmParams.hpp:47
An LstmDescriptor for the LstmLayer.
bool GetInputScalar(const Operation &operation, uint32_t inputIndex, OperandType type, OutputType &outValue, const Model &model, const ConversionData &data, bool optional=false)
uint32_t m_DilationX
Dilation factor value for width dimension.
uint32_t m_PadTop
Padding top value in the height dimension.
Status SetViewSize(uint32_t view, uint32_t coord, uint32_t value)
Set the size of the views.
bool m_KeepDims
Enable/disable keep dimensions. If true, then the reduced dimensions that are of length 1 are kept...
std::vector< unsigned int > m_BlockShape
Block shape values.
float m_Eps
Epsilon, small scalar value added to variance to avoid dividing by zero. Defaults to 1e-12f...
A L2NormalizationDescriptor for the L2NormalizationLayer.
const ConstTensor * m_ProjectionBias
Definition: LstmParams.hpp:56
int32_t GetQuantizationOffset() const
Definition: Tensor.cpp:478
const armnn::TensorInfo & GetTensorInfo() const
armnn::DataLayout OptionalDataLayout(const Operation &operation, uint32_t inputIndex, const Model &model, ConversionData &data)
const ConstTensor * m_InputToForgetWeights
An ArgMinMaxDescriptor for ArgMinMaxLayer.
Definition: Descriptors.hpp:67
float GetQuantizationScale() const
Definition: Tensor.cpp:461
DataType GetDataType() const
Definition: Tensor.hpp:198
Provides access to the appropriate indexes for Channels, Height and Width based on DataLayout...
An OriginsDescriptor for the ConcatLayer.
float m_ProjectionClip
Clipping threshold value for the projection.
bool IsDivisionSupported(const BackendId &backend, const TensorInfo &input0, const TensorInfo &input1, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
bool has_value() const noexcept
Definition: Optional.hpp:53
A FullyConnectedDescriptor for the FullyConnectedLayer.
armnn::TensorInfo GetTensorInfoForOperand(const Operand &operand)
const TensorInfo * m_CellLayerNormWeights
Definition: LstmParams.hpp:108
bool m_BiasEnabled
Enable/disable bias.
const TensorInfo * m_InputToForgetWeights
Definition: LstmParams.hpp:90
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:327
float m_InputIntermediateScale
Input intermediate quantization scale.
bool IsSpaceToDepthSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const SpaceToDepthDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const TensorInfo * m_ProjectionBias
Definition: LstmParams.hpp:105
uint32_t m_TargetWidth
Target width value.
A GatherDescriptor for the GatherLayer.
bool m_PeepholeEnabled
Enable/disable peephole.
bool ConvertPaddings(const Operation &operation, const Model &model, ConversionData &data, unsigned int rank, armnn::PadDescriptor &padDescriptor)
bool m_HalfPixelCenters
Half Pixel Centers.
uint32_t m_PadTop
Padding top value in the height dimension.
void SetQuantizationScale(float scale)
Definition: Tensor.cpp:473
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
A QLstmDescriptor for the QLstmLayer.
armnn::TensorShape ExpandDims(const armnn::TensorShape &tensorShape, int axis)
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
std::vector< unsigned int > m_BlockShape
Block shape value.
std::vector< int > m_Stride
Stride values for the input that will be sliced.
bool IsMinimumSupported(const BackendId &backend, const TensorInfo &input0, const TensorInfo &input1, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:36
const TensorInfo * m_CellToInputWeights
Definition: LstmParams.hpp:97
const TensorInfo & GetInfo() const
Definition: Tensor.hpp:295
min(a, max(b, input)) ReLu1 & ReLu6.
uint32_t m_TargetHeight
Target height value.
uint32_t m_ActivationFunc
The activation function to use.
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
bool IsNormalizationSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const NormalizationDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
bool IsMeanSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const MeanDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const ConstTensor * m_RecurrentToInputWeights
float m_ClippingThresCell
Clipping threshold value for the cell state.
unsigned int m_BlockSize
Scalar specifying the input block size. It must be >= 1.
A BatchMatMulDescriptor for the BatchMatMul operator.
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)
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
bool IsDynamicTensor(const armnn::TensorInfo &tensorInfo)
Checks if a tensor info represents a dynamic tensor.
float m_ForgetIntermediateScale
Forget intermediate quantization scale.
::android::nn::OperandType OperandType
Definition: Converter.hpp:27
float m_Beta
Beta, the offset scalar value applied for the normalized tensor. Defaults to 1.0. ...
const TensorInfo * m_InputGateBias
Definition: LstmParams.hpp:100
bool IsConvolution2dSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const Convolution2dDescriptor &descriptor, const TensorInfo &weights, const Optional< TensorInfo > &biases, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
std::vector< int > m_End
End values for the input that will be sliced.
const ConstTensor * m_CellLayerNormWeights
Definition: LstmParams.hpp:59
const ConstTensor * m_ForgetGateBias
Definition: LstmParams.hpp:52
A SpaceToBatchNdDescriptor for the SpaceToBatchNdLayer.
const ConstTensor * m_InputToCellWeights
Definition: LstmParams.hpp:42
bool IsWeightsValid(const Operation &operation, uint32_t inputIndex, const Model &model)
Utility functions.
const ConstTensor * m_InputToOutputWeights
Definition: LstmParams.hpp:43
::android::nn::Operation Operation
Definition: Converter.hpp:28
NormalizationAlgorithmChannel m_NormChannelType
Normalization channel algorithm to use (Across, Within).
const TensorInfo * m_InputToOutputWeights
Definition: LstmParams.hpp:92
float m_CellClip
Clipping threshold value for the cell state.
float m_A
Alpha upper bound value used by the activation functions. (BoundedReLu, Linear, TanH, Elu).
Definition: Descriptors.hpp:61
uint32_t m_DilationX
Dilation along x axis.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
bool m_CifgEnabled
Enable/disable cifg (coupled input & forget gate).
LayerInputHandle ConvertToLayerInputHandle(const Operation &operation, uint32_t inputIndex, const Model &model, ConversionData &data, const armnn::PermutationVector &dimensionMappings, const LayerInputHandle *inputHandle)
uint32_t m_PadLeft
Padding left value in the width dimension.
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)
bool m_AlignCorners
Aligned corners.
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
int32_t m_Axis
The axis in params to gather indices from.
A ElementwiseUnaryDescriptor for the ElementwiseUnaryLayer.
const TensorInfo * m_InputToInputWeights
Definition: LstmParams.hpp:89
const ConstTensor * m_RecurrentToForgetWeights
Definition: LstmParams.hpp:45
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
bool GetInputPaddingScheme(const Operation &operation, uint32_t inputIndex, PaddingScheme &outPaddingScheme, const Model &model, const ConversionData &data)
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
std::vector< std::pair< unsigned int, unsigned int > > m_Crops
The values to crop from the input dimension.
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 m_ProjectionEnabled
Enable/disable the projection layer.
ArgMinMaxFunction
Definition: Types.hpp:102
const armnn::ConstTensor & GetConstTensor() const
bool IsConnectedToDequantize(armnn::IOutputSlot *ioutputSlot)
OriginsDescriptor ConcatDescriptor
constexpr char const * GetComparisonOperationAsCString(ComparisonOperation operation)
Definition: TypesUtils.hpp:57
bool IsBatchToSpaceNdSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const BatchToSpaceNdDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
void SetConcatAxis(unsigned int concatAxis)
Set the concatenation axis value.
const ConstTensor * m_RecurrentToCellWeights
virtual bool IsTensorInfoSet() const =0
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
ResizeMethod
Definition: Types.hpp:152
A MeanDescriptor for the MeanLayer.
const ConstTensor * m_RecurrentToInputWeights
Definition: LstmParams.hpp:44
const ConstTensor * m_InputToOutputWeights
const TensorInfo * m_CellToForgetWeights
Definition: LstmParams.hpp:98
UnaryOperation
Definition: Types.hpp:124
bool IsFloorSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
const TensorInfo * m_RecurrentToForgetWeights
Definition: LstmParams.hpp:94
bool m_LayerNormEnabled
Enable/disable layer normalization.
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
Definition: NumericCast.hpp:35
uint32_t m_PadRight
Padding right value in the width dimension.
bool IsOperandConstant(const Operand &operand)
A TransposeDescriptor for the TransposeLayer.
A StridedSliceDescriptor for the StridedSliceLayer.
virtual const TensorInfo & GetTensorInfo() const =0
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
bool IsPreluSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &alpha, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
int m_Axis
Axis to reduce across the input tensor.
Definition: Descriptors.hpp:83
bool IsSplitterSupported(const BackendId &backend, const TensorInfo &input, const std::vector< std::reference_wrapper< TensorInfo >> &outputs, const ViewsDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
void SetQuantizationOffset(int32_t offset)
Definition: Tensor.cpp:489
bool isQuantizedOperand(const OperandType &operandType)
void Connect(armnn::IConnectableLayer *from, armnn::IConnectableLayer *to, const armnn::TensorInfo &tensorInfo, unsigned int fromIndex, unsigned int toIndex)
Definition: TestUtils.cpp:14
OriginsDescriptor CreateDescriptorForConcatenation(TensorShapeIt first, TensorShapeIt last, unsigned int concatenationDimension)
Convenience template to create an OriginsDescriptor to use when creating a ConcatLayer for performing...
bool IsAdditionSupported(const BackendId &backend, const TensorInfo &input0, const TensorInfo &input1, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
armnn::TensorShape TransposeTensorShape(const armnn::TensorShape &srcShape, const armnn::PermutationVector &mappings)
Definition: Transpose.cpp:98
virtual int Connect(IInputSlot &destination)=0
void SanitizeQuantizationScale(LayerInputHandle &weight, LayerInputHandle &input)
Krichevsky 2012: Local Brightness Normalization.
bool GetOptionalConvolutionDilationParams(const Operation &operation, uint32_t dilationXIndex, ConvolutionDescriptor &descriptor, const Model &model, const ConversionData &data)
const ConstTensor * m_OutputLayerNormWeights
Definition: LstmParams.hpp:60
A NormalizationDescriptor for the NormalizationLayer.
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
An InstanceNormalizationDescriptor for InstanceNormalizationLayer.
::android::nn::Model Model
Definition: Converter.hpp:24
void SetQuantizationScales(const std::vector< float > &scales)
Definition: Tensor.cpp:456
const TensorInfo * m_CellBias
Definition: LstmParams.hpp:102
float m_CellIntermediateScale
Cell intermediate quantization scale.
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:195
unsigned int GetChannelsIndex() const
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
A SoftmaxDescriptor for the SoftmaxLayer.
float m_Beta
Beta value for the normalization equation.
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.
bool m_CifgEnabled
Enable/disable CIFG (coupled input & forget gate).
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.
uint32_t m_NormSize
Depth radius value.
Status SetViewOriginCoord(uint32_t view, uint32_t coord, uint32_t value)
Set the view origin coordinates.
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square, Elu).
Definition: Descriptors.hpp:59
bool IsPadSupported(const BackendId &backend, const TensorInfo &input, const TensorInfo &output, const PadDescriptor &descriptor, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
bool GetInputFloat32(const Operation &operation, uint32_t inputIndex, float &outValue, const Model &model, const ConversionData &data)
A FillDescriptor for the FillLayer.
bool IsMultiplicationSupported(const BackendId &backend, const TensorInfo &input0, const TensorInfo &input1, const TensorInfo &output, char *reasonIfUnsupported=nullptr, size_t reasonIfUnsupportedMaxLength=1024)
Deprecated in favor of IBackend and ILayerSupport interfaces.
uint32_t m_PadLeft
Padding left value in the width dimension.
half_float::half Half
Definition: Converter.cpp:14
const ConstTensor * m_InputToForgetWeights
Definition: LstmParams.hpp:41
Status SetViewOriginCoord(uint32_t view, uint32_t coord, uint32_t value)
Set the view origin coordinates.
constexpr unsigned int GetDataTypeSize(DataType dataType)
Definition: TypesUtils.hpp:151
uint32_t m_PadRight
Padding right value in the width dimension.
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.
int32_t m_HiddenStateZeroPoint
Hidden State zero point.
bool m_ConstantWeights
Enable/disable constant weights and biases.
const ConstTensor * m_InputToInputWeights
Definition: LstmParams.hpp:40