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