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