blob: beee00d11a938e169290b2d3ca336bf3d87da82c [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#pragma once
7
8#include "CanonicalUtils.hpp"
9
10#include <armnn/ArmNN.hpp>
11#include <armnn/BackendHelper.hpp>
12#include <armnn/utility/Assert.hpp>
13#include <armnn/utility/IgnoreUnused.hpp>
14#include <armnn/utility/NumericCast.hpp>
15
16#include <armnnUtils/DataLayoutIndexed.hpp>
17#include <armnnUtils/Transpose.hpp>
18
19#include <ActivationFunctor.h>
20#include <CpuExecutor.h>
21#include <OperationsUtils.h>
22
23#include <armnnUtils/FloatingPointComparison.hpp>
24
25#include <log/log.h>
26#include <vector>
27
28inline const android::nn::Model::Subgraph& getMainModel(const android::nn::Model& model) { return model.main; }
29
30namespace armnn_driver
31{
32
33///
34/// Helper classes
35///
36
37#include <nnapi/OperandTypes.h>
38#include <nnapi/Result.h>
39#include <nnapi/TypeUtils.h>
40#include <nnapi/Types.h>
41#include <nnapi/Validation.h>
42
43using Model = ::android::nn::Model;
44using Operand = ::android::nn::Operand;
45using OperandLifeTime = ::android::nn::Operand::LifeTime;
46using OperandType = ::android::nn::OperandType;
47using Operation = ::android::nn::Operation;
48using OperationType = ::android::nn::OperationType;
49using ErrorStatus = ::android::nn::ErrorStatus;
50
51struct ConversionData
52{
53 ConversionData(const std::vector<armnn::BackendId>& backends)
54 : m_Backends(backends)
55 , m_Network(nullptr, nullptr)
56 , m_DynamicInputsEncountered(false)
57 {}
58
59 const std::vector<armnn::BackendId> m_Backends;
60 armnn::INetworkPtr m_Network;
61 std::vector<armnn::IOutputSlot*> m_OutputSlotForOperand;
62 std::vector<::android::nn::RunTimePoolInfo> m_MemPools;
63 bool m_DynamicInputsEncountered;
64};
65
66class LayerInputHandle
67{
68public:
69 LayerInputHandle();
70 LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo);
71
72 bool IsValid() const;
73
74 void Connect(armnn::IInputSlot& inputSlot);
75
76 void Disconnect(armnn::IInputSlot& inputSlot);
77
78 const armnn::TensorInfo& GetTensorInfo() const;
79
80 void SanitizeQuantizationScale(LayerInputHandle& weight, LayerInputHandle& input);
81
Sadik Armaganb0161572022-08-03 11:27:05 +010082 armnn::IOutputSlot* GetOutputSlot() const;
83
Sadik Armagan8f397a12022-06-17 15:38:22 +010084private:
85 armnn::IOutputSlot* m_OutputSlot;
86 bool m_Valid;
87 armnn::TensorInfo m_TensorInfo;
88};
89
90class ConstTensorPin
91{
92public:
93 // Creates an invalid tensor pin (can be used to signal errors)
94 // The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid
95 ConstTensorPin(bool optional = false);
96
97 // @param tensorInfo TensorInfo associated with the tensor.
98 // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
99 // the model being converted.
100 // @param numBytes Number of bytes for the tensor data.
101 ConstTensorPin(armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
102 const armnn::PermutationVector& mappings);
103
104 ConstTensorPin(const ConstTensorPin& other) = delete;
105 ConstTensorPin(ConstTensorPin&& other) = default;
106
107 bool IsValid() const;
108 bool IsOptional() const;
109
110 const armnn::ConstTensor& GetConstTensor() const;
111 const armnn::ConstTensor* GetConstTensorPtr() const;
112
113private:
114 armnn::ConstTensor m_ConstTensor;
115
116 // Owned memory for swizzled tensor data, only required if the tensor needed
117 // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
118 // the pools associated with the model being converted.
119 std::vector<uint8_t> m_SwizzledTensorData;
120
121 // optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given
122 bool m_Optional;
123};
124
125enum class ConversionResult
126{
127 Success,
128 ErrorMappingPools,
129 UnsupportedFeature
130};
131
132} // namespace armnn_driver
133
134///
135/// Utility functions
136///
137
138namespace
139{
140using namespace armnn_driver;
141
142// Convenience function to log the reason for failing to convert a model.
143// @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
144template<class... Args>
145static bool Fail(const char* formatStr, Args&&... args)
146{
147 ALOGD(formatStr, std::forward<Args>(args)...);
148 return false;
149}
150
151// Convenience macro to call an Is*Supported function and log caller name together with reason for lack of support.
152// Called as: FORWARD_LAYER_SUPPORT_FUNC(__func__, Is*Supported, backends, a, b, c, d, e)
153#define FORWARD_LAYER_SUPPORT_FUNC(funcName, func, backends, supported, ...) \
154try \
155{ \
156 for (auto&& backendId : backends) \
157 { \
158 auto layerSupportObject = armnn::GetILayerSupportByBackendId(backendId); \
159 if (layerSupportObject.IsBackendRegistered()) \
160 { \
161 std::string reasonIfUnsupported; \
162 supported = \
163 layerSupportObject.func(__VA_ARGS__, armnn::Optional<std::string&>(reasonIfUnsupported)); \
164 if (supported) \
165 { \
166 break; \
167 } \
168 else \
169 { \
170 if (reasonIfUnsupported.size() > 0) \
171 { \
172 VLOG(DRIVER) << funcName << ": not supported by armnn: " << reasonIfUnsupported.c_str(); \
173 } \
174 else \
175 { \
176 VLOG(DRIVER) << funcName << ": not supported by armnn"; \
177 } \
178 } \
179 } \
180 else \
181 { \
182 VLOG(DRIVER) << funcName << ": backend not registered: " << backendId.Get().c_str(); \
183 } \
184 } \
185 if (!supported) \
186 { \
187 VLOG(DRIVER) << funcName << ": not supported by any specified backend"; \
188 } \
189} \
190catch (const armnn::InvalidArgumentException &e) \
191{ \
192 throw armnn::InvalidArgumentException(e, "Failed to check layer support", CHECK_LOCATION()); \
193}
194
195inline armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
196{
197 return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
198}
199
200// Support within the 1.3 driver for specific tensor data types
201inline bool IsOperandTypeSupportedForTensors(OperandType type)
202{
203 return type == OperandType::BOOL ||
204 type == OperandType::TENSOR_BOOL8 ||
205 type == OperandType::TENSOR_FLOAT16 ||
206 type == OperandType::TENSOR_FLOAT32 ||
207 type == OperandType::TENSOR_QUANT8_ASYMM ||
208 type == OperandType::TENSOR_QUANT8_ASYMM_SIGNED ||
209 type == OperandType::TENSOR_QUANT8_SYMM ||
210 type == OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL ||
211 type == OperandType::TENSOR_QUANT16_SYMM ||
212 type == OperandType::TENSOR_INT32;
213}
214
215inline bool IsBool(Operand operand)
216{
217 return operand.type == OperandType::BOOL;
218}
219
220inline bool Is12OrLaterOperand(Operand)
221{
222 return true;
223}
224
225
226template<typename LayerHandleType>
227armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network,
228 LayerHandleType& inputLayer,
229 armnn::TensorInfo reshapeInfo)
230{
231 armnn::ReshapeDescriptor reshapeDescriptor;
232 reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
233
234 armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor);
235 ARMNN_ASSERT(reshapeLayer != nullptr);
236
237 // Attach the input layer to the reshape layer
238 inputLayer.Connect(reshapeLayer->GetInputSlot(0));
239 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo);
240
241 return *reshapeLayer;
242}
243
244
245 armnn::TensorShape FlattenFullyConnectedInput(const armnn::TensorShape& inputShape,
246 const armnn::TensorShape& weightsShape)
247{
248 if (inputShape.GetNumDimensions() > 2U)
249 {
250 unsigned int totalInputElements = inputShape.GetNumElements();
251 unsigned int inputSize = weightsShape[1];
252
253 unsigned int batchSize = totalInputElements / inputSize;
254
255 if(totalInputElements % batchSize != 0)
256 {
257 throw std::runtime_error("Failed to deduce tensor shape");
258 }
259
260 return armnn::TensorShape({batchSize, inputSize});
261 }
262 else
263 {
264 return inputShape;
265 }
266}
267
268inline bool VerifyFullyConnectedShapes(const armnn::TensorShape& inputShape,
269 const armnn::TensorShape& weightsShape,
270 const armnn::TensorShape& outputShape,
271 bool transposeWeightMatrix)
272{
273 unsigned int dimIdx = transposeWeightMatrix ? 0 : 1;
274 return (inputShape[0] == outputShape[0] && weightsShape[dimIdx] == outputShape[1]);
275}
276
277bool BroadcastTensor(LayerInputHandle& input0,
278 LayerInputHandle& input1,
279 armnn::IConnectableLayer* startLayer,
280 ConversionData& data)
281{
282 ARMNN_ASSERT(startLayer != nullptr);
283
284 const armnn::TensorInfo& inputInfo0 = input0.GetTensorInfo();
285 const armnn::TensorInfo& inputInfo1 = input1.GetTensorInfo();
286
287 unsigned int inputDimensions0 = inputInfo0.GetNumDimensions();
288 unsigned int inputDimensions1 = inputInfo1.GetNumDimensions();
289
290 if (inputDimensions0 == inputDimensions1)
291 {
292 // The inputs have the same number of dimensions, simply connect them to the given layer as they are
293 input0.Connect(startLayer->GetInputSlot(0));
294 input1.Connect(startLayer->GetInputSlot(1));
295
296 return true;
297 }
298
299 // Since the number of dimensions do not match then we need to add degenerate dimensions
300 // to the "smaller" tensor using a reshape, while keeping the order of the inputs.
301
302 unsigned int maxInputDimensions = std::max(inputDimensions0, inputDimensions1);
303 unsigned int sizeDifference = std::abs(armnn::numeric_cast<int>(inputDimensions0) -
304 armnn::numeric_cast<int>(inputDimensions1));
305
306 bool input0IsSmaller = inputDimensions0 < inputDimensions1;
307 LayerInputHandle& smallInputHandle = input0IsSmaller ? input0 : input1;
308 const armnn::TensorInfo& smallInfo = smallInputHandle.GetTensorInfo();
309
310 const armnn::TensorShape& smallShape = smallInfo.GetShape();
311 std::vector<unsigned int> reshapedDimensions(maxInputDimensions, 1);
312 for (unsigned int i = sizeDifference; i < maxInputDimensions; i++)
313 {
314 reshapedDimensions[i] = smallShape[i - sizeDifference];
315 }
316
317 armnn::TensorInfo reshapedInfo = smallInfo;
318 reshapedInfo.SetShape(armnn::TensorShape{ armnn::numeric_cast<unsigned int>(reshapedDimensions.size()),
319 reshapedDimensions.data() });
320
321 // RehsapeDescriptor that is ignored in the IsReshapeSupported function
322 armnn::ReshapeDescriptor reshapeDescriptor;
323
324 bool isSupported = false;
325 FORWARD_LAYER_SUPPORT_FUNC(__func__,
326 IsReshapeSupported,
327 data.m_Backends,
328 isSupported,
329 smallInfo,
330 reshapedInfo,
331 reshapeDescriptor);
332 if (!isSupported)
333 {
334 return false;
335 }
336
337 ARMNN_ASSERT(data.m_Network != nullptr);
338 armnn::IConnectableLayer& reshapeLayer = AddReshapeLayer(*data.m_Network, smallInputHandle, reshapedInfo);
339
340 if (input0IsSmaller)
341 {
342 // Input0 is the "smaller" tensor, connect the reshape layer as follows:
343 //
344 // Input0 Input1
345 // | |
346 // Reshape |
347 // \ /
348 // StartLayer
349
350 reshapeLayer.GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
351 input1.Connect(startLayer->GetInputSlot(1));
352 }
353 else
354 {
355 // Input1 is the "smaller" tensor, connect the reshape layer as follows:
356 //
357 // Input0 Input1
358 // | |
359 // | Reshape
360 // \ /
361 // StartLayer
362
363 input0.Connect(startLayer->GetInputSlot(0));
364 reshapeLayer.GetOutputSlot(0).Connect(startLayer->GetInputSlot(1));
365 }
366
367 return true;
368}
369
370void CalcPadding(uint32_t input,
371 uint32_t kernel,
372 uint32_t stride,
373 uint32_t& outPadHead,
374 uint32_t& outPadTail,
375 PaddingScheme scheme)
376{
377 int32_t padHead;
378 int32_t padTail;
379 calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
380 outPadHead = armnn::numeric_cast<uint32_t>(padHead);
381 outPadTail = armnn::numeric_cast<uint32_t>(padTail);
382}
383
384void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t dilation, uint32_t& outPadHead,
385 uint32_t& outPadTail, ::android::nn::PaddingScheme scheme)
386{
387 int32_t padHead;
388 int32_t padTail;
389 calculateExplicitPadding(input, stride, dilation, kernel, scheme, &padHead, &padTail);
390 outPadHead = armnn::numeric_cast<uint32_t>(padHead);
391 outPadTail = armnn::numeric_cast<uint32_t>(padTail);
392}
393
394inline void CalcPaddingTransposeConv(uint32_t output, uint32_t kernel, int32_t stride, int32_t& outPadHead,
395 int32_t& outPadTail, ::android::nn::PaddingScheme scheme)
396{
397 calculateExplicitPaddingTransposeConv(output, stride, kernel, scheme, &outPadHead, &outPadTail);
398}
399
400Shape GetOperandShape(const Operand& operand)
401{
402 Shape shape;
403 shape.type = OperandType(operand.type);
404 shape.dimensions = operand.dimensions;
405 shape.scale = operand.scale;
406 shape.offset = operand.zeroPoint;
407 return shape;
408}
409
410
411// ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
412// what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
413// we accept some tolerance. We don't want ArmNN itself to accept these inconsistencies as it is up to the
414// user (us, in this case) to ensure they match.
415void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
416 const armnn::TensorInfo& weightInfo,
417 const armnn::TensorInfo& inputInfo)
418{
419 if (weightInfo.HasPerAxisQuantization())
420 {
421 // NOTE: Bias scale is always set to 0 for per-axis quantization and
422 // it needs to be calculated: scale[i] = input_scale * weight_scale[i]
423 auto UpdateBiasScaleValue = [&inputInfo](float biasScale) -> float
424 {
425 return biasScale * inputInfo.GetQuantizationScale();
426 };
427
428 std::vector<float> biasScales(weightInfo.GetQuantizationScales());
429 std::transform(biasScales.begin(), biasScales.end(), biasScales.begin(), UpdateBiasScaleValue);
430
431 biasInfo.SetQuantizationScales(biasScales);
432 // bias is expected to be a 1d tensor, set qdim=0
433 biasInfo.SetQuantizationDim(0);
434
435 VLOG(DRIVER) << "Bias quantization params have been updated for per-axis quantization";
436 }
437 else
438 {
439 const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
440 if (biasInfo.GetQuantizationScale() != expectedBiasScale)
441 {
442 if (armnnUtils::within_percentage_tolerance(biasInfo.GetQuantizationScale(), expectedBiasScale, 1.0f))
443 {
444 VLOG(DRIVER) << "Bias quantization scale has been modified to match input * weights";
445 biasInfo.SetQuantizationScale(expectedBiasScale);
446 }
447 }
448 }
449}
450
451// 4D Tensor Permutations
452const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U });
453const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U });
454const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U });
455
456// 3D Permutation Vectors
457const armnn::PermutationVector RotateTensorLeft({ 1U, 2U, 0U });
458const armnn::PermutationVector RotateTensorRight({ 2U, 0U, 1U });
459
460template<typename OSlot>
461armnn::IConnectableLayer& AddTransposeLayer(armnn::INetwork& network, OSlot& input,
462 const armnn::PermutationVector& mappings)
463{
464 // Add swizzle layer
465 armnn::IConnectableLayer* const layer = network.AddTransposeLayer(mappings);
466
467 ARMNN_ASSERT(layer != nullptr);
468
469 // Connect input to swizzle layer
470 input.Connect(layer->GetInputSlot(0));
471
472 // Setup swizzled output
473 const armnn::TensorInfo outInfo = armnnUtils::TransposeTensorShape(input.GetTensorInfo(), mappings);
474 layer->GetOutputSlot(0).SetTensorInfo(outInfo);
475
476 return *layer;
477}
478
479bool ValidateConcatOutputShape(const std::vector<armnn::TensorShape> & inputShapes,
480 const armnn::TensorShape & outputShape,
481 uint32_t concatDim)
482{
483 // Validate the output shape is correct given the input shapes (which have just been validated)
484 unsigned int numDimensions = inputShapes[0].GetNumDimensions();
485 if (outputShape.GetNumDimensions() != numDimensions)
486 {
487 return Fail("%s: Output shape has wrong number of dimensions", __func__);
488 }
489
490 unsigned int outputSizeAlongConcatenatedDimension = 0;
491 for (unsigned int i = 0; i < inputShapes.size(); i++)
492 {
493 outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
494 }
495
496 for (unsigned int i = 0; i < numDimensions; ++i)
497 {
498 if (i == concatDim)
499 {
500 if (outputShape[i] != outputSizeAlongConcatenatedDimension)
501 {
502 return Fail(
503 "%s: Invalid output shape for dimension %d (%d != %d)",
504 __func__,
505 i,
506 outputShape[i],
507 outputSizeAlongConcatenatedDimension);
508 }
509 }
510 else
511 {
512 if (outputShape[i] != inputShapes[0][i])
513 {
514 return Fail("%s: Invalid output shape", __func__);
515 }
516 }
517 }
518
519 return true;
520}
521
522inline bool RequiresReshape(armnn::TensorShape & inputShape)
523{
524 return inputShape.GetNumDimensions() < 3;
525}
526
527inline void SwizzleInputs(armnn::INetwork& network,
528 std::vector<LayerInputHandle>& inputs,
529 std::vector<armnn::TensorShape>& inputShapes,
530 const armnn::PermutationVector& mapping)
531{
532 if (!mapping.IsEqual(IdentityPermutation4D))
533 {
534 size_t nInputs = inputs.size();
535 for (size_t i=0; i<nInputs; ++i)
536 {
537 // add swizzle layer
538 armnn::IConnectableLayer& swizzleLayer = AddTransposeLayer(network, inputs[i], mapping);
539 auto& outputSlot = swizzleLayer.GetOutputSlot(0);
540 auto& outputInfo = outputSlot.GetTensorInfo();
541 // replace inputs with the swizzled ones
542 inputs[i] = LayerInputHandle(true, &outputSlot, outputInfo);
543 inputShapes[i] = inputs[i].GetTensorInfo().GetShape();
544 }
545 }
546}
547
548bool TransposeInputTensors(ConversionData& data,
549 std::vector<LayerInputHandle>& inputs,
550 std::vector<armnn::TensorShape>& inputShapes,
551 const armnn::PermutationVector& mapping)
552{
553 // If we have a IdentityPermutation4D or IdentityPermutation3D then we are not permuting
554 if (!mapping.IsEqual(IdentityPermutation4D) && !mapping.IsEqual(IdentityPermutation3D))
555 {
556 armnn::TensorInfo outputTransposeInfo;
557 size_t nInputs = inputs.size();
558 for (size_t i=0; i<nInputs; ++i)
559 {
560 // check permute layer
561 armnn::TransposeDescriptor transposeDesc;
562 transposeDesc.m_DimMappings = mapping;
563 outputTransposeInfo = armnnUtils::TransposeTensorShape(inputs[i].GetTensorInfo(), mapping);
564
565 bool isSupported = false;
566 FORWARD_LAYER_SUPPORT_FUNC(__func__,
567 IsTransposeSupported,
568 data.m_Backends,
569 isSupported,
570 inputs[i].GetTensorInfo(),
571 outputTransposeInfo,
572 transposeDesc);
573 if (!isSupported)
574 {
575 return false;
576 }
577
578 }
579 SwizzleInputs(*data.m_Network, inputs, inputShapes, mapping);
580 }
581 return true;
582}
583
584bool CreateConcatPermutationParameters(const unsigned int numberOfDimensions,
585 int32_t & concatDimension,
586 std::pair<armnn::PermutationVector, armnn::PermutationVector> & permutationPair)
587{
588 bool needPermute = false;
589 ARMNN_ASSERT(numberOfDimensions >= 3);
590
591 // ArmNN uses Compute Library subtensors to perform concatenation
592 // This only works when concatenating along dimension 0, 1 or 3 for a 4-D tensor,
593 // or along dimension 0 or 2 for a 3-D tensor.
594 if (numberOfDimensions == 4 && concatDimension == 2)
595 {
596 concatDimension = 1;
597 permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2);
598 needPermute = true;
599 }
600 else if (numberOfDimensions == 3 && concatDimension == 1)
601 {
602 concatDimension = 0;
603 permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight);
604 needPermute = true;
605 }
606 // If the tensor is 3-D and the concat dimension is 2 then we don't need to permute but we do need to change the
607 // permutation identity to only have 3 dimensions
608 else if (numberOfDimensions == 3 && concatDimension == 2)
609 {
610 permutationPair = std::make_pair(IdentityPermutation3D, IdentityPermutation3D);
611 }
612 return needPermute;
613}
614
615} // anonymous namespace
616
617namespace armnn_driver
618{
619using namespace android::nn;
620
621//// Creates an ArmNN activation layer and connects it to the given layer, if the
622//// passed in AndroidNN activation function requires so.
623//// @return The end layer of the sequence of layers built for the given AndroidNN
624//// activation function or nullptr if an error occurred (e.g. unsupported activation).
625//// Note that the end layer matches the input layer if no activation is required
626//// (the sequence of layers has length 1).
627armnn::IConnectableLayer* ProcessActivation(const armnn::TensorInfo& tensorInfo,
628 ActivationFn activation,
629 armnn::IConnectableLayer* prevLayer,
630 ConversionData& data);
631
632
633inline const Operand* GetInputOperand(const Operation& operation,
634 uint32_t inputIndex,
635 const Model& model,
636 bool failOnIndexOutOfBounds = true)
637{
638 if (inputIndex >= operation.inputs.size())
639 {
640 if (failOnIndexOutOfBounds)
641 {
642 Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
643 }
644 return nullptr;
645 }
646
647 // Model should have been validated beforehand
648 ARMNN_ASSERT(operation.inputs[inputIndex] < getMainModel(model).operands.size());
649 return &getMainModel(model).operands[operation.inputs[inputIndex]];
650}
651
652inline const Operand* GetOutputOperand(const Operation& operation,
653 uint32_t outputIndex,
654 const Model& model)
655{
656 if (outputIndex >= operation.outputs.size())
657 {
658 Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
659 return nullptr;
660 }
661
662 // Model should have been validated beforehand
663 ARMNN_ASSERT(operation.outputs[outputIndex] < getMainModel(model).operands.size());
664
665 return &getMainModel(model).operands[operation.outputs[outputIndex]];
666}
667
668const void* GetOperandValueReadOnlyAddress(const Operand& operand,
669 const Model& model,
670 const ConversionData& data,
671 bool optional = false);
672
673inline bool GetOperandType(const Operation& operation,
674 uint32_t inputIndex,
675 const Model& model,
676 OperandType& type)
677{
678 const Operand* operand = GetInputOperand(operation, inputIndex, model);
679 if (!operand)
680 {
681 return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
682 }
683
684 type = operand->type;
685 return true;
686}
687
688inline bool IsOperandConstant(const Operand& operand)
689{
690 OperandLifeTime lifetime = operand.lifetime;
691
692 return lifetime == OperandLifeTime::CONSTANT_COPY ||
693 lifetime == OperandLifeTime::CONSTANT_REFERENCE ||
694 lifetime == OperandLifeTime::POINTER ||
695 lifetime == OperandLifeTime::NO_VALUE;
696}
697
698bool IsWeightsValid(const Operation& operation, uint32_t inputIndex, const Model& model);
699
700ConstTensorPin ConvertOperandToConstTensorPin(const Operand& operand,
701 const Model& model,
702 const ConversionData& data,
703 const armnn::PermutationVector& dimensionMappings = g_DontPermute,
704 const armnn::TensorShape* overrideTensorShape = nullptr,
Sadik Armagan1e276f32022-07-19 12:37:20 +0100705 bool optional = false,
706 const armnn::DataType* overrideDataType = nullptr);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100707
708inline ConstTensorPin ConvertOperationInputToConstTensorPin(
709 const Operation& operation,
710 uint32_t inputIndex,
711 const Model& model,
712 const ConversionData& data,
713 const armnn::PermutationVector& dimensionMappings = g_DontPermute,
714 const armnn::TensorShape* overrideTensorShape = nullptr,
715 bool optional = false)
716{
717 const Operand* operand = GetInputOperand(operation, inputIndex, model);
718 if (!operand)
719 {
720 Fail("%s: failed to get input operand: index=%u", __func__, inputIndex);
721 return ConstTensorPin();
722 }
723 return ConvertOperandToConstTensorPin(*operand,
724 model,
725 data,
726 dimensionMappings,
727 overrideTensorShape,
728 optional);
729}
730
731template <typename OutputType>
732bool GetInputScalar(const Operation& operation,
733 uint32_t inputIndex,
734 OperandType type,
735 OutputType& outValue,
736 const Model& model,
737 const ConversionData& data,
738 bool optional = false)
739{
740 const Operand* operand = GetInputOperand(operation, inputIndex, model);
741 if (!optional && !operand)
742 {
743 return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
744 }
745
746 if (!optional && operand->type != type)
747 {
748 VLOG(DRIVER) << __func__ << ": unexpected operand type: " << operand->type << " should be: " << type;
749 return false;
750 }
751
752 if (!optional && operand->location.length != sizeof(OutputType))
753 {
754 return Fail("%s: incorrect operand location length: %i (should be %i)",
755 __func__, operand->location.length, sizeof(OutputType));
756 }
757
758 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
759 if (!optional && !valueAddress)
760 {
761 return Fail("%s: failed to get address for operand", __func__);
762 }
763
764 if(!optional)
765 {
766 outValue = *(static_cast<const OutputType*>(valueAddress));
767 }
768
769 return true;
770}
771
772inline bool GetInputInt32(const Operation& operation,
773 uint32_t inputIndex,
774 int32_t& outValue,
775 const Model& model,
776 const ConversionData& data)
777{
778 return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue, model, data);
779}
780
781inline bool GetInputFloat32(const Operation& operation,
782 uint32_t inputIndex,
783 float& outValue,
784 const Model& model,
785 const ConversionData& data)
786{
787 return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue, model, data);
788}
789
790inline bool GetInputActivationFunctionImpl(const Operation& operation,
791 uint32_t inputIndex,
792 OperandType type,
793 ActivationFn& outActivationFunction,
794 const Model& model,
795 const ConversionData& data)
796{
797 if (type != OperandType::INT32 && type != OperandType::TENSOR_INT32)
798 {
799 VLOG(DRIVER) << __func__ << ": unexpected operand type: " << type
800 << " should be OperandType::INT32 or OperandType::TENSOR_INT32";
801 return false;
802 }
803
804 int32_t activationFunctionAsInt;
805 if (!GetInputScalar(operation, inputIndex, type, activationFunctionAsInt, model, data))
806 {
807 return Fail("%s: failed to get activation input value", __func__);
808 }
809 outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
810 return true;
811}
812
813inline bool GetInputActivationFunction(const Operation& operation,
814 uint32_t inputIndex,
815 ActivationFn& outActivationFunction,
816 const Model& model,
817 const ConversionData& data)
818{
819 return GetInputActivationFunctionImpl(operation,
820 inputIndex,
821 OperandType::INT32,
822 outActivationFunction,
823 model,
824 data);
825}
826
827inline bool GetInputActivationFunctionFromTensor(const Operation& operation,
828 uint32_t inputIndex,
829 ActivationFn& outActivationFunction,
830 const Model& model,
831 const ConversionData& data)
832{
833 // This only accepts a 1-D tensor of size 1
834 return GetInputActivationFunctionImpl(operation,
835 inputIndex,
836 OperandType::INT32,
837 outActivationFunction,
838 model,
839 data);
840}
841
842
843inline bool GetOptionalInputActivation(const Operation& operation,
844 uint32_t inputIndex,
845 ActivationFn& activationFunction,
846 const Model& model,
847 const ConversionData& data)
848{
849 if (operation.inputs.size() <= inputIndex)
850 {
851 activationFunction = ActivationFn::kActivationNone;
852 }
853 else
854 {
855 if (!GetInputActivationFunction(operation, inputIndex, activationFunction, model, data))
856 {
857 return Fail("%s: Operation has invalid inputs", __func__);
858 }
859 }
860 return true;
861}
862
863template<typename ConvolutionDescriptor>
864bool GetOptionalConvolutionDilationParams(const Operation& operation,
865 uint32_t dilationXIndex,
866 ConvolutionDescriptor& descriptor,
867 const Model& model,
868 const ConversionData& data)
869{
870 bool success = true;
871 if (operation.inputs.size() >= dilationXIndex + 2)
872 {
873 success &= GetInputScalar(operation,
874 dilationXIndex,
875 OperandType::INT32,
876 descriptor.m_DilationX,
877 model,
878 data);
879 success &= GetInputScalar(operation,
880 dilationXIndex + 1,
881 OperandType::INT32,
882 descriptor.m_DilationY,
883 model,
884 data);
885 }
886
887 return success;
888}
889
890inline bool GetOptionalBool(const Operation& operation,
891 uint32_t inputIndex,
892 const Model& model,
893 const ConversionData& data)
894{
895 const Operand* operand = GetInputOperand(operation, inputIndex, model);
896 if (!operand)
897 {
898 return false;
899 }
900
901 if (!IsBool(*operand))
902 {
903 return false;
904 }
905
906 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
907 if (!valueAddress)
908 {
909 return false;
910 }
911
912 return *(static_cast<const bool*>(valueAddress));
913}
914
915bool GetTensorInt32Values(const Operand& operand,
916 std::vector<int32_t>& outValues,
917 const Model& model,
918 const ConversionData& data);
919
920bool GetInputPaddingScheme(const Operation& operation,
921 uint32_t inputIndex,
922 PaddingScheme& outPaddingScheme,
923 const Model& model,
924 const ConversionData& data);
925
926LayerInputHandle ConvertToLayerInputHandle(const Operation& operation,
927 uint32_t inputIndex,
928 const Model& model,
929 ConversionData& data,
Sadik Armagan1e276f32022-07-19 12:37:20 +0100930 const armnn::PermutationVector& dimensionMappings = g_DontPermute,
931 const LayerInputHandle* inputHandle = nullptr);
Sadik Armagan8f397a12022-06-17 15:38:22 +0100932
933bool SetupAndTrackLayerOutputSlot(const Operation& operation,
934 uint32_t operationOutputIndex,
935 armnn::IConnectableLayer& layer,
936 uint32_t layerOutputIndex,
937 const Model& model,
938 ConversionData& data,
939 const armnn::TensorInfo* overrideOutputInfo = nullptr,
940 const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc = nullptr,
941 const ActivationFn& activationFunction = ActivationFn::kActivationNone,
942 bool inferOutputShapes = false);
943
944armnn::DataLayout OptionalDataLayout(const Operation& operation,
945 uint32_t inputIndex,
946 const Model& model,
947 ConversionData& data);
948
949inline bool SetupAndTrackLayerOutputSlot(
950 const Operation& operation,
951 uint32_t outputIndex,
952 armnn::IConnectableLayer& layer,
953 const Model& model,
954 ConversionData& data,
955 const armnn::TensorInfo* overrideOutputInfo = nullptr,
956 const std::function <void (const armnn::TensorInfo&, bool&)>& validateFunc = nullptr,
957 const ActivationFn& activationFunction = ActivationFn::kActivationNone)
958{
959 return SetupAndTrackLayerOutputSlot(operation,
960 outputIndex,
961 layer,
962 outputIndex,
963 model,
964 data,
965 overrideOutputInfo,
966 validateFunc,
967 activationFunction);
968}
969
970bool ConvertToActivation(const Operation& operation,
971 const char* operationName,
972 const armnn::ActivationDescriptor& activationDesc,
973 const Model& model,
974 ConversionData& data);
975
976bool ConvertPaddings(const Operation& operation,
977 const Model& model,
978 ConversionData& data,
979 unsigned int rank,
980 armnn::PadDescriptor& padDescriptor);
981bool ConvertReduce(const Operation& operation,
982 const Model& model,
983 ConversionData& data,
984 armnn::ReduceOperation reduceOperation);
985
986bool ConvertPooling2d(const Operation& operation,
987 const char* operationName,
988 armnn::PoolingAlgorithm poolType,
989 const Model& model,
990 ConversionData& data);
991
992inline bool IsQSymm8(const Operand& operand)
993{
994 return operand.type == OperandType::TENSOR_QUANT8_SYMM;
995}
996
997enum class DequantizeStatus
998{
999 SUCCESS,
1000 NOT_REQUIRED,
1001 INVALID_OPERAND
1002};
1003
1004using DequantizeResult = std::tuple<std::unique_ptr<float[]>, size_t, armnn::TensorInfo, DequantizeStatus>;
1005
1006DequantizeResult DequantizeIfRequired(size_t operand_index,
1007 const Operation& operation,
1008 const Model& model,
1009 const ConversionData& data);
1010
1011ConstTensorPin DequantizeAndMakeConstTensorPin(const Operation& operation,
1012 const Model& model,
1013 const ConversionData& data,
1014 size_t operandIndex,
1015 bool optional = false);
1016
Sadik Armaganb0161572022-08-03 11:27:05 +01001017bool IsConnectedToDequantize(armnn::IOutputSlot* ioutputSlot);
1018
Sadik Armagan8f397a12022-06-17 15:38:22 +01001019} // namespace armnn_driver