blob: 7be06683a5e595f962310661d0d3e101045ec66e [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 Sloyan0bd4c622023-04-27 11:48:26 +01005
6#pragma once
7
8#include <OpaqueDelegateUtils.hpp>
9#include <SharedFunctions.hpp>
10
11namespace armnnOpaqueDelegate
12{
13
14TfLiteStatus VisitFullyConnectedOperator(DelegateData& delegateData,
15 TfLiteOpaqueContext* tfLiteContext,
16 TfLiteOpaqueNode* tfLiteNode,
17 int nodeIndex,
18 int32_t operatorCode)
19{
20 auto numInputs = TfLiteOpaqueNodeNumberOfInputs(tfLiteNode);
21 if (numInputs < 2)
22 {
23 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
24 tfLiteContext,
25 "TfLiteArmnnOpaqueDelegate: Minimum number of inputs (%d != %d) in node #%d",
26 2, numInputs, nodeIndex);
27 return kTfLiteError;
28 }
29 TF_LITE_ENSURE_STATUS(ValidateNumOutputs(tfLiteContext, tfLiteNode, 1, nodeIndex));
30
31 // Gather input indices and use to get input tensor.
32 const int* inputTensors;
33 if (TfLiteOpaqueNodeInputs(tfLiteNode, &inputTensors, &numInputs) != kTfLiteOk)
34 {
35 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
36 tfLiteContext,
37 "TfLiteArmnnOpaqueDelegate: Unable to gather input tensor indices from node #%d: ",
38 nodeIndex);
39 return kTfLiteError;
40 }
41
42 const TfLiteOpaqueTensor* tfLiteInputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[0]);
43 if (!IsValid(tfLiteContext, tfLiteInputTensor, operatorCode, nodeIndex))
44 {
45 return kTfLiteError;
46 }
47
48 const TfLiteOpaqueTensor* tfLiteWeightsTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[1]);
49 if (!IsValid(tfLiteContext, tfLiteWeightsTensor, operatorCode, nodeIndex))
50 {
51 return kTfLiteError;
52 }
53
54 // Gather output indices and use to get output tensors.
55 int numOutputs = 0;
56 const int* outputTensors;
57 if (TfLiteOpaqueNodeOutputs(tfLiteNode, &outputTensors, &numOutputs) != kTfLiteOk)
58 {
59 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
60 tfLiteContext,
61 "TfLiteArmnnOpaqueDelegate: Unable to gather output tensor indices from node #%d: ",
62 nodeIndex);
63 return kTfLiteError;
64 }
65
66 const TfLiteOpaqueTensor* tfLiteOutputTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, outputTensors[0]);
67 if (!IsValid(tfLiteContext, tfLiteOutputTensor, operatorCode, nodeIndex))
68 {
69 return kTfLiteError;
70 }
71
72 const armnn::TensorInfo& inputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
73 const armnn::TensorInfo& weightsTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteWeightsTensor);
74 const armnn::TensorInfo& outputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor, true);
75
76 // Check that we support fused activation before we attempt to create a layer
77 auto* tfLiteNodeParameters =
78 reinterpret_cast<TfLiteFullyConnectedParams*>(TfLiteOpaqueNodeGetBuiltinData(tfLiteNode));
79 TfLiteFusedActivation activationType=kTfLiteActNone;
80 if (tfLiteNodeParameters)
81 {
82 activationType = tfLiteNodeParameters->activation;
83 TfLiteStatus activationStatus = ValidateFusedActivationOperator(delegateData, tfLiteContext, outputTensorInfo,
84 outputTensorInfo, activationType);
85 if(activationStatus != kTfLiteOk)
86 {
87 return kTfLiteError;
88 }
89 }
90
91 // Fully Connected Layer accepts two dimensional weights input
92 int32_t weightsDimension = static_cast<int32_t>(weightsTensorInfo.GetNumDimensions());
93 if (weightsDimension != 2)
94 {
95 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
96 tfLiteContext,
97 "TfLiteArmnnOpaqueDelegate: Dimension #$d for Fully Connected weights is not supported by Armnn"
98 " in operator #%d node #%d: ", weightsDimension, operatorCode, nodeIndex);
99 return kTfLiteError;
100 }
101
102 armnn::TensorInfo biasTensorInfo;
103 const TfLiteOpaqueTensor* tfLiteBiasTensor = nullptr;
104
105 bool biasEnabled = IsOptionalOperandPresent(tfLiteNode, 2);
106 if (biasEnabled)
107 {
108 // Use input indices to get bias tensor.
109 tfLiteBiasTensor = TfLiteOpaqueContextGetOpaqueTensor(tfLiteContext, inputTensors[2]);
110 if (!IsValid(tfLiteContext, tfLiteBiasTensor, operatorCode, nodeIndex))
111 {
112 return kTfLiteError;
113 }
114 biasTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteBiasTensor);
115 }
116 else
117 {
118 biasTensorInfo = armnn::TensorInfo(armnn::TensorShape({1}), GetDataType(tfLiteInputTensor));
119 }
120
121 armnn::TensorInfo reshapedTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteInputTensor);
122 if (inputTensorInfo.GetNumDimensions() > 2)
123 {
124 // Calculate reshape to flatten to 2D [batch_size, input_size]
125 std::vector<unsigned int> reshapedDimensions(2);
126 reshapedDimensions[1] = weightsTensorInfo.GetShape()[1];
127 reshapedDimensions[0] = inputTensorInfo.GetNumElements() / reshapedDimensions[1];
128
129 if (inputTensorInfo.GetNumElements() % reshapedDimensions[1] != 0)
130 {
131 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
132 tfLiteContext,
133 "TfLiteArmnnOpaqueDelegate: Failed to deduce input tensor shape from filter size #%d #%d node #%d: ",
134 reshapedDimensions[1], operatorCode, nodeIndex);
135 return kTfLiteError;
136 }
137
138 reshapedTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
139 }
140 armnn::TensorInfo reshapedOutputTensorInfo = GetTensorInfoForTfLiteOpaqueTensor(tfLiteOutputTensor);
141
142 if (outputTensorInfo.GetNumDimensions() > 2)
143 {
144 // Calculate reshape to flatten to 2D [batch_size, input_size]
145 std::vector<unsigned int> reshapedDimensions(2);
146 reshapedDimensions[1] = weightsTensorInfo.GetShape()[0];
147 reshapedDimensions[0] = outputTensorInfo.GetNumElements() / reshapedDimensions[1];
148
149 if (outputTensorInfo.GetNumElements() % reshapedDimensions[1] != 0)
150 {
151 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
152 tfLiteContext,
153 "TfLiteArmnnOpaqueDelegate: Failed to deduce output tensor shape from filter size #%d #%d node #%d: ",
154 reshapedDimensions[1], operatorCode, nodeIndex);
155 return kTfLiteError;
156 }
157 reshapedOutputTensorInfo.SetShape(armnn::TensorShape{ 2, reshapedDimensions.data() });
158 }
159
160 armnn::FullyConnectedDescriptor descriptor;
161 descriptor.m_TransposeWeightMatrix = true;
162 descriptor.m_BiasEnabled = biasEnabled;
163 descriptor.m_ConstantWeights = weightsTensorInfo.IsConstant();
164
165 bool isSupported = false;
166 armnn::BackendId setBackend;
167 auto validateFunc = [&](const armnn::TensorInfo& outputTensorInfo, bool& isSupported)
168 {
169
170 FORWARD_LAYER_OPAQUE_SUPPORT_FUNC("FULLY_CONNECTED",
171 tfLiteContext,
172 IsFullyConnectedSupported,
173 delegateData.m_Backends,
174 isSupported,
175 setBackend,
176 reshapedTensorInfo,
177 outputTensorInfo,
178 weightsTensorInfo,
179 biasTensorInfo,
180 descriptor);
181 };
182
183 if (!delegateData.m_Network)
184 {
185 validateFunc(reshapedOutputTensorInfo, isSupported);
186 return isSupported ? kTfLiteOk : kTfLiteError;
187 }
188
Mike Kellya2806502023-08-03 10:42:11 +0100189 auto layerName = GetName(armnn::LayerType::FullyConnected, nodeIndex);
190 armnn::IConnectableLayer* layer = delegateData.m_Network->AddFullyConnectedLayer(descriptor, layerName.c_str());
Matthew Sloyan0bd4c622023-04-27 11:48:26 +0100191 layer->SetBackendId(setBackend);
192 ARMNN_ASSERT(layer != nullptr);
193
194 // Add a constant layer for weights and biases if inputs are constant.
195 if (weightsTensorInfo.IsConstant())
196 {
197 auto weightsTensor = CreateConstTensor(tfLiteWeightsTensor, weightsTensorInfo);
198
199 armnn::IConnectableLayer* weightsLayer = delegateData.m_Network->AddConstantLayer(weightsTensor);
200
201 weightsLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(1u));
202 weightsLayer->GetOutputSlot(0).SetTensorInfo(weightsTensorInfo);
203 }
204
205 if (biasEnabled)
206 {
207 if(biasTensorInfo.IsConstant())
208 {
209 auto biasTensor = CreateConstTensor(tfLiteBiasTensor, biasTensorInfo);
210
211 armnn::IConnectableLayer* biasLayer = delegateData.m_Network->AddConstantLayer(biasTensor);
212 ARMNN_ASSERT(biasLayer != nullptr);
213
214 biasLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(2u));
215 biasLayer->GetOutputSlot(0).SetTensorInfo(biasTensorInfo);
216 }
217 }
218
219 // The data input can also be constant, so we must check that this is also allocated to an input slot
220 if(inputTensorInfo.IsConstant())
221 {
222 auto input = CreateConstTensor(tfLiteInputTensor, inputTensorInfo);
223
224 armnn::IConnectableLayer* inputLayer = delegateData.m_Network->AddConstantLayer(input);
225 inputLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0u));
226 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
227 }
228
229 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
230 outputSlot.SetTensorInfo(outputTensorInfo);
231
232 armnn::IConnectableLayer* reshapeLayer = nullptr;
233 if (inputTensorInfo.GetNumDimensions() > 2)
234 {
235 // Add reshape to flatten to 2D [batch_size, input_size]
236 armnn::ReshapeDescriptor reshapeDescriptor;
237 reshapeDescriptor.m_TargetShape = reshapedTensorInfo.GetShape();
238 reshapeLayer = delegateData.m_Network->AddReshapeLayer(reshapeDescriptor);
239 ARMNN_ASSERT(reshapeLayer != nullptr);
240
241 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedTensorInfo);
242
243 // Connect
244 delegateData.m_OutputSlotForNode[inputTensors[0]]->Connect(reshapeLayer->GetInputSlot(0));
245 reshapeLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
246
247 if (!descriptor.m_ConstantWeights)
248 {
249 delegateData.m_OutputSlotForNode[inputTensors[1]]->Connect(layer->GetInputSlot(1));
250 }
251
252 if (biasEnabled && !biasTensorInfo.IsConstant())
253 {
254 delegateData.m_OutputSlotForNode[inputTensors[2]]->Connect(layer->GetInputSlot(2));
255 }
256 delegateData.m_OutputSlotForNode[outputTensors[0]] = &outputSlot;
257 }
258
259 if (reshapeLayer == nullptr)
260 {
261 if(Connect(layer, tfLiteContext, tfLiteNode, delegateData) != kTfLiteOk)
262 {
263 return kTfLiteError;
264 }
265 }
266
267 if (outputTensorInfo.GetNumDimensions() > 2)
268 {
269 layer = AddReshapeLayer(tfLiteContext,
270 tfLiteNode,
271 layer,
272 reshapedOutputTensorInfo,
273 outputTensorInfo,
Mike Kellya2806502023-08-03 10:42:11 +0100274 delegateData,
275 nodeIndex);
Matthew Sloyan0bd4c622023-04-27 11:48:26 +0100276 if (!layer)
277 {
278 TF_LITE_OPAQUE_MAYBE_KERNEL_LOG(
279 tfLiteContext,
280 "TfLiteArmnnOpaqueDelegate: Failed to add reshape for FullyConnected #%d node #%d: ",
281 operatorCode,
282 nodeIndex);
283 return kTfLiteError;
284 }
285 }
286
287 if (!tfLiteNodeParameters)
288 {
289 // No Activation
290 return kTfLiteOk;
291 }
292
293 // Check and Create Activation
Mike Kellya2806502023-08-03 10:42:11 +0100294 return FusedActivation(tfLiteContext, tfLiteNode, activationType, layer, 0, delegateData, nodeIndex);
Matthew Sloyan0bd4c622023-04-27 11:48:26 +0100295}
296
297} // namespace armnnOpaqueDelegate