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