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