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