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