blob: d4ef9ca9c5221c816ff741ec59148f6ec327b839 [file] [log] [blame]
Matthew Sloyan11572322023-03-16 10:17:51 +00001//
2// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#pragma once
7
8#include <armnn_delegate.hpp>
9#include <DelegateUtils.hpp>
10
11#include <armnn/ArmNN.hpp>
12#include <armnn/BackendHelper.hpp>
13#include <armnn/utility/Assert.hpp>
14#include <armnn/utility/NumericCast.hpp>
15
16#include <armnnUtils/Permute.hpp>
17#include <armnnUtils/TensorUtils.hpp>
18
19#include <tensorflow/lite/builtin_ops.h>
20#include <tensorflow/lite/c/builtin_op_data.h>
21#include <tensorflow/lite/c/common.h>
22#include <tensorflow/lite/c/c_api_opaque.h>
23#include <tensorflow/lite/minimal_logging.h>
24#include <tensorflow/lite/kernels/kernel_util.h>
25
26namespace
27{
28
29// Macro to call an Is<layer_name>Supported function and log caller name together with reason for lack of support
30#define FORWARD_LAYER_OPAQUE_SUPPORT_FUNC(opName, tfLiteContext, func, backends, supported, setBackend, ...) \
31try \
32{ \
33 for (auto&& backendId : backends) \
34 { \
35 auto layerSupportObject = armnn::GetILayerSupportByBackendId(backendId); \
36 if (layerSupportObject.IsBackendRegistered()) \
37 { \
38 std::string reasonIfUnsupported; \
39 supported = \
40 layerSupportObject.func(__VA_ARGS__, armnn::Optional<std::string&>(reasonIfUnsupported)); \
41 if (supported) \
42 { \
43 setBackend = backendId; \
44 break; \
45 } \
46 else \
47 { \
48 if (reasonIfUnsupported.size() > 0) \
49 { \
50 TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING, \
51 "%s: not supported by armnn: %s", opName, reasonIfUnsupported.c_str()); \
52 } \
53 else \
54 { \
55 TFLITE_LOG_PROD(tflite::TFLITE_LOG_WARNING, \
56 "%s: not supported by armnn", opName); \
57 } \
58 } \
59 } \
60 else \
61 { \
62 TF_LITE_OPAQUE_KERNEL_LOG(tfLiteContext, "%s: backend not registered: %s", \
63 opName, backendId.Get().c_str()); \
64 } \
65 } \
66 if (!supported) \
67 { \
68 TF_LITE_OPAQUE_KERNEL_LOG(tfLiteContext, "%s: not supported by any specified backend", opName); \
69 } \
70} \
71catch (const armnn::InvalidArgumentException &e) \
72{ \
73 throw armnn::InvalidArgumentException(e, "Failed to check layer support", CHECK_LOCATION()); \
74}
75
76TfLiteStatus ValidateNumInputs(TfLiteOpaqueContext* tfLiteContext,
77 TfLiteOpaqueNode* tfLiteNode,
78 const unsigned int expectedSize,
79 int nodeIndex)
80{
81 int numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
82 if (static_cast<unsigned int>(numInputs) != expectedSize)
83 {
84 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
85 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unexpected number of inputs (%d != %d) in node #%d",
86 numInputs, expectedSize, nodeIndex);
87 return kTfLiteError;
88 }
89 return kTfLiteOk;
90}
91
92TfLiteStatus ValidateNumOutputs(TfLiteOpaqueContext* tfLiteContext,
93 TfLiteOpaqueNode* tfLiteNode,
94 const unsigned int expectedSize,
95 int nodeIndex)
96{
97 auto numOutputs = TfLiteOpaqueNodeNumberOfOutputs(tfLiteNode);
98 if (static_cast<unsigned int>(numOutputs) != expectedSize)
99 {
100 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
101 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unexpected number of outputs (%d != %d) in node #%d",
102 numOutputs, expectedSize, nodeIndex);
103 return kTfLiteError;
104 }
105 return kTfLiteOk;
106}
107
108bool IsConstantTensor(const TfLiteOpaqueTensor* tfLiteTensor)
109{
110 auto tensorAllocationType = TfLiteOpaqueTensorGetAllocationType(tfLiteTensor);
111 if (tensorAllocationType == kTfLiteMmapRo)
112 {
113 return true;
114 }
115 return false;
116}
117
118bool IsDynamicTensor(const TfLiteOpaqueTensor& tfLiteTensor)
119{
120 auto tensorAllocationType = TfLiteOpaqueTensorGetAllocationType(&tfLiteTensor);
121 if (tensorAllocationType == kTfLiteDynamic)
122 {
123 return true;
124 }
125 return false;
126}
127
128bool IsValid(const TfLiteOpaqueTensor* tfLiteTensor)
129{
130 return tfLiteTensor == nullptr ? false : true;
131}
132
133bool IsValid(TfLiteOpaqueContext* tfLiteContext,
134 const TfLiteOpaqueTensor& tfLiteTensor,
135 int32_t operatorCode,
136 int32_t nodeIndex)
137{
138 if(!IsValid(&tfLiteTensor))
139 {
140 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
141 tfLiteContext,
142 "TfLiteArmnnDelegate: Invalid TfLite tensor in operator #%d node #%d: ",
143 operatorCode, nodeIndex);
144 return false;
145 }
146 if (IsDynamicTensor(tfLiteTensor))
147 {
148 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
149 tfLiteContext,
150 "TfLiteArmnnDelegate: Dynamic tensors are not supported in operator #%d node #%d: ",
151 operatorCode, nodeIndex);
152 return false;
153 }
154 return true;
155}
156
157bool IsAffineQuantization(const TfLiteOpaqueTensor& tfLiteTensor)
158{
159 auto quantizationInfo = TfLiteOpaqueTensorGetQuantization(&tfLiteTensor);
160 if (quantizationInfo.type == kTfLiteAffineQuantization)
161 {
162 return true;
163 }
164 return false;
165}
166
167// Load input indices into array if found and validate.
168// This replaces node->inputs->data.
169TfLiteStatus GetInputIndices(const int* inputIndices,
170 TfLiteOpaqueNode* tfLiteNode,
171 TfLiteOpaqueContext* tfLiteContext,
172 unsigned int numInputs)
173{
174 int actualNumInputs = 0;
175
176 TfLiteStatus status = TfLiteOpaqueNodeInputs(tfLiteNode, &inputIndices, &actualNumInputs);
177 if(status != kTfLiteOk)
178 {
179 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
180 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unable to gather input information from node.");
181 return kTfLiteError;
182 }
183
184 if (static_cast<unsigned int>(actualNumInputs) != numInputs)
185 {
186 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
187 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unexpected number of inputs (%d != %d) in node.",
188 actualNumInputs, numInputs);
189 return kTfLiteError;
190 }
191
192 return kTfLiteOk;
193}
194
195// Load output indices into array if found and validate.
196// This replaces node->outputs->data.
197TfLiteStatus GetOutputIndices(const int* outputIndices,
198 TfLiteOpaqueNode* tfLiteNode,
199 TfLiteOpaqueContext* tfLiteContext,
200 unsigned int numOutputs)
201{
202 int actualNumOutputs = 0;
203
204 TfLiteStatus status = TfLiteOpaqueNodeOutputs(tfLiteNode, &outputIndices, &actualNumOutputs);
205 if(status != kTfLiteOk)
206 {
207 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
208 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unable to gather output information from node.");
209 return kTfLiteError;
210 }
211
212 if (static_cast<unsigned int>(actualNumOutputs) != numOutputs)
213 {
214 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
215 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Unexpected number of outputs (%d != %d) in node.",
216 actualNumOutputs, numOutputs);
217 return kTfLiteError;
218 }
219
220 return kTfLiteOk;
221}
222
223TfLiteStatus Connect(armnn::IConnectableLayer* layer,
224 TfLiteOpaqueContext* tfLiteContext,
225 TfLiteOpaqueNode* tfLiteNode,
226 armnnOpaqueDelegate::DelegateData& data)
227{
228 // Get array of indices, replaces node->inputs->data
229 const int* inputIndices = nullptr;
230 TfLiteStatus inputStatus = GetInputIndices(inputIndices, tfLiteNode, tfLiteContext, layer->GetNumInputSlots());
231 if(inputStatus != kTfLiteOk)
232 {
233 return kTfLiteError;
234 }
235
236 // Connect the input slots
237 for (unsigned int inputIndex = 0; inputIndex < layer->GetNumInputSlots(); ++inputIndex)
238 {
239 if (data.m_OutputSlotForNode[inputIndices[inputIndex]] != nullptr)
240 {
241 data.m_OutputSlotForNode[inputIndices[inputIndex]]->Connect(layer->GetInputSlot(inputIndex));
242 }
243 }
244
245 // Get array of indices, replaces node->outputs->data
246 const int* outputIndices = nullptr;
247 TfLiteStatus outputStatus = GetOutputIndices(outputIndices, tfLiteNode, tfLiteContext, layer->GetNumOutputSlots());
248 if(outputStatus != kTfLiteOk)
249 {
250 return kTfLiteError;
251 }
252
253 // Prepare output slots
254 for (unsigned int outputIndex = 0; outputIndex < layer->GetNumOutputSlots(); ++outputIndex)
255 {
256 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(outputIndex);
257 data.m_OutputSlotForNode[static_cast<unsigned long>(outputIndices[outputIndex])] = &outputSlot;
258 }
259
260 return kTfLiteOk;
261}
262
263TfLiteStatus FusedActivation(TfLiteOpaqueContext* tfLiteContext,
264 TfLiteOpaqueNode* tfLiteNode,
265 TfLiteFusedActivation activationType,
266 armnn::IConnectableLayer* prevLayer,
267 unsigned int outputSlotIndex,
268 armnnOpaqueDelegate::DelegateData& data)
269{
270 const armnn::TensorInfo& activationOutputInfo = prevLayer->GetOutputSlot(outputSlotIndex).GetTensorInfo();
271
272 armnn::ActivationDescriptor activationDesc;
273
274 switch (activationType)
275 {
276 case kTfLiteActNone:
277 {
278 // No Activation
279 return kTfLiteOk;
280 }
281 case kTfLiteActRelu:
282 {
283 activationDesc.m_Function = armnn::ActivationFunction::ReLu;
284 break;
285 }
286 case kTfLiteActReluN1To1:
287 {
288 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
289 activationDesc.m_A = 1.0f;
290 activationDesc.m_B = -1.0f;
291 break;
292 }
293 case kTfLiteActRelu6:
294 {
295 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
296 activationDesc.m_A = 6.0f;
297 activationDesc.m_B = 0.0f;
298 break;
299 }
300 case kTfLiteActSigmoid:
301 {
302 activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
303 break;
304 }
305 case kTfLiteActTanh:
306 {
307 activationDesc.m_Function = armnn::ActivationFunction::TanH;
308 activationDesc.m_A = 1.0f;
309 activationDesc.m_B = 1.0f;
310 break;
311 }
312 default:
313 return kTfLiteError;
314 }
315
316 bool isSupported = false;
317 armnn::BackendId setBackend;
318 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("ACTIVATION",
319 tfLiteContext,
320 IsActivationSupported,
321 data.m_Backends,
322 isSupported,
323 setBackend,
324 activationOutputInfo,
325 activationOutputInfo,
326 activationDesc);
327 if (!isSupported)
328 {
329 return kTfLiteError;
330 }
331 armnn::IConnectableLayer* activationLayer = data.m_Network->AddActivationLayer(activationDesc);
332 activationLayer->SetBackendId(setBackend);
333
334 ARMNN_ASSERT(activationLayer != nullptr);
335 activationLayer->GetOutputSlot(0).SetTensorInfo(activationOutputInfo);
336
337 // Get array of indices, replaces node->outputs->data
338 const int* outputIndices = nullptr;
339 TfLiteStatus status = GetOutputIndices(outputIndices,
340 tfLiteNode,
341 tfLiteContext,
342 activationLayer->GetNumOutputSlots());
343 if(status != kTfLiteOk)
344 {
345 return kTfLiteError;
346 }
347
348 // Connect and prepare output slots
349 for (unsigned int outputIndex = 0; outputIndex < activationLayer->GetNumOutputSlots(); ++outputIndex)
350 {
351 data.m_OutputSlotForNode[static_cast<unsigned long>(
352 outputIndices[outputIndex])]->Connect(activationLayer->GetInputSlot(0));
353
354 armnn::IOutputSlot& outputSlot = activationLayer->GetOutputSlot(outputIndex);
355 data.m_OutputSlotForNode[static_cast<unsigned long>(outputIndices[outputIndex])] = &outputSlot;
356 }
357 return kTfLiteOk;
358}
359
360armnn::IConnectableLayer* AddReshapeLayer(TfLiteOpaqueContext* tfLiteContext,
361 TfLiteOpaqueNode* tfLiteNode,
362 armnn::IConnectableLayer* prevLayer,
363 armnn::TensorInfo reshapedOutputTensorInfo,
364 armnn::TensorInfo outputTensorInfo,
365 armnnOpaqueDelegate::DelegateData& data)
366{
367 armnn::ReshapeDescriptor desc;
368 desc.m_TargetShape = outputTensorInfo.GetShape();
369
370 bool isSupported = false;
371 armnn::BackendId setBackend;
372 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("RESHAPE",
373 tfLiteContext,
374 IsReshapeSupported,
375 data.m_Backends,
376 isSupported,
377 setBackend,
378 reshapedOutputTensorInfo,
379 outputTensorInfo,
380 desc);
381
382 if (!isSupported)
383 {
384 return nullptr;
385 }
386
387 armnn::IConnectableLayer* reshapeLayer = data.m_Network->AddReshapeLayer(desc);
388 reshapeLayer->SetBackendId(setBackend);
389 ARMNN_ASSERT(reshapeLayer != nullptr);
390
391 prevLayer->GetOutputSlot(0).SetTensorInfo(reshapedOutputTensorInfo);
392 reshapeLayer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
393
394 // Gather array of indices and it's length, replaces node->outputs->data[i]
395 const int* outputIndices = nullptr;
396 int numOutputs = 0;
397
398 TfLiteStatus status = TfLiteOpaqueNodeOutputs(tfLiteNode, &outputIndices, &numOutputs);
399 if(status != kTfLiteOk)
400 {
401 throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unable to gather output information from node.");
402 }
403
404 if (static_cast<unsigned int>(numOutputs) != reshapeLayer->GetNumOutputSlots())
405 {
406 throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unexpected number of outputs (" +
407 std::to_string(numOutputs) +
408 "!= " +
409 std::to_string(reshapeLayer->GetNumOutputSlots()) +
410 ") in node.");
411 }
412
413 // Connect and prepare output slots
414 for (unsigned int outputIndex = 0; outputIndex < reshapeLayer->GetNumOutputSlots(); ++outputIndex)
415 {
416 data.m_OutputSlotForNode[static_cast<unsigned long>(
417 outputIndices[outputIndex])]->Connect(reshapeLayer->GetInputSlot(0));
418
419 armnn::IOutputSlot& outputSlot = reshapeLayer->GetOutputSlot(outputIndex);
420 data.m_OutputSlotForNode[static_cast<unsigned long>(outputIndices[outputIndex])] = &outputSlot;
421 }
422 return reshapeLayer;
423}
424
425armnn::DataType GetDataType(const TfLiteOpaqueTensor* tfLiteTensor)
426{
427 switch (TfLiteOpaqueTensorType(tfLiteTensor))
428 {
429 case kTfLiteBool:
430 return armnn::DataType::Boolean;
431 case kTfLiteFloat32:
432 return armnn::DataType::Float32;
433 case kTfLiteFloat16:
434 return armnn::DataType::Float16;
435 case kTfLiteUInt8:
436 return armnn::DataType::QAsymmU8;
437 case kTfLiteInt8:
438 {
439 auto quantizationInfo = TfLiteOpaqueTensorGetQuantization(tfLiteTensor);
440 if (quantizationInfo.type == kTfLiteAffineQuantization)
441 {
442 auto* quantization =
443 reinterpret_cast<TfLiteAffineQuantization*>(quantizationInfo.params);
444
445 if (quantization->zero_point != nullptr && quantization->zero_point->size == 1)
446 {
447 return armnn::DataType::QAsymmS8;
448 }
449 else
450 {
451 return armnn::DataType::QSymmS8;
452 }
453 }
454 else
455 {
456 return armnn::DataType::QAsymmS8;
457 }
458 }
459 case kTfLiteInt16:
460 return armnn::DataType::QSymmS16;
461 case kTfLiteInt32:
462 return armnn::DataType::Signed32;
463 case kTfLiteInt64:
464 return armnn::DataType::Signed64;
465 default:
466 throw armnn::Exception(
467 &"TfLiteArmnnDelegate: Unsupported data type: " [ TfLiteOpaqueTensorType(tfLiteTensor) ]);
468 }
469}
470
471armnn::TensorInfo GetTensorInfoForTfLiteOpaqueTensor(const TfLiteOpaqueTensor* tfLiteTensor, bool isOutput = false)
472{
473 armnn::DataType type = GetDataType(tfLiteTensor);
474 armnn::TensorInfo ret;
475
476 auto tensorDimensionSize = TfLiteOpaqueTensorNumDims(tfLiteTensor);
477 if (tensorDimensionSize == 0)
478 {
479 // If input tensor does not have a shape
480 // assuming that it has 1D tensor
481 if (!isOutput)
482 {
483 std::vector<unsigned int> safeShape = { 1 };
484 bool dimensionsSpecificity[1] = { true };
485
486 armnn::TensorShape tensorShape(armnn::numeric_cast<unsigned int>(safeShape.size()),
487 safeShape.data(),
488 dimensionsSpecificity);
489 ret = armnn::TensorInfo(tensorShape, type);
490
491 if(IsConstantTensor(tfLiteTensor))
492 {
493 ret.SetConstant(true);
494 }
495 }
496 else
497 {
498 armnn::TensorShape tensorShape(armnn::Dimensionality::NotSpecified);
499 ret = armnn::TensorInfo(tensorShape, type);
500 }
501 }
502 else
503 {
504 std::vector<unsigned int> tensorDims(static_cast<unsigned int>(tensorDimensionSize));
505 bool dimensionsSpecificity[5] = { true, true, true, true, true };
506
507 for (int32_t i = 0; i < tensorDimensionSize; ++i)
508 {
509 int32_t dim = TfLiteOpaqueTensorDim(tfLiteTensor, i);
510
511 if (dim == 0)
512 {
513 dimensionsSpecificity[i] = false;
514 }
515 tensorDims[i] = static_cast<unsigned int>(dim);
516 }
517
518 armnn::TensorShape tensorShape(static_cast<unsigned int>(tensorDimensionSize),
519 tensorDims.data(),
520 dimensionsSpecificity);
521
522 if(IsConstantTensor(tfLiteTensor))
523 {
524 ret = armnn::TensorInfo(tensorShape, type);
525 ret.SetConstant(true);
526 }
527 else
528 {
529 ret = armnn::TensorInfo(tensorShape, type);
530 }
531 }
532
533 auto quantizationInfo = TfLiteOpaqueTensorGetQuantization(tfLiteTensor);
534 if (quantizationInfo.type == kTfLiteAffineQuantization)
535 {
536 // get per-channel quantization parameters
537 const auto* affineQuantization =
538 reinterpret_cast<TfLiteAffineQuantization*>(quantizationInfo.params);
539 if (affineQuantization->scale->size > 1)
540 {
541 std::vector<float> quantizationScales;
542 for (unsigned int i = 0; i < static_cast<unsigned int>(affineQuantization->scale->size); ++i)
543 {
544 quantizationScales.push_back(affineQuantization->scale->data[i]);
545 }
546 ret.SetQuantizationScales(quantizationScales);
547 ret.SetQuantizationDim(armnn::numeric_cast<unsigned int>(affineQuantization->quantized_dimension));
548 }
549 else
550 {
551 ret.SetQuantizationScale(affineQuantization->scale->data[0]);
552 ret.SetQuantizationOffset(affineQuantization->zero_point->data[0]);
553 }
554 }
555 else
556 {
557 auto quantizationParameters = TfLiteOpaqueTensorGetQuantizationParams(tfLiteTensor);
558 ret.SetQuantizationScale(quantizationParameters.scale);
559 ret.SetQuantizationOffset(quantizationParameters.zero_point);
560 }
561
562 return ret;
563}
564
565armnn::ConstTensor CreateConstTensor(const TfLiteOpaqueTensor* tfLiteTensor,
566 const armnn::TensorInfo& tensorInfo)
567{
568 auto allocType = TfLiteOpaqueTensorGetAllocationType(tfLiteTensor);
569 if (allocType != kTfLiteMmapRo)
570 {
571 throw armnn::Exception("TfLiteArmnnDelegate: Not constant allocation type: " + std::to_string(allocType));
572 }
573
574 return armnn::ConstTensor(tensorInfo, TfLiteOpaqueTensorData(tfLiteTensor));
575}
576
577armnn::ConstTensor* GetConstTensorForTfLiteTensor(const TfLiteOpaqueContext* tfLiteContext,
578 TfLiteOpaqueNode* tfLiteNode,
579 int index)
580{
581 const TfLiteOpaqueTensor* tfLiteTensor = TfLiteOpaqueNodeGetInput(tfLiteContext, tfLiteNode, index);
582 armnn::TensorInfo tensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteTensor);
583
584 return new armnn::ConstTensor(tensorInfo, TfLiteOpaqueTensorData(tfLiteTensor));
585}
586
587bool IsOptionalOperandPresent(TfLiteOpaqueNode* tfLiteNode, const int operandIndex)
588{
589 // Gather array of indices and it's length, replaces node->inputs->data[i] and node->inputs->size
590 const int* inputIndices = nullptr;
591 int numInputs = 0;
592
593 TfLiteStatus status = TfLiteOpaqueNodeInputs(tfLiteNode, &inputIndices, &numInputs);
594 if(status != kTfLiteOk)
595 {
596 throw armnn::Exception("TfLiteArmnnOpaqueDelegate: Unable to gather input information from node.");
597 }
598
599 // If the inputs array has fewer than operandIndex entries or if the entry at operandIndex has a value of -1 or
600 // less then the input is not present.
601 if (numInputs > operandIndex && inputIndices[operandIndex] >= 0)
602 {
603 return true;
604 }
605 return false;
606}
607
608TfLiteStatus ProcessInputs(armnn::IConnectableLayer* layer,
609 armnnOpaqueDelegate::DelegateData& delegateData,
610 TfLiteOpaqueContext* tfLiteContext,
611 TfLiteOpaqueNode* tfLiteNode)
612{
613 // Get array of indices, replaces node->inputs->data
614 const int* inputIndices = nullptr;
615 TfLiteStatus status = GetInputIndices(inputIndices, tfLiteNode, tfLiteContext, layer->GetNumInputSlots());
616 if(status != kTfLiteOk)
617 {
618 return kTfLiteError;
619 }
620
621 // Process input tensors
622 // If input tensor is a Constant tensor create a constant layer and connect it to the network
623 for (int32_t inputIndex = 0; inputIndex < static_cast<int32_t>(layer->GetNumInputSlots()); ++inputIndex)
624 {
625 const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueNodeGetInput(tfLiteContext, tfLiteNode, inputIndex);
626
627 if (IsConstantTensor(tfLiteInputTensor))
628 {
629 armnn::TensorInfo inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
630
631 bool isSupported = false;
632 armnn::BackendId setBackend;
633 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("CONSTANT",
634 tfLiteContext,
635 IsConstantSupported,
636 delegateData.m_Backends,
637 isSupported,
638 setBackend,
639 inputTensorInfo);
640 if (!isSupported)
641 {
642 return kTfLiteError;
643 }
644
645 auto constantInput = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
646
647 armnn::IConnectableLayer* constantLayer = delegateData.m_Network->AddConstantLayer(constantInput);
648 constantLayer->SetBackendId(setBackend);
649 armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
650 outputSlot.SetTensorInfo(inputTensorInfo);
651
652 delegateData.m_OutputSlotForNode[inputIndices[inputIndex]] = &outputSlot;
653 }
654 }
655 return kTfLiteOk;
656}
657
658} // namespace anonymous