blob: e4393e7bb0b6d1d7cd0a38e1261653330e2912cf [file] [log] [blame]
Francis Murtaghc4fb0dd2023-03-16 17:01:56 +00001//
2// Copyright © 2023 Arm Ltd and Contributors. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
Matthew Sloyan080ffd82023-04-24 12:53:04 +01005
6#include <OpaqueDelegateUtils.hpp>
7#include <SharedFunctions.hpp>
8
9#include <tensorflow/lite/builtin_ops.h>
10#include <tensorflow/lite/c/builtin_op_data.h>
11#include <tensorflow/lite/c/common.h>
12#include <tensorflow/lite/minimal_logging.h>
13
14namespace armnnOpaqueDelegate
15{
16
17TfLiteStatus VisitConv2dOperator(DelegateData& delegateData,
18 TfLiteOpaqueContext* tfLiteContext,
19 TfLiteOpaqueNode* tfLiteNode,
20 int nodeIndex,
21 int32_t operatorCode)
22{
23 auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
24 if (numInputs < 2)
25 {
26 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
27 tfLiteContext,
28 "TfLiteArmnnOpaqueDelegate: Minimum number of inputs (%d != %d) in node #%d",
29 2, numInputs, nodeIndex);
30 return kTfLiteError;
31 }
32 TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex));
33
34 // Gather input indices and use to get input tensor.
35 const int* inputTensors;
36 if (TfLiteOpaqueNodeInputs(tfLiteNode, &inputTensors, &numInputs) != kTfLiteOk)
37 {
38 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
39 tfLiteContext,
40 "TfLiteArmnnOpaqueDelegate: Unable to gather input tensor indices from node #%d: ",
41 nodeIndex);
42 return kTfLiteError;
43 }
44
45 const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]);
46 if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex))
47 {
48 return kTfLiteError;
49 }
Matthew Sloyan080ffd82023-04-24 12:53:04 +010050
51 // Use input indices to get filter tensor.
52 const TfLiteOpaqueTensor* tfLiteFilterTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[1]);
Matthew Sloyan2b04ec32023-04-26 11:42:46 +010053 if (!IsValid(tfLiteContext, tfLiteFilterTensor, operatorCode, nodeIndex))
Matthew Sloyan080ffd82023-04-24 12:53:04 +010054 {
Matthew Sloyan080ffd82023-04-24 12:53:04 +010055 return kTfLiteError;
56 }
57
58 // Gather output indices and use to get output tensors.
59 int numOutputs = 0;
60 const int* outputTensors;
61 if (TfLiteOpaqueNodeOutputs(tfLiteNode, &outputTensors, &numOutputs) != kTfLiteOk)
62 {
63 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
64 tfLiteContext,
65 "TfLiteArmnnOpaqueDelegate: Unable to gather output tensor indices from node #%d: ",
66 nodeIndex);
67 return kTfLiteError;
68 }
69
70 const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]);
71 if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex))
72 {
73 return kTfLiteError;
74 }
Matthew Sloyan080ffd82023-04-24 12:53:04 +010075
76 const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
77 const armnn::TensorInfo& filterTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteFilterTensor);
78 const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true);
79
80 auto* tfLiteNodeParameters = reinterpret_cast<TfLiteConvParams*>(TfLiteOpaqueNodeGetBuiltinData(tfLiteNode));
81 TfLiteFusedActivation activationType = kTfLiteActNone;
82 if (tfLiteNodeParameters)
83 {
84 activationType = tfLiteNodeParameters->activation;
85 TfLiteStatus activationStatus = ValidateFusedActivationOperator(delegateData,
86 tfLiteContext,
87 outputTensorInfo,
88 outputTensorInfo,
89 activationType);
90 if(activationStatus != kTfLiteOk)
91 {
92 return kTfLiteError;
93 }
94 }
95
96 armnn::TensorInfo biasTensorInfo;
97 const TfLiteOpaqueTensor* tfLiteBiasTensor = nullptr;
98
99 bool biasEnabled = IsOptionalOperandPresent(tfLiteNode, 2);
100 if(biasEnabled)
101 {
102 // Use input indices to get bias tensor.
103 tfLiteBiasTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[2]);
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100104 if (!IsValid(tfLiteContext, tfLiteBiasTensor, operatorCode, nodeIndex))
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100105 {
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100106 return kTfLiteError;
107 }
108 biasTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteBiasTensor);
109 }
110 else
111 {
112 biasTensorInfo = armnn::TensorInfo(armnn::TensorShape({1}), GetDataType(tfLiteInputTensor));
113 }
114
115 armnn::Optional<armnn::TensorInfo> optionalBiasInfo(biasTensorInfo);
116
117 armnn::Convolution2dDescriptor descriptor;
118 descriptor.m_BiasEnabled = biasEnabled;
119 descriptor.m_StrideX = NonNegative(tfLiteNodeParameters->stride_width, nodeIndex);
120 descriptor.m_StrideY = NonNegative(tfLiteNodeParameters->stride_height, nodeIndex);
121 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
122 descriptor.m_DilationX = NonNegative(tfLiteNodeParameters->dilation_width_factor, nodeIndex);
123 descriptor.m_DilationY = NonNegative(tfLiteNodeParameters->dilation_height_factor, nodeIndex);
124
125 // TfLite uses NHWC tensors
126 const unsigned int inputHeight = inputTensorInfo.GetShape()[1];
127 const unsigned int inputWidth = inputTensorInfo.GetShape()[2];
128
129 const unsigned int filterHeight = filterTensorInfo.GetShape()[1];
130 const unsigned int filterWidth = filterTensorInfo.GetShape()[2];
131
132 // Calculate padding
133 CalcPadding(inputHeight, filterHeight, descriptor.m_StrideY, descriptor.m_DilationY,
134 descriptor.m_PadTop, descriptor.m_PadBottom, tfLiteNodeParameters->padding);
135 CalcPadding(inputWidth, filterWidth, descriptor.m_StrideX, descriptor.m_DilationX,
136 descriptor.m_PadLeft, descriptor.m_PadRight, tfLiteNodeParameters->padding);
137
138 armnn::BackendId setBackend;
139 if (!delegateData.m_Network)
140 {
Mike Kelly080d45d2023-11-10 17:11:53 +0000141 bool filterIsConst = filterTensorInfo.IsConstant();
142
143 if (!filterIsConst)
144 {
145 filterIsConst = WillInputBeOptimizedToConst(tfLiteContext, inputTensors[1]);
146 }
147 armnn::TensorInfo filterTensorInfoCopy(filterTensorInfo);
148 filterTensorInfoCopy.SetConstant(filterIsConst);
149 armnn::Optional<armnn::TensorInfo> optionalBiasInfoCopy(biasTensorInfo);
150
151 if (biasEnabled)
152 {
153 bool biasIsConst = biasTensorInfo.IsConstant();
154
155 if (!biasIsConst)
156 {
157 biasIsConst = WillInputBeOptimizedToConst(tfLiteContext, inputTensors[2]);
158 }
159 optionalBiasInfoCopy.value().SetConstant(biasIsConst);
160 }
161
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100162 bool isSupported = false;
163 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("CONV2D",
164 tfLiteContext,
165 IsConvolution2dSupported,
166 delegateData.m_Backends,
167 isSupported,
168 setBackend,
169 inputTensorInfo,
170 outputTensorInfo,
171 descriptor,
Mike Kelly080d45d2023-11-10 17:11:53 +0000172 filterTensorInfoCopy,
173 optionalBiasInfoCopy);
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100174 return isSupported ? kTfLiteOk : kTfLiteError;
175 }
176
177 // Set up filter and biases
Mike Kellya2806502023-08-03 10:42:11 +0100178 auto layerName = GetName(armnn::LayerType::Convolution2d, nodeIndex);
179 armnn::IConnectableLayer* layer = delegateData.m_Network->AddConvolution2dLayer(descriptor, layerName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100180 layer->SetBackendId(setBackend);
181
182 if(filterTensorInfo.IsConstant())
183 {
184 auto filter = CreateConstTensor(tfLiteFilterTensor, filterTensorInfo);
185
Mike Kellya2806502023-08-03 10:42:11 +0100186 auto filterName = GetName(armnn::LayerType::Constant, nodeIndex, "Filter");
187 armnn::IConnectableLayer* weightsLayer = delegateData.m_Network->AddConstantLayer(filter, filterName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100188 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
189 weightsLayer->GetOutputSlot(0).SetTensorInfo(filterTensorInfo);
190 }
191
192 if (biasEnabled)
193 {
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100194 if (biasTensorInfo.IsConstant())
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100195 {
196 auto biasTensor = CreateConstTensor(tfLiteBiasTensor, biasTensorInfo);
Mike Kellya2806502023-08-03 10:42:11 +0100197
198 auto biasName = GetName(armnn::LayerType::Constant, nodeIndex, "Bias");
199 armnn::IConnectableLayer* biasLayer = delegateData.m_Network->AddConstantLayer(biasTensor,
200 biasName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100201 ARMNN_ASSERT(biasLayer != nullptr);
202 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
203 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensorInfo);
204 }
205 }
206
207 // The data input can also be constant, so we must check that this is also allocated to an input slot
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100208 if (inputTensorInfo.IsConstant())
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100209 {
210 auto input = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
211
Mike Kellya2806502023-08-03 10:42:11 +0100212 auto inputName = GetName(armnn::LayerType::Constant, nodeIndex, "Input");
213 armnn::IConnectableLayer* inputLayer = delegateData.m_Network->AddConstantLayer(input, inputName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100214 inputLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
215 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
216 }
217
218 ARMNN_ASSERT(layer != nullptr);
219
220 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
221 outputSlot.SetTensorInfo(outputTensorInfo);
222
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100223 if (Connect(layer, tfLiteContext, tfLiteNode, delegateData) != kTfLiteOk)
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100224 {
225 return kTfLiteError;
226 }
227
228 if (!tfLiteNodeParameters)
229 {
230 // No Activation
231 return kTfLiteOk;
232 }
233
234 // Check and Create activation
Mike Kellya2806502023-08-03 10:42:11 +0100235 return FusedActivation(tfLiteContext, tfLiteNode, activationType, layer, 0, delegateData, nodeIndex);
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100236}
237
238TfLiteStatus VisitDepthwiseConv2dOperator(DelegateData& delegateData,
239 TfLiteOpaqueContext* tfLiteContext,
240 TfLiteOpaqueNode* tfLiteNode,
241 int nodeIndex,
242 int32_t operatorCode)
243{
244 auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
245 if (numInputs < 2)
246 {
247 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
248 tfLiteContext,
249 "TfLiteArmnnOpaqueDelegate: Minimum number of inputs (%d != %d) in node #%d",
250 2, numInputs, nodeIndex);
251 return kTfLiteError;
252 }
253 TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex));
254
255 // Gather input indices and use to get input tensor.
256 const int* inputTensors;
257 if (TfLiteOpaqueNodeInputs(tfLiteNode, &inputTensors, &numInputs) != kTfLiteOk)
258 {
259 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
260 tfLiteContext,
261 "TfLiteArmnnOpaqueDelegate: Unable to gather input tensor indices from node #%d: ",
262 nodeIndex);
263 return kTfLiteError;
264 }
265
266 const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]);
267 if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex))
268 {
269 return kTfLiteError;
270 }
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100271
272 // Use input indices to get filter tensor.
273 const TfLiteOpaqueTensor* tfLiteFilterTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[1]);
Matthew Sloyan2b04ec32023-04-26 11:42:46 +0100274 if (!IsValid(tfLiteContext, tfLiteFilterTensor, operatorCode, nodeIndex))
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100275 {
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100276 return kTfLiteError;
277 }
278
279 // Gather output indices and use to get output tensors.
280 int numOutputs = 0;
281 const int* outputTensors;
282 if (TfLiteOpaqueNodeOutputs(tfLiteNode, &outputTensors, &numOutputs) != kTfLiteOk)
283 {
284 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
285 tfLiteContext,
286 "TfLiteArmnnOpaqueDelegate: Unable to gather output tensor indices from node #%d: ",
287 nodeIndex);
288 return kTfLiteError;
289 }
290
291 const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]);
292 if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex))
293 {
294 return kTfLiteError;
295 }
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100296
297 const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
298 const armnn::TensorInfo& filterTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteFilterTensor);
299 const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true);
300
301 auto* tfLiteNodeParameters =
302 reinterpret_cast<TfLiteDepthwiseConvParams*>(TfLiteOpaqueNodeGetBuiltinData(tfLiteNode));
303
304 TfLiteFusedActivation activationType = kTfLiteActNone;
305 if (tfLiteNodeParameters)
306 {
307 activationType = tfLiteNodeParameters->activation;
308 TfLiteStatus activationStatus = ValidateFusedActivationOperator(delegateData,
309 tfLiteContext,
310 outputTensorInfo,
311 outputTensorInfo,
312 activationType);
313 if(activationStatus != kTfLiteOk)
314 {
315 return kTfLiteError;
316 }
317 }
318
319 armnn::TensorInfo biasTensorInfo;
320 const TfLiteOpaqueTensor* tfLiteBiasTensor = nullptr;
321
322 bool biasEnabled = IsOptionalOperandPresent(tfLiteNode, 2);
323 if(biasEnabled)
324 {
325 // Use input indices to get bias tensor.
326 tfLiteBiasTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[2]);
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100327 if (!IsValid(tfLiteContext, tfLiteBiasTensor, operatorCode, nodeIndex))
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100328 {
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100329 return kTfLiteError;
330 }
331 biasTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteBiasTensor);
332 }
333 else
334 {
335 biasTensorInfo = armnn::TensorInfo(armnn::TensorShape({1}), GetDataType(tfLiteInputTensor));
336 }
337
338 armnn::DepthwiseConvolution2dDescriptor descriptor;
339 descriptor.m_BiasEnabled = biasEnabled;
340 descriptor.m_StrideX = NonNegative(tfLiteNodeParameters->stride_width, nodeIndex);
341 descriptor.m_StrideY = NonNegative(tfLiteNodeParameters->stride_height, nodeIndex);
342 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
343 descriptor.m_DilationX = NonNegative(tfLiteNodeParameters->dilation_width_factor, nodeIndex);
344 descriptor.m_DilationY = NonNegative(tfLiteNodeParameters->dilation_height_factor, nodeIndex);
345
346 // Assuming input is NHWC
347 unsigned int inputHeight = inputTensorInfo.GetShape()[1];
348 unsigned int inputWidth = inputTensorInfo.GetShape()[2];
349
350 // TensorflowLite weights come in the format [1, H, W, I * M]
351 unsigned int filterHeight = filterTensorInfo.GetShape()[1];
352 unsigned int filterWidth = filterTensorInfo.GetShape()[2];
353
354 // Calculate padding
355 CalcPadding(inputHeight, filterHeight, descriptor.m_StrideY, descriptor.m_DilationY,
356 descriptor.m_PadTop, descriptor.m_PadBottom, tfLiteNodeParameters->padding);
357 CalcPadding(inputWidth, filterWidth, descriptor.m_StrideX, descriptor.m_DilationX,
358 descriptor.m_PadLeft, descriptor.m_PadRight, tfLiteNodeParameters->padding);
359
360 armnn::BackendId setBackend;
361 if (!delegateData.m_Network)
362 {
Mike Kelly080d45d2023-11-10 17:11:53 +0000363 bool filterIsConst = filterTensorInfo.IsConstant();
364
365 if (!filterIsConst)
366 {
367 filterIsConst = WillInputBeOptimizedToConst(tfLiteContext, inputTensors[1]);
368 }
369 armnn::TensorInfo filterTensorInfoCopy(filterTensorInfo);
370 filterTensorInfoCopy.SetConstant(filterIsConst);
371
372 armnn::Optional<armnn::TensorInfo> optionalBiasInfoCopy(biasTensorInfo);
373
374 if (biasEnabled)
375 {
376 bool biasIsConst = biasTensorInfo.IsConstant();
377
378 if (!biasIsConst)
379 {
380 biasIsConst = WillInputBeOptimizedToConst(tfLiteContext, inputTensors[2]);
381 }
382 optionalBiasInfoCopy.value().SetConstant(biasIsConst);
383 }
384
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100385 bool isSupported = false;
386 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("DEPTHWISE_CONV2D",
387 tfLiteContext,
388 IsDepthwiseConvolutionSupported,
389 delegateData.m_Backends,
390 isSupported,
391 setBackend,
392 inputTensorInfo,
393 outputTensorInfo,
394 descriptor,
Mike Kelly080d45d2023-11-10 17:11:53 +0000395 filterTensorInfoCopy,
396 optionalBiasInfoCopy);
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100397 return isSupported ? kTfLiteOk : kTfLiteError;
398 }
399
Mike Kellya2806502023-08-03 10:42:11 +0100400 auto layerName = GetName(armnn::LayerType::DepthwiseConvolution2d, nodeIndex);
401 armnn::IConnectableLayer* layer = delegateData.m_Network->AddDepthwiseConvolution2dLayer(descriptor,
402 layerName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100403 layer->SetBackendId(setBackend);
404
405 if(filterTensorInfo.IsConstant())
406 {
407 // For depthwise the weights layout is the same as for tflite [1, H, W, I*M]. No permutation required.
408 auto filter = CreateConstTensor(tfLiteFilterTensor, filterTensorInfo);
409
Mike Kellya2806502023-08-03 10:42:11 +0100410 auto filterName = GetName(armnn::LayerType::Constant, nodeIndex, "Filter");
411 armnn::IConnectableLayer* weightsLayer = delegateData.m_Network->AddConstantLayer(filter, filterName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100412 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
413 weightsLayer->GetOutputSlot(0).SetTensorInfo(filterTensorInfo);
414 }
415
416 if (biasEnabled)
417 {
418 if(biasTensorInfo.IsConstant())
419 {
420 auto biasTensor = CreateConstTensor(tfLiteBiasTensor, biasTensorInfo);
421
Mike Kellya2806502023-08-03 10:42:11 +0100422 auto biasName = GetName(armnn::LayerType::Constant, nodeIndex, "Bias");
423 armnn::IConnectableLayer* biasLayer = delegateData.m_Network->AddConstantLayer(biasTensor,
424 biasName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100425 ARMNN_ASSERT(biasLayer != nullptr);
426 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
427 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensorInfo);
428 }
429 }
430
431 // The data input can also be constant, so we must check that this is also allocated to an input slot
432 if(inputTensorInfo.IsConstant())
433 {
434 auto input = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
435
Mike Kellya2806502023-08-03 10:42:11 +0100436 auto inputName = GetName(armnn::LayerType::Constant, nodeIndex, "Input");
437 armnn::IConnectableLayer* inputLayer = delegateData.m_Network->AddConstantLayer(input, inputName.c_str());
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100438 inputLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
439 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
440 }
441
442 ARMNN_ASSERT(layer != nullptr);
443
444 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
445 outputSlot.SetTensorInfo(outputTensorInfo);
446
447 if(Connect(layer, tfLiteContext, tfLiteNode, delegateData) != kTfLiteOk)
448 {
449 return kTfLiteError;
450 }
451
452 if (!tfLiteNodeParameters)
453 {
454 // No Activation
455 return kTfLiteOk;
456 }
457 // Check and create activation
Mike Kellya2806502023-08-03 10:42:11 +0100458 return FusedActivation(tfLiteContext, tfLiteNode, activationType, layer, 0, delegateData, nodeIndex);
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100459}
460
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100461TfLiteStatus VisitConv3dOperator(DelegateData& delegateData,
462 TfLiteOpaqueContext* tfLiteContext,
463 TfLiteOpaqueNode* tfLiteNode,
464 int nodeIndex,
465 int32_t operatorCode)
466{
467 auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
468 if (numInputs < 2)
469 {
470 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
471 tfLiteContext, "TfLiteArmnnOpaqueDelegate: Minimum number of inputs (%d != %d) in node #%d",
472 2, numInputs, nodeIndex);
473 return kTfLiteError;
474 }
475 TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex));
476
477 armnn::Convolution3dDescriptor descriptor;
478 auto* params = reinterpret_cast<TfLiteConv3DParams*>(TfLiteOpaqueNodeGetBuiltinData(tfLiteNode));
479
480 bool biasEnabled = IsOptionalOperandPresent(tfLiteNode, 2);
481 descriptor.m_BiasEnabled = biasEnabled;
482 descriptor.m_DataLayout = armnn::DataLayout::NDHWC;
483 descriptor.m_StrideX = NonNegative(params->stride_width, nodeIndex);
484 descriptor.m_StrideY = NonNegative(params->stride_height, nodeIndex);
485 descriptor.m_StrideZ = NonNegative(params->stride_depth, nodeIndex);
486 descriptor.m_DilationX = NonNegative(params->dilation_width_factor, nodeIndex);
487 descriptor.m_DilationY = NonNegative(params->dilation_height_factor, nodeIndex);
488 descriptor.m_DilationZ = NonNegative(params->dilation_depth_factor, nodeIndex);
489
490 // Gather input indices and use to get input tensor.
491 const int* inputTensors;
492 if (TfLiteOpaqueNodeInputs(tfLiteNode, &inputTensors, &numInputs) != kTfLiteOk)
493 {
494 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
495 tfLiteContext,
496 "TfLiteArmnnOpaqueDelegate: Unable to gather input tensor indices from node #%d: ",
497 nodeIndex);
498 return kTfLiteError;
499 }
500
501 const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]);
502 if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex))
503 {
504 return kTfLiteError;
505 }
506
507 // Use input indices to get filter tensor.
508 const TfLiteOpaqueTensor* tfLiteFilterTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[1]);
509 if (!IsValid(tfLiteContext, tfLiteFilterTensor, operatorCode, nodeIndex))
510 {
511 return kTfLiteError;
512 }
513
514 // Gather output indices and use to get output tensors.
515 int numOutputs = 0;
516 const int* outputTensors;
517 if (TfLiteOpaqueNodeOutputs(tfLiteNode, &outputTensors, &numOutputs) != kTfLiteOk)
518 {
519 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
520 tfLiteContext,
521 "TfLiteArmnnOpaqueDelegate: Unable to gather output tensor indices from node #%d: ",
522 nodeIndex);
523 return kTfLiteError;
524 }
525
526 const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]);
527 if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex))
528 {
529 return kTfLiteError;
530 }
531
532 const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
533 const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true);
534
535 auto* tfLiteNodeParameters = reinterpret_cast<TfLiteConv3DParams*>(TfLiteOpaqueNodeGetBuiltinData(tfLiteNode));
536 TfLiteFusedActivation activationType=kTfLiteActNone;
537 if (tfLiteNodeParameters)
538 {
539 activationType = tfLiteNodeParameters->activation;
540 TfLiteStatus activationStatus = ValidateFusedActivationOperator(delegateData, tfLiteContext, outputTensorInfo,
541 outputTensorInfo, activationType);
542 if(activationStatus != kTfLiteOk)
543 {
544 return kTfLiteError;
545 }
546
547 }
548
549 const armnn::TensorInfo& filterTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteFilterTensor);
550
551 armnn::TensorInfo biasTensorInfo;
552 const TfLiteOpaqueTensor* tfLiteBiasTensor = nullptr;
553
554 if (biasEnabled)
555 {
556 // Use input indices to get bias tensor.
557 tfLiteBiasTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[2]);
558 if (!IsValid(tfLiteContext, tfLiteBiasTensor, operatorCode, nodeIndex))
559 {
560 return kTfLiteError;
561 }
562 biasTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteBiasTensor);
563 }
564 else
565 {
566 biasTensorInfo = armnn::TensorInfo(armnn::TensorShape({1}), GetDataType(tfLiteInputTensor));
567 }
568
569 armnn::Optional<armnn::TensorInfo> optionalBiasInfo(biasTensorInfo);
570
571 // TfLite uses NDHWC tensors
572 const unsigned int inputDepth = inputTensorInfo.GetShape()[1];
573 const unsigned int inputHeight = inputTensorInfo.GetShape()[2];
574 const unsigned int inputWidth = inputTensorInfo.GetShape()[3];
575
576 // Assuming the filter is DHWIO : Depth, Height, Width, OutputChannels, InputChannels
577 const unsigned int filterDepth = filterTensorInfo.GetShape()[0];
578 const unsigned int filterHeight = filterTensorInfo.GetShape()[1];
579 const unsigned int filterWidth = filterTensorInfo.GetShape()[2];
580
581 // Calculate padding
582 CalcPadding(inputDepth, filterDepth, descriptor.m_StrideZ, descriptor.m_DilationZ,
583 descriptor.m_PadFront, descriptor.m_PadBack, params->padding);
584 CalcPadding(inputHeight, filterHeight, descriptor.m_StrideY, descriptor.m_DilationY,
585 descriptor.m_PadTop, descriptor.m_PadBottom, params->padding);
586 CalcPadding(inputWidth, filterWidth, descriptor.m_StrideX, descriptor.m_DilationX,
587 descriptor.m_PadLeft, descriptor.m_PadRight, params->padding);
588
589 // If the m_Network is a nullptr, this signals that a prerequisite TfLite callback is required to clarify the
590 // support for the operator
591 // If supported, VisitConvolutionOperator will be called again to add the layer to the network as seen below.
592 armnn::BackendId setBackend;
593 if (!delegateData.m_Network)
594 {
595 bool isSupported = false;
596 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("CONV3D",
597 tfLiteContext,
598 IsConvolution3dSupported,
599 delegateData.m_Backends,
600 isSupported,
601 setBackend,
602 inputTensorInfo,
603 outputTensorInfo,
604 descriptor,
605 filterTensorInfo,
606 optionalBiasInfo);
607 return isSupported ? kTfLiteOk : kTfLiteError;
608 }
609
Mike Kellya2806502023-08-03 10:42:11 +0100610 auto layerName = GetName(armnn::LayerType::Convolution3d, nodeIndex);
611 armnn::IConnectableLayer* layer = delegateData.m_Network->AddConvolution3dLayer(descriptor, layerName.c_str());
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100612 layer->SetBackendId(setBackend);
613 ARMNN_ASSERT(layer != nullptr);
614
615 // Add a constant layer for weights and biases if inputs are constant,
616 // which are connected to the Convolution3d layer as inputs.
617 if (filterTensorInfo.IsConstant())
618 {
619 auto filter = CreateConstTensor(tfLiteFilterTensor,
620 filterTensorInfo);
621
Mike Kellya2806502023-08-03 10:42:11 +0100622 auto filterName = GetName(armnn::LayerType::Constant, nodeIndex, "Filter");
623 armnn::IConnectableLayer* weightsLayer = delegateData.m_Network->AddConstantLayer(filter, filterName.c_str());
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100624 ARMNN_ASSERT(weightsLayer != nullptr);
625
626 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
627 weightsLayer->GetOutputSlot(0).SetTensorInfo(filterTensorInfo);
628 }
629
630 if (biasEnabled)
631 {
632 if (biasTensorInfo.IsConstant())
633 {
634 auto biasTensor = CreateConstTensor(tfLiteBiasTensor, biasTensorInfo);
635
Mike Kellya2806502023-08-03 10:42:11 +0100636 auto biasName = GetName(armnn::LayerType::Constant, nodeIndex, "Bias");
637 armnn::IConnectableLayer* biasLayer = delegateData.m_Network->AddConstantLayer(biasTensor,
638 biasName.c_str());
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100639 ARMNN_ASSERT(biasLayer != nullptr);
640
641 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
642 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensorInfo);
643 }
644 }
645
646 // The data input can also be constant, so we must check that this is also allocated to an input slot
647 if (inputTensorInfo.IsConstant())
648 {
649 auto input = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
650
Mike Kellya2806502023-08-03 10:42:11 +0100651 auto inputName = GetName(armnn::LayerType::Constant, nodeIndex, "Input");
652 armnn::IConnectableLayer* inputLayer = delegateData.m_Network->AddConstantLayer(input, inputName.c_str());
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100653 inputLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
654 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
655 }
656
657 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
658 outputSlot.SetTensorInfo(outputTensorInfo);
659
660 if (Connect(layer, tfLiteContext, tfLiteNode, delegateData) != kTfLiteOk)
661 {
662 return kTfLiteError;
663 }
664
665 if (!tfLiteNodeParameters)
666 {
667 // No Activation
668 return kTfLiteOk;
669 }
670
671 // Check and create activation
Mike Kellya2806502023-08-03 10:42:11 +0100672 return FusedActivation(tfLiteContext, tfLiteNode, activationType, layer, 0, delegateData, nodeIndex);
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100673}
674
675
676
677TfLiteStatus VisitTransposeConv2dOperator(DelegateData& delegateData,
678 TfLiteOpaqueContext* tfLiteContext,
679 TfLiteOpaqueNode* tfLiteNode,
680 int nodeIndex,
681 int32_t operatorCode)
682{
683 TF_LITE_ENSURE_STATUS(ValidateNumInputs(tfLiteContext, tfLiteNode, 3, nodeIndex));
684 TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex));
685
686 armnn::TransposeConvolution2dDescriptor descriptor;
687 auto* parameters = reinterpret_cast<TfLiteTransposeConvParams*>(TfLiteOpaqueNodeGetBuiltinData(tfLiteNode));
688 descriptor.m_BiasEnabled = false;
689 descriptor.m_StrideX = NonNegative(parameters->stride_width, nodeIndex);
690 descriptor.m_StrideY = NonNegative(parameters->stride_height, nodeIndex);
691 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
692
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100693 auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
694 // Gather input indices and use to get input tensor.
695 const int* inputTensors;
696 if (TfLiteOpaqueNodeInputs(tfLiteNode, &inputTensors, &numInputs) != kTfLiteOk)
697 {
698 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
699 tfLiteContext,
700 "TfLiteArmnnOpaqueDelegate: Unable to gather input tensor indices from node #%d: ",
701 nodeIndex);
702 return kTfLiteError;
703 }
704
705 const TfLiteOpaqueTensor* tfLiteOutputShapeTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext,
706 inputTensors[0]);
707 if (!IsValid(tfLiteContext, tfLiteOutputShapeTensor, operatorCode, nodeIndex))
708 {
709 return kTfLiteError;
710 }
711
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100712 const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[2]);
713 if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex))
714 {
715 return kTfLiteError;
716 }
717
718 const TfLiteOpaqueTensor* tfLiteFilterTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[1]);
719 if (!IsValid(tfLiteContext, tfLiteFilterTensor, operatorCode, nodeIndex))
720 {
721 return kTfLiteError;
722 }
723
724 // Gather output indices and use to get output tensors.
725 int numOutputs = 0;
726 const int* outputTensors;
727 if (TfLiteOpaqueNodeOutputs(tfLiteNode, &outputTensors, &numOutputs) != kTfLiteOk)
728 {
729 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
730 tfLiteContext,
731 "TfLiteArmnnOpaqueDelegate: Unable to gather output tensor indices from node #%d: ",
732 nodeIndex);
733 return kTfLiteError;
734 }
735 const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]);
736 if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex))
737 {
738 return kTfLiteError;
739 }
740
741 const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
742 const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true);
743 const armnn::TensorInfo& filterTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteFilterTensor);
744
745 // TfLite uses NHWC tensors
746 const unsigned int inputHeight = inputTensorInfo.GetShape()[1];
747 const unsigned int inputWidth = inputTensorInfo.GetShape()[2];
748
749 const unsigned int filterHeight = filterTensorInfo.GetShape()[1];
750 const unsigned int filterWidth = filterTensorInfo.GetShape()[2];
751
Matthew Sloyanc52190a2023-05-08 11:33:55 +0100752 // This block determines the output shape of the transpose convolution.
753 // If the output shape tensor is a constant, we can access the data at load time and set the shape of the layer.
754 // If this is not constant, we do not have access to the shape data, so we have to use infer output shape.
755 if (IsConstantTensor(tfLiteOutputShapeTensor))
756 {
757 const armnn::TensorInfo outputShapeTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputShapeTensor);
758 std::vector<int32_t> outputShape(outputShapeTensorInfo.GetNumElements());
759 if (outputShapeTensorInfo.GetDataType() == armnn::DataType::Signed32)
760 {
761 for(unsigned int i=0; i < outputShapeTensorInfo.GetNumElements(); ++i)
762 {
763 outputShape[i] = static_cast<int32_t*>(TfLiteOpaqueTensorData(tfLiteOutputShapeTensor))[i];
764 }
765 }
766
767 if (outputShapeTensorInfo.GetDataType() == armnn::DataType::QAsymmU8)
768 {
769 for(unsigned int i=0; i < outputShapeTensorInfo.GetNumElements(); ++i)
770 {
771 outputShape[i] = static_cast<uint8_t*>(TfLiteOpaqueTensorData(tfLiteOutputShapeTensor))[i];
772 }
773 }
774
775 // Change from signed to unsigned int to store in TransposeConvolution2dDescriptor.
776 for (int dimension : outputShape)
777 {
778 descriptor.m_OutputShape.push_back(static_cast<unsigned int>(dimension));
779 }
780 descriptor.m_OutputShapeEnabled = true;
781
782 // TfLite uses NHWC tensors
783 const unsigned int outputHeight = descriptor.m_OutputShape[1];
784 const unsigned int outputWidth = descriptor.m_OutputShape[2];
785
786 CalcPadding(inputHeight,
787 filterHeight,
788 descriptor.m_StrideY,
789 1, // DilationY
790 descriptor.m_PadTop,
791 descriptor.m_PadBottom,
792 parameters->padding,
793 outputHeight);
794
795 CalcPadding(inputWidth,
796 filterWidth,
797 descriptor.m_StrideX,
798 1, // DilationX
799 descriptor.m_PadLeft,
800 descriptor.m_PadRight,
801 parameters->padding,
802 outputWidth);
803 }
804 else
805 {
806 CalcPadding(inputHeight,
807 filterHeight,
808 descriptor.m_StrideY,
809 1, // DilationY
810 descriptor.m_PadTop,
811 descriptor.m_PadBottom,
812 parameters->padding);
813
814 CalcPadding(inputWidth,
815 filterWidth,
816 descriptor.m_StrideX,
817 1, // DilationX
818 descriptor.m_PadLeft,
819 descriptor.m_PadRight,
820 parameters->padding);
821 }
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100822
823 // Set up filter
824 auto filterTensor = CreateConstTensor(tfLiteFilterTensor,
825 filterTensorInfo);
826 armnn::BackendId setBackend;
827 if (!delegateData.m_Network)
828 {
829 bool isSupported = false;
830 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("TRANSPOSE_CONV2D",
831 tfLiteContext,
832 IsTransposeConvolution2dSupported,
833 delegateData.m_Backends,
834 isSupported,
835 setBackend,
836 inputTensorInfo,
837 outputTensorInfo,
838 descriptor,
839 filterTensorInfo,
840 armnn::EmptyOptional());
841 return isSupported ? kTfLiteOk : kTfLiteError;
842 }
843
Mike Kellya2806502023-08-03 10:42:11 +0100844 auto layerName = GetName(armnn::LayerType::TransposeConvolution2d, nodeIndex);
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100845 armnn::IConnectableLayer* layer = delegateData.m_Network->AddTransposeConvolution2dLayer(descriptor,
846 filterTensor,
Mike Kellya2806502023-08-03 10:42:11 +0100847 armnn::EmptyOptional(),
848 layerName.c_str());
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100849 layer->SetBackendId(setBackend);
850 ARMNN_ASSERT(layer != nullptr);
851
852 // The data input can be constant, so we must check that this is allocated to an input slot
853 if(inputTensorInfo.IsConstant())
854 {
855 auto input = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
856
Mike Kellya2806502023-08-03 10:42:11 +0100857 auto inputName = GetName(armnn::LayerType::Constant, nodeIndex, "Input");
858 armnn::IConnectableLayer *inputLayer = delegateData.m_Network->AddConstantLayer(input, inputName.c_str());
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100859 inputLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
860 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
861 }
862
863 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
864 outputSlot.SetTensorInfo(outputTensorInfo);
865
866
867 // Connect
868 if (delegateData.m_OutputSlotForNode[static_cast<unsigned int>(inputTensors[2])] != nullptr)
869 {
870 delegateData.m_OutputSlotForNode[static_cast<unsigned int>(inputTensors[2])]->
871 Connect(layer->GetInputSlot(0));
872 }
873
874 if (Connect(layer, tfLiteContext, tfLiteNode, delegateData) != kTfLiteOk)
875 {
876 return kTfLiteError;
877 }
878
879 return kTfLiteOk;
880}
881
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100882TfLiteStatus VisitConvolutionOperator(DelegateData& delegateData,
883 TfLiteOpaqueContext* tfLiteContext,
884 TfLiteOpaqueNode* tfLiteNode,
885 int nodeIndex,
886 int32_t operatorCode)
887{
888 switch(operatorCode)
889 {
890 case kTfLiteBuiltinConv2d:
891 return VisitConv2dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, operatorCode);
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100892 case kTfLiteBuiltinConv3d:
893 return VisitConv3dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, operatorCode);
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100894 case kTfLiteBuiltinDepthwiseConv2d:
895 return VisitDepthwiseConv2dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, operatorCode);
Francis Murtagh3a9e7ba2023-04-26 15:58:39 +0100896 case kTfLiteBuiltinTransposeConv:
897 return VisitTransposeConv2dOperator(delegateData, tfLiteContext, tfLiteNode, nodeIndex, operatorCode);
Matthew Sloyan080ffd82023-04-24 12:53:04 +0100898 default:
899 return kTfLiteError;
900 }
901}
902
903}