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