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