blob: 5d52b4a7797bec801ef6d29ab9ceb177877d586a [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 }
Teresa Charlind9360332022-08-30 14:27:10 +01002236
2237 IConnectableLayer* weightsLayer = data.m_Network->AddConstantLayer(groupWeights);
2238 IConnectableLayer* biasLayer = data.m_Network->AddConstantLayer(groupBiases);
2239 IConnectableLayer* convLayer = data.m_Network->AddConvolution2dLayer(desc);
2240
Sadik Armagan8f397a12022-06-17 15:38:22 +01002241 if (!convLayer)
2242 {
2243 return Fail("%s: AddConvolution2dLayer failed", __func__);
2244 }
2245
2246 splitterLayer->GetOutputSlot(group).Connect(convLayer->GetInputSlot(0));
Teresa Charlind9360332022-08-30 14:27:10 +01002247 weightsLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(1));
2248 biasLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(2));
2249
2250 weightsLayer->GetOutputSlot(0).SetTensorInfo(groupWeightsInfo);
2251 biasLayer->GetOutputSlot(0).SetTensorInfo(groupBiasesInfo);
Sadik Armagan8f397a12022-06-17 15:38:22 +01002252 convLayer->GetOutputSlot(0).SetTensorInfo(groupOutputInfo);
2253
2254 if(isDynamic)
2255 {
2256 convLayer->GetOutputSlot(0).IsTensorInfoSet();
2257
2258 validateFunc(convLayer->GetOutputSlot(0).GetTensorInfo(), isSupported);
2259
2260 outputInfo = convLayer->GetOutputSlot(0).GetTensorInfo();
2261
2262 if (!isSupported)
2263 {
2264 return false;
2265 }
2266 }
2267
2268 convLayers[index] = convLayer;
2269 }
2270 }
2271
2272 //
2273 // Set up Concat layer
2274 //
2275 ConcatDescriptor concatDescriptor;
2276 // Equivalent to outputShape[channelsIndex], but we can't know the outputShape in the case of dynamic tensors
2277 concatDescriptor = ConcatDescriptor(weightsShape[0]);
2278 for (unsigned int group = 0u; group < numGroups; ++group)
2279 {
2280 for (unsigned int m = 0u; m < channelMultiplier; ++m)
2281 {
2282 auto index = group * channelMultiplier + m;
2283 concatDescriptor.SetViewOriginCoord(index, channelsIndex, index);
2284 concatDescriptor.SetConcatAxis(channelsIndex);
2285 }
2286 }
2287
2288 isSupported = false;
2289 FORWARD_LAYER_SUPPORT_FUNC(__func__,
2290 IsConcatSupported,
2291 data.m_Backends,
2292 isSupported,
2293 std::vector<const TensorInfo*>(numGroups * channelMultiplier, &groupOutputInfo),
2294 outputInfo,
2295 concatDescriptor);
2296
2297 if (!isSupported)
2298 {
2299 return false;
2300 }
2301
2302 IConnectableLayer* concatLayer = data.m_Network->AddConcatLayer(concatDescriptor);
2303 if (!concatLayer)
2304 {
2305 return Fail("%s: AddConcatLayer failed", __func__);
2306 }
2307
2308 for (unsigned int group = 0u; group < numGroups; ++group)
2309 {
2310 for (unsigned int m = 0u; m < channelMultiplier; ++m)
2311 {
2312 auto index = group * channelMultiplier + m;
2313 convLayers[index]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(index));
2314 }
2315 }
2316 concatLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2317
2318 return SetupAndTrackLayerOutputSlot(operation, 0, *concatLayer, model,
2319 data, nullptr, nullptr, activation);
2320}
2321
2322bool Converter::ConvertHardSwish(const Operation& operation, const Model& model, ConversionData& data)
2323{
2324 VLOG(DRIVER) << "Converter::ConvertHardSwish()";
2325 ActivationDescriptor desc;
2326 desc.m_Function = ActivationFunction::HardSwish;
2327
2328 return ::ConvertToActivation(operation, __func__, desc, model, data);
2329}
2330
2331bool Converter::ConvertInstanceNormalization(const Operation& operation, const Model& model, ConversionData& data)
2332{
2333 VLOG(DRIVER) << "Converter::ConvertInstanceNormalization()";
2334
2335 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2336 if (!input.IsValid())
2337 {
2338 return Fail("%s: Operation has an invalid input 0", __func__);
2339 }
2340
2341 const Operand* output = GetOutputOperand(operation, 0, model);
2342 if (!output)
2343 {
2344 return Fail("%s: Operation has an invalid output", __func__);
2345 }
2346
2347 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2348
2349 // Determine data type of input tensor
2350 OperandType inputType;
2351 if (!GetOperandType(operation, 0, model, inputType))
2352 {
2353 return Fail("%s: Operation has invalid inputs", __func__);
2354 }
2355
2356 InstanceNormalizationDescriptor desc;
2357
2358 // Read gamma, beta & epsilon
2359 if (inputType == OperandType::TENSOR_FLOAT16)
2360 {
2361 Half fp16Gamma;
2362 Half fp16Beta;
2363 Half fp16Epsilon;
2364
2365 if (!GetInputScalar(operation, 1, OperandType::FLOAT16, fp16Gamma, model, data) ||
2366 !GetInputScalar(operation, 2, OperandType::FLOAT16, fp16Beta, model, data) ||
2367 !GetInputScalar(operation, 3, OperandType::FLOAT16, fp16Epsilon, model, data))
2368 {
2369 return Fail("%s: Operation has invalid inputs (FLOAT16)", __func__);
2370 }
2371
2372 desc.m_Gamma = static_cast<float>(fp16Gamma);
2373 desc.m_Beta = static_cast<float>(fp16Beta);
2374 desc.m_Eps = static_cast<float>(fp16Epsilon);
2375 }
2376 else if (inputType == OperandType::TENSOR_FLOAT32)
2377 {
2378 if (!GetInputScalar(operation, 1, OperandType::FLOAT32, desc.m_Gamma, model, data) ||
2379 !GetInputScalar(operation, 2, OperandType::FLOAT32, desc.m_Beta, model, data) ||
2380 !GetInputScalar(operation, 3, OperandType::FLOAT32, desc.m_Eps, model, data))
2381 {
2382 return Fail("%s: Operation has invalid inputs (FLOAT32)", __func__);
2383 }
2384 }
2385 else
2386 {
2387 return Fail("%s: Unsupported input tensor type: %d", __func__, inputType);
2388 }
2389
2390 desc.m_DataLayout = OptionalDataLayout(operation, 4, model, data);
2391
2392 bool isSupported = false;
2393 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2394 {
2395 FORWARD_LAYER_SUPPORT_FUNC(__func__,
2396 IsInstanceNormalizationSupported,
2397 data.m_Backends,
2398 isSupported,
2399 input.GetTensorInfo(),
2400 outputInfo,
2401 desc);
2402 };
2403
2404 if(IsDynamicTensor(outputInfo))
2405 {
2406 isSupported = AreDynamicTensorsSupported();
2407 }
2408 else
2409 {
2410 validateFunc(outputInfo, isSupported);
2411 }
2412
2413 if (!isSupported)
2414 {
2415 return false;
2416 }
2417
2418 IConnectableLayer* layer = data.m_Network->AddInstanceNormalizationLayer(desc);
2419 input.Connect(layer->GetInputSlot(0));
2420
2421 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2422}
2423
2424bool Converter::ConvertL2Normalization(const Operation& operation, const Model& model, ConversionData& data)
2425{
2426 VLOG(DRIVER) << "Converter::ConvertL2Normalization()";
2427
2428 if (operation.inputs.size() != 1)
2429 {
2430 return Fail("%s: Optional inputs are not supported", __func__);
2431 }
2432
2433 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2434 if (!input.IsValid())
2435 {
2436 return Fail("%s: Operation has invalid inputs", __func__);
2437 }
2438
2439 const Operand* output = GetOutputOperand(operation, 0, model);
2440 if (!output)
2441 {
2442 return Fail("%s: Could not read output 0", __func__);
2443 }
2444
2445 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
2446 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2447
2448 if (outputInfo.GetNumDimensions() != 4u)
2449 {
2450 return Fail("%s: Tensor Rank other than 4 is not supported", __func__);
2451 }
2452
2453 armnn::L2NormalizationDescriptor desc;
2454 desc.m_DataLayout = armnn::DataLayout::NHWC;
2455
2456 bool isSupported = false;
2457 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2458 {
2459 FORWARD_LAYER_SUPPORT_FUNC(__func__,
2460 IsL2NormalizationSupported,
2461 data.m_Backends,
2462 isSupported,
2463 inputInfo,
2464 outputInfo,
2465 desc);
2466 };
2467
2468 if(!IsDynamicTensor(outputInfo))
2469 {
2470 validateFunc(outputInfo, isSupported);
2471 }
2472 else
2473 {
2474 isSupported = AreDynamicTensorsSupported();
2475 }
2476
2477 if (!isSupported)
2478 {
2479 return false;
2480 }
2481
2482 armnn::IConnectableLayer* layer = data.m_Network->AddL2NormalizationLayer(desc);
2483 assert(layer != nullptr);
2484 input.Connect(layer->GetInputSlot(0));
2485
2486 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2487}
2488
2489bool Converter::ConvertL2Pool2d(const Operation& operation, const Model& model, ConversionData& data)
2490{
2491 VLOG(DRIVER) << "Converter::ConvertL2Pool2d()";
2492 return ConvertPooling2d(operation, __func__, PoolingAlgorithm::L2, model, data);
2493}
2494
2495bool Converter::ConvertLocalResponseNormalization(const Operation& operation,
2496 const Model& model,
2497 ConversionData& data)
2498{
2499 VLOG(DRIVER) << "Converter::ConvertLocalResponseNormalization()";
2500
2501 if (operation.inputs.size() != 5)
2502 {
2503 return Fail("%s: Optional inputs are not supported", __func__);
2504 }
2505
2506 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2507 if (!input.IsValid())
2508 {
2509 return Fail("%s: Operation has invalid inputs", __func__);
2510 }
2511
2512 const Operand* output = GetOutputOperand(operation, 0, model);
2513 if (!output)
2514 {
2515 return Fail("%s: Could not read output 0", __func__);
2516 }
2517
2518 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
2519 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2520
2521 if (outputInfo.GetNumDimensions() != 4u)
2522 {
2523 return Fail("%s: Tensor Rank other than 4 is not supported", __func__);
2524 }
2525
2526 armnn::NormalizationDescriptor descriptor;
2527 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
2528 descriptor.m_NormChannelType = armnn::NormalizationAlgorithmChannel::Across;
2529 descriptor.m_NormMethodType = armnn::NormalizationAlgorithmMethod::LocalBrightness;
2530
2531 if (!input.IsValid() ||
2532 !GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_NormSize, model, data) ||
2533 !GetInputFloat32(operation, 2, descriptor.m_K, model, data) ||
2534 !GetInputFloat32(operation, 3, descriptor.m_Alpha, model, data) ||
2535 !GetInputFloat32(operation, 4, descriptor.m_Beta, model, data))
2536 {
2537 return Fail("%s: Operation has invalid inputs", __func__);
2538 }
2539
2540 // ArmNN expects normSize to be the full size of the normalization
2541 // window rather than the radius as in AndroidNN.
2542 descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize);
2543
2544 bool isSupported = false;
2545 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2546 {
2547 FORWARD_LAYER_SUPPORT_FUNC(__func__,
2548 IsNormalizationSupported,
2549 data.m_Backends,
2550 isSupported,
2551 inputInfo,
2552 outputInfo,
2553 descriptor);
2554 };
2555
2556 if(!IsDynamicTensor(outputInfo))
2557 {
2558 validateFunc(outputInfo, isSupported);
2559 }
2560 else
2561 {
2562 isSupported = AreDynamicTensorsSupported();
2563 }
2564
2565 if (!isSupported)
2566 {
2567 return false;
2568 }
2569
2570
2571 armnn::IConnectableLayer* layer = data.m_Network->AddNormalizationLayer(descriptor);
2572 assert(layer != nullptr);
2573 input.Connect(layer->GetInputSlot(0));
2574
2575 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2576}
2577
2578bool Converter::ConvertLogicalBinary(const Operation& operation,
2579 const Model& model,
2580 ConversionData& data,
2581 armnn::LogicalBinaryOperation logicalOperation)
2582{
2583 VLOG(DRIVER) << "Converter::ConvertLogicalBinary()";
2584 VLOG(DRIVER) << "ConvertLogicalBinary()";
2585 VLOG(DRIVER) << "logicalOperation = " << GetLogicalBinaryOperationAsCString(logicalOperation);
2586
2587 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
2588 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
2589
2590 if (!(input0.IsValid() && input1.IsValid()))
2591 {
2592 return Fail("%s: Operation has invalid inputs", __func__);
2593 }
2594
2595 const Operand* output = GetOutputOperand(operation, 0, model);
2596 if (!output)
2597 {
2598 return Fail("%s: Could not read output 0", __func__);
2599 }
2600
2601 const TensorInfo& inputInfo0 = input0.GetTensorInfo();
2602 const TensorInfo& inputInfo1 = input1.GetTensorInfo();
2603 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2604
2605 LogicalBinaryDescriptor descriptor(logicalOperation);
2606
2607 bool isSupported = false;
2608
2609 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2610 {
2611 FORWARD_LAYER_SUPPORT_FUNC(__func__,
2612 IsLogicalBinarySupported,
2613 data.m_Backends,
2614 isSupported,
2615 inputInfo0,
2616 inputInfo1,
2617 outputInfo,
2618 descriptor);
2619 };
2620
2621 if(!IsDynamicTensor(outputInfo))
2622 {
2623 validateFunc(outputInfo, isSupported);
2624 }
2625 else
2626 {
2627 isSupported = AreDynamicTensorsSupported();
2628 }
2629
2630 if (!isSupported)
2631 {
2632 return false;
2633 }
2634
2635 IConnectableLayer* layer = data.m_Network->AddLogicalBinaryLayer(descriptor);
2636 assert(layer != nullptr);
2637
2638 bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
2639 if (!isReshapeSupported)
2640 {
2641 return false;
2642 }
2643
2644 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2645}
2646
2647bool Converter::ConvertLogistic(const Operation& operation, const Model& model, ConversionData& data)
2648{
2649 VLOG(DRIVER) << "Converter::ConvertLogistic()";
2650 armnn::ActivationDescriptor desc;
2651 desc.m_Function = armnn::ActivationFunction::Sigmoid;
2652
2653 return ConvertToActivation(operation, __func__, desc, model, data);
2654}
2655
2656bool Converter::ConvertLogSoftmax(const Operation& operation, const Model& model, ConversionData& data)
2657{
2658 VLOG(DRIVER) << "Converter::ConvertLogSoftmax()";
2659
2660 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2661 if (!input.IsValid())
2662 {
2663 return Fail("%s: Failed to read input 0", __func__);
2664 }
2665
2666 const Operand* output = GetOutputOperand(operation, 0, model);
2667 if (!output)
2668 {
2669 return Fail("%s: Failed to read output", __func__);
2670 }
2671
2672 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2673
2674 // Determine data type of input tensor
2675 OperandType inputType;
2676 if (!GetOperandType(operation, 0, model, inputType))
2677 {
2678 return Fail("%s: Operation has invalid inputs", __func__);
2679 }
2680
2681 LogSoftmaxDescriptor descriptor;
2682
2683 // Read beta
2684 if (inputType == OperandType::TENSOR_FLOAT16)
2685 {
2686 Half fp16Beta;
2687 if (!GetInputScalar(operation, 1, OperandType::FLOAT16, fp16Beta, model, data))
2688 {
2689 return Fail("%s: Failed to read input 1 (FLOAT16)", __func__);
2690 }
2691
2692 descriptor.m_Beta = static_cast<float>(fp16Beta);
2693 }
2694 else if (inputType == OperandType::TENSOR_FLOAT32)
2695 {
2696 if (!GetInputScalar(operation, 1, OperandType::FLOAT32, descriptor.m_Beta, model, data))
2697 {
2698 return Fail("%s: Failed to read input 1 (FLOAT32)", __func__);
2699 }
2700 }
2701 else
2702 {
2703 return Fail("%s: Unsupported input tensor type: %d", __func__, inputType);
2704 }
2705
2706 // Read axis
2707 if (!GetInputInt32(operation, 2, descriptor.m_Axis, model, data))
2708 {
2709 return Fail("%s: Failed to read input 2", __func__);
2710 }
2711
2712 bool isSupported = false;
2713 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
2714 {
2715 FORWARD_LAYER_SUPPORT_FUNC(__func__,
2716 IsLogSoftmaxSupported,
2717 data.m_Backends,
2718 isSupported,
2719 input.GetTensorInfo(),
2720 outputInfo,
2721 descriptor);
2722 };
2723
2724 if(IsDynamicTensor(outputInfo))
2725 {
2726 isSupported = AreDynamicTensorsSupported();
2727 }
2728 else
2729 {
2730 validateFunc(outputInfo, isSupported);
2731 }
2732
2733 if (!isSupported)
2734 {
2735 return false;
2736 }
2737
2738 IConnectableLayer* layer = data.m_Network->AddLogSoftmaxLayer(descriptor);
2739 if (!layer)
2740 {
2741 return Fail("%s: AddLogSoftmaxLayer() returned nullptr", __func__);
2742 }
2743
2744 input.Connect(layer->GetInputSlot(0));
2745
2746 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
2747}
2748
2749bool Converter::ConvertLstm(const Operation& operation, const Model& model, ConversionData& data)
2750{
2751 VLOG(DRIVER) << "Converter::ConvertLstm()";
2752
2753 // Inputs:
2754 // 00: The input: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, input_size], where
2755 // “batch_size” corresponds to the batching dimension, and “input_size” is the size of the input.
2756 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
2757 if (!input.IsValid())
2758 {
2759 return Fail("%s: Could not read input 0: input", __func__);
2760 }
2761 // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
2762 LayerInputHandle outputStateIn = ConvertToLayerInputHandle(operation, 18, model, data);
2763 if (!outputStateIn.IsValid())
2764 {
2765 return Fail("%s: Could not read input 18: outputStateIn", __func__);
2766 }
2767 // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
2768 LayerInputHandle cellStateIn = ConvertToLayerInputHandle(operation, 19, model, data);
2769 if (!cellStateIn.IsValid())
2770 {
2771 return Fail("%s: Could not read input 19: cellStateIn", __func__);
2772 }
2773
2774 // Get the mandatory input tensors:
2775 // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2776 // [num_units, input_size].
2777 const ConstTensorPin inputToForgetWeightsPin =
2778 (DequantizeAndMakeConstTensorPin(operation, model, data, 2));
2779 // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2780 // [num_units, input_size].
2781 const ConstTensorPin inputToCellWeightsPin =
2782 (DequantizeAndMakeConstTensorPin(operation, model, data, 3));
2783 // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2784 // [num_units, input_size].
2785 const ConstTensorPin inputToOutputWeightsPin =
2786 (DequantizeAndMakeConstTensorPin(operation, model, data, 4));
2787 // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2788 // [num_units, output_size].
2789 const ConstTensorPin recurrentToForgetWeightsPin =
2790 (DequantizeAndMakeConstTensorPin(operation, model, data, 6));
2791 // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2792 // [num_units, output_size].
2793 const ConstTensorPin recurrentToCellWeightsPin =
2794 (DequantizeAndMakeConstTensorPin(operation, model, data, 7));
2795 // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2796 // [num_units, output_size].
2797 const ConstTensorPin recurrentToOutputWeightsPin =
2798 (DequantizeAndMakeConstTensorPin(operation, model, data, 8));
2799 // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2800 const ConstTensorPin forgetGateBiasPin =
2801 ConvertOperationInputToConstTensorPin(operation, 13, model, data);
2802 // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2803 const ConstTensorPin cellBiasPin =
2804 ConvertOperationInputToConstTensorPin(operation, 14, model, data);
2805 // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2806 const ConstTensorPin outputGateBiasPin =
2807 ConvertOperationInputToConstTensorPin(operation, 15, model, data);
2808
2809 if (!inputToForgetWeightsPin.IsValid() ||
2810 !inputToCellWeightsPin.IsValid() ||
2811 !inputToOutputWeightsPin.IsValid() ||
2812 !recurrentToForgetWeightsPin.IsValid() ||
2813 !recurrentToCellWeightsPin.IsValid() ||
2814 !recurrentToOutputWeightsPin.IsValid() ||
2815 !forgetGateBiasPin.IsValid() ||
2816 !cellBiasPin.IsValid() ||
2817 !outputGateBiasPin.IsValid())
2818 {
2819 return Fail("%s: Operation has invalid tensor inputs", __func__);
2820 }
2821
2822 // Get the optional input tensors:
2823 // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2824 // [num_units, input_size], where “num_units” corresponds to the number of cell units.
2825 const ConstTensorPin inputToInputWeightsPin =
2826 (DequantizeAndMakeConstTensorPin(operation, model, data, 1, true));
2827 // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2828 // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
2829 // “num_units”), or the second dimension of the “projection_weights”, if defined.
2830 const ConstTensorPin recurrentToInputWeightsPin =
2831 (DequantizeAndMakeConstTensorPin(operation, model, data, 5, true));
2832 // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2833 const ConstTensorPin cellToInputWeightsPin =
2834 (DequantizeAndMakeConstTensorPin(operation, model, data, 9, true));
2835 // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2836 const ConstTensorPin cellToForgetWeightsPin =
2837 (DequantizeAndMakeConstTensorPin(operation, model, data, 10, true));
2838 // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2839 const ConstTensorPin cellToOutputWeightsPin =
2840 (DequantizeAndMakeConstTensorPin(operation, model, data, 11, true));
2841 // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
2842 const ConstTensorPin inputGateBiasPin =
2843 ConvertOperationInputToConstTensorPin(operation,
2844 12,
2845 model,
2846 data,
2847 g_DontPermute,
2848 nullptr,
2849 true);
2850
2851 // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
2852 // [output_size, num_units].
2853 const ConstTensorPin projectionWeightsPin =
2854 (DequantizeAndMakeConstTensorPin(operation, model, data, 16, true));
2855 // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [output_size].
2856 const ConstTensorPin projectionBiasPin =
2857 ConvertOperationInputToConstTensorPin(operation,
2858 17,
2859 model,
2860 data,
2861 g_DontPermute,
2862 nullptr,
2863 true);
2864
2865 if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional()) ||
2866 (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional()) ||
2867 (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional()) ||
2868 (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional()) ||
2869 (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional()) ||
2870 (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional()) ||
2871 (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional()) ||
2872 (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
2873 {
2874 return Fail("%s: Operation has invalid tensor inputs", __func__);
2875 }
2876
2877 // Get the mandatory input scalars (actually 1-D tensors of size 1):
2878 // 20: The activation function: A value indicating the activation function:
2879 // 0: None; 1: Relu; 3: Relu6; 4: Tanh; 6: Sigmoid.
2880 // 21: The clipping threshold: for the cell state, such that values are bound within [-cell_clip, cell_clip].
2881 // If set to 0.0 then clipping is disabled.
2882 // 22: The clipping threshold: for the output from the projection layer, such that values are bound within
2883 // [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
2884 ActivationFn activation = ActivationFn::kActivationNone;
2885 float cellClip;
2886 float projClip;
2887 if (!GetInputActivationFunctionFromTensor(operation, 20, activation, model, data) ||
2888 !GetInputScalar(operation, 21, OperandType::FLOAT32, cellClip, model, data) ||
2889 !GetInputScalar(operation, 22, OperandType::FLOAT32, projClip, model, data))
2890 {
2891 return Fail("%s: Operation has invalid scalar inputs", __func__);
2892 }
2893
2894 // Get the normalization tensors
2895 // 23: The input layer normalization weights. A 1-D tensor of shape [num_units].
2896 // Used to rescale normalized inputs to activation at input gate.
2897 const ConstTensorPin inputLayerNormWeightsPin
2898 (DequantizeAndMakeConstTensorPin(operation, model, data, 23, true));
2899
2900 // 24: The forget layer normalization weights. A 1-D tensor of shape [num_units].
2901 // Used to rescale normalized inputs to activation at forget gate.
2902 const ConstTensorPin forgetLayerNormWeightsPin =
2903 ConvertOperationInputToConstTensorPin(operation,
2904 24,
2905 model,
2906 data,
2907 g_DontPermute,
2908 nullptr,
2909 true);
2910
2911 // 25: The cell layer normalization weights. A 1-D tensor of shape [num_units].
2912 // Used to rescale normalized inputs to activation at cell gate.
2913 const ConstTensorPin cellLayerNormWeightsPin =
2914 ConvertOperationInputToConstTensorPin(operation,
2915 25,
2916 model,
2917 data,
2918 g_DontPermute,
2919 nullptr,
2920 true);
2921
2922 // 26: The output layer normalization weights. A 1-D tensor of shape [num_units].
2923 // Used to rescale normalized inputs to activation at output gate.
2924 const ConstTensorPin outputLayerNormWeightsPin =
2925 ConvertOperationInputToConstTensorPin(operation,
2926 26,
2927 model,
2928 data,
2929 g_DontPermute,
2930 nullptr,
2931 true);
2932
2933 // Outputs:
2934 // 00: The scratch buffer: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units * 4]
2935 // with CIFG, or [batch_size, num_units * 3] without CIFG.
2936 const Operand* scratchBuffer = GetOutputOperand(operation, 0, model);
2937 if (!scratchBuffer)
2938 {
2939 return Fail("%s: Could not read output 0: scratchBuffer", __func__);
2940 }
2941 // 01: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
2942 const Operand* outputStateOut = GetOutputOperand(operation, 1, model);
2943 if (!outputStateOut)
2944 {
2945 return Fail("%s: Could not read output 1: outputStateOut", __func__);
2946 }
2947 // 02: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
2948 const Operand* cellStateOut = GetOutputOperand(operation, 2, model);
2949 if (!cellStateOut)
2950 {
2951 return Fail("%s: Could not read output 2: cellStateOut", __func__);
2952 }
2953 // 03: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. This is
2954 // effectively the same as the current “output state (out)” value.
2955 const Operand* output = GetOutputOperand(operation, 3, model);
2956 if (!output)
2957 {
2958 return Fail("%s: Could not read output 3: output", __func__);
2959 }
2960
2961 // set the params structure for the AddLstmLayer call
2962 LstmInputParams params;
2963 params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
2964 params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
2965 params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
2966 params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
2967 params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
2968 params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
2969 params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
2970 params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
2971 params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
2972 params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
2973 params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
2974 params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
2975 params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
2976 params.m_CellBias = cellBiasPin.GetConstTensorPtr();
2977 params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
2978 params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
2979 params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
2980 params.m_InputLayerNormWeights = inputLayerNormWeightsPin.GetConstTensorPtr();
2981 params.m_ForgetLayerNormWeights = forgetLayerNormWeightsPin.GetConstTensorPtr();
2982 params.m_CellLayerNormWeights = cellLayerNormWeightsPin.GetConstTensorPtr();
2983 params.m_OutputLayerNormWeights = outputLayerNormWeightsPin.GetConstTensorPtr();
2984
2985 // set the layer descriptor
2986 LstmDescriptor desc;
2987 desc.m_ActivationFunc = activation;
2988 desc.m_ClippingThresCell = cellClip;
2989 desc.m_ClippingThresProj = projClip;
2990 desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
2991 params.m_RecurrentToInputWeights == nullptr ||
2992 params.m_InputGateBias == nullptr);
2993 desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
2994 params.m_CellToOutputWeights != nullptr);
2995 desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
2996 desc.m_LayerNormEnabled = (params.m_InputLayerNormWeights != nullptr ||
2997 params.m_ForgetLayerNormWeights != nullptr ||
2998 params.m_CellLayerNormWeights != nullptr ||
2999 params.m_OutputLayerNormWeights != nullptr);
3000
3001 // validate the optional input groups
3002 if (desc.m_CifgEnabled &&
3003 (params.m_InputToInputWeights != nullptr ||
3004 params.m_RecurrentToInputWeights != nullptr ||
3005 params.m_InputGateBias != nullptr))
3006 {
3007 return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
3008 " and input gate bias must be provided", __func__);
3009 }
3010
3011 if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
3012 {
3013 return Fail("%s: projection bias should not be provided without projection weights", __func__);
3014 }
3015
3016 if (desc.m_PeepholeEnabled &&
3017 (params.m_CellToForgetWeights == nullptr ||
3018 params.m_CellToOutputWeights == nullptr ||
3019 (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
3020 {
3021 return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
3022 " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
3023 }
3024
3025 if (desc.m_LayerNormEnabled &&
3026 (params.m_ForgetLayerNormWeights == nullptr ||
3027 params.m_CellLayerNormWeights == nullptr ||
3028 params.m_OutputLayerNormWeights == nullptr ||
3029 (!desc.m_CifgEnabled && params.m_InputLayerNormWeights == nullptr)))
3030 {
3031 return Fail("%s: All, or none, of forget-norm weights, cell-norm weights and output-norm weights must be"
3032 " provided and, if CIFG is not enabled, input-norm weights must also be provided", __func__);
3033 }
3034
3035 // Check if the layer is supported
3036 // Inputs
3037 const TensorInfo& inputInfo = input.GetTensorInfo();
3038 const TensorInfo& outputStateInInfo = outputStateIn.GetTensorInfo();
3039 const TensorInfo& cellStateInInfo = cellStateIn.GetTensorInfo();
3040
3041 // Outputs
3042 const TensorInfo& scratchBufferInfo = GetTensorInfoForOperand(*scratchBuffer);
3043 const TensorInfo& outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
3044 const TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
3045 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3046
3047 // Basic parameters
3048 LstmInputParamsInfo paramsInfo;
3049 paramsInfo.m_InputToForgetWeights = &(params.m_InputToForgetWeights->GetInfo());
3050 paramsInfo.m_InputToCellWeights = &(params.m_InputToCellWeights->GetInfo());
3051 paramsInfo.m_InputToOutputWeights = &(params.m_InputToOutputWeights->GetInfo());
3052 paramsInfo.m_RecurrentToForgetWeights = &(params.m_RecurrentToForgetWeights->GetInfo());
3053 paramsInfo.m_RecurrentToCellWeights = &(params.m_RecurrentToCellWeights->GetInfo());
3054 paramsInfo.m_RecurrentToOutputWeights = &(params.m_RecurrentToOutputWeights->GetInfo());
3055 paramsInfo.m_ForgetGateBias = &(params.m_ForgetGateBias->GetInfo());
3056 paramsInfo.m_CellBias = &(params.m_CellBias->GetInfo());
3057 paramsInfo.m_OutputGateBias = &(params.m_OutputGateBias->GetInfo());
3058
3059 // Optional parameters
3060 if (!desc.m_CifgEnabled)
3061 {
3062 paramsInfo.m_InputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
3063 paramsInfo.m_RecurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo());
3064 if (params.m_CellToInputWeights != nullptr)
3065 {
3066 paramsInfo.m_CellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
3067 }
3068 paramsInfo.m_InputGateBias = &(params.m_InputGateBias->GetInfo());
3069 }
3070
3071 if (desc.m_ProjectionEnabled)
3072 {
3073 paramsInfo.m_ProjectionWeights = &(params.m_ProjectionWeights->GetInfo());
3074 if (params.m_ProjectionBias != nullptr)
3075 {
3076 paramsInfo.m_ProjectionBias = &(params.m_ProjectionBias->GetInfo());
3077 }
3078 }
3079
3080 if (desc.m_PeepholeEnabled)
3081 {
3082 paramsInfo.m_CellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
3083 paramsInfo.m_CellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
3084 }
3085
3086 if (desc.m_LayerNormEnabled)
3087 {
3088 if(!desc.m_CifgEnabled)
3089 {
3090 paramsInfo.m_InputLayerNormWeights = &(params.m_InputLayerNormWeights->GetInfo());
3091 }
3092 paramsInfo.m_ForgetLayerNormWeights = &(params.m_ForgetLayerNormWeights->GetInfo());
3093 paramsInfo.m_CellLayerNormWeights = &(params.m_CellLayerNormWeights->GetInfo());
3094 paramsInfo.m_OutputLayerNormWeights = &(params.m_OutputLayerNormWeights->GetInfo());
3095 }
3096
3097 bool isSupported = false;
3098 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3099 {
3100 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3101 IsLstmSupported,
3102 data.m_Backends,
3103 isSupported,
3104 inputInfo,
3105 outputStateInInfo,
3106 cellStateInInfo,
3107 scratchBufferInfo,
3108 outputStateOutInfo,
3109 cellStateOutInfo,
3110 outputInfo,
3111 desc,
3112 paramsInfo);
3113 };
3114
3115 bool isDynamic = false;
3116 if (!IsDynamicTensor(outputStateOutInfo) &&
3117 !IsDynamicTensor(scratchBufferInfo) &&
3118 !IsDynamicTensor(cellStateOutInfo) &&
3119 !IsDynamicTensor(outputInfo))
3120 {
3121 validateFunc(outputInfo, isSupported);
3122 }
3123 else
3124 {
3125 isDynamic = true;
3126 isSupported = AreDynamicTensorsSupported();
3127 }
3128
3129 if (!isSupported)
3130 {
3131 return false;
3132 }
3133
3134 // Add the layer
3135 IConnectableLayer* layer = data.m_Network->AddLstmLayer(desc, params, "Lstm");
3136
3137 input.Connect(layer->GetInputSlot(0));
3138 outputStateIn.Connect(layer->GetInputSlot(1));
3139 cellStateIn.Connect(layer->GetInputSlot(2));
3140
3141 if (!isDynamic)
3142 {
3143 return (
3144 SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
3145 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
3146 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data) &&
3147 SetupAndTrackLayerOutputSlot(operation, 3, *layer, 3, model, data));
3148 }
3149 else
3150 {
3151 return (
3152 SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
3153 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
3154 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data) &&
3155 SetupAndTrackLayerOutputSlot(
3156 operation, 3, *layer, 3, model, data, nullptr, validateFunc, ActivationFn::kActivationNone, true));
3157 }
3158
3159}
3160
3161bool Converter::ConvertMaxPool2d(const Operation& operation, const Model& model, ConversionData& data)
3162{
3163 VLOG(DRIVER) << "Converter::ConvertMaxPool2d()";
3164 return ConvertPooling2d(operation, __func__, PoolingAlgorithm::Max, model, data);
3165}
3166
3167bool Converter::ConvertMaximum(const Operation& operation, const Model& model, ConversionData& data)
3168{
3169 VLOG(DRIVER) << "Converter::ConvertMaximum()";
3170
3171 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
3172 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
3173
3174 if (!input0.IsValid() || !input1.IsValid())
3175 {
3176 return Fail("%s: Operation has invalid inputs", __func__);
3177 }
3178
3179 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
3180 if (!outputOperand)
3181 {
3182 return Fail("%s: Could not read output", __func__);
3183 }
3184
3185 const TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
3186
3187 bool isSupported = false;
3188 auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
3189 {
3190 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3191 IsMaximumSupported,
3192 data.m_Backends,
3193 isSupported,
3194 input0.GetTensorInfo(),
3195 input1.GetTensorInfo(),
3196 outInfo);
3197 };
3198
3199 if(IsDynamicTensor(outInfo))
3200 {
3201 isSupported = AreDynamicTensorsSupported();
3202 }
3203 else
3204 {
3205 validateFunc(outInfo, isSupported);
3206 }
3207
3208 if (!isSupported)
3209 {
3210 return false;
3211 }
3212
3213 IConnectableLayer* layer = data.m_Network->AddMaximumLayer();
3214 assert(layer != nullptr);
3215 bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
3216 if (!isReshapeSupported)
3217 {
3218 return false;
3219 }
3220
3221 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3222}
3223
3224bool Converter::ConvertMean(const Operation& operation, const Model& model, ConversionData& data)
3225{
3226 VLOG(DRIVER) << "Converter::ConvertMean()";
3227
3228 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3229 if (!input.IsValid())
3230 {
3231 return Fail("%s: Operation has invalid inputs", __func__);
3232 }
3233
3234 const Operand* output = GetOutputOperand(operation, 0, model);
3235 if (!output)
3236 {
3237 return Fail("%s: Could not read output 0", __func__);
3238 }
3239
3240 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3241
3242 const Operand* axisOperand = GetInputOperand(operation, 1, model);
3243 if (!axisOperand)
3244 {
3245 return Fail("%s: Could not read input 1", __func__);
3246 }
3247
3248 std::vector<int32_t> axis;
3249 if (!GetTensorInt32Values(*axisOperand, axis, model, data))
3250 {
3251 return Fail("%s: Input 1 has invalid values", __func__);
3252 }
3253
3254 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
3255
3256 // Convert the axis to unsigned int and remove duplicates.
3257 unsigned int rank = inputInfo.GetNumDimensions();
3258 std::set<unsigned int> uniqueAxis;
3259 std::transform(axis.begin(), axis.end(),
3260 std::inserter(uniqueAxis, uniqueAxis.begin()),
3261 [rank](int i) -> unsigned int { return (i + rank) % rank; });
3262
3263 // Get the "keep dims" flag.
3264 int32_t keepDims = 0;
3265 if (!GetInputInt32(operation, 2, keepDims, model, data))
3266 {
3267 return Fail("%s: Could not read input 2", __func__);
3268 }
3269
3270 armnn::MeanDescriptor descriptor;
3271 descriptor.m_Axis.assign(uniqueAxis.begin(), uniqueAxis.end());
3272 descriptor.m_KeepDims = keepDims > 0;
3273
3274 bool isSupported = false;
3275 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3276 {
3277 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3278 IsMeanSupported,
3279 data.m_Backends,
3280 isSupported,
3281 inputInfo,
3282 outputInfo,
3283 descriptor);
3284 };
3285
3286 if(!IsDynamicTensor(outputInfo))
3287 {
3288 validateFunc(outputInfo, isSupported);
3289 }
3290 else
3291 {
3292 isSupported = AreDynamicTensorsSupported();
3293 }
3294
3295 if (!isSupported)
3296 {
3297 return false;
3298 }
3299
3300 armnn::IConnectableLayer* const layer = data.m_Network->AddMeanLayer(descriptor);
3301 assert(layer != nullptr);
3302 input.Connect(layer->GetInputSlot(0));
3303
3304 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3305}
3306
3307bool Converter::ConvertMinimum(const Operation& operation, const Model& model, ConversionData& data)
3308{
3309 VLOG(DRIVER) << "Converter::ConvertMinimum()";
3310
3311 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
3312 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
3313
3314 if (!input0.IsValid() || !input1.IsValid())
3315 {
3316 return Fail("%s: Operation has invalid inputs", __func__);
3317 }
3318
3319 const Operand* output = GetOutputOperand(operation, 0, model);
3320 if (!output)
3321 {
3322 return Fail("%s: Could not read output 0", __func__);
3323 }
3324
3325 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3326
3327 bool isSupported = false;
3328 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3329 {
3330 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3331 IsMinimumSupported,
3332 data.m_Backends,
3333 isSupported,
3334 input0.GetTensorInfo(),
3335 input1.GetTensorInfo(),
3336 outputInfo);
3337 };
3338
3339 if(IsDynamicTensor(outputInfo))
3340 {
3341 isSupported = AreDynamicTensorsSupported();
3342 }
3343 else
3344 {
3345 validateFunc(outputInfo, isSupported);
3346 }
3347
3348 if (!isSupported)
3349 {
3350 return false;
3351 }
3352
3353 IConnectableLayer* const layer = data.m_Network->AddMinimumLayer();
3354 assert(layer != nullptr);
3355 bool isReshapeSupported = BroadcastTensor(input0, input1, layer, data);
3356 if (!isReshapeSupported)
3357 {
3358 return false;
3359 }
3360
3361 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3362}
3363
3364bool Converter::ConvertMul(const Operation& operation, const Model& model, ConversionData& data)
3365{
3366 VLOG(DRIVER) << "Converter::ConvertMul()";
3367
3368 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
3369 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
3370
3371 if (!input0.IsValid() || !input1.IsValid())
3372 {
3373 return Fail("%s: Operation has invalid inputs", __func__);
3374 }
3375
3376 // The FuseActivation parameter is always the input index 2
3377 // and it should be optional
3378 ActivationFn activationFunction;
3379 if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
3380 {
3381 return Fail("%s: Operation has invalid inputs", __func__);
3382 }
3383
3384 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
3385
3386 if (outputOperand == nullptr)
3387 {
3388 return false;
3389 }
3390
3391 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
3392
3393 bool isSupported = false;
3394 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3395 {
3396 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3397 IsMultiplicationSupported,
3398 data.m_Backends,
3399 isSupported,
3400 input0.GetTensorInfo(),
3401 input1.GetTensorInfo(),
3402 outputInfo);
3403 };
3404
3405 if(!IsDynamicTensor(outputInfo))
3406 {
3407 validateFunc(outputInfo, isSupported);
3408 }
3409 else
3410 {
3411 isSupported = AreDynamicTensorsSupported();
3412 }
3413
3414 if (!isSupported)
3415 {
3416 return false;
3417 }
3418
3419 armnn::IConnectableLayer* const startLayer = data.m_Network->AddMultiplicationLayer();
3420
3421 bool isReshapeSupported = BroadcastTensor(input0, input1, startLayer, data);
3422 if (!isReshapeSupported)
3423 {
3424 return false;
3425 }
3426
3427 return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
3428 data, nullptr, validateFunc, activationFunction);
3429}
3430
3431bool Converter::ConvertPad(const Operation& operation, const Model& model, ConversionData& data)
3432{
3433 VLOG(DRIVER) << "Converter::ConvertPad()";
3434
3435 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3436 if (!input.IsValid())
3437 {
3438 return Fail("%s: Operation has invalid inputs", __func__);
3439 }
3440
3441 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
3442 unsigned int rank = inputInfo.GetNumDimensions();
3443
3444 armnn::PadDescriptor descriptor;
3445 if (!ConvertPaddings(operation, model, data, rank, descriptor))
3446 {
3447 return Fail("%s: Could not convert paddings", __func__);
3448 }
3449
3450 // For a ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED tensor,
3451 // the scale and zeroPoint must be the same as input0
3452 // Before Android Q, the pad value for ANEURALNETWORKS_TENSOR_QUANT8_ASYMM was undefined. Since Android Q the pad
3453 // value must be "logical zero" we set it to be equal to the QuantizationOffset so effectively it ends up as
3454 // (QuantizationOffset - QuantizationOffset) * scale = 0.
3455 if (inputInfo.GetDataType() == armnn::DataType::QAsymmU8 || inputInfo.GetDataType() == armnn::DataType::QAsymmS8)
3456 {
3457 descriptor.m_PadValue = inputInfo.GetQuantizationOffset();
3458 }
3459
3460 const Operand* output = GetOutputOperand(operation, 0, model);
3461 if (!output)
3462 {
3463 return Fail("%s: Could not read output", __func__);
3464 }
3465
3466 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3467
3468 bool isSupported = false;
3469 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3470 {
3471 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3472 IsPadSupported,
3473 data.m_Backends,
3474 isSupported,
3475 inputInfo,
3476 outputInfo,
3477 descriptor);
3478 };
3479
3480 if(!IsDynamicTensor(outputInfo))
3481 {
3482 validateFunc(outputInfo, isSupported);
3483 }
3484 else
3485 {
3486 isSupported = AreDynamicTensorsSupported();
3487 }
3488
3489 if (!isSupported)
3490 {
3491 return false;
3492 }
3493
3494 armnn::IConnectableLayer* const layer = data.m_Network->AddPadLayer(descriptor);
3495 assert(layer != nullptr);
3496 input.Connect(layer->GetInputSlot(0));
3497
3498 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3499}
3500
3501bool Converter::ConvertPadV2(const Operation& operation, const Model& model, ConversionData& data)
3502{
3503 VLOG(DRIVER) << "Converter::ConvertPadV2()";
3504
3505 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3506 if (!input.IsValid())
3507 {
3508 return Fail("%s: Could not read input 0", __func__);
3509 }
3510
3511 const Operand* output = GetOutputOperand(operation, 0, model);
3512 if (!output)
3513 {
3514 return Fail("%s: Could not read output", __func__);
3515 }
3516
3517 const TensorInfo& inputInfo = input.GetTensorInfo();
3518 unsigned int rank = inputInfo.GetNumDimensions();
3519
3520 PadDescriptor descriptor;
3521 if (!ConvertPaddings(operation, model, data, rank, descriptor))
3522 {
3523 return Fail("%s: Could not convert paddings", __func__);
3524 }
3525
3526 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3527
3528 // Determine type of padding value
3529 OperandType operandType0;
3530 OperandType operandType2;
3531
3532 if (!GetOperandType(operation, 0, model, operandType0) ||
3533 !GetOperandType(operation, 2, model, operandType2))
3534 {
3535 return Fail("%s: Operation has invalid inputs", __func__);
3536 }
3537
3538 // Read value to use for padding
3539 if (operandType0 == OperandType::TENSOR_FLOAT16 && operandType2 == OperandType::FLOAT16)
3540 {
3541 Half f16PadValue;
3542 if (!GetInputScalar(operation, 2, operandType2, f16PadValue, model, data))
3543 {
3544 return Fail("%s: Could not read input 2 (FLOAT16)", __func__);
3545 }
3546
3547 descriptor.m_PadValue = f16PadValue;
3548 }
3549 else if (operandType0 == OperandType::TENSOR_FLOAT32 && operandType2 == OperandType::FLOAT32)
3550 {
3551 if (!GetInputFloat32(operation, 2, descriptor.m_PadValue, model, data))
3552 {
3553 return Fail("%s: Could not read input 2 (FLOAT32)", __func__);
3554 }
3555 }
3556 else if (isQuantizedOperand(operandType0) && operandType2 == OperandType::INT32)
3557 {
3558 int32_t intPadValue = 0;
3559 if (!GetInputInt32(operation, 2, intPadValue, model, data))
3560 {
3561 return Fail("%s: Could not read input 2 (INT32)", __func__);
3562 }
3563 descriptor.m_PadValue = intPadValue;
3564 }
3565 else
3566 {
3567 return Fail("%s: Operation has invalid inputs: type mismatch", __func__);
3568 }
3569
3570 bool isSupported = false;
3571 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3572 {
3573 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3574 IsPadSupported,
3575 data.m_Backends,
3576 isSupported,
3577 inputInfo,
3578 outputInfo,
3579 descriptor);
3580 };
3581
3582 if(IsDynamicTensor(outputInfo))
3583 {
3584 isSupported = AreDynamicTensorsSupported();
3585 }
3586 else
3587 {
3588 validateFunc(outputInfo, isSupported);
3589 }
3590
3591 if (!isSupported)
3592 {
3593 return false;
3594 }
3595
3596 IConnectableLayer* const layer = data.m_Network->AddPadLayer(descriptor);
3597 assert(layer != nullptr);
3598 input.Connect(layer->GetInputSlot(0));
3599
3600 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3601}
3602
3603bool Converter::ConvertPrelu(const Operation& operation, const Model& model, ConversionData& data)
3604{
3605 VLOG(DRIVER) << "Converter::ConvertPrelu()";
3606
3607 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3608 LayerInputHandle alpha = ConvertToLayerInputHandle(operation, 1, model, data);
3609
3610 if (!input.IsValid() || !alpha.IsValid())
3611 {
3612 return Fail("%s: Operation has invalid inputs", __func__);
3613 }
3614
3615 const Operand* output = GetOutputOperand(operation, 0, model);
3616
3617 if (!output)
3618 {
3619 return Fail("%s: Could not read output", __func__);
3620 }
3621
3622 const TensorInfo& inputInfo = input.GetTensorInfo();
3623 const TensorInfo& alphaInfo = alpha.GetTensorInfo();
3624 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
3625
3626 bool isSupported = false;
3627 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3628 {
3629 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3630 IsPreluSupported,
3631 data.m_Backends,
3632 isSupported,
3633 inputInfo,
3634 alphaInfo,
3635 outputInfo);
3636 };
3637
3638 if(IsDynamicTensor(outputInfo))
3639 {
3640 isSupported = AreDynamicTensorsSupported();
3641 }
3642 else
3643 {
3644 validateFunc(outputInfo, isSupported);
3645 }
3646
3647 if (!isSupported)
3648 {
3649 return false;
3650 }
3651
3652 IConnectableLayer* const layer = data.m_Network->AddPreluLayer();
3653
3654 if (!layer)
3655 {
3656 return Fail("%s: AddPreluLayer failed", __func__);
3657 }
3658
3659 bool isReshapeSupported = BroadcastTensor(input, alpha, layer, data);
3660 if (!isReshapeSupported)
3661 {
3662 return false;
3663 }
3664
3665 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3666}
3667
3668bool Converter::ConvertQuantize(const Operation& operation, const Model& model, ConversionData& data)
3669{
3670 VLOG(DRIVER) << "Converter::ConvertQuantize()";
3671
3672 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3673 if (!input.IsValid())
3674 {
3675 return Fail("%s: Operation has invalid input", __func__);
3676 }
3677
3678 const Operand* const outputOperand = GetOutputOperand(operation, 0, model);
3679 if (!outputOperand)
3680 {
3681 return Fail("%s: Operation has invalid outputs", __func__);
3682 }
3683
3684 const TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
3685
3686 bool isSupported = false;
3687 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
3688 {
3689 FORWARD_LAYER_SUPPORT_FUNC(__func__,
3690 IsQuantizeSupported,
3691 data.m_Backends,
3692 isSupported,
3693 input.GetTensorInfo(),
3694 outputInfo);
3695 };
3696
3697 if(IsDynamicTensor(outputInfo))
3698 {
3699 isSupported = AreDynamicTensorsSupported();
3700 }
3701 else
3702 {
3703 validateFunc(outputInfo, isSupported);
3704 }
3705
3706 if (!isSupported)
3707 {
3708 return false;
3709 }
3710
3711 IConnectableLayer* const layer = data.m_Network->AddQuantizeLayer();
3712 assert(layer != nullptr);
3713 input.Connect(layer->GetInputSlot(0));
3714
3715 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
3716}
3717
3718bool Converter::ConvertQuantizedLstm(const Operation& operation, const Model& model, ConversionData& data)
3719{
3720 VLOG(DRIVER) << "Converter::ConvertQuantizedLstm()";
3721
3722 VLOG(DRIVER) << "ConvertQuantizedLstm()";
3723
3724 //Inputs:
3725 // 0: The input: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape [numBatches, inputSize]
3726 // specifying the input to the LSTM cell. Tensor is quantized with a fixed quantization range of -1, 127/128.
3727 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
3728 if (!input.IsValid())
3729 {
3730 return Fail("%s: Could not read input 0: input", __func__);
3731 }
3732
3733 // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM, of shape [batch_size, output_size].
3734 LayerInputHandle outputStatePrevTimeStep = ConvertToLayerInputHandle(operation, 18, model, data);
3735 if (!outputStatePrevTimeStep.IsValid())
3736 {
3737 return Fail("%s: Could not read input 18: outputStatePrevTimeStep", __func__);
3738 }
3739
3740 // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape [batch_size, num_units].
3741 LayerInputHandle cellStatePrevTimeStep = ConvertToLayerInputHandle(operation, 19, model, data);
3742 if (!cellStatePrevTimeStep.IsValid())
3743 {
3744 return Fail("%s: Could not read input 19: cellStatePrevTimeStep", __func__);
3745 }
3746
3747 // Get the mandatory input tensors:
3748
3749 // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3750 // [num_units, input_size].
3751 const ConstTensorPin inputToForgetWeightsPin =
3752 ConvertOperationInputToConstTensorPin(operation, 2, model, data);
3753
3754 // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3755 // [num_units, input_size].
3756 const ConstTensorPin inputToCellWeightsPin =
3757 ConvertOperationInputToConstTensorPin(operation, 3, model, data);
3758
3759 // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3760 // [num_units, input_size].
3761 const ConstTensorPin inputToOutputWeightsPin =
3762 ConvertOperationInputToConstTensorPin(operation, 4, model, data);
3763
3764 // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3765 // [num_units, output_size].
3766 const ConstTensorPin recurrentToForgetWeightsPin =
3767 ConvertOperationInputToConstTensorPin(operation, 6, model, data);
3768
3769 // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3770 // [num_units, output_size].
3771 const ConstTensorPin recurrentToCellWeightsPin =
3772 ConvertOperationInputToConstTensorPin(operation, 7, model, data);
3773
3774 // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3775 // [num_units, output_size].
3776 const ConstTensorPin recurrentToOutputWeightsPin =
3777 ConvertOperationInputToConstTensorPin(operation, 8, model, data);
3778
3779 // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3780 const ConstTensorPin forgetGateBiasPin =
3781 ConvertOperationInputToConstTensorPin(operation, 13, model, data);
3782
3783 // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3784 const ConstTensorPin cellBiasPin =
3785 ConvertOperationInputToConstTensorPin(operation, 14, model, data);
3786
3787 // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3788 const ConstTensorPin outputGateBiasPin =
3789 ConvertOperationInputToConstTensorPin(operation, 15, model, data);
3790
3791 if (!inputToForgetWeightsPin.IsValid() ||
3792 !inputToCellWeightsPin.IsValid() ||
3793 !inputToOutputWeightsPin.IsValid() ||
3794 !recurrentToForgetWeightsPin.IsValid() ||
3795 !recurrentToCellWeightsPin.IsValid() ||
3796 !recurrentToOutputWeightsPin.IsValid() ||
3797 !forgetGateBiasPin.IsValid() ||
3798 !cellBiasPin.IsValid() ||
3799 !outputGateBiasPin.IsValid())
3800 {
3801 return Fail("%s: Operation has invalid tensor inputs", __func__);
3802 }
3803
3804 // Get the optional input tensors:
3805
3806 // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3807 // [num_units, input_size], where “num_units” corresponds to the number of cell units.
3808 const ConstTensorPin inputToInputWeightsPin =
3809 ConvertOperationInputToConstTensorPin(operation,
3810 1,
3811 model,
3812 data,
3813 g_DontPermute,
3814 nullptr,
3815 true);
3816
3817 // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3818 // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
3819 // “num_units”), or the second dimension of the “projection_weights”, if defined.
3820 const ConstTensorPin recurrentToInputWeightsPin =
3821 ConvertOperationInputToConstTensorPin(operation,
3822 5,
3823 model,
3824 data,
3825 g_DontPermute,
3826 nullptr,
3827 true);
3828
3829 // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape
3830 // [num_units].
3831 const ConstTensorPin cellToInputWeightsPin =
3832 ConvertOperationInputToConstTensorPin(operation,
3833 9,
3834 model,
3835 data,
3836 g_DontPermute,
3837 nullptr,
3838 true);
3839
3840 // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape
3841 // [num_units].
3842 const ConstTensorPin cellToForgetWeightsPin =
3843 ConvertOperationInputToConstTensorPin(operation,
3844 10,
3845 model,
3846 data,
3847 g_DontPermute,
3848 nullptr,
3849 true);
3850
3851 // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape
3852 // [num_units].
3853 const ConstTensorPin cellToOutputWeightsPin =
3854 ConvertOperationInputToConstTensorPin(operation,
3855 11,
3856 model,
3857 data,
3858 g_DontPermute,
3859 nullptr,
3860 true);
3861
3862 // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [num_units].
3863 const ConstTensorPin inputGateBiasPin =
3864 ConvertOperationInputToConstTensorPin(operation,
3865 12,
3866 model,
3867 data,
3868 g_DontPermute,
3869 nullptr,
3870 true);
3871
3872 // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_SYMM, of shape
3873 // [output_size, num_units].
3874 const ConstTensorPin projectionWeightsPin =
3875 ConvertOperationInputToConstTensorPin(operation,
3876 16,
3877 model,
3878 data,
3879 g_DontPermute,
3880 nullptr,
3881 true);
3882
3883 // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_INT32, of shape [output_size].
3884 const ConstTensorPin projectionBiasPin =
3885 ConvertOperationInputToConstTensorPin(operation,
3886 17,
3887 model,
3888 data,
3889 g_DontPermute,
3890 nullptr,
3891 true);
3892
3893 if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional())
3894 || (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional())
3895 || (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional())
3896 || (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional())
3897 || (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional())
3898 || (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional())
3899 || (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional())
3900 || (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
3901 {
3902 return Fail("%s: Operation has invalid tensor inputs", __func__);
3903 }
3904
3905
3906 // Get the optional normalization tensors
3907
3908 // 20: The input layer normalization weights. A 1-D tensor of shape [num_units] ANEURALNETWORKS_TENSOR_QUANT16_SYMM.
3909 // Used to rescale normalized inputs to activation at input gate.
3910 const ConstTensorPin inputLayerNormWeightsPin =
3911 ConvertOperationInputToConstTensorPin(operation,
3912 20,
3913 model,
3914 data,
3915 g_DontPermute,
3916 nullptr,
3917 true);
3918
3919 // 21: The forget layer normalization weights. A 1-D tensor of shape [num_units] ANEURALNETWORKS_TENSOR_QUANT16_SYMM
3920 // Used to rescale normalized inputs to activation at forget gate.
3921 const ConstTensorPin forgetLayerNormWeightsPin =
3922 ConvertOperationInputToConstTensorPin(operation,
3923 21,
3924 model,
3925 data,
3926 g_DontPermute,
3927 nullptr,
3928 true);
3929
3930 // 22: The cell layer normalization weights. A 1-D tensor of shape [num_units] ANEURALNETWORKS_TENSOR_QUANT16_SYMM.
3931 // Used to rescale normalized inputs to activation at cell gate.
3932 const ConstTensorPin cellLayerNormWeightsPin =
3933 ConvertOperationInputToConstTensorPin(operation,
3934 22,
3935 model,
3936 data,
3937 g_DontPermute,
3938 nullptr,
3939 true);
3940
3941 // 23: The output layer normalization weights. A 1-D tensor of shape [num_units].
3942 // Used to rescale normalized inputs to activation at output gate.
3943 const ConstTensorPin outputLayerNormWeightsPin =
3944 ConvertOperationInputToConstTensorPin(operation,
3945 23,
3946 model,
3947 data,
3948 g_DontPermute,
3949 nullptr,
3950 true);
3951
3952 if ((!inputLayerNormWeightsPin.IsValid() && !inputLayerNormWeightsPin.IsOptional())
3953 || (!forgetLayerNormWeightsPin.IsValid() && !forgetLayerNormWeightsPin.IsOptional())
3954 || (!cellLayerNormWeightsPin.IsValid() && !cellLayerNormWeightsPin.IsOptional())
3955 || (!outputLayerNormWeightsPin.IsValid() && !outputLayerNormWeightsPin.IsOptional()))
3956 {
3957 return Fail("%s: Operation has invalid tensor inputs", __func__);
3958 }
3959
3960 // Get the optional input scalars:
3961 // 24: The cell clip: If provided the cell state is clipped by this value prior to the cell output activation.
3962 // 25: The projection clip: If provided and projection is enabled, this is used for clipping the projected values.
3963
3964 // Get the mandatory input scalars:
3965 // 26: The scale of the intermediate result of matmul, i.e. input to layer normalization, at input gate.
3966 // 27: The scale of the intermediate result of matmul, i.e. input to layer normalization, at forget gate.
3967 // 28: The scale of the intermediate result of matmul, i.e. input to layer normalization, at cell gate.
3968 // 29: The scale of the intermediate result of matmul, i.e. input to layer normalization, at output gate.
3969 // 30: The zero point of the hidden state, i.e. input to projection.
3970 // 31: The scale of the hidden state, i.e. input to projection.
3971 float cellClip, projClip, matMulInputGate, matMulForgetGate, matMulCellGate, matMulOutputGate, projInputScale;
3972 int projInputZeroPoint;
3973
3974 if (!GetInputScalar(operation, 24, OperandType::FLOAT32, cellClip, model, data, true) ||
3975 !GetInputScalar(operation, 25, OperandType::FLOAT32, projClip, model, data, true) ||
3976 !GetInputScalar(operation, 26, OperandType::FLOAT32, matMulInputGate, model, data) ||
3977 !GetInputScalar(operation, 27, OperandType::FLOAT32, matMulForgetGate, model, data) ||
3978 !GetInputScalar(operation, 28, OperandType::FLOAT32, matMulCellGate, model, data) ||
3979 !GetInputScalar(operation, 29, OperandType::FLOAT32, matMulOutputGate, model, data) ||
3980 !GetInputScalar(operation, 30, OperandType::INT32, projInputZeroPoint, model, data) ||
3981 !GetInputScalar(operation, 31, OperandType::FLOAT32, projInputScale, model, data))
3982 {
3983 return Fail("%s: Operation has invalid scalar inputs", __func__);
3984 }
3985
3986 // Outputs:
3987 // 0: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, of shape [batch_size,
3988 // output_size].
3989 const Operand* outputStateOut = GetOutputOperand(operation, 0, model);
3990 if (!outputStateOut)
3991 {
3992 return Fail("%s: Could not read output 0: outputStateOut", __func__);
3993 }
3994
3995 // 1: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT16_SYMM, of shape [batch_size, num_units].
3996 const Operand* cellStateOut = GetOutputOperand(operation, 1, model);
3997 if (!cellStateOut)
3998 {
3999 return Fail("%s: Could not read output 1: cellStateOut", __func__);
4000 }
4001
4002 // 2: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_QUANT8_ASYMM_SIGNED, of shape [batch_size, output_size].
4003 // This is effectively the same as the current “output state (out)” value.
4004 const Operand* output = GetOutputOperand(operation, 2, model);
4005 if (!output)
4006 {
4007 return Fail("%s: Could not read output 2: output", __func__);
4008 }
4009
4010 // set the params structure for the AddLstmLayer call
4011 LstmInputParams params;
4012 params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
4013 params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
4014 params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
4015 params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
4016 params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
4017 params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
4018 params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
4019 params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
4020 params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
4021 params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
4022 params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
4023 params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
4024 params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
4025 params.m_CellBias = cellBiasPin.GetConstTensorPtr();
4026 params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
4027 params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
4028 params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
4029 params.m_InputLayerNormWeights = inputLayerNormWeightsPin.GetConstTensorPtr();
4030 params.m_ForgetLayerNormWeights = forgetLayerNormWeightsPin.GetConstTensorPtr();
4031 params.m_CellLayerNormWeights = cellLayerNormWeightsPin.GetConstTensorPtr();
4032 params.m_OutputLayerNormWeights = outputLayerNormWeightsPin.GetConstTensorPtr();
4033
4034 // set the layer descriptor
4035 QLstmDescriptor desc;
4036 desc.m_CellClip = cellClip;
4037 desc.m_ProjectionClip = projClip;
4038 desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
4039 params.m_RecurrentToInputWeights == nullptr ||
4040 params.m_InputGateBias == nullptr);
4041 desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
4042 params.m_CellToOutputWeights != nullptr);
4043 desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
4044 desc.m_LayerNormEnabled = (params.m_InputLayerNormWeights != nullptr ||
4045 params.m_ForgetLayerNormWeights != nullptr ||
4046 params.m_CellLayerNormWeights != nullptr ||
4047 params.m_OutputLayerNormWeights != nullptr);
4048 desc.m_InputIntermediateScale = matMulInputGate;
4049 desc.m_ForgetIntermediateScale = matMulForgetGate;
4050 desc.m_CellIntermediateScale = matMulCellGate;
4051 desc.m_OutputIntermediateScale = matMulOutputGate;
4052 desc.m_HiddenStateScale = projInputScale;
4053 desc.m_HiddenStateZeroPoint = projInputZeroPoint;
4054
4055 // validate the optional input groups
4056 if (desc.m_CifgEnabled &&
4057 (params.m_InputToInputWeights != nullptr ||
4058 params.m_RecurrentToInputWeights != nullptr ||
4059 params.m_InputGateBias != nullptr))
4060 {
4061 return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
4062 " and input gate bias must be provided", __func__);
4063 }
4064
4065 if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
4066 {
4067 return Fail("%s: projection bias should not be provided without projection weights", __func__);
4068 }
4069
4070 if (desc.m_PeepholeEnabled &&
4071 (params.m_CellToForgetWeights == nullptr ||
4072 params.m_CellToOutputWeights == nullptr ||
4073 (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
4074 {
4075 return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
4076 " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
4077 }
4078
4079 if (desc.m_LayerNormEnabled &&
4080 (params.m_ForgetLayerNormWeights == nullptr ||
4081 params.m_CellLayerNormWeights == nullptr ||
4082 params.m_OutputLayerNormWeights == nullptr ||
4083 (!desc.m_CifgEnabled && params.m_InputLayerNormWeights == nullptr)))
4084 {
4085 return Fail("%s: All, or none, of forget-norm weights, cell-norm weights and output-norm weights must be"
4086 " provided and, if CIFG is not enabled, input-norm weights must also be provided", __func__);
4087 }
4088
4089 // Basic parameters
4090 LstmInputParamsInfo paramsInfo;
4091 paramsInfo.m_InputToForgetWeights = &(params.m_InputToForgetWeights->GetInfo());
4092 paramsInfo.m_InputToCellWeights = &(params.m_InputToCellWeights->GetInfo());
4093 paramsInfo.m_InputToOutputWeights = &(params.m_InputToOutputWeights->GetInfo());
4094 paramsInfo.m_RecurrentToForgetWeights = &(params.m_RecurrentToForgetWeights->GetInfo());
4095 paramsInfo.m_RecurrentToCellWeights = &(params.m_RecurrentToCellWeights->GetInfo());
4096 paramsInfo.m_RecurrentToOutputWeights = &(params.m_RecurrentToOutputWeights->GetInfo());
4097 paramsInfo.m_ForgetGateBias = &(params.m_ForgetGateBias->GetInfo());
4098 paramsInfo.m_CellBias = &(params.m_CellBias->GetInfo());
4099 paramsInfo.m_OutputGateBias = &(params.m_OutputGateBias->GetInfo());
4100
4101 // Inputs
4102 const TensorInfo& inputInfo = input.GetTensorInfo();
4103 const TensorInfo& outputStatePrevTimeStepInfo = outputStatePrevTimeStep.GetTensorInfo();
4104 const TensorInfo& cellStatePrevTimeStepInfo = cellStatePrevTimeStep.GetTensorInfo();
4105
4106 // Outputs
4107 TensorInfo outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
4108 TensorInfo outputInfo = GetTensorInfoForOperand(*output);
4109 const TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
4110
4111 // Optional parameters
4112 if (!desc.m_CifgEnabled)
4113 {
4114 paramsInfo.m_InputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
4115 paramsInfo.m_RecurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo());
4116 if (desc.m_PeepholeEnabled)
4117 {
4118 paramsInfo.m_CellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
4119 }
4120 paramsInfo.m_InputGateBias = &(params.m_InputGateBias->GetInfo());
4121 }
4122
4123
4124 if (desc.m_ProjectionEnabled)
4125 {
4126 paramsInfo.m_ProjectionWeights = &(params.m_ProjectionWeights->GetInfo());
4127 if (params.m_ProjectionBias != nullptr)
4128 {
4129 paramsInfo.m_ProjectionBias = &(params.m_ProjectionBias->GetInfo());
4130 }
4131 }
4132 else
4133 {
4134 // If Projection is disabled, override non-const outputs to change the quant info with hidden params, then
4135 // create a new const TensorInfo based on this
4136 outputStateOutInfo.SetQuantizationScale(projInputScale);
4137 outputStateOutInfo.SetQuantizationOffset(projInputZeroPoint);
4138 outputInfo.SetQuantizationScale(projInputScale);
4139 outputInfo.SetQuantizationOffset(projInputZeroPoint);
4140 }
4141
4142 const TensorInfo constOutputStateOutInfo(outputStateOutInfo);
4143 const TensorInfo constOutputInfo(outputInfo);
4144
4145 if (desc.m_PeepholeEnabled)
4146 {
4147 paramsInfo.m_CellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
4148 paramsInfo.m_CellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
4149 }
4150
4151 if (desc.m_LayerNormEnabled)
4152 {
4153 if(!desc.m_CifgEnabled)
4154 {
4155 paramsInfo.m_InputLayerNormWeights = &(params.m_InputLayerNormWeights->GetInfo());
4156 }
4157 paramsInfo.m_ForgetLayerNormWeights = &(params.m_ForgetLayerNormWeights->GetInfo());
4158 paramsInfo.m_CellLayerNormWeights = &(params.m_CellLayerNormWeights->GetInfo());
4159 paramsInfo.m_OutputLayerNormWeights = &(params.m_OutputLayerNormWeights->GetInfo());
4160 }
4161
4162 // Check if the layer is supported
4163 bool isSupported = false;
4164 auto validateFunc = [&](const armnn::TensorInfo& cellStateOutInfo, bool& isSupported)
4165 {
4166 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4167 IsQLstmSupported,
4168 data.m_Backends,
4169 isSupported,
4170 inputInfo,
4171 outputStatePrevTimeStepInfo,
4172 cellStatePrevTimeStepInfo,
4173 constOutputStateOutInfo,
4174 cellStateOutInfo,
4175 constOutputInfo,
4176 desc,
4177 paramsInfo);
4178 };
4179
4180 bool isDynamic = false;
4181 if (!IsDynamicTensor(constOutputStateOutInfo) &&
4182 !IsDynamicTensor(cellStateOutInfo) &&
4183 !IsDynamicTensor(constOutputInfo))
4184 {
4185 validateFunc(outputInfo, isSupported);
4186 }
4187 else
4188 {
4189 isDynamic = true;
4190 isSupported = AreDynamicTensorsSupported();
4191 }
4192
4193 if (!isSupported)
4194 {
4195 return false;
4196 }
4197
4198 // Add the layer
4199 IConnectableLayer* layer = data.m_Network->AddQLstmLayer(desc, params, "QLstm");
4200
4201 input.Connect(layer->GetInputSlot(0));
4202 outputStatePrevTimeStep.Connect(layer->GetInputSlot(1));
4203 cellStatePrevTimeStep.Connect(layer->GetInputSlot(2));
4204
4205 if (!isDynamic)
4206 {
4207 return ( SetupAndTrackLayerOutputSlot(
4208 operation, 0, *layer, 0, model, data, &constOutputStateOutInfo) &&
4209 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
4210 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data, &constOutputInfo));
4211 }
4212 else
4213 {
4214 return ( SetupAndTrackLayerOutputSlot(
4215 operation, 0, *layer, 0, model, data, &constOutputStateOutInfo) &&
4216 SetupAndTrackLayerOutputSlot(
4217 operation, 1, *layer, 1, model, data, nullptr, validateFunc,
4218 ActivationFn::kActivationNone, true) &&
4219 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data, &constOutputInfo));
4220 }
4221}
4222
4223bool Converter::ConvertQuantized16BitLstm(const Operation& operation, const Model& model, ConversionData& data)
4224{
4225 VLOG(DRIVER) << "Converter::ConvertQuantized16BitLstm()";
4226 VLOG(DRIVER) << "Policy::ConvertQuantized16BitLstm()";
4227
4228 //Inputs:
4229 // 0: The input: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape [numBatches, inputSize]
4230 // specifying the input to the LSTM cell. Tensor is quantized with a fixed quantization range of -1, 127/128.
4231 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4232 if (!input.IsValid())
4233 {
4234 return Fail("%s: Could not read input 0: input", __func__);
4235 }
4236
4237 //13: The previous cell state: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT16_SYMM and shape
4238 // [numBatches, outputSize] specifying the cell state from the previous time step of the LSTM cell.
4239 // It is quantized using a quantization range of -2^4, 2^4 * 32767/32768.
4240 LayerInputHandle previousCellStateIn = ConvertToLayerInputHandle(operation, 13, model, data);
4241 if (!previousCellStateIn.IsValid())
4242 {
4243 return Fail("%s: Could not read input 13: previousCellStateIn", __func__);
4244 }
4245
4246 // 14: The previous output state: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4247 // [numBathes, outputSize] specifying the output of the LSTM cell from previous time-step. Tensor
4248 // is quantized with a fixed quantization range of -1, 127/128.
4249 LayerInputHandle previousOutputIn = ConvertToLayerInputHandle(operation, 14, model, data);
4250 if (!previousOutputIn.IsValid())
4251 {
4252 return Fail("%s: Could not read input 14: previousOutputIn", __func__);
4253 }
4254
4255 // Get the input tensors:
4256 // 1: The input-to-input weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4257 // [outputSize, inputSize] specifying input-to-input 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 inputToInputWeightsPin =
4260 ConvertOperationInputToConstTensorPin(operation, 1, model, data);
4261
4262 // 2: The input-to-forget weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4263 // [outputSize, inputSize] specifying input-to-forget 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 inputToForgetWeightsPin =
4266 ConvertOperationInputToConstTensorPin(operation, 2, model, data);
4267
4268 // 3: The input-to-cell weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4269 // [outputSize, inputSize] specifying input-to-cell 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 inputToCellWeightsPin =
4272 ConvertOperationInputToConstTensorPin(operation, 3, model, data);
4273
4274 // 4: The input-to-output weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4275 // [outputSize, inputSize] specifying input-to-output part of weights for fully-connected layer inside the
4276 // LSTM cell. Quantization zero point and scale must be the same across all the weights.
4277 const ConstTensorPin inputToOutputWeightsPin =
4278 ConvertOperationInputToConstTensorPin(operation, 4, model, data);
4279
4280 // 5: The recurrent-to-input weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4281 // [outputSize, outputSize] specifying recurrent-to-input 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 recurrentToInputWeightsPin =
4284 ConvertOperationInputToConstTensorPin(operation, 5, model, data);
4285
4286 // 6: The recurrent-to-forget weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4287 // [outputSize, outputSize] specifying recurrent-to-forget 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 recurrentToForgetWeightsPin =
4290 ConvertOperationInputToConstTensorPin(operation, 6, model, data);
4291
4292 // 7: The recurrent-to-cell weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4293 // [outputSize, outputSize] specifying recurrent-to-cell 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 recurrentToCellWeightsPin =
4296 ConvertOperationInputToConstTensorPin(operation, 7, model, data);
4297
4298 // 8: The recurrent-to-output weights. A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape
4299 // [outputSize, outputSize] specifying recurrent-to-output part of weights for fully-connected layer inside
4300 // the LSTM cell. Quantization zero point and scale must be the same across all the weights.
4301 const ConstTensorPin recurrentToOutputWeightsPin =
4302 ConvertOperationInputToConstTensorPin(operation, 8, model, data);
4303
4304 // 9: The input gate bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying the
4305 // 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 inputGateBiasPin =
4308 ConvertOperationInputToConstTensorPin(operation, 9, model, data);
4309
4310 // 10: The forget gate bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying
4311 // the bias for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product
4312 // of input and weights scales and zeroPoint equal to 0.
4313 const ConstTensorPin forgetGateBiasPin =
4314 ConvertOperationInputToConstTensorPin(operation, 10, model, data);
4315
4316 // 11:The cell bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying the bias
4317 // for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product of input
4318 // and weights scales and zeroPoint equal to 0.
4319 const ConstTensorPin cellBiasPin =
4320 ConvertOperationInputToConstTensorPin(operation, 11, model, data);
4321
4322 // 12:The output gate bias. A 1-D tensor of type ANEURALNETWORKS_TENSOR_INT32 and shape [outputSize] specifying
4323 // the bias for the fully-connected layer inside the LSTM cell. Bias is quantized with scale being a product
4324 // of input and weights scales and zeroPoint equal to 0.
4325 const ConstTensorPin outputGateBiasPin =
4326 ConvertOperationInputToConstTensorPin(operation, 12, model, data);
4327
4328 if (!inputToInputWeightsPin.IsValid() ||
4329 !inputToForgetWeightsPin.IsValid() ||
4330 !inputToCellWeightsPin.IsValid() ||
4331 !inputToOutputWeightsPin.IsValid() ||
4332 !recurrentToInputWeightsPin.IsValid() ||
4333 !recurrentToForgetWeightsPin.IsValid() ||
4334 !recurrentToCellWeightsPin.IsValid() ||
4335 !recurrentToOutputWeightsPin.IsValid() ||
4336 !inputGateBiasPin.IsValid() ||
4337 !forgetGateBiasPin.IsValid() ||
4338 !cellBiasPin.IsValid() ||
4339 !outputGateBiasPin.IsValid())
4340 {
4341 return Fail("%s: Operation has invalid tensor inputs", __func__);
4342 }
4343
4344 // Outputs:
4345 // 0: The cell state: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT16_SYMM and shape [numBatches, outputSize]
4346 // which contains a cell state from the current time step. Tensor is quantized using a quantization range
4347 // of -2^4, 2^4 * 32767/32768.
4348 const Operand* cellStateOut = GetOutputOperand(operation, 0, model);
4349 if (!cellStateOut)
4350 {
4351 return Fail("%s: Could not read output 0: cellStateOut", __func__);
4352 }
4353
4354 // 1: The output: A 2-D tensor of type ANEURALNETWORKS_TENSOR_QUANT8_ASYMM and shape [numBathes, outputSize] which
4355 // contains the output value. Tensor is quantized with a fixed quantization range of -1, 127/128.
4356 const Operand* output = GetOutputOperand(operation, 1, model);
4357 if (!output)
4358 {
4359 return Fail("%s: Could not read output 1: output", __func__);
4360 }
4361
4362 // Inputs
4363 const TensorInfo& inputInfo = input.GetTensorInfo();
4364 const TensorInfo& previousCellStateInInfo = previousCellStateIn.GetTensorInfo();
4365 const TensorInfo& previousOutputInInfo = previousOutputIn.GetTensorInfo();
4366
4367 // Outputs
4368 const TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
4369 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
4370
4371 // Dynamic tensors currently not supported
4372 if (IsDynamicTensor(cellStateOutInfo) || IsDynamicTensor(outputInfo))
4373 {
4374 return Fail("%s: Dynamic output tensors are not supported", __func__);
4375 }
4376
4377 QuantizedLstmInputParams params;
4378
4379 params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
4380 params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
4381 params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
4382 params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
4383 params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
4384 params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
4385 params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
4386 params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
4387 params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
4388 params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
4389 params.m_CellBias = cellBiasPin.GetConstTensorPtr();
4390 params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
4391
4392 QuantizedLstmInputParamsInfo paramsInfo;
4393 paramsInfo.m_InputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
4394 paramsInfo.m_InputToForgetWeights = &(params.m_InputToForgetWeights->GetInfo());
4395 paramsInfo.m_InputToCellWeights = &(params.m_InputToCellWeights->GetInfo());
4396 paramsInfo.m_InputToOutputWeights = &(params.m_InputToOutputWeights->GetInfo());
4397 paramsInfo.m_RecurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo());
4398 paramsInfo.m_RecurrentToForgetWeights = &(params.m_RecurrentToForgetWeights->GetInfo());
4399 paramsInfo.m_RecurrentToCellWeights = &(params.m_RecurrentToCellWeights->GetInfo());
4400 paramsInfo.m_RecurrentToOutputWeights = &(params.m_RecurrentToOutputWeights->GetInfo());
4401 paramsInfo.m_InputGateBias = &(params.m_InputGateBias->GetInfo());
4402 paramsInfo.m_ForgetGateBias = &(params.m_ForgetGateBias->GetInfo());
4403 paramsInfo.m_CellBias = &(params.m_CellBias->GetInfo());
4404 paramsInfo.m_OutputGateBias = &(params.m_OutputGateBias->GetInfo());
4405
4406 bool isSupported = false;
4407 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4408 {
4409 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4410 IsQuantizedLstmSupported,
4411 data.m_Backends,
4412 isSupported,
4413 inputInfo,
4414 previousCellStateInInfo,
4415 previousOutputInInfo,
4416 cellStateOutInfo,
4417 outputInfo,
4418 paramsInfo);
4419 };
4420
4421 bool isDynamic = false;
4422 if (!IsDynamicTensor(cellStateOutInfo) &&
4423 !IsDynamicTensor(outputInfo))
4424 {
4425 validateFunc(outputInfo, isSupported);
4426 }
4427 else
4428 {
4429 isDynamic = true;
4430 isSupported = AreDynamicTensorsSupported();
4431 }
4432
4433 if (!isSupported)
4434 {
4435 return false;
4436 }
4437
4438 IConnectableLayer* const layer = data.m_Network->AddQuantizedLstmLayer(params, "QuantizedLstm");
4439 input.Connect(layer->GetInputSlot(0));
4440 previousCellStateIn.Connect(layer->GetInputSlot(1));
4441 previousOutputIn.Connect(layer->GetInputSlot(2));
4442
4443 if (!isDynamic)
4444 {
4445 return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
4446 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data));
4447 }
4448 else
4449 {
4450 return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
4451 SetupAndTrackLayerOutputSlot(
4452 operation, 1, *layer, 1, model, data, nullptr, validateFunc, ActivationFn::kActivationNone, true));
4453 }
4454
4455}
4456
4457bool Converter::ConvertRank(const Operation& operation, const Model& model, ConversionData& data)
4458{
4459 VLOG(DRIVER) << "Converter::ConvertRank()";
4460
4461 const Operand* inputOperand = GetInputOperand(operation, 0, model);
4462 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4463
4464 if (inputOperand == nullptr || outputOperand == nullptr)
4465 {
4466 return Fail("%s: Operation has invalid inputs", __func__);
4467 }
4468
4469 const Shape inputOperandShape = GetOperandShape(*inputOperand);
4470 const Shape outputOperandShape = GetOperandShape(*outputOperand);
4471
4472 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4473 if (!input.IsValid())
4474 {
4475 return Fail("%s: Could not read input 0", __func__);
4476 }
4477
4478 armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
4479 if (IsDynamicTensor(outInfo))
4480 {
4481 return Fail("%s: Dynamic output tensors are not supported", __func__);
4482 }
4483
4484 bool isSupported = false;
4485 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4486 IsRankSupported,
4487 data.m_Backends,
4488 isSupported,
4489 input.GetTensorInfo(),
4490 outInfo);
4491 if (!isSupported)
4492 {
4493 return false;
4494 }
4495
4496 armnn::IConnectableLayer* layer = data.m_Network->AddRankLayer();
4497 assert(layer != nullptr);
4498 input.Connect(layer->GetInputSlot(0));
4499
4500 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, &outInfo);
4501}
4502
4503bool Converter::ConvertReLu(const Operation& operation, const Model& model, ConversionData& data)
4504{
4505 VLOG(DRIVER) << "Converter::ConvertReLu()";
4506 armnn::ActivationDescriptor desc;
4507 desc.m_Function = armnn::ActivationFunction::ReLu;
4508
4509
4510 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4511 if (!input.IsValid())
4512 {
4513 return Fail("%s: Input 0 is invalid", "operationName");
4514 }
4515
4516 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4517 if (!outputOperand)
4518 {
4519 return false;
4520 }
4521
4522 const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
4523
4524 bool isSupported = false;
4525
4526 auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
4527 {
4528 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4529 IsActivationSupported,
4530 data.m_Backends,
4531 isSupported,
4532 input.GetTensorInfo(),
4533 outInfo,
4534 desc);
4535 };
4536
4537 if(IsDynamicTensor(outInfo))
4538 {
4539 isSupported = AreDynamicTensorsSupported();
4540 }
4541 else
4542 {
4543 validateFunc(outInfo, isSupported);
4544 }
4545
4546 if (!isSupported)
4547 {
4548 return false;
4549 }
4550
4551 armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(desc);
4552 ARMNN_ASSERT(layer != nullptr);
4553 input.Connect(layer->GetInputSlot(0));
4554
4555 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4556}
4557
4558bool Converter::ConvertReLu1(const Operation& operation, const Model& model, ConversionData& data)
4559{
4560 VLOG(DRIVER) << "Converter::ConvertReLu1()";
4561 armnn::ActivationDescriptor desc;
4562 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
4563 desc.m_A = 1.0f;
4564 desc.m_B = -1.0f;
4565
4566 return ConvertToActivation(operation, __func__, desc, model, data);
4567}
4568
4569bool Converter::ConvertReLu6(const Operation& operation, const Model& model, ConversionData& data)
4570{
4571 VLOG(DRIVER) << "Converter::ConvertReLu6()";
4572 armnn::ActivationDescriptor desc;
4573 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
4574 desc.m_A = 6.0f;
4575
4576 return ConvertToActivation(operation, __func__, desc, model, data);
4577}
4578
4579bool Converter::ConvertReshape(const Operation& operation, const Model& model, ConversionData& data)
4580{
4581 VLOG(DRIVER) << "Converter::ConvertReshape()";
4582
4583 const Operand* inputOperand = GetInputOperand(operation, 0, model);
4584 const Operand* requestedShapeOperand = GetInputOperand(operation, 1, model);
4585 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4586
4587 if (inputOperand == nullptr
4588 || requestedShapeOperand == nullptr
4589 || outputOperand == nullptr)
4590 {
4591 return Fail("%s: Operation has invalid inputs", __func__);
4592 }
4593
4594 if (requestedShapeOperand->dimensions.size() != 1)
4595 {
4596 return Fail("%s: Input 1 expected to be one-dimensional (found %i dimensions)",
4597 __func__, requestedShapeOperand->dimensions.size());
4598 }
4599
4600 std::vector<int32_t> targetDimensions;
4601 if (!GetTensorInt32Values(*requestedShapeOperand, targetDimensions, model, data))
4602 {
4603 return Fail("%s: Could not read values of input 1", __func__);
4604 }
4605
4606 const Shape inputOperandShape = GetOperandShape(*inputOperand);
4607
4608 Shape requestedShape;
4609 // targetDimensions may contain special values (e.g. -1). reshapePrepare() is an AndroidNN provided utility
4610 // function that resolves these values into a fully specified tensor shape.
4611 if (!reshapePrepare(inputOperandShape, targetDimensions.data(), targetDimensions.size(), &requestedShape))
4612 {
4613 return Fail("%s: Failed to resolve the requested shape", __func__);
4614 }
4615
4616 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4617 if (!input.IsValid())
4618 {
4619 return Fail("%s: Could not read input 0", __func__);
4620 }
4621
4622 armnn::ReshapeDescriptor reshapeDescriptor;
4623 reshapeDescriptor.m_TargetShape = armnn::TensorShape(requestedShape.dimensions.size(),
4624 requestedShape.dimensions.data());
4625
4626 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
4627
4628 bool isSupported = false;
4629 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4630 {
4631 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4632 IsReshapeSupported,
4633 data.m_Backends,
4634 isSupported,
4635 input.GetTensorInfo(),
4636 outputInfo,
4637 reshapeDescriptor);
4638 };
4639
4640 if(!IsDynamicTensor(outputInfo))
4641 {
4642 validateFunc(outputInfo, isSupported);
4643 }
4644 else
4645 {
4646 isSupported = AreDynamicTensorsSupported();
4647 }
4648
4649 if (!isSupported)
4650 {
4651 return false;
4652 }
4653
4654 armnn::IConnectableLayer* layer = data.m_Network->AddReshapeLayer(reshapeDescriptor);
4655 assert(layer != nullptr);
4656 input.Connect(layer->GetInputSlot(0));
4657
4658 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4659}
4660
4661bool Converter::ConvertResize(const Operation& operation,
4662 const Model& model,
4663 ConversionData& data,
4664 ResizeMethod resizeMethod)
4665{
4666 VLOG(DRIVER) << "Converter::ConvertResize()";
4667 VLOG(DRIVER) << "resizeMethod = " << GetResizeMethodAsCString(resizeMethod);
4668
4669 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4670 if (!input.IsValid())
4671 {
4672 return Fail("%s: Could not read input 0", __func__);
4673 }
4674
4675 const Operand* output = GetOutputOperand(operation, 0, model);
4676 if (!output)
4677 {
4678 return Fail("%s: Could not read output 0", __func__);
4679 }
4680
4681 const TensorInfo& inputInfo = input.GetTensorInfo();
4682 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
4683
4684 ResizeDescriptor descriptor;
4685 descriptor.m_Method = resizeMethod;
4686 descriptor.m_DataLayout = OptionalDataLayout(operation, 3, model, data);
4687
4688 OperandType operandType1;
4689 OperandType operandType2;
4690
4691 if (!GetOperandType(operation, 1, model, operandType1) ||
4692 !GetOperandType(operation, 2, model, operandType2))
4693 {
4694 return Fail("%s: Operation has invalid inputs", __func__);
4695 }
4696
4697 if (operandType1 != operandType2)
4698 {
4699 return Fail("%s: Operation has invalid inputs. Type of input 1 and 2 should be the same", __func__);
4700 }
4701
4702 if (operandType1 == OperandType::INT32)
4703 {
4704 // Case 1: resizing by shape
4705 int32_t targetWidth = 0;
4706 int32_t targetHeight = 0;
4707
4708 if (!GetInputInt32(operation, 1, targetWidth, model, data) ||
4709 !GetInputInt32(operation, 2, targetHeight, model, data))
4710 {
4711 return Fail("%s: Operation has invalid inputs for resizing by shape", __func__);
4712 }
4713
4714 if (targetWidth < 0 || targetHeight < 0)
4715 {
4716 return Fail("%s: Operation has invalid inputs for resizing by shape. "
4717 "Target width/height cannot be < 0", __func__);
4718 }
4719
4720 descriptor.m_TargetWidth = static_cast<uint32_t>(targetWidth);
4721 descriptor.m_TargetHeight = static_cast<uint32_t>(targetHeight);
4722 }
4723 else if (operandType1 == OperandType::FLOAT32)
4724 {
4725 // Case 2: resizing by scale
4726 float widthScale = 1.0f;
4727 float heightScale = 1.0f;
4728
4729 if (!GetInputFloat32(operation, 1, widthScale, model, data) ||
4730 !GetInputFloat32(operation, 2, heightScale, model, data))
4731 {
4732 return Fail("%s: Operation has invalid inputs for resizing by scale", __func__);
4733 }
4734
4735 const TensorShape& inputShape = inputInfo.GetShape();
4736 armnnUtils::DataLayoutIndexed dataLayoutIndexed(descriptor.m_DataLayout);
4737
4738 float width = inputShape[dataLayoutIndexed.GetWidthIndex()];
4739 float height = inputShape[dataLayoutIndexed.GetHeightIndex()];
4740
4741 descriptor.m_TargetWidth = std::floor(width * widthScale);
4742 descriptor.m_TargetHeight = std::floor(height * heightScale);
4743 }
4744 else if (operandType1 == OperandType::FLOAT16)
4745 {
4746 Half widthScale;
4747 Half heightScale;
4748
4749 if (!GetInputScalar(operation, 1, OperandType::FLOAT16, widthScale, model, data) ||
4750 !GetInputScalar(operation, 2, OperandType::FLOAT16, heightScale, model, data))
4751 {
4752 return Fail("%s: Operation has invalid inputs for resizing by scale", __func__);
4753 }
4754
4755 const TensorShape& inputShape = inputInfo.GetShape();
4756 armnnUtils::DataLayoutIndexed dataLayoutIndexed(descriptor.m_DataLayout);
4757
4758 Half width = static_cast<Half>(inputShape[dataLayoutIndexed.GetWidthIndex()]);
4759 Half height = static_cast<Half>(inputShape[dataLayoutIndexed.GetHeightIndex()]);
4760
4761 descriptor.m_TargetWidth = std::floor(width * widthScale);
4762 descriptor.m_TargetHeight = std::floor(height * heightScale);
4763 }
4764 else
4765 {
4766 return Fail("%s: Operand has invalid data type for resizing by scale", __func__);
4767 }
4768
4769 descriptor.m_AlignCorners = GetOptionalBool(operation, 4, model, data);
4770 descriptor.m_HalfPixelCenters = GetOptionalBool(operation, 5, model, data);
4771
4772 bool isSupported = false;
4773 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4774 {
4775 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4776 IsResizeSupported,
4777 data.m_Backends,
4778 isSupported,
4779 inputInfo,
4780 outputInfo,
4781 descriptor);
4782 };
4783
4784 if(IsDynamicTensor(outputInfo))
4785 {
4786 isSupported = AreDynamicTensorsSupported();
4787 }
4788 else
4789 {
4790 validateFunc(outputInfo, isSupported);
4791 }
4792
4793 if (!isSupported)
4794 {
4795 return false;
4796 }
4797
4798 IConnectableLayer* layer = data.m_Network->AddResizeLayer(descriptor);
4799 assert(layer != nullptr);
4800 input.Connect(layer->GetInputSlot(0));
4801
4802 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4803}
4804
4805bool Converter::ConvertSpaceToBatchNd(const Operation& operation, const Model& model, ConversionData& data)
4806{
4807 VLOG(DRIVER) << "Converter::ConvertSpaceToBatchNd()";
4808
4809 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4810 if(!input.IsValid())
4811 {
4812 return Fail("%s: Operation has invalid inputs", __func__);
4813 }
4814
4815 const armnn::TensorInfo &inputInfo = input.GetTensorInfo();
4816 unsigned int rank = inputInfo.GetNumDimensions();
4817 unsigned int spatialDim = rank - 2;
4818
4819 if(rank != 4)
4820 {
4821 Fail("%s: Only inputs with rank 4 are supported", __func__);
4822 }
4823
4824 const Operand *output = GetOutputOperand(operation, 0, model);
4825 if(!output)
4826 {
4827 return Fail("%s: Could not read output 0", __func__);
4828 }
4829
4830 const armnn::TensorInfo &outputInfo = GetTensorInfoForOperand(*output);
4831
4832 const Operand *blockShapeOperand = GetInputOperand(operation, 1, model);
4833 const Operand *paddingsOperand = GetInputOperand(operation, 2, model);
4834
4835 armnn::TensorShape blockShapeOperandShape = GetTensorShapeForOperand(*blockShapeOperand);
4836 if(blockShapeOperandShape.GetNumDimensions() != 1 || blockShapeOperandShape.GetNumElements() != spatialDim)
4837 {
4838 return Fail("%s: Operation has invalid block shape operand: expected shape [%d]", __func__, spatialDim);
4839 }
4840
4841 std::vector<int32_t> blockShape;
4842 if(!GetTensorInt32Values(*blockShapeOperand, blockShape, model, data))
4843 {
4844 return Fail("%s: Operation has an invalid or unsupported block size operand", __func__);
4845 }
4846 if(std::any_of(blockShape.cbegin(), blockShape.cend(), [](int32_t i)
4847 { return i < 1; }))
4848 {
4849 return Fail("%s: Block shape must be at least 1 in all dimensions.", __func__);
4850 }
4851
4852 armnn::TensorShape paddingsOperandShape = GetTensorShapeForOperand(*paddingsOperand);
4853 if(paddingsOperandShape.GetNumDimensions() != 2 || paddingsOperandShape.GetNumElements() != 2 * spatialDim)
4854 {
4855 return Fail("%s: Operation has invalid paddings operand: expected shape [%d, 2]", __func__, spatialDim);
4856 }
4857
4858 std::vector<std::pair<unsigned int, unsigned int>> paddingList;
4859 std::vector<int32_t> paddings;
4860 if(!GetTensorInt32Values(*paddingsOperand, paddings, model, data))
4861 {
4862 return Fail("%s: Operation has an invalid or unsupported paddings operand", __func__);
4863 }
4864 for (unsigned int i = 0; i < paddings.size() - 1; i += 2)
4865 {
4866 int paddingBeforeInput = paddings[i];
4867 int paddingAfterInput = paddings[i + 1];
4868 if(paddingBeforeInput < 0 || paddingAfterInput < 0)
4869 {
4870 return Fail("%s: Operation has invalid paddings operand, invalid padding values.", __func__);
4871 }
4872
4873 paddingList.emplace_back((unsigned int) paddingBeforeInput, (unsigned int) paddingAfterInput);
4874 }
4875
4876 armnn::SpaceToBatchNdDescriptor descriptor;
4877 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
4878 descriptor.m_BlockShape.assign(blockShape.cbegin(), blockShape.cend());
4879 descriptor.m_PadList.assign(paddingList.cbegin(), paddingList.cend());
4880
4881 if(Is12OrLaterOperand(*output))
4882 {
4883 descriptor.m_DataLayout = OptionalDataLayout(operation, 3, model, data);
4884 }
4885
4886 bool isSupported = false;
4887 auto validateFunc = [&](const armnn::TensorInfo &outputInfo, bool &isSupported)
4888 {
4889 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4890 IsSpaceToBatchNdSupported,
4891 data.m_Backends,
4892 isSupported,
4893 inputInfo,
4894 outputInfo,
4895 descriptor);
4896 };
4897
4898 if(IsDynamicTensor(outputInfo))
4899 {
4900 isSupported = AreDynamicTensorsSupported();
4901 } else
4902 {
4903 validateFunc(outputInfo, isSupported);
4904 }
4905
4906 if(!isSupported)
4907 {
4908 return false;
4909 }
4910
4911 armnn::IConnectableLayer *const layer = data.m_Network->AddSpaceToBatchNdLayer(descriptor);
4912 assert(layer != nullptr);
4913 input.Connect(layer->GetInputSlot(0));
4914
4915 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4916}
4917
4918bool Converter::ConvertSpaceToDepth(const Operation& operation, const Model& model, ConversionData& data)
4919{
4920 VLOG(DRIVER) << "Converter::ConvertSpaceToDepth()";
4921
4922 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4923 if (!input.IsValid() )
4924 {
4925 return Fail("%s: Operation has invalid inputs", __func__);
4926 }
4927
4928 const TensorInfo& inputInfo = input.GetTensorInfo();
4929 unsigned int rank = inputInfo.GetNumDimensions();
4930 if (rank != 4)
4931 {
4932 return Fail("%s: Only inputs with rank 4 are supported", __func__);
4933 }
4934
4935 const Operand* output = GetOutputOperand(operation, 0, model);
4936 if (!output)
4937 {
4938 return Fail("%s: Could not read output 0", __func__);
4939 }
4940
4941 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
4942
4943 SpaceToDepthDescriptor desc;
4944
4945 GetInputScalar(operation, 1, OperandType::INT32, desc.m_BlockSize, model, data);
4946
4947 if (desc.m_BlockSize <= 1)
4948 {
4949 return Fail("%s: Block size must be at least 1 in all dimensions");
4950 }
4951
4952 desc.m_DataLayout = OptionalDataLayout(operation, 2, model, data);
4953
4954 bool isSupported = false;
4955 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
4956 {
4957 FORWARD_LAYER_SUPPORT_FUNC(__func__,
4958 IsSpaceToDepthSupported,
4959 data.m_Backends,
4960 isSupported,
4961 inputInfo,
4962 outputInfo,
4963 desc);
4964 };
4965
4966 if(IsDynamicTensor(outputInfo))
4967 {
4968 isSupported = AreDynamicTensorsSupported();
4969 }
4970 else
4971 {
4972 validateFunc(outputInfo, isSupported);
4973 }
4974
4975 if (!isSupported)
4976 {
4977 return false;
4978 }
4979
4980 IConnectableLayer* const layer = data.m_Network->AddSpaceToDepthLayer(desc);
4981 assert(layer != nullptr);
4982 input.Connect(layer->GetInputSlot(0));
4983
4984 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
4985}
4986
4987bool Converter::ConvertSoftmax(const Operation& operation, const Model& model, ConversionData& data)
4988{
4989 VLOG(DRIVER) << "Converter::ConvertSoftmax()";
4990
4991 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
4992 if (!input.IsValid())
4993 {
4994 return Fail("%s: Operation has invalid inputs", __func__);
4995 }
4996
4997 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
4998 if (!outputOperand)
4999 {
5000 return Fail("%s: Operation has no outputs", __func__);
5001 }
5002
5003 const TensorInfo& outputInfo = GetTensorInfoForOperand(*outputOperand);
5004
5005 SoftmaxDescriptor desc;
5006 OperandType outputType = outputOperand->type;
5007
5008 // Read beta value
5009 if (outputType == OperandType::TENSOR_FLOAT16)
5010 {
5011 Half value;
5012
5013 if (!GetInputScalar(operation, 1, OperandType::FLOAT16, value, model, data))
5014 {
5015 return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
5016 }
5017
5018 desc.m_Beta = static_cast<float>(value);
5019 }
5020 else
5021 {
5022 if (!GetInputFloat32(operation, 1, desc.m_Beta, model, data))
5023 {
5024 return Fail("%s: Operation has invalid inputs %d", __func__, outputType);
5025 }
5026 }
5027
5028 if (operation.inputs.size() > 2 && !GetInputScalar(operation,
5029 2,
5030 OperandType::INT32,
5031 desc.m_Axis,
5032 model,
5033 data))
5034 {
5035 return Fail("%s: Operation has invalid inputs", __func__);
5036 }
5037
5038 bool isSupported = false;
5039 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5040 {
5041 FORWARD_LAYER_SUPPORT_FUNC(__func__,
5042 IsSoftmaxSupported,
5043 data.m_Backends,
5044 isSupported,
5045 input.GetTensorInfo(),
5046 outputInfo,
5047 desc);
5048 };
5049
5050 if(IsDynamicTensor(outputInfo))
5051 {
5052 isSupported = AreDynamicTensorsSupported();
5053 }
5054 else
5055 {
5056 validateFunc(outputInfo, isSupported);
5057 }
5058
5059 if (!isSupported)
5060 {
5061 return false;
5062 }
5063
5064 IConnectableLayer* layer = data.m_Network->AddSoftmaxLayer(desc);
5065 assert(layer != nullptr);
5066 input.Connect(layer->GetInputSlot(0));
5067
5068 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5069}
5070
5071bool Converter::ConvertSub(const Operation& operation, const Model& model, ConversionData& data)
5072{
5073 VLOG(DRIVER) << "Converter::ConvertSub()";
5074
5075 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
5076 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
5077
5078 if (!input0.IsValid() || !input1.IsValid())
5079 {
5080 return Fail("%s: Operation has invalid inputs", __func__);
5081 }
5082
5083 // The FuseActivation parameter is always the input index 2
5084 // and it should be optional
5085 ActivationFn activationFunction;
5086 if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
5087 {
5088 return Fail("%s: Operation has invalid inputs", __func__);
5089 }
5090
5091 const Operand* output = GetOutputOperand(operation, 0, model);
5092 if (!output)
5093 {
5094 return Fail("%s: Could not read output 0", __func__);
5095 }
5096
5097 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5098
5099 bool isSupported = false;
5100 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5101 {
5102 FORWARD_LAYER_SUPPORT_FUNC(__func__,
5103 IsSubtractionSupported,
5104 data.m_Backends,
5105 isSupported,
5106 input0.GetTensorInfo(),
5107 input1.GetTensorInfo(),
5108 outputInfo);
5109 };
5110
5111 if(IsDynamicTensor(outputInfo))
5112 {
5113 isSupported = AreDynamicTensorsSupported();
5114 }
5115 else
5116 {
5117 validateFunc(outputInfo, isSupported);
5118 }
5119
5120 if (!isSupported)
5121 {
5122 return false;
5123 }
5124
5125 armnn::IConnectableLayer* const startLayer = data.m_Network->AddSubtractionLayer();
5126
5127 bool isReshapeSupported = BroadcastTensor(input0, input1, startLayer, data);
5128 if (!isReshapeSupported)
5129 {
5130 return false;
5131 }
5132 return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
5133 data, nullptr, validateFunc, activationFunction);
5134}
5135
5136bool Converter::ConvertTanH(const Operation& operation, const Model& model, ConversionData& data)
5137{
5138 VLOG(DRIVER) << "Converter::ConvertTanH()";
5139
5140 armnn::ActivationDescriptor desc;
5141 desc.m_Function = armnn::ActivationFunction::TanH;
5142 desc.m_A = 1.0f; // android nn does not support tanH parameters
5143 desc.m_B = 1.0f; // set to 1.0f for unity scaling
5144
5145 return ConvertToActivation(operation, __func__, desc, model, data);
5146}
5147
5148bool Converter::ConvertTransposeConv2d(const Operation& operation, const Model& model, ConversionData& data)
5149{
5150 VLOG(DRIVER) << "Converter::ConvertTransposeConv2d()";
5151
5152 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5153
5154 if (!input.IsValid())
5155 {
5156 return Fail("%s: Operation has invalid inputs", __func__);
5157 }
5158
5159 const Operand* output = GetOutputOperand(operation, 0, model);
5160
5161 if (!output)
5162 {
5163 return Fail("%s: Could not read output 0", __func__);
5164 }
5165
5166 const TensorInfo& inputInfo = input.GetTensorInfo();
5167 const TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5168
5169 // ArmNN does not currently support non-fixed weights or bias
5170 // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
5171 const Operand* weightsOperand = GetInputOperand(operation, 1, model);
5172
5173 if (weightsOperand == nullptr)
5174 {
5175 return Fail("%s: Operand is invalid", __func__);
5176 }
5177 TransposeConvolution2dDescriptor desc;
5178 desc.m_DataLayout = DataLayout::NHWC;
5179
5180 // Determine whether padding is implicit or explicit
5181 bool implicitPadding = operation.inputs.size() == 9;
5182
5183 if (implicitPadding )
5184 {
5185 desc.m_DataLayout = OptionalDataLayout(operation, 8, model, data);
5186 }
5187 else
5188 {
5189 desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data);
5190 }
5191
5192 armnnUtils::DataLayoutIndexed dataLayoutIndexed(desc.m_DataLayout);
5193 unsigned int widthIndex = dataLayoutIndexed.GetWidthIndex();
5194 unsigned int heightIndex = dataLayoutIndexed.GetHeightIndex();
5195
5196 const PermutationVector OHWIToOIHW = {0, 2, 3, 1};
5197
5198 // The shape of the weight is [depth_out, filter_height, filter_width, depth_in].
5199 // We have to permute it to OIHW if the data layout is NCHW.
5200 const ConstTensorPin weightsPin = (desc.m_DataLayout == DataLayout::NCHW) ?
5201 ConvertOperationInputToConstTensorPin(operation, 1,
5202 model, data, OHWIToOIHW) :
5203 ConvertOperationInputToConstTensorPin(operation, 1, model, data);
5204
5205 // Bias is a 1D tensor
5206 const ConstTensorPin biasPin =
5207 ConvertOperationInputToConstTensorPin(operation, 2, model, data);
5208
5209 if (!weightsPin.IsValid())
5210 {
5211 return Fail("%s: Operation has invalid weights", __func__);
5212 }
5213
5214 if (!biasPin.IsValid())
5215 {
5216 return Fail("%s: Operation has invalid biases", __func__);
5217 }
5218
5219 ConstTensor weights = weightsPin.GetConstTensor();
5220 ConstTensor bias = biasPin.GetConstTensor();
5221 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), inputInfo);
5222
5223 ActivationFn activation;
5224
5225 if (implicitPadding)
5226 {
5227 int32_t strideX{0};
5228 int32_t strideY{0};
5229 int32_t padLeft{0};
5230 int32_t padRight{0};
5231 int32_t padTop{0};
5232 int32_t padBottom{0};
5233
5234 ::android::nn::PaddingScheme paddingScheme;
5235 if (!GetInputPaddingScheme(operation, 4, paddingScheme, model, data) ||
5236 !GetInputScalar(operation, 5, OperandType::INT32, strideX, model, data) ||
5237 !GetInputScalar(operation, 6, OperandType::INT32, strideY, model, data) ||
5238 !GetInputActivationFunction(operation, 7, activation, model, data))
5239 {
5240 return Fail("%s: Operation has invalid inputs (implicit padding)", __func__);
5241 }
5242
5243 const uint32_t kernelX = weights.GetShape()[widthIndex];
5244 const uint32_t kernelY = weights.GetShape()[heightIndex];
5245
5246 // If output shape has been specified as a parameter then extract it and make it available.
5247 const Operand* outputShapeOperand = GetInputOperand(operation, 3, model, false);
5248 std::vector<int32_t> outputShape;
5249 if ((outputShapeOperand) && (GetTensorInt32Values(*outputShapeOperand, outputShape, model, data)))
5250 {
5251 // Change from signed to unsigned int to store in TransposeConvolution2dDescriptor.
5252 for (int dimension : outputShape)
5253 {
5254 desc.m_OutputShape.push_back(static_cast<unsigned int>(dimension));
5255 }
5256 desc.m_OutputShapeEnabled = true;
5257 }
5258
5259 uint32_t outputX;
5260 uint32_t outputY;
5261
5262 if (IsDynamicTensor(outputInfo))
5263 {
5264 if (outputShape.size() == 0)
5265 {
5266 return Fail("%s: Padding sizes cannot be inferred", __func__);
5267 }
5268
5269 outputX = outputShape[widthIndex];
5270 outputY = outputShape[heightIndex];
5271 }
5272 else
5273 {
5274 outputX = outputInfo.GetShape()[widthIndex];
5275 outputY = outputInfo.GetShape()[heightIndex];
5276 }
5277
5278 CalcPaddingTransposeConv(outputX, kernelX, strideX, padLeft, padRight, paddingScheme);
5279 CalcPaddingTransposeConv(outputY, kernelY, strideY, padTop, padBottom, paddingScheme);
5280
5281 // NOTE: The Android NN API allows for negative padding values in TransposeConv2d,
5282 // but Arm NN only supports values >= 0
5283 if (padLeft < 0 || padRight < 0 || padTop < 0 || padBottom < 0)
5284 {
5285 return Fail("%s: Negative padding values are not supported", __func__);
5286 }
5287
5288 desc.m_StrideX = armnn::numeric_cast<uint32_t>(strideX);
5289 desc.m_StrideY = armnn::numeric_cast<uint32_t>(strideY);
5290 desc.m_PadLeft = armnn::numeric_cast<uint32_t>(padLeft);
5291 desc.m_PadRight = armnn::numeric_cast<uint32_t>(padRight);
5292 desc.m_PadTop = armnn::numeric_cast<uint32_t>(padTop);
5293 desc.m_PadBottom = armnn::numeric_cast<uint32_t>(padBottom);
5294 }
5295 else if (operation.inputs.size() == 11)
5296 {
5297 // explicit padding
5298 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data) ||
5299 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data) ||
5300 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data) ||
5301 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data) ||
5302 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data) ||
5303 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data) ||
5304 !GetInputActivationFunction(operation, 9, activation, model, data))
5305 {
5306 return Fail("%s: Operation has invalid inputs (explicit padding)", __func__);
5307 }
5308 }
5309 else
5310 {
5311 return Fail("%s: Unsupported number of operation inputs", __func__);
5312 }
5313
5314 desc.m_BiasEnabled = true;
5315 Optional<TensorInfo> biases(bias.GetInfo());
5316
5317 bool isSupported = false;
5318 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5319 {
5320 FORWARD_LAYER_SUPPORT_FUNC(__func__,
5321 IsTransposeConvolution2dSupported,
5322 data.m_Backends,
5323 isSupported,
5324 inputInfo,
5325 outputInfo,
5326 desc,
5327 weights.GetInfo(),
5328 biases);
5329 };
5330
5331 if(IsDynamicTensor(outputInfo))
5332 {
5333 isSupported = AreDynamicTensorsSupported();
5334 }
5335 else
5336 {
5337 validateFunc(outputInfo, isSupported);
5338 }
5339 if (!isSupported)
5340 {
5341 return false;
5342 }
5343
5344 IConnectableLayer* startLayer =
5345 data.m_Network->AddTransposeConvolution2dLayer(desc, weights, Optional<ConstTensor>(bias));
5346 if (!startLayer)
5347 {
5348 return Fail("%s: AddTransposeConvolution2dLayer failed", __func__);
5349 }
5350
5351 input.Connect(startLayer->GetInputSlot(0));
5352
5353 return SetupAndTrackLayerOutputSlot(operation, 0, *startLayer, model,
5354 data, nullptr, validateFunc, activation);
5355}
5356
5357bool Converter::ConvertSqrt(const Operation& operation, const Model& model, ConversionData& data)
5358{
5359 VLOG(DRIVER) << "Converter::ConvertSqrt()";
5360 ActivationDescriptor desc;
5361 desc.m_Function = ActivationFunction::Sqrt;
5362
5363 return ::ConvertToActivation(operation, __func__, desc, model, data);
5364}
5365
5366bool Converter::ConvertSqueeze(const Operation& operation, const Model& model, ConversionData& data)
5367{
5368 VLOG(DRIVER) << "Converter::ConvertSqueeze()";
5369
5370 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5371 if (!input.IsValid())
5372 {
5373 return Fail("%s: Operation has invalid inputs", __func__);
5374 }
5375
5376 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
5377 unsigned int rank = inputInfo.GetNumDimensions();
5378 if (rank > 4)
5379 {
5380 Fail("%s: Inputs with rank greater than 4 are not supported", __func__);
5381 }
5382
5383 const Operand* output = GetOutputOperand(operation, 0, model);
5384 if (!output)
5385 {
5386 return Fail("%s: Could not read output 0", __func__);
5387 }
5388
5389 if (IsDynamicTensor(GetTensorInfoForOperand(*output)) && !(AreDynamicTensorsSupported()))
5390 {
5391 return Fail("%s: Dynamic output tensors are not supported", __func__);
5392 }
5393
5394 // NOTE: Axis is an optional parameter to SQUEEZE, therefore we do not want to generate a failure
5395 // if the operand index is out of bounds.
5396 const Operand* axisOperand = GetInputOperand(operation, 1, model, false);
5397
5398 const uint32_t dimensionSequence[] = { 0, 1, 2, 3 };
5399
5400 std::vector<int32_t> axis;
5401 if (!axisOperand)
5402 {
5403 axis.assign(dimensionSequence,
5404 dimensionSequence + rank);
5405 }
5406 else if (!GetTensorInt32Values(*axisOperand, axis, model, data))
5407 {
5408 return Fail("%s: Operation has an invalid or unsupported axis operand", __func__);
5409 }
5410
5411 std::vector<uint32_t> outputDims;
5412 for (unsigned int i = 0; i < rank; i++)
5413 {
5414 bool skipSqueeze = (std::find(axis.begin(), axis.end(), i) == axis.end());
5415 auto currentDimension = inputInfo.GetShape()[i];
5416 if (skipSqueeze || currentDimension != 1)
5417 {
5418 outputDims.push_back(currentDimension);
5419 }
5420 }
5421
5422 armnn::TensorShape outShape = armnn::TensorShape(outputDims.size(), outputDims.data());
5423
5424 armnn::TensorInfo outputInfo = inputInfo;
5425 outputInfo.SetShape(outShape);
5426
5427 armnn::ReshapeDescriptor reshapeDesc;
5428 reshapeDesc.m_TargetShape = outputInfo.GetShape();
5429
5430 bool isSupported = false;
5431 FORWARD_LAYER_SUPPORT_FUNC(__func__,
5432 IsReshapeSupported,
5433 data.m_Backends,
5434 isSupported,
5435 inputInfo,
5436 outputInfo,
5437 reshapeDesc);
5438
5439 if (!isSupported)
5440 {
5441 return false;
5442 }
5443
5444 armnn::IConnectableLayer* const layer = data.m_Network->AddReshapeLayer(reshapeDesc);
5445 assert(layer != nullptr);
5446 input.Connect(layer->GetInputSlot(0));
5447
5448 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
5449}
5450
5451bool Converter::ConvertStridedSlice(const Operation& operation, const Model& model, ConversionData& data)
5452{
5453 VLOG(DRIVER) << "Converter::ConvertStridedSlice()";
5454
5455 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5456 if (!input.IsValid())
5457 {
5458 return Fail("%s: Operation has invalid inputs", __func__);
5459 }
5460
5461 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
5462 unsigned int rank = inputInfo.GetNumDimensions();
5463 if (rank > 4)
5464 {
5465 Fail("%s: Inputs with rank greater than 4 are not supported", __func__);
5466 }
5467
5468 const Operand* output = GetOutputOperand(operation, 0, model);
5469 if (!output)
5470 {
5471 return Fail("%s: Could not read output 0", __func__);
5472 }
5473
5474 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5475
5476 const Operand* beginOperand = GetInputOperand(operation, 1, model);
5477 const Operand* endOperand = GetInputOperand(operation, 2, model);
5478 const Operand* stridesOperand = GetInputOperand(operation, 3, model);
5479
5480 std::vector<int32_t> beginValues;
5481 std::vector<int32_t> endValues;
5482 std::vector<int32_t> stridesValues;
5483
5484 // The length of the beginOperand, endOperand and stridesOperand must be of a rank(input)
5485 auto ValidateInputOperands = [&] (const Operand& operand, std::vector<int32_t>& operandValues)
5486 {
5487 if (!GetTensorInt32Values(operand, operandValues, model, data))
5488 {
5489 return false;
5490 }
5491
5492 if (operandValues.size() != rank)
5493 {
5494 return false;
5495 }
5496
5497 return true;
5498 };
5499
5500 if (!ValidateInputOperands(*beginOperand, beginValues)
5501 || !ValidateInputOperands(*endOperand, endValues)
5502 || !ValidateInputOperands(*stridesOperand, stridesValues))
5503 {
5504 return Fail("%s: Operation has invalid input operand", __func__);
5505 }
5506
5507 // Stride cannot have value '0'
5508 if (std::any_of(stridesValues.cbegin(), stridesValues.cend(), [](int32_t i){ return i == 0; }))
5509 {
5510 return Fail("%s: Stride must be non-zero value.", __func__);
5511 }
5512
5513 armnn::StridedSliceDescriptor descriptor;
5514 descriptor.m_Begin.assign(beginValues.cbegin(), beginValues.cend());
5515 descriptor.m_End.assign(endValues.cbegin(), endValues.cend());
5516 descriptor.m_Stride.assign(stridesValues.cbegin(), stridesValues.cend());
5517 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
5518
5519 // Get the "begin_mask", "end_mask", and "shrink_axis_mask" flags
5520 if (!GetInputInt32(operation, 4, descriptor.m_BeginMask, model, data) ||
5521 !GetInputInt32(operation, 5, descriptor.m_EndMask, model, data) ||
5522 !GetInputInt32(operation, 6, descriptor.m_ShrinkAxisMask, model, data))
5523 {
5524 return Fail("%s: Operation has invalid inputs", __func__);
5525 }
5526
5527 bool isSupported = false;
5528 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5529 {
5530 FORWARD_LAYER_SUPPORT_FUNC(__func__,
5531 IsStridedSliceSupported,
5532 data.m_Backends,
5533 isSupported,
5534 inputInfo,
5535 outputInfo,
5536 descriptor);
5537 };
5538
5539 if(IsDynamicTensor(outputInfo))
5540 {
5541 isSupported = AreDynamicTensorsSupported();
5542 }
5543 else
5544 {
5545 validateFunc(outputInfo, isSupported);
5546 }
5547
5548 if (!isSupported)
5549 {
5550 return false;
5551 }
5552
5553 // Check if slice can fit in a inferred output
5554 armnn::TensorShape inputShape = inputInfo.GetShape();
5555 for (unsigned int i = 0; i < inputShape.GetNumDimensions(); i++)
5556 {
5557 int stride = descriptor.m_Stride[i];
5558
5559 if (descriptor.m_ShrinkAxisMask & (1 << i))
5560 {
5561 // If the difference between the start point and the end point of the slice on an axis being shrunk
5562 // is greater than 1 then throw an error as the output will not be large enough to hold the slice
5563 if (((descriptor.m_Begin[i] - descriptor.m_End[i]) > 1)
5564 || ((descriptor.m_Begin[i] - descriptor.m_End[i]) < -1))
5565 {
5566 return Fail("%s: StridedSlice: Output will not be large enough to hold the slice", __func__);
5567 }
5568
5569 if(stride < 0)
5570 {
5571 return Fail("%s: StridedSlice: Stride can not be negative while ShrinkAxisMask is set.", __func__);
5572 }
5573 }
5574 }
5575
5576 armnn::IConnectableLayer* const layer = data.m_Network->AddStridedSliceLayer(descriptor);
5577 assert(layer != nullptr);
5578 input.Connect(layer->GetInputSlot(0));
5579
5580 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5581}
5582
5583bool Converter::ConvertTranspose(const Operation& operation, const Model& model, ConversionData& data)
5584{
5585 VLOG(DRIVER) << "Converter::ConvertTranspose()";
5586
5587 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
5588 if (!input.IsValid())
5589 {
5590 return Fail("%s: Operation has invalid inputs", __func__);
5591 }
5592
5593 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
5594 unsigned int rank = inputInfo.GetNumDimensions();
5595 if (rank > 4)
5596 {
5597 Fail("%s: Inputs with rank greater than 4 are not supported", __func__);
5598 }
5599
5600 // NOTE: Axis is an optional parameter to TRANSPOSE, therefore we do not want to generate a failure
5601 // if the operand index is out of bounds.
5602 const Operand* permOperand = GetInputOperand(operation, 1, model, false);
5603
5604 std::vector<int32_t> perm(rank);
5605 if (!permOperand || (permOperand->lifetime == OperandLifeTime::NO_VALUE))
5606 {
5607 for (unsigned int i = rank; i > 0; i--)
5608 {
5609 perm[rank - i] = armnn::numeric_cast<int> (i - 1);
5610 }
5611 }
5612 else if (!GetTensorInt32Values(*permOperand, perm, model, data))
5613 {
5614 return Fail("%s: Operation has an invalid or unsupported permutation operand", __func__);
5615 }
5616
5617 std::vector<uint32_t> outputDims(perm.begin(), perm.begin() + rank);
5618
5619 armnn::TransposeDescriptor transposeDesc;
5620 transposeDesc.m_DimMappings = armnn::PermutationVector(outputDims.data(), outputDims.size());
5621
5622 const Operand* output = GetOutputOperand(operation, 0, model);
5623 if (!output)
5624 {
5625 return Fail("%s: Could not read output 0", __func__);
5626 }
5627
5628 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
5629
5630 bool isSupported = false;
5631 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
5632 {
5633 FORWARD_LAYER_SUPPORT_FUNC(__func__,
5634 IsTransposeSupported,
5635 data.m_Backends,
5636 isSupported,
5637 inputInfo,
5638 outputInfo,
5639 transposeDesc);
5640 };
5641
5642 if(IsDynamicTensor(outputInfo))
5643 {
5644 isSupported = AreDynamicTensorsSupported();
5645 }
5646 else
5647 {
5648 validateFunc(outputInfo, isSupported);
5649 }
5650
5651 if (!isSupported)
5652 {
5653 return false;
5654 }
5655
5656 armnn::IConnectableLayer* const layer = data.m_Network->AddTransposeLayer(transposeDesc);
5657 assert(layer != nullptr);
5658 input.Connect(layer->GetInputSlot(0));
5659
5660 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
5661}
5662
5663} // namespace armnn_driver