blob: 8c31a92ff2e4fc6596ffbd33b7a0947a8c631799 [file] [log] [blame]
Sadik Armagan8f397a12022-06-17 15:38:22 +01001//
Kevin May7fbf8102023-08-23 10:07:26 +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 "ConversionUtils.hpp"
7#include <armnnUtils/Permute.hpp>
8
9///
10/// Helper classes
11///
12
13namespace armnn_driver
14{
15
16LayerInputHandle::LayerInputHandle()
17 : m_OutputSlot(nullptr)
18 , m_Valid(false)
19{}
20
21LayerInputHandle::LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo)
22 : m_OutputSlot(outputSlot)
23 , m_Valid(valid)
24 , m_TensorInfo(tensorInfo)
25{}
26
27bool LayerInputHandle::IsValid() const
28{
29 return m_Valid;
30}
31
32void LayerInputHandle::Connect(armnn::IInputSlot& inputSlot)
33{
34 ARMNN_ASSERT(IsValid());
35 if (m_OutputSlot)
36 {
37 m_OutputSlot->Connect(inputSlot);
38 }
39}
40
41void LayerInputHandle::Disconnect(armnn::IInputSlot& inputSlot)
42{
43 ARMNN_ASSERT(IsValid());
44 if (m_OutputSlot)
45 {
46 m_OutputSlot->Disconnect(inputSlot);
47 }
48}
49
50const armnn::TensorInfo& LayerInputHandle::GetTensorInfo() const
51{
52 return m_TensorInfo;
53}
54
55void LayerInputHandle::SanitizeQuantizationScale(LayerInputHandle& weight, LayerInputHandle& input)
56{
57 if (m_OutputSlot)
58 {
59 armnn::TensorInfo weightInfo = weight.GetTensorInfo();
60 armnn::TensorInfo inputInfo = input.GetTensorInfo();
61 armnn::TensorInfo biasInfo = GetTensorInfo();
62
63 SanitizeBiasQuantizationScale(biasInfo, weightInfo, inputInfo);
64
65 m_TensorInfo = biasInfo;
66 m_OutputSlot->SetTensorInfo(biasInfo);
67 }
68}
69
Sadik Armaganb0161572022-08-03 11:27:05 +010070armnn::IOutputSlot* LayerInputHandle::GetOutputSlot() const
71{
72 return m_OutputSlot;
73}
74
Sadik Armagan8f397a12022-06-17 15:38:22 +010075ConstTensorPin::ConstTensorPin(bool optional)
76 : m_Optional(optional)
77{}
78
79ConstTensorPin::ConstTensorPin(armnn::TensorInfo& tensorInfo,
80 const void* valueStart,
81 uint32_t numBytes,
82 const armnn::PermutationVector& mappings)
83 : m_Optional(false)
84{
85 armnn::IgnoreUnused(numBytes);
86 if (tensorInfo.GetNumBytes() != numBytes)
87 {
88 VLOG(DRIVER) << "The size of ConstTensor does not match its TensorInfo.";
89 }
90
91 const bool needsSwizzling = (mappings.GetSize() > 0);
92 if (needsSwizzling)
93 {
94 m_SwizzledTensorData.resize(tensorInfo.GetNumBytes());
95 SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings);
96
97 m_ConstTensor = armnn::ConstTensor(tensorInfo, m_SwizzledTensorData.data());
98 }
99 else
100 {
101 m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart);
102 }
103}
104
105bool ConstTensorPin::IsValid() const
106{
107 return m_ConstTensor.GetMemoryArea() != nullptr;
108}
109
110bool ConstTensorPin::IsOptional() const
111{
112 return m_Optional;
113}
114
115const armnn::ConstTensor& ConstTensorPin::GetConstTensor() const
116{
117 return m_ConstTensor;
118}
119
120const armnn::ConstTensor* ConstTensorPin::GetConstTensorPtr() const
121{
122 if (IsValid() && m_ConstTensor.GetNumElements() > 0)
123 {
124 return &m_ConstTensor;
125 }
126 // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided)
127 return nullptr;
128}
129
130///
131/// Utility functions
132///
133
134bool IsWeightsValid(const Operation& operation,
135 uint32_t inputIndex,
Kevin May7fbf8102023-08-23 10:07:26 +0100136 const Model& model,
137 const bool isOptional = true)
Sadik Armagan8f397a12022-06-17 15:38:22 +0100138{
139 const Operand* operand = GetInputOperand(operation, inputIndex, model);
140 if (!operand)
141 {
142 Fail("%s: failed to get input operand %i", __func__, inputIndex);
143 return false;
144 }
Kevin May7fbf8102023-08-23 10:07:26 +0100145 // If the operand is not an optional operand it cannot have a NO_VALUE lifetime
146 if (!isOptional && operand->lifetime == OperandLifeTime::NO_VALUE)
147 {
148 return false;
149 }
Sadik Armagan8f397a12022-06-17 15:38:22 +0100150 if (operand->lifetime != OperandLifeTime::CONSTANT_COPY
151 && operand->lifetime != OperandLifeTime::CONSTANT_REFERENCE
152 && operand->lifetime != OperandLifeTime::NO_VALUE)
153 {
154 return false;
155 }
156 return true;
157}
158
159ConstTensorPin ConvertOperandToConstTensorPin(const Operand& operand,
160 const Model& model,
161 const ConversionData& data,
162 const armnn::PermutationVector& dimensionMappings,
163 const armnn::TensorShape* overrideTensorShape,
Sadik Armagan1e276f32022-07-19 12:37:20 +0100164 bool optional,
165 const armnn::DataType* overrideDataType)
Sadik Armagan8f397a12022-06-17 15:38:22 +0100166{
167 if (!IsOperandTypeSupportedForTensors(operand.type))
168 {
169 VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor" << operand.type;
170 return ConstTensorPin();
171 }
172
173 if (!optional && !IsOperandConstant(operand))
174 {
175 VLOG(DRIVER) << __func__ << ": lifetime for input tensor: r" << operand.lifetime;
176 return ConstTensorPin();
177 }
178
179 const void* const valueStart = GetOperandValueReadOnlyAddress(operand, model, data, optional);
180 if (!valueStart)
181 {
182 if (optional)
183 {
184 // optional tensor with no values is not really an error; return it as invalid, but marked as optional
185 return ConstTensorPin(true);
186 }
187 // mandatory tensor with no values
188 Fail("%s: failed to get operand address", __func__);
189 return ConstTensorPin();
190 }
191
192 armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
193
Sadik Armagan1e276f32022-07-19 12:37:20 +0100194 if (overrideTensorShape)
Sadik Armagan8f397a12022-06-17 15:38:22 +0100195 {
196 tensorInfo.SetShape(*overrideTensorShape);
197 }
Sadik Armagan1e276f32022-07-19 12:37:20 +0100198
199 if (overrideDataType)
200 {
201 tensorInfo.SetDataType(*overrideDataType);
202 }
203
204 // Make sure isConstant flag is set.
205 tensorInfo.SetConstant();
Sadik Armagan8f397a12022-06-17 15:38:22 +0100206 return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
207}
208
209LayerInputHandle ConvertToLayerInputHandle(const Operation& operation,
210 uint32_t inputIndex,
211 const Model& model,
212 ConversionData& data,
Sadik Armagan1e276f32022-07-19 12:37:20 +0100213 const armnn::PermutationVector& dimensionMappings,
214 const LayerInputHandle* inputHandle)
Sadik Armagan8f397a12022-06-17 15:38:22 +0100215{
216
217 const Operand* operand = GetInputOperand(operation, inputIndex, model);
218 if (!operand)
219 {
220 Fail("%s: failed to get input operand %i", __func__, inputIndex);
221 return LayerInputHandle();
222 }
223
224 if (!IsOperandTypeSupportedForTensors(operand->type))
225 {
226 VLOG(DRIVER) << __func__ << ": unsupported operand type for tensor: " << operand->type;
227 return LayerInputHandle();
228 }
229
230 try
231 {
232 armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
233
234 if (IsDynamicTensor(operandTensorInfo))
235 {
236 data.m_DynamicInputsEncountered = true;
237
238 const uint32_t operandIndex = operation.inputs[inputIndex];
239
240 // Check if the dynamic input tensors have been inferred by one of the previous layers
241 // If not we can't support them
242 if (data.m_OutputSlotForOperand.size() >= operandIndex && data.m_OutputSlotForOperand[operandIndex])
243 {
244 operandTensorInfo = data.m_OutputSlotForOperand[operandIndex]->GetTensorInfo();
245 }
246 else
247 {
248 Fail("%s: Type 2 dynamic input tensors are not supported", __func__);
249 return LayerInputHandle();
250 }
251 }
252
253 switch (operand->lifetime)
254 {
255 case OperandLifeTime::SUBGRAPH_INPUT:
256 {
257 // NOTE: We must check whether we can support the input tensor on at least one
258 // of the provided backends; otherwise we cannot convert the operation
259 bool isInputSupported = false;
260 FORWARD_LAYER_SUPPORT_FUNC(__func__,
261 IsInputSupported,
262 data.m_Backends,
263 isInputSupported,
Cathal Corbett53837672022-09-01 11:34:37 +0100264 armnn::BackendId(),
Sadik Armagan8f397a12022-06-17 15:38:22 +0100265 operandTensorInfo);
266
267 if (!isInputSupported)
268 {
269 Fail("%s: unsupported input tensor", __func__);
270 return LayerInputHandle();
271 }
272
273 [[clang::fallthrough]]; // intentional fallthrough
274 }
275 case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
276 case OperandLifeTime::SUBGRAPH_OUTPUT:
277 {
278 // The tensor is either an operand internal to the model, or a model input.
279 // It can be associated with an ArmNN output slot for an existing layer.
280
281 // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
282 const uint32_t operandIndex = operation.inputs[inputIndex];
283 return LayerInputHandle(true, data.m_OutputSlotForOperand[operandIndex], operandTensorInfo);
284 }
285 case OperandLifeTime::CONSTANT_COPY: // intentional fallthrough
286 case OperandLifeTime::POINTER:
287 case OperandLifeTime::CONSTANT_REFERENCE:
288 {
Sadik Armagan1e276f32022-07-19 12:37:20 +0100289 auto constantTensorDataType = operandTensorInfo.GetDataType();
Sadik Armagan8f397a12022-06-17 15:38:22 +0100290 // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
Sadik Armagan1e276f32022-07-19 12:37:20 +0100291 ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand,
292 model,
293 data,
294 dimensionMappings,
295 nullptr,
296 false,
297 &constantTensorDataType);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100298 if (tensorPin.IsValid())
299 {
300 bool isSupported = false;
Cathal Corbett53837672022-09-01 11:34:37 +0100301 armnn::BackendId setBackend;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100302 FORWARD_LAYER_SUPPORT_FUNC(__func__,
303 IsConstantSupported,
304 data.m_Backends,
305 isSupported,
Cathal Corbett53837672022-09-01 11:34:37 +0100306 setBackend,
Sadik Armagan8f397a12022-06-17 15:38:22 +0100307 tensorPin.GetConstTensor().GetInfo());
308 if (!isSupported)
309 {
310 return LayerInputHandle();
311 }
312
313 armnn::IConnectableLayer* constantLayer =
314 data.m_Network->AddConstantLayer(tensorPin.GetConstTensor());
Cathal Corbett53837672022-09-01 11:34:37 +0100315 constantLayer->SetBackendId(setBackend);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100316 armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
Sadik Armaganb9570c12022-06-21 11:39:41 +0100317 armnn::TensorInfo constantTensorInfo = tensorPin.GetConstTensor().GetInfo();
318 outputSlot.SetTensorInfo(constantTensorInfo);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100319
Sadik Armaganb9570c12022-06-21 11:39:41 +0100320 return LayerInputHandle(true, &outputSlot, constantTensorInfo);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100321 }
322 else
323 {
324 Fail("%s: invalid operand tensor", __func__);
325 return LayerInputHandle();
326 }
327 break;
328 }
329 default:
330 {
331 VLOG(DRIVER) << __func__ << ": unsupported lifetime for input tensor: " << operand->lifetime;
332 return LayerInputHandle();
333 }
334 }
335 }
336 catch (UnsupportedOperand<OperandType>& e)
337 {
338 VLOG(DRIVER) << __func__ << ": Operand type: " << e.m_type << " not supported in ArmnnDriver";
339 return LayerInputHandle();
340 }
341}
342
343bool ConvertPaddings(const Operation& operation,
344 const Model& model,
345 ConversionData& data,
346 unsigned int rank,
347 armnn::PadDescriptor& padDescriptor)
348{
349 const Operand* paddingsOperand = GetInputOperand(operation, 1, model);
350 if (!paddingsOperand)
351 {
352 return Fail("%s: Could not read paddings operand", __func__);
353 }
354
355 armnn::TensorShape paddingsOperandShape = GetTensorShapeForOperand(*paddingsOperand);
356 if (paddingsOperandShape.GetNumDimensions() != 2 || paddingsOperandShape.GetNumElements() != rank * 2)
357 {
358 return Fail("%s: Operation has invalid paddings operand: expected shape [%d, 2]", __func__, rank);
359 }
360
361 std::vector<int32_t> paddings;
362 if (!GetTensorInt32Values(*paddingsOperand, paddings, model, data))
363 {
364 return Fail("%s: Operation has invalid or unsupported paddings operand", __func__);
365 }
366
367 // add padding for each dimension of input tensor.
368 for (unsigned int i = 0; i < paddings.size() - 1; i += 2)
369 {
370 int paddingBeforeInput = paddings[i];
371 int paddingAfterInput = paddings[i + 1];
372
373 if (paddingBeforeInput < 0 || paddingAfterInput < 0)
374 {
375 return Fail("%s: Operation has invalid paddings operand, invalid padding values.", __func__);
376 }
377
378 padDescriptor.m_PadList.emplace_back((unsigned int) paddingBeforeInput, (unsigned int) paddingAfterInput);
379 }
380
381 return true;
382}
383
384
385bool ConvertPooling2d(const Operation& operation,
386 const char* operationName,
387 armnn::PoolingAlgorithm poolType,
388 const Model& model,
389 ConversionData& data)
390{
391
392 VLOG(DRIVER) << "Converter::ConvertL2Pool2d()";
393
394 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
395 if (!input.IsValid())
396 {
397 return Fail("%s: Operation Could not read input 0", operationName);
398 }
399
400 const Operand* output = GetOutputOperand(operation, 0, model);
401 if (!output)
402 {
403 return Fail("%s: Could not read output 0", __func__);
404 }
405
406 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
407 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
408
409 armnn::Pooling2dDescriptor desc;
410 desc.m_PoolType = poolType;
411 desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor;
412 desc.m_DataLayout = armnn::DataLayout::NHWC;
413
414 ActivationFn activation;
415
416 auto inputSize = operation.inputs.size();
417
418 if (inputSize >= 10)
419 {
420 // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
421 if (!GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft, model, data) ||
422 !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight, model, data) ||
423 !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop, model, data) ||
424 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom, model, data) ||
425 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX, model, data) ||
426 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY, model, data) ||
427 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth, model, data) ||
428 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight, model, data) ||
429 !GetInputActivationFunction(operation, 9, activation, model, data))
430 {
431 return Fail("%s: Operation has invalid inputs", operationName);
432 }
433
434 if (Is12OrLaterOperand(*output))
435 {
436 desc.m_DataLayout = OptionalDataLayout(operation, 10, model, data);
437 }
438 }
439 else
440 {
441 // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
442 ::android::nn::PaddingScheme scheme;
443 if (!GetInputPaddingScheme(operation, 1, scheme, model, data) ||
444 !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX, model, data) ||
445 !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY, model, data) ||
446 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth, model, data) ||
447 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight, model, data) ||
448 !GetInputActivationFunction(operation, 6, activation, model, data))
449 {
450 return Fail("%s: Operation has invalid inputs", operationName);
451 }
452
453 if (Is12OrLaterOperand(*output))
454 {
455 desc.m_DataLayout = OptionalDataLayout(operation, 7, model, data);
456 }
457
458 const armnnUtils::DataLayoutIndexed dataLayout(desc.m_DataLayout);
459 const unsigned int inputWidth = inputInfo.GetShape()[dataLayout.GetWidthIndex()];
460 const unsigned int inputHeight = inputInfo.GetShape()[dataLayout.GetHeightIndex()];
461
462 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
463 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
464 }
465
466 bool isSupported = false;
Cathal Corbett53837672022-09-01 11:34:37 +0100467 armnn::BackendId setBackend;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100468 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
469 {
470 FORWARD_LAYER_SUPPORT_FUNC(__func__,
471 IsPooling2dSupported,
472 data.m_Backends,
473 isSupported,
Cathal Corbett53837672022-09-01 11:34:37 +0100474 setBackend,
Sadik Armagan8f397a12022-06-17 15:38:22 +0100475 inputInfo,
476 outputInfo,
477 desc);
478
479 };
480
481 if(IsDynamicTensor(outputInfo))
482 {
483 isSupported = AreDynamicTensorsSupported();
484 }
485 else
486 {
487 validateFunc(outputInfo, isSupported);
488 }
489
490 if (!isSupported)
491 {
492 return false;
493 }
494
495 armnn::IConnectableLayer* pooling2dLayer = data.m_Network->AddPooling2dLayer(desc);
Cathal Corbett53837672022-09-01 11:34:37 +0100496 pooling2dLayer->SetBackendId(setBackend);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100497 if (!pooling2dLayer)
498 {
499 return Fail("%s: AddPooling2dLayer failed", __func__);
500 }
501
502 input.Connect(pooling2dLayer->GetInputSlot(0));
503
504 if (!isSupported)
505 {
506 return false;
507 }
508
509 return SetupAndTrackLayerOutputSlot(operation, 0, *pooling2dLayer, model,
510 data, nullptr, validateFunc, activation);
511}
512
513bool ConvertReduce(const Operation& operation,
514 const Model& model,
515 ConversionData& data,
516 armnn::ReduceOperation reduceOperation)
517{
518 armnn::ReduceDescriptor descriptor;
519 descriptor.m_ReduceOperation = reduceOperation;
520
521 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
522 if (!input.IsValid())
523 {
524 return Fail("%s: Operation has invalid inputs", __func__);
525 }
526 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
527
528 const Operand* output = GetOutputOperand(operation, 0, model);
529 if (!output)
530 {
531 return Fail("%s: Could not read output 0", __func__);
532 }
533 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
534
535 const Operand* axisOperand = GetInputOperand(operation, 1, model);
536 if (!axisOperand)
537 {
538 return Fail("%s: Could not read input 1", __func__);
539 }
540 std::vector<int32_t> axis;
541 if (!GetTensorInt32Values(*axisOperand, axis, model, data))
542 {
543 return Fail("%s: Input 1 has invalid values", __func__);
544 }
545
546 // Convert the axis to unsigned int and remove duplicates.
547 unsigned int rank = inputInfo.GetNumDimensions();
548 std::set<unsigned int> uniqueAxis;
549 std::transform(axis.begin(), axis.end(),
550 std::inserter(uniqueAxis, uniqueAxis.begin()),
551 [rank](int i) -> unsigned int { return (i + rank) % rank; });
552 descriptor.m_vAxis.assign(uniqueAxis.begin(), uniqueAxis.end());
553
554 // Get the "keep dims" flag.
555 if (!GetInputScalar(operation, 2, OperandType::BOOL, descriptor.m_KeepDims, model, data))
556 {
557 return Fail("%s: Could not read input 2", __func__);
558 }
559
560 bool isSupported = false;
Cathal Corbett53837672022-09-01 11:34:37 +0100561 armnn::BackendId setBackend;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100562 auto validateFunc = [&](const armnn::TensorInfo& outputInfo, bool& isSupported)
563 {
564 FORWARD_LAYER_SUPPORT_FUNC(__func__,
565 IsReduceSupported,
566 data.m_Backends,
567 isSupported,
Cathal Corbett53837672022-09-01 11:34:37 +0100568 setBackend,
Sadik Armagan8f397a12022-06-17 15:38:22 +0100569 inputInfo,
570 outputInfo,
571 descriptor);
572 };
573
574 if(!IsDynamicTensor(outputInfo))
575 {
576 validateFunc(outputInfo, isSupported);
577 }
578 else
579 {
580 isSupported = AreDynamicTensorsSupported();
581 }
582
583 if (!isSupported)
584 {
585 return false;
586 }
587
588 armnn::IConnectableLayer* const layer = data.m_Network->AddReduceLayer(descriptor);
Cathal Corbett53837672022-09-01 11:34:37 +0100589 layer->SetBackendId(setBackend);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100590 assert(layer != nullptr);
591 input.Connect(layer->GetInputSlot(0));
592
593 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
594}
595
596
597bool ConvertToActivation(const Operation& operation,
598 const char* operationName,
599 const armnn::ActivationDescriptor& activationDesc,
600 const Model& model,
601 ConversionData& data)
602{
603 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
604 if (!input.IsValid())
605 {
606 return Fail("%s: Input 0 is invalid", operationName);
607 }
608
609 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
610 if (!outputOperand)
611 {
612 return false;
613 }
614
615 const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
616
617 bool isSupported = false;
Cathal Corbett53837672022-09-01 11:34:37 +0100618 armnn::BackendId setBackend;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100619 auto validateFunc = [&](const armnn::TensorInfo& outInfo, bool& isSupported)
620 {
621 FORWARD_LAYER_SUPPORT_FUNC(__func__,
622 IsActivationSupported,
623 data.m_Backends,
624 isSupported,
Cathal Corbett53837672022-09-01 11:34:37 +0100625 setBackend,
Sadik Armagan8f397a12022-06-17 15:38:22 +0100626 input.GetTensorInfo(),
627 outInfo,
628 activationDesc);
629 };
630
631 if(IsDynamicTensor(outInfo))
632 {
633 isSupported = AreDynamicTensorsSupported();
634 }
635 else
636 {
637 validateFunc(outInfo, isSupported);
638 }
639
640 if (!isSupported)
641 {
642 return false;
643 }
644
645 armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(activationDesc);
Cathal Corbett53837672022-09-01 11:34:37 +0100646 layer->SetBackendId(setBackend);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100647 ARMNN_ASSERT(layer != nullptr);
648 input.Connect(layer->GetInputSlot(0));
649
650 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data, nullptr, validateFunc);
651}
652
653DequantizeResult DequantizeIfRequired(size_t operand_index,
654 const Operation& operation,
655 const Model& model,
656 const ConversionData& data)
657{
658 const Operand* weightsOperand = GetInputOperand(operation, operand_index, model);
659 if (!weightsOperand)
660 {
661 return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::INVALID_OPERAND };
662 }
663
664 if (IsOperandConstant(*weightsOperand))
665 {
666 // Weights are already constant
667 return { nullptr, 0, armnn::TensorInfo(), DequantizeStatus::NOT_REQUIRED };
668 }
669
670 const size_t weightsInputIndex = operation.inputs[operand_index];
671
672 // The weights are a non const tensor, this indicates they might be the output of a dequantize op.
673 // Iterate over the nodes and find the previous operation which should be DEQUANTIZE
674 for (uint32_t operationIdx = 0; operationIdx < getMainModel(model).operations.size(); ++operationIdx)
675 {
676 // Search for the DEQUANTIZE op which has the operand with index equal to operandIndex
677 const auto& operationIt = getMainModel(model).operations[operationIdx];
678 if (operationIt.type != OperationType::DEQUANTIZE)
679 {
680 continue;
681 }
682
683 size_t outOpIndex = weightsInputIndex + 1;
684 for (size_t i = 0; outOpIndex != weightsInputIndex && i < operationIt.outputs.size(); ++i)
685 {
686 outOpIndex = operationIt.outputs[i];
687 }
688
689 if (outOpIndex != weightsInputIndex)
690 {
691 continue;
692 }
693
694 const Operand* operand = GetInputOperand(operationIt, 0, model);
695 ARMNN_ASSERT(operand);
696
697 if (!IsQSymm8(*operand))
698 {
699 // Only supporting dequantize from QSYMM8 to FLOAT
700 break;
701 }
702
703 // Allocate a new buffer for the dequantized data and manually dequantize
704 const void* startValue = GetOperandValueReadOnlyAddress(*operand, model, data);
705 if (!startValue)
706 {
707 // Failed to get the operand address
708 break;
709 }
710
711 const uint8_t* quantizedBuffer = reinterpret_cast<const uint8_t*>(startValue);
712 size_t dequantizedBufferLength = operand->location.length;
713 const float quantizationScale = operand->scale;
714
715 auto dequantizedBuffer = std::make_unique<float[]>(dequantizedBufferLength + 1);
716 for (size_t i = 0; i < dequantizedBufferLength; ++i)
717 {
718 float* dstPtr = dequantizedBuffer.get();
719 ARMNN_ASSERT(dstPtr);
720 *dstPtr++ = quantizedBuffer[i] * quantizationScale;
721 }
722
723 // Construct tensor info for dequantized ConstTensor
724 armnn::TensorInfo tensorInfo(operand->dimensions.size(),
725 operand->dimensions.data(),
726 armnn::DataType::Float32);
727
728 return { std::move(dequantizedBuffer), dequantizedBufferLength * sizeof(float),
729 std::move(tensorInfo),
730 DequantizeStatus::SUCCESS };
731 }
732
733 return { nullptr, 0, armnn::TensorInfo() , DequantizeStatus::NOT_REQUIRED};
734}
735
736ConstTensorPin DequantizeAndMakeConstTensorPin(const Operation& operation,
737 const Model& model,
738 const ConversionData& data,
739 size_t operandIndex,
740 bool optional)
741{
742 DequantizeResult dequantized = DequantizeIfRequired(operandIndex,operation, model, data);
743
744 DequantizeStatus status = std::get<3>(dequantized);
745 switch (status)
746 {
747 case DequantizeStatus::INVALID_OPERAND:
748 {
749 // return invalid const tensor pin
750 return ConstTensorPin();
751 }
752 case DequantizeStatus::NOT_REQUIRED:
753 {
754 return ConvertOperationInputToConstTensorPin(
755 operation, operandIndex, model, data, g_DontPermute, nullptr, optional);
756 }
757 case DequantizeStatus::SUCCESS:
758 default:
759 {
760 return ConstTensorPin(
761 std::get<2>(dequantized), std::get<0>(dequantized).get(), std::get<1>(dequantized), g_DontPermute);
762 }
763 }
764}
765
766bool GetInputPaddingScheme(const Operation& operation,
767 uint32_t inputIndex,
768 PaddingScheme& outPaddingScheme,
769 const Model& model,
770 const ConversionData& data)
771{
772 int32_t paddingSchemeAsInt;
773 if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt, model, data))
774 {
775 return Fail("%s: failed to get padding scheme input value", __func__);
776 }
777
778 outPaddingScheme = static_cast<::android::nn::PaddingScheme>(paddingSchemeAsInt);
779 return true;
780}
781
782const void* GetOperandValueReadOnlyAddress(const Operand& operand,
783 const Model& model,
784 const ConversionData& data,
785 bool optional)
786{
787 const void* valueStart = nullptr;
788 switch (operand.lifetime)
789 {
790 case OperandLifeTime::CONSTANT_COPY:
791 {
792 valueStart = model.operandValues.data() + operand.location.offset;
793 break;
794 }
795 case OperandLifeTime::POINTER:
796 {
797 // Pointer specified in the model
798 valueStart = std::get<const void*>(operand.location.pointer);
799 break;
800 }
801 case OperandLifeTime::CONSTANT_REFERENCE:
802 {
803 // Constant specified via a Memory object
804 valueStart = GetMemoryFromPool(operand.location, data.m_MemPools);
805 break;
806 }
807 case OperandLifeTime::NO_VALUE:
808 {
809 // An optional input tensor with no values is not an error so should not register as a fail
810 if (optional)
811 {
812 valueStart = nullptr;
813 break;
814 }
815 [[fallthrough]];
816 }
817 default:
818 {
819 VLOG(DRIVER) << __func__ << ": unsupported/invalid operand lifetime:: " << operand.lifetime;
820 valueStart = nullptr;
821 }
822 }
823
824 return valueStart;
825}
826
827bool GetTensorInt32Values(const Operand& operand,
828 std::vector<int32_t>& outValues,
829 const Model& model,
830 const ConversionData& data)
831{
832 if (operand.type != OperandType::TENSOR_INT32)
833 {
834 VLOG(DRIVER) << __func__ << ": invalid operand type: " << operand.type;
835 return false;
836 }
837
838 const void* startAddress = GetOperandValueReadOnlyAddress(operand, model, data);
839 if (!startAddress)
840 {
841 VLOG(DRIVER) << __func__ << ": failed to get operand address " << operand.type;
842 return false;
843 }
844
845 // Check number of bytes is sensible
846 const uint32_t numBytes = operand.location.length;
847 if (numBytes % sizeof(int32_t) != 0)
848 {
849 return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
850 __func__, numBytes, sizeof(int32_t));
851 }
852
853 outValues.resize(numBytes / sizeof(int32_t));
854 memcpy(outValues.data(), startAddress, numBytes);
855 return true;
856}
857
858armnn::DataLayout OptionalDataLayout(const Operation& operation,
859 uint32_t inputIndex,
860 const Model& model,
861 ConversionData& data)
862{
863 const Operand* operand = GetInputOperand(operation, inputIndex, model);
864 if (!operand)
865 {
866 return armnn::DataLayout::NHWC;
867 }
868
869 if (!IsBool(*operand))
870 {
871 return armnn::DataLayout::NHWC;
872 }
873
874 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
875 if (!valueAddress)
876 {
877 return armnn::DataLayout::NHWC;
878 }
879
880 if (*(static_cast<const bool*>(valueAddress)))
881 {
882 return armnn::DataLayout::NCHW;
883 }
884 else
885 {
886 return armnn::DataLayout::NHWC;
887 }
888}
889
890armnn::IConnectableLayer* ProcessActivation(const armnn::TensorInfo& tensorInfo,
891 ActivationFn activation,
892 armnn::IConnectableLayer* prevLayer,
893 ConversionData& data)
894{
895 ARMNN_ASSERT(prevLayer->GetNumOutputSlots() == 1);
896
897 prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
898
899 armnn::IConnectableLayer* activationLayer = prevLayer;
900
901 if (activation != ActivationFn::kActivationNone)
902 {
903 armnn::ActivationDescriptor activationDesc;
904 switch (activation)
905 {
906 case ActivationFn::kActivationRelu:
907 {
908 activationDesc.m_Function = armnn::ActivationFunction::ReLu;
909 break;
910 }
911 case ActivationFn::kActivationRelu1:
912 {
913 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
914 activationDesc.m_A = 1.0f;
915 activationDesc.m_B = -1.0f;
916 break;
917 }
918 case ActivationFn::kActivationRelu6:
919 {
920 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
921 activationDesc.m_A = 6.0f;
922 break;
923 }
924 case ActivationFn::kActivationSigmoid:
925 {
926 activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
927 break;
928 }
929 case ActivationFn::kActivationTanh:
930 {
931 activationDesc.m_Function = armnn::ActivationFunction::TanH;
932 activationDesc.m_A = 1.0f;
933 activationDesc.m_B = 1.0f;
934 break;
935 }
936 default:
937 {
938 Fail("%s: Invalid activation enum value %i", __func__, activation);
939 return nullptr;
940 }
941 }
942
943 bool isSupported = false;
Cathal Corbett53837672022-09-01 11:34:37 +0100944 armnn::BackendId setBackend;
Sadik Armagan8f397a12022-06-17 15:38:22 +0100945 FORWARD_LAYER_SUPPORT_FUNC(__func__,
946 IsActivationSupported,
947 data.m_Backends,
948 isSupported,
Cathal Corbett53837672022-09-01 11:34:37 +0100949 setBackend,
Sadik Armagan8f397a12022-06-17 15:38:22 +0100950 prevLayer->GetOutputSlot(0).GetTensorInfo(),
951 tensorInfo,
952 activationDesc);
953 if (!isSupported)
954 {
955 return nullptr;
956 }
957
958 activationLayer = data.m_Network->AddActivationLayer(activationDesc);
Cathal Corbett53837672022-09-01 11:34:37 +0100959 activationLayer->SetBackendId(setBackend);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100960
961 prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0));
962 activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
963 }
964
965 return activationLayer;
966}
967
968bool SetupAndTrackLayerOutputSlot(const Operation& operation,
969 uint32_t operationOutputIndex,
970 armnn::IConnectableLayer& layer,
971 uint32_t layerOutputIndex,
972 const Model& model,
973 ConversionData& data,
974 const armnn::TensorInfo* overrideOutputInfo,
975 const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc,
976 const ActivationFn& activationFunction,
977 bool inferOutputShapes)
978{
979 const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex, model);
980 if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots()))
981 {
982 return false;
983 }
984
985 armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex);
986 if (overrideOutputInfo == nullptr)
987 {
988 outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
989 }
990 else
991 {
992 outputSlot.SetTensorInfo(*overrideOutputInfo);
993 }
994
995 bool isSupported = false;
996 if (validateFunc && (IsDynamicTensor(outputSlot.GetTensorInfo()) || inferOutputShapes))
997 {
998 // Type one dynamic tensors require the previous layer's output shape for inference
999 for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex)
1000 {
1001 if(!layer.GetInputSlot(inputSlotIndex).GetConnection())
1002 {
1003 return false;
1004 }
1005 }
1006 // IsTensorInfoSet will infer the dynamic output shape
1007 outputSlot.IsTensorInfoSet();
1008 // Once the shape is inferred we can validate it
1009 validateFunc(outputSlot.GetTensorInfo(), isSupported);
1010
1011 if(!isSupported)
1012 {
1013 for (unsigned int inputSlotIndex = 0; inputSlotIndex < layer.GetNumInputSlots(); ++inputSlotIndex)
1014 {
1015 layer.GetInputSlot(inputSlotIndex).GetConnection()->Disconnect(layer.GetInputSlot(inputSlotIndex));
1016 }
1017 return false;
1018 }
1019 }
1020
1021 const uint32_t operandIndex = operation.outputs[operationOutputIndex];
1022
1023 if (activationFunction != ActivationFn::kActivationNone)
1024 {
1025 const armnn::TensorInfo& activationOutputInfo = outputSlot.GetTensorInfo();
1026 armnn::IConnectableLayer* const endLayer = ProcessActivation(activationOutputInfo, activationFunction,
1027 &layer, data);
1028
1029 if (!endLayer)
1030 {
1031 return Fail("%s: ProcessActivation failed", __func__);
1032 }
1033
1034 armnn::IOutputSlot& activationOutputSlot = endLayer->GetOutputSlot(layerOutputIndex);
1035 data.m_OutputSlotForOperand[operandIndex] = &activationOutputSlot;
1036 }
1037 else
1038 {
1039 data.m_OutputSlotForOperand[operandIndex] = &outputSlot;
1040 }
1041
1042 return true;
1043}
1044
Sadik Armaganb0161572022-08-03 11:27:05 +01001045bool IsConnectedToDequantize(armnn::IOutputSlot* ioutputSlot)
1046{
1047 VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize()";
1048 if (!ioutputSlot)
1049 {
1050 return false;
1051 }
1052 VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() ioutputSlot is valid.";
1053 // Find the connections and layers..
1054 armnn::IConnectableLayer& owningLayer = ioutputSlot->GetOwningIConnectableLayer();
1055 if (owningLayer.GetType() == armnn::LayerType::Dequantize)
1056 {
1057 VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() connected to Dequantize Layer.";
1058 armnn::IInputSlot& inputSlot = owningLayer.GetInputSlot(0);
1059 armnn::IOutputSlot* connection = inputSlot.GetConnection();
1060 if (connection)
1061 {
1062 VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() Dequantize Layer has a connection.";
1063 armnn::IConnectableLayer& connectedLayer =
1064 connection->GetOwningIConnectableLayer();
1065 if (connectedLayer.GetType() == armnn::LayerType::Constant)
1066 {
1067 VLOG(DRIVER) << "ConversionUtils::IsConnectedToDequantize() Dequantize Layer connected to Constant";
1068 return true;
1069 }
1070 }
1071 }
1072 return false;
1073}
1074
Sadik Armagan8f397a12022-06-17 15:38:22 +01001075} // namespace armnn_driver