blob: 719d1a24521f114449adec6d14a85848b1242016 [file] [log] [blame]
arovir01b0717b52018-09-05 17:03:25 +01001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// SPDX-License-Identifier: MIT
4//
5
6#include "HalPolicy.hpp"
7
arovir015602b192018-10-04 16:15:02 +01008#include "armnn/Optional.hpp"
9
arovir01b0717b52018-09-05 17:03:25 +010010namespace armnn_driver
11{
12namespace hal_1_0
13{
14
15bool HalPolicy::ConvertOperation(const Operation& operation, const Model& model, ConversionData& data)
16{
17 switch (operation.type)
18 {
19 case V1_0::OperationType::ADD:
20 return ConvertAdd(operation, model, data);
21 case V1_0::OperationType::AVERAGE_POOL_2D:
22 return ConvertAveragePool2d(operation, model, data);
23 case V1_0::OperationType::CONCATENATION:
24 return ConvertConcatenation(operation, model, data);
25 case V1_0::OperationType::CONV_2D:
26 return ConvertConv2d(operation, model, data);
27 case V1_0::OperationType::DEPTHWISE_CONV_2D:
28 return ConvertDepthwiseConv2d(operation, model, data);
29 case V1_0::OperationType::FLOOR:
30 return ConvertFloor(operation, model, data);
31 case V1_0::OperationType::FULLY_CONNECTED:
32 return ConvertFullyConnected(operation, model, data);
33 case V1_0::OperationType::LOCAL_RESPONSE_NORMALIZATION:
34 return ConvertLocalResponseNormalization(operation, model, data);
35 case V1_0::OperationType::LOGISTIC:
36 return ConvertLogistic(operation, model, data);
37 case V1_0::OperationType::LSTM:
38 return ConvertLstm(operation, model, data);
39 case V1_0::OperationType::L2_NORMALIZATION:
40 return ConvertL2Normalization(operation, model, data);
41 case V1_0::OperationType::L2_POOL_2D:
42 return ConvertL2Pool2d(operation, model, data);
43 case V1_0::OperationType::MAX_POOL_2D:
44 return ConvertMaxPool2d(operation, model, data);
45 case V1_0::OperationType::MUL:
46 return ConvertMul(operation, model, data);
47 case V1_0::OperationType::RELU:
48 return ConvertReLu(operation, model, data);
49 case V1_0::OperationType::RELU1:
50 return ConvertReLu1(operation, model, data);
51 case V1_0::OperationType::RELU6:
52 return ConvertReLu6(operation, model, data);
53 case V1_0::OperationType::SOFTMAX:
54 return ConvertSoftmax(operation, model, data);
55 case V1_0::OperationType::TANH:
56 return ConvertTanH(operation, model, data);
57 case V1_0::OperationType::RESHAPE:
58 return ConvertReshape(operation, model, data);
59 case V1_0::OperationType::RESIZE_BILINEAR:
60 return ConvertResizeBilinear(operation, model, data);
61 default:
62 return Fail("%s: Operation type %s not supported in ArmnnDriver",
63 __func__, toString(operation.type).c_str());
64 }
65}
66
67bool HalPolicy::ConvertAdd(const Operation& operation, const Model& model, ConversionData& data)
68{
69 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
70 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
71
72 if (!input0.IsValid() || !input1.IsValid())
73 {
74 return Fail("%s: Operation has invalid inputs", __func__);
75 }
76
77 // The FuseActivation parameter is always the input index 2
78 // and it should be optional
79 ActivationFn activationFunction;
80 if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
81 {
82 return Fail("%s: Operation has invalid inputs", __func__);
83 }
84
85 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
86 if (!outputOperand)
87 {
88 return false;
89 }
90
91 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
92
93 if (!IsLayerSupported(__func__,
94 armnn::IsAdditionSupported,
95 data.m_Compute,
96 input0.GetTensorInfo(),
97 input1.GetTensorInfo(),
98 outInfo))
99 {
100 return false;
101 }
102
103 armnn::IConnectableLayer* const startLayer = data.m_Network->AddAdditionLayer();
104 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer, data);
105
106 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
107 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
108
109 if (endLayer != nullptr)
110 {
111 BroadcastTensor(input0, input1, startLayer, *data.m_Network);
112 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data);
113 }
114 else
115 {
116 return Fail("%s: ProcessActivation failed", __func__);
117 }
118}
119
120bool HalPolicy::ConvertAveragePool2d(const Operation& operation, const Model& model, ConversionData& data)
121{
122 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Average, model, data);
123}
124
125bool HalPolicy::ConvertConcatenation(const Operation& operation, const Model& model, ConversionData& data)
126{
127 // The first N (0..N-1) inputs are tensors. The Nth input is the concatenation axis.
128 if (operation.inputs.size() <= 1)
129 {
130 return Fail("%s: Operation has insufficient arguments", __func__);
131 }
132
133 // Get inputs and outputs
134 const std::size_t numInputTensors = operation.inputs.size() - 1;
135
136 int32_t concatDim;
137 if (!GetInputScalar(operation, numInputTensors, OperandType::INT32, concatDim, model, data))
138 {
139 return Fail("%s: Operation has invalid inputs", __func__);
140 }
141
142 const Operand* const outputOperand = GetOutputOperand(operation, 0, model);
143 if (!outputOperand)
144 {
145 return Fail("%s: Operation has no outputs", __func__);
146 }
147
148
149 armnn::TensorInfo outputInfo = GetTensorInfoForOperand(*outputOperand);
150 armnn::TensorShape outputShape = outputInfo.GetShape();
151
152 //
153 // handle negative concat dims along the lines of tensorflow as described here:
154 // https://www.tensorflow.org/api_docs/python/tf/concat
155 // "negative axis refers to axis + rank(values)-th dimension"
156 //
157 if (concatDim < 0)
158 {
159 concatDim += outputShape.GetNumDimensions();
160 }
161
162 if (concatDim >= static_cast<int32_t>(outputShape.GetNumDimensions()) || concatDim < 0)
163 {
164 return Fail("%s: Operation has invalid concat axis: %d", __func__, concatDim);
165 }
166
167 std::vector<LayerInputHandle> inputHandles;
168 std::vector<armnn::TensorShape> inputShapes;
169
170 inputHandles.reserve(numInputTensors);
171 inputShapes.reserve(numInputTensors);
172
173 bool inputsHaveBeenReshaped = false;
174 unsigned int tensorDimensionsAdded = 0;
175
176 for (uint32_t i = 0; i < numInputTensors; ++i)
177 {
178 const Operand* const operand = GetInputOperand(operation, i, model);
179 if (!operand)
180 {
181 return Fail("%s: Operation has invalid inputs", __func__);
182 }
183
184 armnn::TensorShape operandShape = GetTensorShapeForOperand(*operand);
185 LayerInputHandle operandInputHandle = ConvertToLayerInputHandle(operation, i, model, data);
186
187 if (operandShape.GetNumDimensions() == 0)
188 {
189 return Fail("%s: Operands with rank 0 are not supported", __func__);
190 }
191
192 if (RequiresReshape(operandShape))
193 {
194 inputsHaveBeenReshaped = true;
195
196 armnn::TensorInfo reshapeInfo = operandInputHandle.GetTensorInfo();
197
198 // Expand the tensor to three dimensions
199 if (operandShape.GetNumDimensions() == 2)
200 {
201 reshapeInfo.SetShape(armnn::TensorShape({1, operandShape[0], operandShape[1]}));
202 tensorDimensionsAdded = 1;
203 }
204 else
205 {
206 reshapeInfo.SetShape(armnn::TensorShape({1, 1, operandShape[0]}));
207 tensorDimensionsAdded = 2;
208 }
209
210 armnn::IConnectableLayer& newReshape = AddReshapeLayer(
211 *data.m_Network,
212 operandInputHandle,
213 reshapeInfo
214 );
215
216 // Point to the reshape operation rather then the input operation
217 operandShape = reshapeInfo.GetShape();
218 operandInputHandle = LayerInputHandle(true, &newReshape.GetOutputSlot(0), reshapeInfo);
219 }
220
221 inputShapes.emplace_back(operandShape);
222 inputHandles.emplace_back(operandInputHandle);
223
224 if (!inputHandles.back().IsValid())
225 {
226 return Fail("%s: Operation has invalid inputs", __func__);
227 }
228 }
229
230 BOOST_ASSERT(inputShapes.size() == inputHandles.size());
231
232 if (inputsHaveBeenReshaped)
233 {
234 // Adjust the concatenation dimension by the amount of dimensions added (if any)
235 concatDim += tensorDimensionsAdded;
236
237 // Add extra dimensions to the output shape to reflect the addition of the reshape layers
238 if (tensorDimensionsAdded == 1)
239 {
240 outputShape = armnn::TensorShape({1, outputShape[0], outputShape[1]});
241 }
242 else if (tensorDimensionsAdded == 2)
243 {
narpra01f176d5a2018-11-18 20:17:48 +0000244 outputShape = armnn::TensorShape({1, 1, outputShape[0]});
arovir01b0717b52018-09-05 17:03:25 +0100245 }
246 }
247
narpra01f176d5a2018-11-18 20:17:48 +0000248 // Check if permutations is required and get the pair of permutations required for the concatenation.
249 // Permutation is required when the concat dimension is 2 for a 4D tensor or 1 for a 3D tensor.
arovir01b0717b52018-09-05 17:03:25 +0100250 std::pair<armnn::PermutationVector, armnn::PermutationVector> permutationPair =
251 std::make_pair(IdentityPermutation4D, IdentityPermutation4D);
252
narpra01f176d5a2018-11-18 20:17:48 +0000253 bool needPermute = CreateConcatPermutationParameters(inputShapes[0].GetNumDimensions(), concatDim, permutationPair);
arovir01b0717b52018-09-05 17:03:25 +0100254
narpra01f176d5a2018-11-18 20:17:48 +0000255 if (needPermute)
256 {
257 outputShape = armnnUtils::Permuted(outputShape, permutationPair.first);
258 }
259
arovir01b0717b52018-09-05 17:03:25 +0100260 outputInfo.SetShape(outputShape);
261
262 // this is no-op for identity swizzles, otherwise it replaces both
263 // the handles and shapes with the swizzled layer output handles and shapes
264 SwizzleInputs(*data.m_Network, inputHandles, inputShapes, permutationPair.first);
265
266 // Create an armnn merger layer descriptor - this will also perform validation on the input shapes
267 armnn::OriginsDescriptor mergerDescriptor;
narpra01f176d5a2018-11-18 20:17:48 +0000268
arovir01b0717b52018-09-05 17:03:25 +0100269 try
270 {
narpra01f176d5a2018-11-18 20:17:48 +0000271 // The merger descriptor is always created across the only supported concat dimension
272 // which is 0, 1 or 3 for a 4-D tensor, or 0 or 2 for a 3-D tensor.
arovir01b0717b52018-09-05 17:03:25 +0100273 mergerDescriptor =
274 armnn::CreateMergerDescriptorForConcatenation(
275 inputShapes.begin(), inputShapes.end(), concatDim);
276 }
277 catch (const armnn::Exception& error)
278 {
279 return Fail("%s: Error preparing merger descriptor. %s", __func__, error.what());
280 }
281
282 // Validate the output shape is correct given the input shapes based on the
narpra01f176d5a2018-11-18 20:17:48 +0000283 // only valid concat dimension which is 0, 1 or 3 for a 4-D tensor, or 0 or 2 for a 3-D tensor.
arovir01b0717b52018-09-05 17:03:25 +0100284 if (!ValidateConcatOutputShape(inputShapes, outputShape, concatDim))
285 {
286 return Fail("%s: Error validating the output shape for concat", __func__);
287 }
288
289 std::vector<const armnn::TensorInfo*> inputTensorInfos;
290 std::transform(inputHandles.begin(), inputHandles.end(), std::back_inserter(inputTensorInfos),
291 [](const LayerInputHandle& h) -> const armnn::TensorInfo*{ return &h.GetTensorInfo(); });
292 if (!IsLayerSupported(__func__,
293 armnn::IsMergerSupported,
294 data.m_Compute,
295 inputTensorInfos,
narpra01f176d5a2018-11-18 20:17:48 +0000296 outputInfo,
arovir01b0717b52018-09-05 17:03:25 +0100297 mergerDescriptor))
298 {
299 return false;
300 }
301
302 armnn::IConnectableLayer* layer = data.m_Network->AddMergerLayer(mergerDescriptor);
303 assert(layer != nullptr);
304 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
305
306 // Connect inputs to the layer
307 const int numInputSlots = layer->GetNumInputSlots();
308 assert(static_cast<std::size_t>(numInputSlots) == inputHandles.size());
309 for (int i = 0; i < numInputSlots; ++i)
310 {
311 // connect the input directly to the merge (concat) layer
312 inputHandles[static_cast<unsigned int>(i)].Connect(layer->GetInputSlot(i));
313 }
314
narpra01f176d5a2018-11-18 20:17:48 +0000315 if (needPermute)
316 {
317 // Add permutation layer and connect the output to it, the permutation becomes the output layer
318 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(*data.m_Network,
319 layer->GetOutputSlot(0),
320 permutationPair.second);
321 layer = &deswizzleLayer;
322 }
arovir01b0717b52018-09-05 17:03:25 +0100323
324 if (inputsHaveBeenReshaped)
325 {
326 armnn::TensorInfo afterConcatInfo = layer->GetOutputSlot(0).GetTensorInfo();
327
328 // Undo the reshape knowing the amount of dimensions added
329 if (tensorDimensionsAdded == 1)
330 {
331 afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[1],
332 afterConcatInfo.GetShape()[2] }));
333 }
334 else if (tensorDimensionsAdded == 2)
335 {
narpra01f176d5a2018-11-18 20:17:48 +0000336 afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[2] }));
arovir01b0717b52018-09-05 17:03:25 +0100337 }
338
339 layer = &AddReshapeLayer(
340 *data.m_Network,
341 layer->GetOutputSlot(0),
342 afterConcatInfo
343 );
344 }
345
346 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
347}
348
349bool HalPolicy::ConvertConv2d(const Operation& operation, const Model& model, ConversionData& data)
350{
351 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
352 if (!input.IsValid())
353 {
354 return Fail("%s: Operation has invalid inputs", __func__);
355 }
356
357 const Operand* output = GetOutputOperand(operation, 0, model);
358 if (!output)
359 {
360 return Fail("%s: Could not read output 0", __func__);
361 }
362
363 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
364 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
365
arovir01b0717b52018-09-05 17:03:25 +0100366 // ArmNN does not currently support non-fixed weights or bias
narpra01fb60a562018-10-30 15:46:01 +0000367 const ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, model, data);
arovir01b0717b52018-09-05 17:03:25 +0100368 const ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2, model, data);
369
370 if (!weightsPin.IsValid() || !biasPin.IsValid())
371 {
372 return Fail("%s: Operation has invalid inputs", __func__);
373 }
374
375 armnn::ConstTensor weights = weightsPin.GetConstTensor();
376 armnn::ConstTensor bias = biasPin.GetConstTensor();
narpra01fb60a562018-10-30 15:46:01 +0000377 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), inputInfo);
arovir01b0717b52018-09-05 17:03:25 +0100378
379 armnn::Convolution2dDescriptor desc;
narpra01fb60a562018-10-30 15:46:01 +0000380 desc.m_DataLayout = armnn::DataLayout::NHWC;
arovir01b0717b52018-09-05 17:03:25 +0100381 ActivationFn activation;
382
383 if (operation.inputs.size() == 10)
384 {
385 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data) ||
386 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data) ||
387 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data) ||
388 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data) ||
389 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data) ||
390 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data) ||
391 !GetInputActivationFunction(operation, 9, activation, model, data))
392 {
393 return Fail("%s: Operation has invalid inputs", __func__);
394 }
395 }
396 else if (operation.inputs.size() == 7)
397 {
398 android::nn::PaddingScheme paddingScheme;
399 if (!GetInputPaddingScheme(operation, 3, paddingScheme, model, data) ||
400 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX, model, data) ||
401 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY, model, data) ||
402 !GetInputActivationFunction(operation, 6, activation, model, data))
403 {
404 return Fail("%s: Operation has invalid inputs", __func__);
405 }
406
narpra01fb60a562018-10-30 15:46:01 +0000407 const uint32_t kernelX = weights.GetShape()[2];
408 const uint32_t kernelY = weights.GetShape()[1];
409 const uint32_t inputX = inputInfo.GetShape()[2];
410 const uint32_t inputY = inputInfo.GetShape()[1];
arovir01b0717b52018-09-05 17:03:25 +0100411
412 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
413 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
414 }
415 else
416 {
417 return Fail("%s: Unsupported number of operation inputs", __func__);
418 }
419
420 desc.m_BiasEnabled = true;
arovir015602b192018-10-04 16:15:02 +0100421 armnn::Optional<armnn::TensorInfo> biases(bias.GetInfo());
arovir01b0717b52018-09-05 17:03:25 +0100422
423 if (!IsLayerSupported(__func__,
424 armnn::IsConvolution2dSupported,
425 data.m_Compute,
narpra01fb60a562018-10-30 15:46:01 +0000426 inputInfo,
427 outputInfo,
arovir01b0717b52018-09-05 17:03:25 +0100428 desc,
429 weights.GetInfo(),
430 biases))
431 {
432 return false;
433 }
434
435 armnn::IConnectableLayer* startLayer = data.m_Network->AddConvolution2dLayer(desc, weights, bias);
arovir01b0717b52018-09-05 17:03:25 +0100436
narpra01fb60a562018-10-30 15:46:01 +0000437 if (!startLayer)
arovir01b0717b52018-09-05 17:03:25 +0100438 {
narpra01fb60a562018-10-30 15:46:01 +0000439 return Fail("%s: AddConvolution2dLayer failed", __func__);
arovir01b0717b52018-09-05 17:03:25 +0100440 }
narpra01fb60a562018-10-30 15:46:01 +0000441
442 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activation, startLayer, data);
443
444 if (!endLayer)
arovir01b0717b52018-09-05 17:03:25 +0100445 {
446 return Fail("%s: ProcessActivation failed", __func__);
447 }
narpra01fb60a562018-10-30 15:46:01 +0000448
449 input.Connect(startLayer->GetInputSlot(0));
450
451 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data);
arovir01b0717b52018-09-05 17:03:25 +0100452}
453
454bool HalPolicy::ConvertDepthwiseConv2d(const Operation& operation, const Model& model, ConversionData& data)
455{
456 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
457 if (!input.IsValid())
458 {
459 return Fail("%s: Operation has invalid inputs", __func__);
460 }
461
462 const Operand* output = GetOutputOperand(operation, 0, model);
463 if (!output)
464 {
465 return Fail("%s: Could not read output 0", __func__);
466 }
467
468 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
469 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
470
arovir01b0717b52018-09-05 17:03:25 +0100471 // ArmNN does not currently support non-fixed weights or bias
472
473 // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
James Conroy6bf1cf02018-10-12 14:13:18 +0100474 // which is equal to [ M, H, W, I ]
arovir01b0717b52018-09-05 17:03:25 +0100475 const Operand* weightsOperand = GetInputOperand(operation, 1, model);
476
477 if (weightsOperand == nullptr)
478 {
479 return Fail("%s: Operand is invalid", __func__);
480 }
481
482 // Reinterpret weight data as [ H, W, I, M ]
483 armnn::TensorShape weightsShape({ weightsOperand->dimensions[1], weightsOperand->dimensions[2],
484 inputInfo.GetShape()[3],
485 weightsOperand->dimensions[3] / inputInfo.GetShape()[3] });
486
James Conroy6bf1cf02018-10-12 14:13:18 +0100487 // Swizzle weight data [ H, W, I, M ] -> [ M, H, W, I ]
488 const armnn::PermutationVector HWIMToMHWI = { 1U, 2U, 3U, 0U };
489
arovir01b0717b52018-09-05 17:03:25 +0100490 ConstTensorPin weightsPin =
James Conroy6bf1cf02018-10-12 14:13:18 +0100491 ConvertOperationInputToConstTensorPin(operation, 1, model, data, HWIMToMHWI, &weightsShape);
arovir01b0717b52018-09-05 17:03:25 +0100492
493 // Bias is a 1D tensor
494 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2, model, data);
495
496 if (!weightsPin.IsValid() || !biasPin.IsValid())
497 {
498 return Fail("%s: Operation has invalid inputs", __func__);
499 }
500
501 armnn::ConstTensor weights = weightsPin.GetConstTensor();
502 armnn::ConstTensor bias = biasPin.GetConstTensor();
James Conroy6bf1cf02018-10-12 14:13:18 +0100503 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), inputInfo);
arovir01b0717b52018-09-05 17:03:25 +0100504
505 armnn::DepthwiseConvolution2dDescriptor desc;
James Conroy6bf1cf02018-10-12 14:13:18 +0100506 desc.m_DataLayout = armnn::DataLayout::NHWC;
arovir01b0717b52018-09-05 17:03:25 +0100507 ActivationFn activation;
508
509 if (operation.inputs.size() == 11)
510 {
James Conroy6bf1cf02018-10-12 14:13:18 +0100511 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft, model, data) ||
512 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight, model, data) ||
513 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop, model, data) ||
514 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom, model, data) ||
515 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX, model, data) ||
516 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY, model, data) ||
arovir01b0717b52018-09-05 17:03:25 +0100517 !GetInputActivationFunction(operation, 10, activation, model, data))
518 {
519 return Fail("%s: Operation has invalid inputs", __func__);
520 }
521 }
522 else if (operation.inputs.size() == 8)
523 {
524 android::nn::PaddingScheme paddingScheme;
James Conroy6bf1cf02018-10-12 14:13:18 +0100525 if (!GetInputPaddingScheme(operation, 3, paddingScheme, model, data) ||
526 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX, model, data) ||
527 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY, model, data) ||
arovir01b0717b52018-09-05 17:03:25 +0100528 !GetInputActivationFunction(operation, 7, activation, model, data))
529 {
530 return Fail("%s: Operation has invalid inputs", __func__);
531 }
532
James Conroy6bf1cf02018-10-12 14:13:18 +0100533 const uint32_t kernelX = weights.GetShape()[2];
534 const uint32_t kernelY = weights.GetShape()[1];
535 const uint32_t inputX = inputInfo.GetShape()[2];
536 const uint32_t inputY = inputInfo.GetShape()[1];
arovir01b0717b52018-09-05 17:03:25 +0100537
538 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
539 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
540 }
541 else
542 {
543 return Fail("%s: Unsupported number of operation inputs", __func__);
544 }
545
546 desc.m_BiasEnabled = true;
arovir015602b192018-10-04 16:15:02 +0100547 armnn::Optional<armnn::TensorInfo> biases(bias.GetInfo());
arovir01b0717b52018-09-05 17:03:25 +0100548
549 if (!IsLayerSupported(__func__,
550 armnn::IsDepthwiseConvolutionSupported,
551 data.m_Compute,
James Conroy6bf1cf02018-10-12 14:13:18 +0100552 inputInfo,
553 outputInfo,
arovir01b0717b52018-09-05 17:03:25 +0100554 desc,
555 weights.GetInfo(),
556 biases))
557 {
558 return false;
559 }
560
561 armnn::IConnectableLayer* startLayer = data.m_Network->AddDepthwiseConvolution2dLayer(desc, weights, bias);
James Conroy6bf1cf02018-10-12 14:13:18 +0100562 if (!startLayer)
arovir01b0717b52018-09-05 17:03:25 +0100563 {
James Conroy6bf1cf02018-10-12 14:13:18 +0100564 return Fail("%s: AddDepthwiseConvolution2dLayer failed", __func__);
arovir01b0717b52018-09-05 17:03:25 +0100565 }
James Conroy6bf1cf02018-10-12 14:13:18 +0100566
567 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activation, startLayer, data);
568 if (!endLayer)
arovir01b0717b52018-09-05 17:03:25 +0100569 {
570 return Fail("%s: ProcessActivation failed", __func__);
571 }
James Conroy6bf1cf02018-10-12 14:13:18 +0100572
573 input.Connect(startLayer->GetInputSlot(0));
574
575 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data);
arovir01b0717b52018-09-05 17:03:25 +0100576}
577
578bool HalPolicy::ConvertFloor(const Operation& operation, const Model& model, ConversionData& data)
579{
580 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
581 if (!input.IsValid())
582 {
583 return Fail("%s: Operation has invalid inputs", __func__);
584 }
585
586 const Operand* const outputOperand = GetOutputOperand(operation, 0, model);
587 if (!outputOperand)
588 {
589 return Fail("%s: Operation has invalid outputs", __func__);
590 }
591
592 if (!IsLayerSupported(__func__,
593 armnn::IsFloorSupported,
594 data.m_Compute,
595 input.GetTensorInfo(),
596 GetTensorInfoForOperand(*outputOperand)))
597 {
598 return false;
599 }
600
601 armnn::IConnectableLayer* layer = data.m_Network->AddFloorLayer();
602 assert(layer != nullptr);
603 input.Connect(layer->GetInputSlot(0));
604
605 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
606}
607
608bool HalPolicy::ConvertFullyConnected(const Operation& operation, const Model& model, ConversionData& data)
609{
610 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
611 if (!input.IsValid())
612 {
613 return Fail("%s: Operation has invalid inputs", __func__);
614 }
615
616 const Operand* output = GetOutputOperand(operation, 0, model);
617 if (!output)
618 {
619 return Fail("%s: Could not read output 0", __func__);
620 }
621
622 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
623 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
624
625 // ArmNN does not currently support non-fixed weights or bias
626 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, model, data); // 2D
627 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2, model, data); // 1D
628
629 if (!weightsPin.IsValid() || !biasPin.IsValid())
630 {
631 return Fail("%s: Operation has invalid inputs", __func__);
632 }
633
634 armnn::ConstTensor weights = weightsPin.GetConstTensor();
635 armnn::ConstTensor bias = biasPin.GetConstTensor();
636
637 armnn::TensorInfo reshapedInfo = inputInfo;
638 if (inputInfo.GetNumDimensions() > 2U)
639 {
640 unsigned int dim0 = inputInfo.GetShape()[0];
641 unsigned int dim1 = inputInfo.GetShape()[1];
642
643 for (unsigned int i = 2U; i < inputInfo.GetNumDimensions(); ++i)
644 {
645 dim1 *= inputInfo.GetShape()[i];
646 }
647
648 unsigned int divisor = weights.GetInfo().GetShape()[1] / dim1;
649 if(dim0 % divisor != 0)
650 {
651 return Fail("%s: Failed to deduce tensor shape", __func__);
652 }
653
654 reshapedInfo.SetShape(armnn::TensorShape({dim0 / divisor, dim1 * divisor}));
655 }
656
657 // ensuring that the bias value is within 1% of the weights input (small float differences can exist)
658 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), reshapedInfo);
659
660 ActivationFn activationFunction;
661 if (!GetInputActivationFunction(operation, 3, activationFunction, model, data))
662 {
663 return Fail("%s: Operation has invalid inputs", __func__);
664 }
665
666 armnn::FullyConnectedDescriptor desc;
667 desc.m_TransposeWeightMatrix = true;
668 desc.m_BiasEnabled = true;
669
670 if (!IsLayerSupported(__func__,
671 armnn::IsFullyConnectedSupported,
672 data.m_Compute,
673 inputInfo,
674 outputInfo,
675 weights.GetInfo(),
676 bias.GetInfo(),
677 desc))
678 {
679 return false;
680 }
681
682 armnn::IConnectableLayer* startLayer = data.m_Network->AddFullyConnectedLayer(desc, weights, bias);
683 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activationFunction, startLayer, data);
684
685 if (endLayer != nullptr)
686 {
687 if (inputInfo.GetNumDimensions() > 2U)
688 {
689 armnn::ReshapeDescriptor reshapeDescriptor;
690 reshapeDescriptor.m_TargetShape = reshapedInfo.GetShape();
691
692 armnn::IConnectableLayer* reshapeLayer = data.m_Network->AddReshapeLayer(reshapeDescriptor);
693 assert(reshapeLayer != nullptr);
694 input.Connect(reshapeLayer->GetInputSlot(0));
695 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
696 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
697 }
698 else
699 {
700 input.Connect(startLayer->GetInputSlot(0));
701 }
702
703 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data);
704 }
705 else
706 {
707 return Fail("%s: ProcessActivation failed", __func__);
708 }
709}
710
711bool HalPolicy::ConvertLocalResponseNormalization(const Operation& operation,
712 const Model& model,
713 ConversionData& data)
714{
715 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
716 if (!input.IsValid())
717 {
718 return Fail("%s: Operation has invalid inputs", __func__);
719 }
720
721 const Operand* output = GetOutputOperand(operation, 0, model);
722 if (!output)
723 {
724 return Fail("%s: Could not read output 0", __func__);
725 }
726
narpra012fb804a2018-10-22 14:52:32 +0100727 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
arovir01b0717b52018-09-05 17:03:25 +0100728 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
729
arovir01b0717b52018-09-05 17:03:25 +0100730 armnn::NormalizationDescriptor descriptor;
731
narpra012fb804a2018-10-22 14:52:32 +0100732 descriptor.m_DataLayout = armnn::DataLayout::NHWC;
arovir01b0717b52018-09-05 17:03:25 +0100733 descriptor.m_NormChannelType = armnn::NormalizationAlgorithmChannel::Across;
narpra012fb804a2018-10-22 14:52:32 +0100734 descriptor.m_NormMethodType = armnn::NormalizationAlgorithmMethod::LocalBrightness;
arovir01b0717b52018-09-05 17:03:25 +0100735
736 if (!input.IsValid() ||
737 !GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_NormSize, model, data) ||
738 !GetInputFloat32(operation, 2, descriptor.m_K, model, data) ||
739 !GetInputFloat32(operation, 3, descriptor.m_Alpha, model, data) ||
740 !GetInputFloat32(operation, 4, descriptor.m_Beta, model, data))
741 {
742 return Fail("%s: Operation has invalid inputs", __func__);
743 }
744
745 // ArmNN expects normSize to be the full size of the normalization
746 // window rather than the radius as in AndroidNN.
747 descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize);
748
749 if (!IsLayerSupported(__func__,
750 armnn::IsNormalizationSupported,
751 data.m_Compute,
narpra012fb804a2018-10-22 14:52:32 +0100752 inputInfo,
753 outputInfo,
arovir01b0717b52018-09-05 17:03:25 +0100754 descriptor))
755 {
756 return false;
757 }
758
759
760 armnn::IConnectableLayer* layer = data.m_Network->AddNormalizationLayer(descriptor);
761 assert(layer != nullptr);
narpra012fb804a2018-10-22 14:52:32 +0100762 input.Connect(layer->GetInputSlot(0));
arovir01b0717b52018-09-05 17:03:25 +0100763
narpra012fb804a2018-10-22 14:52:32 +0100764 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
arovir01b0717b52018-09-05 17:03:25 +0100765}
766
767bool HalPolicy::ConvertLogistic(const Operation& operation, const Model& model, ConversionData& data)
768{
769 armnn::ActivationDescriptor desc;
770 desc.m_Function = armnn::ActivationFunction::Sigmoid;
771
772 return ConvertToActivation(operation, __func__, desc, model, data);
773}
774
775bool HalPolicy::ConvertLstm(const Operation& operation, const Model& model, ConversionData& data)
776{
777 // Inputs:
778 // 00: The input: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, input_size], where
779 // “batch_size” corresponds to the batching dimension, and “input_size” is the size of the input.
780 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
781 if (!input.IsValid())
782 {
783 return Fail("%s: Could not read input 0: input", __func__);
784 }
785 // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
786 LayerInputHandle outputStateIn = ConvertToLayerInputHandle(operation, 18, model, data);
787 if (!outputStateIn.IsValid())
788 {
789 return Fail("%s: Could not read input 18: outputStateIn", __func__);
790 }
791 // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
792 LayerInputHandle cellStateIn = ConvertToLayerInputHandle(operation, 19, model, data);
793 if (!cellStateIn.IsValid())
794 {
795 return Fail("%s: Could not read input 19: cellStateIn", __func__);
796 }
797
798 // Get the mandatory input tensors:
799 // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
800 // [num_units, input_size].
801 const ConstTensorPin inputToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 2, model, data);
802 // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units, input_size].
803 const ConstTensorPin inputToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 3, model, data);
804 // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
805 // [num_units, input_size].
806 const ConstTensorPin inputToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 4, model, data);
807 // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
808 // [num_units, output_size].
809 const ConstTensorPin recurrentToForgetWeightsPin =
810 ConvertOperationInputToConstTensorPin(operation, 6, model, data);
811 // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
812 // [num_units, output_size].
813 const ConstTensorPin recurrentToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 7, model, data);
814 // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
815 // [num_units, output_size].
816 const ConstTensorPin recurrentToOutputWeightsPin =
817 ConvertOperationInputToConstTensorPin(operation, 8, model, data);
818 // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
819 const ConstTensorPin forgetGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 13, model, data);
820 // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
821 const ConstTensorPin cellBiasPin = ConvertOperationInputToConstTensorPin(operation, 14, model, data);
822 // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
823 const ConstTensorPin outputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 15, model, data);
824
825 if (!inputToForgetWeightsPin.IsValid() ||
826 !inputToCellWeightsPin.IsValid() ||
827 !inputToOutputWeightsPin.IsValid() ||
828 !recurrentToForgetWeightsPin.IsValid() ||
829 !recurrentToCellWeightsPin.IsValid() ||
830 !recurrentToOutputWeightsPin.IsValid() ||
831 !forgetGateBiasPin.IsValid() ||
832 !cellBiasPin.IsValid() ||
833 !outputGateBiasPin.IsValid())
834 {
835 return Fail("%s: Operation has invalid tensor inputs", __func__);
836 }
837
838 // Get the optional input tensors:
839 // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
840 // [num_units, input_size], where “num_units” corresponds to the number of cell units.
841 const ConstTensorPin inputToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 1, model, data);
842 // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
843 // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
844 // “num_units”), or the second dimension of the “projection_weights”, if defined.
845 const ConstTensorPin recurrentToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 5, model, data);
846 // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
847 const ConstTensorPin cellToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 9, model, data);
848 // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
849 const ConstTensorPin cellToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 10, model, data);
850 // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
851 const ConstTensorPin cellToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 11, model, data);
852 // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
853 const ConstTensorPin inputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 12, model, data);
854 // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
855 // [output_size, num_units].
856 const ConstTensorPin projectionWeightsPin = ConvertOperationInputToConstTensorPin(operation, 16, model, data);
857 // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [output_size].
858 const ConstTensorPin projectionBiasPin = ConvertOperationInputToConstTensorPin(operation, 17, model, data);
859
860 if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional()) ||
861 (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional()) ||
862 (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional()) ||
863 (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional()) ||
864 (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional()) ||
865 (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional()) ||
866 (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional()) ||
867 (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
868 {
869 return Fail("%s: Operation has invalid tensor inputs", __func__);
870 }
871
872 // Get the mandatory input scalars (actually 1-D tensors of size 1):
873 // 20: The activation function: A value indicating the activation function:
874 // 0: None; 1: Relu; 3: Relu6; 4: Tanh; 6: Sigmoid.
875 // 21: The clipping threshold: for the cell state, such that values are bound within [-cell_clip, cell_clip].
876 // If set to 0.0 then clipping is disabled.
877 // 22: The clipping threshold: for the output from the projection layer, such that values are bound within
878 // [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
879 ActivationFn activation;
880 float cellClip;
881 float projClip;
882 if (!GetInputActivationFunctionFromTensor(operation, 20, activation, model, data) ||
883 !GetInputScalar(operation, 21, OperandType::FLOAT32, cellClip, model, data) ||
884 !GetInputScalar(operation, 22, OperandType::FLOAT32, projClip, model, data))
885 {
886 return Fail("%s: Operation has invalid scalar inputs", __func__);
887 }
888
889 // Outputs:
890 // 00: The scratch buffer: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units * 4] with
891 // CIFG, or [batch_size, num_units * 3] without CIFG.
892 const Operand* scratchBuffer = GetOutputOperand(operation, 0, model);
893 if (!scratchBuffer)
894 {
895 return Fail("%s: Could not read output 0: scratchBuffer", __func__);
896 }
897 // 01: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
898 const Operand* outputStateOut = GetOutputOperand(operation, 1, model);
899 if (!outputStateOut)
900 {
901 return Fail("%s: Could not read output 1: outputStateOut", __func__);
902 }
903 // 02: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
904 const Operand* cellStateOut = GetOutputOperand(operation, 2, model);
905 if (!cellStateOut)
906 {
907 return Fail("%s: Could not read output 2: cellStateOut", __func__);
908 }
909 // 03: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. This is
910 // effectively the same as the current “output state (out)” value.
911 const Operand* output = GetOutputOperand(operation, 3, model);
912 if (!output)
913 {
914 return Fail("%s: Could not read output 3: output", __func__);
915 }
916
917 // set the params structure for the AddLstmLayer call
918 armnn::LstmInputParams params;
919 params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
920 params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
921 params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
922 params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
923 params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
924 params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
925 params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
926 params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
927 params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
928 params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
929 params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
930 params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
931 params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
932 params.m_CellBias = cellBiasPin.GetConstTensorPtr();
933 params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
934 params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
935 params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
936
937 // set the layer descriptor
938 armnn::LstmDescriptor desc;
939 desc.m_ActivationFunc = activation;
940 desc.m_ClippingThresCell = cellClip;
941 desc.m_ClippingThresProj = projClip;
942 desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
943 params.m_RecurrentToInputWeights == nullptr ||
944 params.m_InputGateBias == nullptr);
945 desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
946 params.m_CellToOutputWeights != nullptr);
947 desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
948
949 // validate the optional input groups
950 if (desc.m_CifgEnabled &&
951 (params.m_InputToInputWeights != nullptr ||
952 params.m_RecurrentToInputWeights != nullptr ||
953 params.m_InputGateBias != nullptr))
954 {
955 return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
956 " and input gate bias must be provided", __func__);
957 }
958
959 if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
960 {
961 return Fail("%s: projection bias should not be provided without projection weights", __func__);
962 }
963
964 if (desc.m_PeepholeEnabled &&
965 (params.m_CellToForgetWeights == nullptr ||
966 params.m_CellToOutputWeights == nullptr ||
967 (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
968 {
969 return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
970 " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
971 }
972
973 // Check if the layer is supported
974 // Inputs
975 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
976 const armnn::TensorInfo& outputStateInInfo = outputStateIn.GetTensorInfo();
977 const armnn::TensorInfo& cellStateInInfo = cellStateIn.GetTensorInfo();
978
979 // Outputs
980 const armnn::TensorInfo& scratchBufferInfo = GetTensorInfoForOperand(*scratchBuffer);
981 const armnn::TensorInfo& outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
982 const armnn::TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
983 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
984
985 // Basic parameters
986 const armnn::TensorInfo& inputToForgetWeights = params.m_InputToForgetWeights->GetInfo();
987 const armnn::TensorInfo& inputToCellWeights = params.m_InputToCellWeights->GetInfo();
988 const armnn::TensorInfo& inputToOutputWeights = params.m_InputToOutputWeights->GetInfo();
989 const armnn::TensorInfo& recurrentToForgetWeights = params.m_RecurrentToForgetWeights->GetInfo();
990 const armnn::TensorInfo& recurrentToCellWeights = params.m_RecurrentToCellWeights->GetInfo();
991 const armnn::TensorInfo& recurrentToOutputWeights = params.m_RecurrentToOutputWeights->GetInfo();
992 const armnn::TensorInfo& forgetGateBias = params.m_ForgetGateBias->GetInfo();
993 const armnn::TensorInfo& cellBias = params.m_CellBias->GetInfo();
994 const armnn::TensorInfo& outputGateBias = params.m_OutputGateBias->GetInfo();
995
996 //Optional parameters
997 const armnn::TensorInfo* inputToInputWeights = nullptr;
998 const armnn::TensorInfo* recurrentToInputWeights = nullptr;
999 const armnn::TensorInfo* cellToInputWeights = nullptr;
1000 const armnn::TensorInfo* inputGateBias = nullptr;
1001 const armnn::TensorInfo* projectionWeights = nullptr;
1002 const armnn::TensorInfo* projectionBias = nullptr;
1003 const armnn::TensorInfo* cellToForgetWeights = nullptr;
1004 const armnn::TensorInfo* cellToOutputWeights = nullptr;
1005
1006 if(!desc.m_CifgEnabled)
1007 {
1008 inputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
1009 recurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo());
1010 if (params.m_CellToInputWeights != nullptr)
1011 {
1012 cellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
1013 }
1014 inputGateBias = &(params.m_InputGateBias->GetInfo());
1015 }
1016
1017 if(desc.m_ProjectionEnabled)
1018 {
1019 projectionWeights = &(params.m_ProjectionWeights->GetInfo());
1020 if (params.m_ProjectionBias != nullptr)
1021 {
1022 projectionBias = &(params.m_ProjectionBias->GetInfo());
1023 }
1024 }
1025
1026 if(desc.m_PeepholeEnabled)
1027 {
1028 cellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
1029 cellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
1030 }
1031
1032 if (!IsLayerSupported(__func__,
1033 armnn::IsLstmSupported,
1034 data.m_Compute,
1035 inputInfo,
1036 outputStateInInfo,
1037 cellStateInInfo,
1038 scratchBufferInfo,
1039 outputStateOutInfo,
1040 cellStateOutInfo,
1041 outputInfo,
1042 desc,
1043 inputToForgetWeights,
1044 inputToCellWeights,
1045 inputToOutputWeights,
1046 recurrentToForgetWeights,
1047 recurrentToCellWeights,
1048 recurrentToOutputWeights,
1049 forgetGateBias,
1050 cellBias,
1051 outputGateBias,
1052 inputToInputWeights,
1053 recurrentToInputWeights,
1054 cellToInputWeights,
1055 inputGateBias,
1056 projectionWeights,
1057 projectionBias,
1058 cellToForgetWeights,
1059 cellToOutputWeights))
1060 {
1061 return false;
1062 }
1063
1064 // Add the layer
1065 armnn::IConnectableLayer* layer = data.m_Network->AddLstmLayer(desc, params, "Lstm");
1066
1067 input.Connect(layer->GetInputSlot(0));
1068 outputStateIn.Connect(layer->GetInputSlot(1));
1069 cellStateIn.Connect(layer->GetInputSlot(2));
1070
1071 return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0, model, data) &&
1072 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1, model, data) &&
1073 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2, model, data) &&
1074 SetupAndTrackLayerOutputSlot(operation, 3, *layer, 3, model, data));
1075}
1076
1077bool HalPolicy::ConvertL2Normalization(const Operation& operation, const Model& model, ConversionData& data)
1078{
1079 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1080 if (!input.IsValid())
1081 {
1082 return Fail("%s: Operation has invalid inputs", __func__);
1083 }
1084
1085 const Operand* output = GetOutputOperand(operation, 0, model);
1086 if (!output)
1087 {
1088 return Fail("%s: Could not read output 0", __func__);
1089 }
1090
1091 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1092 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1093
Matteo Martincigh58f71092018-09-25 15:58:52 +01001094 armnn::L2NormalizationDescriptor desc;
Matteo Martincigh5e0ed9f2018-10-01 09:26:32 +01001095 desc.m_DataLayout = armnn::DataLayout::NHWC;
Matteo Martincigh58f71092018-09-25 15:58:52 +01001096
arovir01b0717b52018-09-05 17:03:25 +01001097 if (!IsLayerSupported(__func__,
1098 armnn::IsL2NormalizationSupported,
1099 data.m_Compute,
Matteo Martincigh5e0ed9f2018-10-01 09:26:32 +01001100 inputInfo,
1101 outputInfo,
Matteo Martincigh58f71092018-09-25 15:58:52 +01001102 desc))
arovir01b0717b52018-09-05 17:03:25 +01001103 {
1104 return false;
1105 }
1106
Matteo Martincigh58f71092018-09-25 15:58:52 +01001107 armnn::IConnectableLayer* layer = data.m_Network->AddL2NormalizationLayer(desc);
arovir01b0717b52018-09-05 17:03:25 +01001108 assert(layer != nullptr);
Matteo Martincigh5e0ed9f2018-10-01 09:26:32 +01001109 input.Connect(layer->GetInputSlot(0));
arovir01b0717b52018-09-05 17:03:25 +01001110
Matteo Martincigh5e0ed9f2018-10-01 09:26:32 +01001111 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
arovir01b0717b52018-09-05 17:03:25 +01001112}
1113
1114bool HalPolicy::ConvertL2Pool2d(const Operation& operation, const Model& model, ConversionData& data)
1115{
1116 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::L2, model, data);
1117}
1118
1119bool HalPolicy::ConvertMaxPool2d(const Operation& operation, const Model& model, ConversionData& data)
1120{
1121 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Max, model, data);
1122}
1123
1124bool HalPolicy::ConvertMul(const Operation& operation, const Model& model, ConversionData& data)
1125{
1126 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0, model, data);
1127 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1, model, data);
1128
1129 if (!input0.IsValid() || !input1.IsValid())
1130 {
1131 return Fail("%s: Operation has invalid inputs", __func__);
1132 }
1133
1134 // The FuseActivation parameter is always the input index 2
1135 // and it should be optional
1136 ActivationFn activationFunction;
1137 if (!GetOptionalInputActivation(operation, 2, activationFunction, model, data))
1138 {
1139 return Fail("%s: Operation has invalid inputs", __func__);
1140 }
1141
1142 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
1143
1144 if (outputOperand == nullptr)
1145 {
1146 return false;
1147 }
1148
1149 const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
1150
1151 if (!IsLayerSupported(__func__,
1152 armnn::IsMultiplicationSupported,
1153 data.m_Compute,
1154 input0.GetTensorInfo(),
1155 input1.GetTensorInfo(),
1156 outInfo))
1157 {
1158 return false;
1159 }
1160
1161 armnn::IConnectableLayer* const startLayer = data.m_Network->AddMultiplicationLayer();
1162 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer, data);
1163
1164 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
1165 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
1166
1167 if (endLayer != nullptr)
1168 {
1169 BroadcastTensor(input0, input1, startLayer, *data.m_Network);
1170 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer, model, data);
1171 }
1172 else
1173 {
1174 return Fail("%s: ProcessActivation failed", __func__);
1175 }
1176}
1177
1178bool HalPolicy::ConvertReLu(const Operation& operation, const Model& model, ConversionData& data)
1179{
1180 armnn::ActivationDescriptor desc;
1181 desc.m_Function = armnn::ActivationFunction::ReLu;
1182
1183 return ConvertToActivation(operation, __func__, desc, model, data);
1184}
1185
1186bool HalPolicy::ConvertReLu1(const Operation& operation, const Model& model, ConversionData& data)
1187{
1188 armnn::ActivationDescriptor desc;
1189 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1190 desc.m_A = 1.0f;
1191 desc.m_B = -1.0f;
1192
1193 return ConvertToActivation(operation, __func__, desc, model, data);
1194}
1195
1196bool HalPolicy::ConvertReLu6(const Operation& operation, const Model& model, ConversionData& data)
1197{
1198 armnn::ActivationDescriptor desc;
1199 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1200 desc.m_A = 6.0f;
1201
1202 return ConvertToActivation(operation, __func__, desc, model, data);
1203}
1204
1205bool HalPolicy::ConvertSoftmax(const Operation& operation, const Model& model, ConversionData& data)
1206{
1207 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1208 if (!input.IsValid())
1209 {
1210 return Fail("%s: Operation has invalid inputs", __func__);
1211 }
1212
1213 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
1214 if (!outputOperand)
1215 {
1216 return Fail("%s: Operation has no outputs", __func__);
1217 }
1218
1219 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
1220
1221 armnn::SoftmaxDescriptor desc;
1222 if (!GetInputFloat32(operation, 1, desc.m_Beta, model, data))
1223 {
1224 return Fail("%s: Operation has invalid inputs", __func__);
1225 }
1226
1227 if (!IsLayerSupported(__func__,
1228 armnn::IsSoftmaxSupported,
1229 data.m_Compute,
1230 input.GetTensorInfo(),
1231 outInfo,
1232 desc))
1233 {
1234 return false;
1235 }
1236
1237 armnn::IConnectableLayer* layer = data.m_Network->AddSoftmaxLayer(desc);
1238 assert(layer != nullptr);
1239 input.Connect(layer->GetInputSlot(0));
1240
1241 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
1242}
1243
1244bool HalPolicy::ConvertTanH(const Operation& operation, const Model& model, ConversionData& data)
1245{
1246 armnn::ActivationDescriptor desc;
1247 desc.m_Function = armnn::ActivationFunction::TanH;
1248 desc.m_A = 1.0f; // android nn does not support tanH parameters
1249 desc.m_B = 1.0f; // set to 1.0f for unity scaling
1250
1251 return ConvertToActivation(operation, __func__, desc, model, data);
1252}
1253
1254bool HalPolicy::ConvertReshape(const Operation& operation, const Model& model, ConversionData& data)
1255{
1256 const Operand* inputOperand = GetInputOperand(operation, 0, model);
1257 const Operand* requestedShapeOperand = GetInputOperand(operation, 1, model);
1258 const Operand* outputOperand = GetOutputOperand(operation, 0, model);
1259
1260 if (inputOperand == nullptr
1261 || requestedShapeOperand == nullptr
1262 || outputOperand == nullptr)
1263 {
1264 return Fail("%s: Operation has invalid inputs", __func__);
1265 }
1266
1267
1268 if (requestedShapeOperand->dimensions.size() != 1)
1269 {
1270 return Fail("%s: Input 1 expected to be one-dimensional (found %i dimensions)",
1271 __func__, requestedShapeOperand->dimensions.size());
1272 }
1273
1274 std::vector<int32_t> targetDimensions;
1275 if (!GetTensorInt32Values(*requestedShapeOperand, targetDimensions, model, data))
1276 {
1277 return Fail("%s: Could not read values of input 1", __func__);
1278 }
1279
1280 const Shape inputOperandShape = GetOperandShape(*inputOperand);
1281
1282 Shape requestedShape;
1283 // targetDimensions may contain special values (e.g. -1). reshapePrepare() is an AndroidNN provided utility
1284 // function that resolves these values into a fully specified tensor shape.
1285 if (!reshapePrepare(inputOperandShape, targetDimensions.data(), targetDimensions.size(), &requestedShape))
1286 {
1287 return Fail("%s: Failed to resolve the requested shape", __func__);
1288 }
1289
1290 const Shape outputOperandShape = GetOperandShape(*outputOperand);
1291 if (!SameShape(requestedShape, outputOperandShape))
1292 {
1293 return Fail("%s: Shape of output operand does not match resolved requested shape", __func__);
1294 }
1295
1296 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1297 if (!input.IsValid())
1298 {
1299 return Fail("%s: Could not read input 0", __func__);
1300 }
1301
1302 if (!IsLayerSupported(__func__,
1303 armnn::IsReshapeSupported,
1304 data.m_Compute,
1305 input.GetTensorInfo()))
1306 {
1307 return false;
1308 }
1309
1310
1311 armnn::ReshapeDescriptor reshapeDescriptor;
1312 reshapeDescriptor.m_TargetShape = armnn::TensorShape(requestedShape.dimensions.size(),
1313 requestedShape.dimensions.data());
1314
1315 armnn::IConnectableLayer* layer = data.m_Network->AddReshapeLayer(reshapeDescriptor);
1316 assert(layer != nullptr);
1317 input.Connect(layer->GetInputSlot(0));
1318
1319 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
1320}
1321
1322bool HalPolicy::ConvertResizeBilinear(const Operation& operation, const Model& model, ConversionData& data)
1323{
1324 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0, model, data);
1325 if (!input.IsValid())
1326 {
1327 return Fail("%s: Could not read input 0", __func__);
1328 }
1329
1330 const Operand* output = GetOutputOperand(operation, 0, model);
1331 if (!output)
1332 {
1333 return Fail("%s: Could not read output 0", __func__);
1334 }
1335
1336 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1337 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1338
Mohamed Nour Abouelseoud81afa302018-10-29 14:32:55 +00001339 armnn::ResizeBilinearDescriptor desc;
1340 desc.m_DataLayout = armnn::DataLayout::NHWC;
arovir01b0717b52018-09-05 17:03:25 +01001341
1342 if (!IsLayerSupported(__func__,
1343 armnn::IsResizeBilinearSupported,
1344 data.m_Compute,
Mohamed Nour Abouelseoud81afa302018-10-29 14:32:55 +00001345 inputInfo))
arovir01b0717b52018-09-05 17:03:25 +01001346 {
1347 return false;
1348 }
1349
arovir01b0717b52018-09-05 17:03:25 +01001350
1351 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_TargetHeight, model, data)
1352 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_TargetWidth, model, data))
1353 {
1354 return Fail("%s: Operation has invalid inputs", __func__);
1355 }
1356
1357 armnn::IConnectableLayer* layer = data.m_Network->AddResizeBilinearLayer(desc);
Mohamed Nour Abouelseoud81afa302018-10-29 14:32:55 +00001358
arovir01b0717b52018-09-05 17:03:25 +01001359 assert(layer != nullptr);
arovir01b0717b52018-09-05 17:03:25 +01001360
Mohamed Nour Abouelseoud81afa302018-10-29 14:32:55 +00001361 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1362 input.Connect(layer->GetInputSlot(0));
arovir01b0717b52018-09-05 17:03:25 +01001363
Mohamed Nour Abouelseoud81afa302018-10-29 14:32:55 +00001364 return SetupAndTrackLayerOutputSlot(operation, 0, *layer, model, data);
arovir01b0717b52018-09-05 17:03:25 +01001365
1366}
1367
1368} // namespace hal_1_0
Matteo Martincigh58f71092018-09-25 15:58:52 +01001369} // namespace armnn_driver