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