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