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