blob: 808fc1ca6b3c1062e203458a8bc388ef9cb76c59 [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#pragma once
7
8#include <armnn/ArmNN.hpp>
9
Mike Kellyb5fdf382019-06-11 16:35:25 +010010#include "armnn/src/armnnUtils/DataLayoutIndexed.hpp"
arovir01b0717b52018-09-05 17:03:25 +010011#include "armnn/src/armnnUtils/Permute.hpp"
12#include "Utils.hpp"
13
14#include <ActivationFunctor.h>
15#include <CpuExecutor.h>
16#include <OperationsUtils.h>
17
18#include <boost/assert.hpp>
19#include <boost/core/ignore_unused.hpp>
Aron Virginas-Tar0e7ab542019-04-10 15:02:31 +010020#include <boost/numeric/conversion/cast.hpp>
arovir01b0717b52018-09-05 17:03:25 +010021#include <boost/test/tools/floating_point_comparison.hpp>
22
23#include <log/log.h>
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +010024#include <vector>
arovir01b0717b52018-09-05 17:03:25 +010025
26namespace armnn_driver
27{
28
29///
30/// Helper classes
31///
32
33struct ConversionData
34{
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +010035 ConversionData(const std::vector<armnn::BackendId>& backends)
36 : m_Backends(backends)
37 , m_Network(nullptr, nullptr)
arovir01b0717b52018-09-05 17:03:25 +010038 {}
39
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +010040 const std::vector<armnn::BackendId> m_Backends;
arovir01b0717b52018-09-05 17:03:25 +010041 armnn::INetworkPtr m_Network;
42 std::vector<armnn::IOutputSlot*> m_OutputSlotForOperand;
43 std::vector<android::nn::RunTimePoolInfo> m_MemPools;
44};
45
46class LayerInputHandle
47{
48public:
49 LayerInputHandle();
50 LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo);
51
52 bool IsValid() const;
53
54 void Connect(armnn::IInputSlot& inputSlot);
55
56 const armnn::TensorInfo& GetTensorInfo() const;
57
58private:
59 armnn::IOutputSlot* m_OutputSlot;
60 bool m_Valid;
61 armnn::TensorInfo m_TensorInfo;
62};
63
64class ConstTensorPin
65{
66public:
67 // Creates an invalid tensor pin (can be used to signal errors)
68 // The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid
69 ConstTensorPin(bool optional = false);
70
71 // @param tensorInfo TensorInfo associated with the tensor.
72 // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
73 // the model being converted.
74 // @param numBytes Number of bytes for the tensor data.
75 ConstTensorPin(const armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
76 const armnn::PermutationVector& mappings);
77
78 ConstTensorPin(const ConstTensorPin& other) = delete;
79 ConstTensorPin(ConstTensorPin&& other) = default;
80
81 bool IsValid() const;
82 bool IsOptional() const;
83
84 const armnn::ConstTensor& GetConstTensor() const;
85 const armnn::ConstTensor* GetConstTensorPtr() const;
86
87private:
88 armnn::ConstTensor m_ConstTensor;
89
90 // Owned memory for swizzled tensor data, only required if the tensor needed
91 // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
92 // the pools associated with the model being converted.
93 std::vector<uint8_t> m_SwizzledTensorData;
94
95 // optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given
96 bool m_Optional;
97};
98
99} // namespace armnn_driver
100
101///
102/// Utility functions
103///
104
105namespace
106{
107
108using namespace armnn_driver;
109using namespace android::nn;
110
111// Convenience function to log the reason for failing to convert a model.
112// @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
113template<class... Args>
114static bool Fail(const char* formatStr, Args&&... args)
115{
116 ALOGD(formatStr, std::forward<Args>(args)...);
117 return false;
118}
119
120// Convenience function to call an Is*Supported function and log caller name together with reason for lack of support.
121// Called as: IsLayerSupported(__func__, Is*Supported, a, b, c, d, e)
122template<typename IsLayerSupportedFunc, typename ... Args>
123bool IsLayerSupported(const char* funcName, IsLayerSupportedFunc f, Args&&... args)
124{
125 std::vector<char> unsupportedReason(1024+1);
126 bool isSupported = f(std::forward<Args>(args)..., unsupportedReason.data(), unsupportedReason.size()-1);
127 if(isSupported)
128 {
129 return true;
130 }
131 else
132 {
133 std::string sUnsupportedReason(unsupportedReason.data());
134 if (sUnsupportedReason.size() > 0)
135 {
136 ALOGD("%s: not supported by armnn: %s", funcName, sUnsupportedReason.c_str());
137 } else
138 {
139 ALOGD("%s: not supported by armnn", funcName);
140 }
141 return false;
142 }
143}
144
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +0100145template<typename IsLayerSupportedFunc, typename ... Args>
146bool IsLayerSupportedForAnyBackend(const char* funcName,
147 IsLayerSupportedFunc f,
148 const std::vector<armnn::BackendId>& backends,
149 Args&&... args)
150{
151 for (auto&& backend : backends)
152 {
153 if (IsLayerSupported(funcName, f, backend, std::forward<Args>(args)...))
154 {
155 return true;
156 }
157 }
158
159 ALOGD("%s: not supported by any specified backend", funcName);
160 return false;
161}
162
Mike Kellyb5fdf382019-06-11 16:35:25 +0100163template<typename Operand>
164armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
arovir01b0717b52018-09-05 17:03:25 +0100165{
166 return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
167}
168
Matthew Bentham912b3622019-05-03 15:49:14 +0100169inline bool IsOperandTypeSupportedForTensors(V1_0::OperandType type)
arovir01b0717b52018-09-05 17:03:25 +0100170{
Matthew Bentham912b3622019-05-03 15:49:14 +0100171 return type == V1_0::OperandType::TENSOR_FLOAT32 ||
172 type == V1_0::OperandType::TENSOR_QUANT8_ASYMM ||
173 type == V1_0::OperandType::TENSOR_INT32;
arovir01b0717b52018-09-05 17:03:25 +0100174}
175
Mike Kellyb5fdf382019-06-11 16:35:25 +0100176#ifdef ARMNN_ANDROID_NN_V1_2
177
178inline bool IsOperandTypeSupportedForTensors(V1_2::OperandType type)
179{
180 return type == V1_2::OperandType::BOOL ||
181 type == V1_2::OperandType::TENSOR_FLOAT16 ||
182 type == V1_2::OperandType::TENSOR_FLOAT32 ||
183 type == V1_2::OperandType::TENSOR_QUANT8_ASYMM ||
184 type == V1_2::OperandType::TENSOR_QUANT16_SYMM ||
185 type == V1_2::OperandType::TENSOR_INT32;
186}
187
188#endif
189
190inline bool IsBool(V1_0::Operand)
191{
192 return false;
193}
194
195#ifdef ARMNN_ANDROID_NN_V1_2
196
197inline bool IsBool(V1_2::Operand operand)
198{
199 return operand.type == V1_2::OperandType::BOOL;
200}
201
202#endif
203
arovir01b0717b52018-09-05 17:03:25 +0100204void BroadcastTensor(LayerInputHandle& input0, LayerInputHandle& input1, armnn::IConnectableLayer* startLayer,
205 armnn::INetwork& network)
206{
207 BOOST_ASSERT(startLayer != nullptr);
208 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
209 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
210
211 if (inputTensorInfo0.GetNumDimensions() != inputTensorInfo1.GetNumDimensions())
212 {
213 // If the number of dimensions do not match then we need to add degenerate dimensions
214 // to the "smaller" tensor using a reshape:
215 // Small Big
216 // | |
217 // Reshape |
218 // \ /
219 // Add
220 bool input0IsBigger = inputTensorInfo0.GetNumDimensions() > inputTensorInfo1.GetNumDimensions();
221
222 LayerInputHandle& smallTensorHandle = input0IsBigger ? input1 : input0;
223 const armnn::TensorInfo& smallTensorDims = smallTensorHandle.GetTensorInfo();
224
225 LayerInputHandle& bigTensorHandle = input0IsBigger ? input0 : input1;
226 const armnn::TensorInfo& bigTensorDims = bigTensorHandle.GetTensorInfo();
227
228 const unsigned int bigTensorDimsNumber = bigTensorDims.GetNumDimensions();
229 std::vector<unsigned int> reshapedDims(bigTensorDimsNumber, 1);
230 unsigned int sizeDifference = bigTensorDimsNumber - smallTensorDims.GetNumDimensions();
231 for (unsigned i = sizeDifference; i < bigTensorDimsNumber; ++i)
232 {
233 reshapedDims[i] = smallTensorDims.GetShape()[i-sizeDifference];
234 }
235 armnn::TensorInfo reshapedInfo = smallTensorDims;
236 reshapedInfo.SetShape(armnn::TensorShape{ static_cast<unsigned int>(reshapedDims.size()),
237 reshapedDims.data() });
238
239 armnn::ReshapeDescriptor reshapeDesc;
240 reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
241 armnn::IConnectableLayer* const reshapeLayer = network.AddReshapeLayer(reshapeDesc);
242 smallTensorHandle.Connect(reshapeLayer->GetInputSlot(0));
243 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
244
245 // Connect the outputs from new reshape and original input layer
246 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
247 bigTensorHandle.Connect(startLayer->GetInputSlot(1));
248 }
249 else
250 {
251 input0.Connect(startLayer->GetInputSlot(0));
252 input1.Connect(startLayer->GetInputSlot(1));
253 }
254}
255
256void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
257 android::nn::PaddingScheme scheme)
258{
259 int32_t padHead;
260 int32_t padTail;
261 calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
262 outPadHead = boost::numeric_cast<uint32_t>(padHead);
263 outPadTail = boost::numeric_cast<uint32_t>(padTail);
264}
265
Matthew Bentham912b3622019-05-03 15:49:14 +0100266Shape GetOperandShape(const V1_0::Operand& operand)
arovir01b0717b52018-09-05 17:03:25 +0100267{
268 Shape shape;
Matthew Bentham912b3622019-05-03 15:49:14 +0100269 shape.type = OperandType(operand.type);
arovir01b0717b52018-09-05 17:03:25 +0100270 shape.dimensions = operand.dimensions;
271 shape.scale = operand.scale;
272 shape.offset = operand.zeroPoint;
273 return shape;
274}
275
276// ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
277// what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
278// we accept some tolerance. We don't want to ArmNN itself to accept these inconsistencies as it is up to the user
279// (us, in this case) to ensure they match.
280void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
281 const armnn::TensorInfo& weightInfo, const armnn::TensorInfo& inputInfo)
282{
283 const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
284 if (biasInfo.GetQuantizationScale() != expectedBiasScale)
285 {
286 boost::math::fpc::close_at_tolerance<float> comparer(boost::math::fpc::percent_tolerance(1.0f));
287 if (comparer(biasInfo.GetQuantizationScale(), expectedBiasScale))
288 {
289 ALOGW("Bias quantization scale has been modified to match input*weights");
290 biasInfo.SetQuantizationScale(expectedBiasScale);
291 }
292 }
293}
294
295// 4D Tensor Permutations
296const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U });
297const armnn::PermutationVector NHWCToArmNN({ 0U, 2U, 3U, 1U });
298const armnn::PermutationVector ArmNNToNHWC({ 0U, 3U, 1U, 2U });
299const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U });
300
301// 3D Permutation Vectors
302const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U });
303const armnn::PermutationVector RotateTensorLeft({ 2U, 0U, 1U });
304const armnn::PermutationVector RotateTensorRight({ 1U, 2U, 0U });
305
306template<typename OSlot>
307armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input,
308 const armnn::PermutationVector& mappings)
309{
310 // Add swizzle layer
311 armnn::IConnectableLayer* const layer = network.AddPermuteLayer(mappings);
312
313 BOOST_ASSERT(layer != nullptr);
314
315 // Connect input to swizzle layer
316 input.Connect(layer->GetInputSlot(0));
317
318 // Setup swizzled output
319 const armnn::TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mappings);
320 layer->GetOutputSlot(0).SetTensorInfo(outInfo);
321
322 return *layer;
323}
324
325void SwizzleIn(armnn::INetwork& network, LayerInputHandle& input, armnn::IConnectableLayer& layer, unsigned int index)
326{
327 // Add swizzle layer
328 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, input, NHWCToArmNN);
329 // Connect swizzled input to layer
330 swizzleLayer.GetOutputSlot(0).Connect(layer.GetInputSlot(index));
331}
332
333armnn::IConnectableLayer& DeswizzleOut(armnn::INetwork& network, armnn::IConnectableLayer& layer, unsigned int index)
334{
335 // Add deswizzle layer
336 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(network, layer.GetOutputSlot(index), ArmNNToNHWC);
337 return deswizzleLayer;
338}
339
340// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly
341armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network,
342 LayerInputHandle& input,
343 armnn::IConnectableLayer& firstLayer,
344 armnn::IConnectableLayer& lastLayer)
345{
346 SwizzleIn(network, input, firstLayer, 0);
347 return DeswizzleOut(network, lastLayer, 0);
348}
349
350// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly
351armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input,
352 armnn::IConnectableLayer& layer)
353{
354 return SwizzleInDeswizzleOut(network, input, layer, layer);
355}
356
357bool ValidateConcatOutputShape(const std::vector<armnn::TensorShape> & inputShapes,
358 const armnn::TensorShape & outputShape,
359 uint32_t concatDim)
360{
361 // Validate the output shape is correct given the input shapes (which have just been validated)
362 unsigned int numDimensions = inputShapes[0].GetNumDimensions();
363 if (outputShape.GetNumDimensions() != numDimensions)
364 {
365 return Fail("%s: Output shape has wrong number of dimensions", __func__);
366 }
367
368 unsigned int outputSizeAlongConcatenatedDimension = 0;
369 for (unsigned int i = 0; i < inputShapes.size(); i++)
370 {
371 outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
372 }
373
374 for (unsigned int i = 0; i < numDimensions; ++i)
375 {
376 if (i == concatDim)
377 {
378 if (outputShape[i] != outputSizeAlongConcatenatedDimension)
379 {
380 return Fail(
381 "%s: Invalid output shape for dimension %d (%d != %d)",
382 __func__,
383 i,
384 outputShape[i],
385 outputSizeAlongConcatenatedDimension);
386 }
387 }
388 else
389 {
390 if (outputShape[i] != inputShapes[0][i])
391 {
392 return Fail("%s: Invalid output shape", __func__);
393 }
394 }
395 }
396
397 return true;
398}
399
400bool RequiresReshape(armnn::TensorShape & inputShape)
401{
402 return inputShape.GetNumDimensions() < 3;
403}
404
405template<typename OSlot>
406armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network, OSlot& inputLayer,
407 armnn::TensorInfo reshapeInfo)
408{
409 armnn::ReshapeDescriptor reshapeDescriptor;
410 reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
411
412 armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor);
413 BOOST_ASSERT(reshapeLayer != nullptr);
414
415 // Attach the input layer to the reshape layer
416 inputLayer.Connect(reshapeLayer->GetInputSlot(0));
417 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo);
418
419 return *reshapeLayer;
420}
421
422void SwizzleInputs(armnn::INetwork& network,
423 std::vector<LayerInputHandle>& inputs,
424 std::vector<armnn::TensorShape>& inputShapes,
425 const armnn::PermutationVector& mapping)
426{
427 if (!mapping.IsEqual(IdentityPermutation4D))
428 {
429 size_t nInputs = inputs.size();
430 for (size_t i=0; i<nInputs; ++i)
431 {
432 // add swizzle layer
433 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, inputs[i], mapping);
434 auto& outputSlot = swizzleLayer.GetOutputSlot(0);
435 auto& outputInfo = outputSlot.GetTensorInfo();
436 // replace inputs with the swizzled ones
437 inputs[i] = LayerInputHandle(true, &outputSlot, outputInfo);
438 inputShapes[i] = inputs[i].GetTensorInfo().GetShape();
439 }
440 }
441}
442
narpra01f176d5a2018-11-18 20:17:48 +0000443bool CreateConcatPermutationParameters(const unsigned int numberOfDimensions,
444 int32_t & concatDimension,
445 std::pair<armnn::PermutationVector, armnn::PermutationVector> & permutationPair)
arovir01b0717b52018-09-05 17:03:25 +0100446{
narpra01f176d5a2018-11-18 20:17:48 +0000447 bool needPermute = false;
arovir01b0717b52018-09-05 17:03:25 +0100448 BOOST_ASSERT(numberOfDimensions >= 3);
449
450 // ArmNN uses Compute Library subtensors to perform concatenation
narpra01f176d5a2018-11-18 20:17:48 +0000451 // This only works when concatenating along dimension 0, 1 or 3 for a 4-D tensor,
452 // or along dimension 0 or 2 for a 3-D tensor.
453 if (numberOfDimensions == 4 && concatDimension == 2)
arovir01b0717b52018-09-05 17:03:25 +0100454 {
narpra01f176d5a2018-11-18 20:17:48 +0000455 concatDimension = 1;
456 permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2);
457 needPermute = true;
arovir01b0717b52018-09-05 17:03:25 +0100458 }
narpra01f176d5a2018-11-18 20:17:48 +0000459 else if (numberOfDimensions == 3 && concatDimension == 1)
arovir01b0717b52018-09-05 17:03:25 +0100460 {
narpra01f176d5a2018-11-18 20:17:48 +0000461 concatDimension = 0;
462 permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight);
463 needPermute = true;
arovir01b0717b52018-09-05 17:03:25 +0100464 }
narpra01f176d5a2018-11-18 20:17:48 +0000465 return needPermute;
arovir01b0717b52018-09-05 17:03:25 +0100466}
467
468} // anonymous namespace
469
470namespace armnn_driver
471{
472
473//// Creates an ArmNN activation layer and connects it to the given layer, if the
474//// passed in AndroidNN activation function requires so.
475//// @return The end layer of the sequence of layers built for the given AndroidNN
476//// activation function or nullptr if an error occurred (e.g. unsupported activation).
477//// Note that the end layer matches the input layer if no activation is required
478//// (the sequence of layers has length 1).
479armnn::IConnectableLayer* ProcessActivation(const armnn::TensorInfo& tensorInfo,
480 ActivationFn activation,
481 armnn::IConnectableLayer* prevLayer,
482 ConversionData& data);
483
484} // namespace armnn_driver
485
486///
487/// Utility templates
488///
489
490namespace armnn_driver
491{
492
493using namespace android::nn;
494
Mike Kellyb5fdf382019-06-11 16:35:25 +0100495template<typename HalOperand, typename HalOperation, typename HalModel>
496const HalOperand* GetInputOperand(const HalOperation& operation, uint32_t inputIndex, const HalModel& model,
497 bool failOnIndexOutOfBounds = true)
arovir01b0717b52018-09-05 17:03:25 +0100498{
499 if (inputIndex >= operation.inputs.size())
500 {
saoste01b8471482018-10-10 09:44:51 +0100501 if (failOnIndexOutOfBounds)
502 {
503 Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
504 }
arovir01b0717b52018-09-05 17:03:25 +0100505 return nullptr;
506 }
507
508 BOOST_ASSERT(operation.inputs[inputIndex] < model.operands.size()); // Model should have been validated beforehand
509 return &model.operands[operation.inputs[inputIndex]];
510}
511
Mike Kellyb5fdf382019-06-11 16:35:25 +0100512template<typename HalOperand, typename HalOperation, typename HalModel>
513const HalOperand* GetOutputOperand(const HalOperation& operation, uint32_t outputIndex, const HalModel& model)
arovir01b0717b52018-09-05 17:03:25 +0100514{
515 if (outputIndex >= operation.outputs.size())
516 {
517 Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
518 return nullptr;
519 }
520
521 // Model should have been validated beforehand
522 BOOST_ASSERT(operation.outputs[outputIndex] < model.operands.size());
523
524 return &model.operands[operation.outputs[outputIndex]];
525}
526
Mike Kellyb5fdf382019-06-11 16:35:25 +0100527template<typename HalOperand, typename HalModel>
528ConstTensorPin ConvertOperandToConstTensorPin(const HalOperand& operand,
arovir01b0717b52018-09-05 17:03:25 +0100529 const HalModel& model,
530 const ConversionData& data,
531 const armnn::PermutationVector& dimensionMappings = g_DontPermute,
532 const armnn::TensorShape* overrideTensorShape = nullptr,
533 bool optional = false)
534{
535 if (!IsOperandTypeSupportedForTensors(operand.type))
536 {
537 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand.type).c_str());
538 return ConstTensorPin();
539 }
540
Kevin Mayf29a2c52019-03-14 11:56:32 +0000541 if (!optional &&
Matthew Bentham912b3622019-05-03 15:49:14 +0100542 operand.lifetime !=V1_0::OperandLifeTime::CONSTANT_COPY &&
543 operand.lifetime !=V1_0::OperandLifeTime::CONSTANT_REFERENCE &&
544 operand.lifetime !=V1_0::OperandLifeTime::NO_VALUE)
arovir01b0717b52018-09-05 17:03:25 +0100545 {
546 Fail("%s: invalid operand lifetime: %s", __func__, toString(operand.lifetime).c_str());
547 return ConstTensorPin();
548 }
549
Kevin Mayf29a2c52019-03-14 11:56:32 +0000550 const void* const valueStart = GetOperandValueReadOnlyAddress(operand, model, data, optional);
arovir01b0717b52018-09-05 17:03:25 +0100551 if (!valueStart)
552 {
553 if (optional)
554 {
555 // optional tensor with no values is not really an error; return it as invalid, but marked as optional
556 return ConstTensorPin(true);
557 }
558 // mandatory tensor with no values
559 Fail("%s: failed to get operand address", __func__);
560 return ConstTensorPin();
561 }
562
563 armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
564 if (overrideTensorShape != nullptr)
565 {
566 tensorInfo.SetShape(*overrideTensorShape);
567 }
568 return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
569}
570
Mike Kellyb5fdf382019-06-11 16:35:25 +0100571template<typename HalOperand, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100572ConstTensorPin ConvertOperationInputToConstTensorPin(const HalOperation& operation,
573 uint32_t inputIndex,
574 const HalModel& model,
575 const ConversionData& data,
576 const armnn::PermutationVector& dimensionMappings = g_DontPermute,
577 const armnn::TensorShape* overrideTensorShape = nullptr,
578 bool optional = false)
579{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100580 const HalOperand* operand = GetInputOperand<HalOperand>(operation, inputIndex, model);
arovir01b0717b52018-09-05 17:03:25 +0100581 if (!operand)
582 {
583 Fail("%s: failed to get input operand: index=%u", __func__, inputIndex);
584 return ConstTensorPin();
585 }
586 return ConvertOperandToConstTensorPin(*operand,
587 model,
588 data,
589 dimensionMappings,
590 overrideTensorShape,
591 optional);
592}
593
Mike Kellyb5fdf382019-06-11 16:35:25 +0100594template<typename HalOperand, typename HalModel>
595const void* GetOperandValueReadOnlyAddress(const HalOperand& operand,
Matthew Bentham912b3622019-05-03 15:49:14 +0100596 const HalModel& model,
597 const ConversionData& data,
Kevin Mayf29a2c52019-03-14 11:56:32 +0000598 bool optional = false)
arovir01b0717b52018-09-05 17:03:25 +0100599{
600 const void* valueStart = nullptr;
601
602 switch (operand.lifetime)
603 {
Matthew Bentham912b3622019-05-03 15:49:14 +0100604 case V1_0::OperandLifeTime::CONSTANT_COPY:
arovir01b0717b52018-09-05 17:03:25 +0100605 {
606 // Constant found in model.operandValues
607 valueStart = &model.operandValues[operand.location.offset];
608 break;
609 }
Matthew Bentham912b3622019-05-03 15:49:14 +0100610 case V1_0::OperandLifeTime::CONSTANT_REFERENCE:
arovir01b0717b52018-09-05 17:03:25 +0100611 {
612 // Constant specified via a Memory object
613 valueStart = GetMemoryFromPool(operand.location, data.m_MemPools);
614 break;
615 }
Matthew Bentham912b3622019-05-03 15:49:14 +0100616 case V1_0::OperandLifeTime::NO_VALUE:
Kevin Mayf29a2c52019-03-14 11:56:32 +0000617 {
618 // An optional input tensor with no values is not an error so should not register as a fail
619 if (optional)
620 {
621 valueStart = nullptr;
622 break;
623 }
Matthew Bentham912b3622019-05-03 15:49:14 +0100624 [[fallthrough]];
Kevin Mayf29a2c52019-03-14 11:56:32 +0000625 }
arovir01b0717b52018-09-05 17:03:25 +0100626 default:
627 {
628 // Unsupported/invalid (e.g. can't get value of an input to the model)
629 Fail("%s: unsupported/invalid operand lifetime: %s",
630 __func__, toString(operand.lifetime).c_str());
631 valueStart = nullptr;
632 }
633 }
634
635 return valueStart;
636}
637
Mike Kellyb5fdf382019-06-11 16:35:25 +0100638template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel, typename OutputType>
arovir01b0717b52018-09-05 17:03:25 +0100639bool GetInputScalar(const HalOperation& operation,
640 uint32_t inputIndex,
Mike Kellyb5fdf382019-06-11 16:35:25 +0100641 HalOperandType type,
arovir01b0717b52018-09-05 17:03:25 +0100642 OutputType& outValue,
643 const HalModel& model,
644 const ConversionData& data)
645{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100646 const HalOperand* operand = GetInputOperand<HalOperand>(operation, inputIndex, model);
arovir01b0717b52018-09-05 17:03:25 +0100647 if (!operand)
648 {
649 return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
650 }
651
652 if (operand->type != type)
653 {
654 return Fail("%s: unexpected operand type: %s (should be %s)",
655 __func__, toString(operand->type).c_str(), toString(type).c_str());
656 }
657
658 if (operand->location.length != sizeof(OutputType))
659 {
660 return Fail("%s: incorrect operand location length: %i (should be %i)",
661 __func__, operand->location.length, sizeof(OutputType));
662 }
663
664 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
665 if (!valueAddress)
666 {
667 return Fail("%s: failed to get address for operand", __func__);
668 }
669
670 outValue = *(static_cast<const OutputType*>(valueAddress));
671 return true;
672}
673
Mike Kellyb5fdf382019-06-11 16:35:25 +0100674template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100675bool GetInputInt32(const HalOperation& operation,
676 uint32_t inputIndex,
677 int32_t& outValue,
678 const HalModel& model,
679 const ConversionData& data)
680{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100681 return GetInputScalar<HalOperand, HalOperandType>(operation, inputIndex, HalOperandType::INT32, outValue, model,
682 data);
arovir01b0717b52018-09-05 17:03:25 +0100683}
684
Mike Kellyb5fdf382019-06-11 16:35:25 +0100685template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100686bool GetInputFloat32(const HalOperation& operation,
687 uint32_t inputIndex,
688 float& outValue,
689 const HalModel& model,
690 const ConversionData& data)
691{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100692 return GetInputScalar<HalOperand, HalOperandType>(operation, inputIndex, HalOperandType::FLOAT32, outValue, model,
693 data);
arovir01b0717b52018-09-05 17:03:25 +0100694}
695
Mike Kellyb5fdf382019-06-11 16:35:25 +0100696template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100697bool GetInputActivationFunctionImpl(const HalOperation& operation,
698 uint32_t inputIndex,
Mike Kellyb5fdf382019-06-11 16:35:25 +0100699 HalOperandType type,
arovir01b0717b52018-09-05 17:03:25 +0100700 ActivationFn& outActivationFunction,
701 const HalModel& model,
702 const ConversionData& data)
703{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100704 if (type != HalOperandType::INT32 && type != HalOperandType::TENSOR_INT32)
arovir01b0717b52018-09-05 17:03:25 +0100705 {
706 return Fail("%s: unexpected operand type: %s (should be %s or %s)",
707 __func__,
708 toString(type).c_str(),
709 toString(OperandType::INT32).c_str(),
710 toString(OperandType::TENSOR_INT32).c_str());
711 }
712
713 int32_t activationFunctionAsInt;
Mike Kellyb5fdf382019-06-11 16:35:25 +0100714 if (!GetInputScalar<HalOperand, HalOperandType>(operation, inputIndex, type, activationFunctionAsInt, model, data))
arovir01b0717b52018-09-05 17:03:25 +0100715 {
716 return Fail("%s: failed to get activation input value", __func__);
717 }
718 outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
719 return true;
720}
721
Mike Kellyb5fdf382019-06-11 16:35:25 +0100722template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100723bool GetInputActivationFunction(const HalOperation& operation,
724 uint32_t inputIndex,
725 ActivationFn& outActivationFunction,
726 const HalModel& model,
727 const ConversionData& data)
728{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100729 return GetInputActivationFunctionImpl<HalOperand, HalOperandType>(operation,
730 inputIndex,
731 HalOperandType::INT32,
732 outActivationFunction,
733 model,
734 data);
arovir01b0717b52018-09-05 17:03:25 +0100735}
736
Mike Kellyb5fdf382019-06-11 16:35:25 +0100737template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100738bool GetInputActivationFunctionFromTensor(const HalOperation& operation,
739 uint32_t inputIndex,
740 ActivationFn& outActivationFunction,
741 const HalModel& model,
742 const ConversionData& data)
743{
744 // This only accepts a 1-D tensor of size 1
Mike Kellyb5fdf382019-06-11 16:35:25 +0100745 return GetInputActivationFunctionImpl<HalOperand, HalOperandType>(operation,
746 inputIndex,
747 HalOperandType::INT32,
748 outActivationFunction,
749 model,
750 data);
arovir01b0717b52018-09-05 17:03:25 +0100751}
752
753
Mike Kellyb5fdf382019-06-11 16:35:25 +0100754template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100755bool GetOptionalInputActivation(const HalOperation& operation,
756 uint32_t inputIndex,
757 ActivationFn& activationFunction,
758 const HalModel& model,
759 const ConversionData& data)
760{
761 if (operation.inputs.size() <= inputIndex)
762 {
763 activationFunction = ActivationFn::kActivationNone;
764 }
765 else
766 {
Mike Kellyb5fdf382019-06-11 16:35:25 +0100767 if (!GetInputActivationFunction<HalOperand, HalOperandType>(operation, inputIndex, activationFunction, model,
768 data))
arovir01b0717b52018-09-05 17:03:25 +0100769 {
770 return Fail("%s: Operation has invalid inputs", __func__);
771 }
772 }
773 return true;
774}
775
Aron Virginas-Tar07c7c9a2019-06-12 14:03:35 +0100776template <typename HalOperand,
777 typename HalOperandType,
778 typename HalOperation,
779 typename HalModel,
780 typename ConvolutionDescriptor>
781bool GetOptionalConvolutionDilationParams(const HalOperation& operation,
782 uint32_t dilationXIndex,
783 ConvolutionDescriptor& descriptor,
784 const HalModel& model,
785 const ConversionData& data)
786{
787 bool success = true;
788 if (operation.inputs.size() >= dilationXIndex + 2)
789 {
790 success &= GetInputScalar<HalOperand, HalOperandType>(operation,
791 dilationXIndex,
792 HalOperandType::INT32,
793 descriptor.m_DilationX,
794 model,
795 data);
796 success &= GetInputScalar<HalOperand, HalOperandType>(operation,
797 dilationXIndex + 1,
798 HalOperandType::INT32,
799 descriptor.m_DilationY,
800 model,
801 data);
802 }
803
804 return success;
805}
806
Mike Kellyb5fdf382019-06-11 16:35:25 +0100807template<typename HalOperand, typename HalOperandType, typename HalModel>
808bool GetTensorInt32Values(const HalOperand& operand,
arovir01b0717b52018-09-05 17:03:25 +0100809 std::vector<int32_t>& outValues,
810 const HalModel& model,
811 const ConversionData& data)
812{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100813 if (operand.type != HalOperandType::TENSOR_INT32)
arovir01b0717b52018-09-05 17:03:25 +0100814 {
815 return Fail("%s: invalid operand type: %s", __func__, toString(operand.type).c_str());
816 }
817
818 const void* startAddress = GetOperandValueReadOnlyAddress(operand, model, data);
819 if (!startAddress)
820 {
821 return Fail("%s: failed to get operand address", __func__, operand.type);
822 }
823
824 // Check number of bytes is sensible
825 const uint32_t numBytes = operand.location.length;
826 if (numBytes % sizeof(int32_t) != 0)
827 {
828 return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
829 __func__, numBytes, sizeof(int32_t));
830 }
831
832 outValues.resize(numBytes / sizeof(int32_t));
833 memcpy(outValues.data(), startAddress, numBytes);
834 return true;
835}
836
Mike Kellyb5fdf382019-06-11 16:35:25 +0100837template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100838bool GetInputPaddingScheme(const HalOperation& operation,
839 uint32_t inputIndex,
840 PaddingScheme& outPaddingScheme,
841 const HalModel& model,
842 const ConversionData& data)
843{
844 int32_t paddingSchemeAsInt;
Mike Kellyb5fdf382019-06-11 16:35:25 +0100845 if (!GetInputInt32<HalOperand, HalOperandType>(operation, inputIndex, paddingSchemeAsInt, model, data))
arovir01b0717b52018-09-05 17:03:25 +0100846 {
847 return Fail("%s: failed to get padding scheme input value", __func__);
848 }
849
850 outPaddingScheme = static_cast<android::nn::PaddingScheme>(paddingSchemeAsInt);
851 return true;
852}
853
Mike Kellyb5fdf382019-06-11 16:35:25 +0100854template<typename HalOperand, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100855LayerInputHandle ConvertToLayerInputHandle(const HalOperation& operation,
856 uint32_t inputIndex,
857 const HalModel& model,
858 ConversionData& data)
859{
Mike Kellyb5fdf382019-06-11 16:35:25 +0100860 const HalOperand* operand = GetInputOperand<HalOperand>(operation, inputIndex, model);
arovir01b0717b52018-09-05 17:03:25 +0100861 if (!operand)
862 {
863 Fail("%s: failed to get input operand %i", __func__, inputIndex);
864 return LayerInputHandle();
865 }
866
867 if (!IsOperandTypeSupportedForTensors(operand->type))
868 {
869 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand->type).c_str());
870 return LayerInputHandle();
871 }
872
873 armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
874
875 switch (operand->lifetime)
876 {
Matthew Bentham912b3622019-05-03 15:49:14 +0100877 case V1_0::OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
878 case V1_0::OperandLifeTime::MODEL_INPUT:
879 case V1_0::OperandLifeTime::MODEL_OUTPUT:
arovir01b0717b52018-09-05 17:03:25 +0100880 {
881 // The tensor is either an operand internal to the model, or a model input.
882 // It can be associated with an ArmNN output slot for an existing layer.
883
884 // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
885 const uint32_t operandIndex = operation.inputs[inputIndex];
886 return LayerInputHandle(true, data.m_OutputSlotForOperand[operandIndex], operandTensorInfo);
887 break;
888 }
Matthew Bentham912b3622019-05-03 15:49:14 +0100889 case V1_0::OperandLifeTime::CONSTANT_COPY:
890 case V1_0::OperandLifeTime::CONSTANT_REFERENCE:
arovir01b0717b52018-09-05 17:03:25 +0100891 {
892 // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
893 ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand, model, data);
894 if (tensorPin.IsValid())
895 {
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +0100896 if (!IsLayerSupportedForAnyBackend(__func__,
897 armnn::IsConstantSupported,
898 data.m_Backends,
899 tensorPin.GetConstTensor().GetInfo()))
arovir01b0717b52018-09-05 17:03:25 +0100900 {
901 return LayerInputHandle();
902 }
903
904 armnn::IConnectableLayer* constantLayer = data.m_Network->AddConstantLayer(tensorPin.GetConstTensor());
905 armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
906 outputSlot.SetTensorInfo(tensorPin.GetConstTensor().GetInfo());
907
908 return LayerInputHandle(true, &outputSlot, operandTensorInfo);
909 }
910 else
911 {
912 Fail("%s: invalid operand tensor", __func__);
913 return LayerInputHandle();
914 }
915 break;
916 }
917 default:
918 {
919 // Unsupported lifetime for an input tensor
920 Fail("%s: unsupported lifetime for input tensor: %s",
921 __func__, toString(operand->lifetime).c_str());
922 return LayerInputHandle();
923 }
924 }
925}
926
Mike Kellyb5fdf382019-06-11 16:35:25 +0100927template<typename HalOperand, typename HalOperation, typename HalModel>
928bool SetupAndTrackLayerOutputSlot(const HalOperation& operation,
929 uint32_t operationOutputIndex,
930 armnn::IConnectableLayer& layer,
931 uint32_t layerOutputIndex,
932 const HalModel& model,
933 ConversionData& data)
934{
935 const HalOperand* outputOperand = GetOutputOperand<HalOperand>(operation, operationOutputIndex, model);
936 if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots()))
937 {
938 return false;
939 }
940
941 armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex);
942
943 const uint32_t operandIndex = operation.outputs[operationOutputIndex];
944 data.m_OutputSlotForOperand[operandIndex] = &outputSlot;
945
946 outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
947
948 return true;
949}
950
951template<typename HalOperand, typename HalOperation, typename HalModel>
952armnn::DataLayout OptionalDataLayout(const HalOperation& operation,
953 uint32_t inputIndex,
954 const HalModel& model,
955 ConversionData& data)
956{
957 const HalOperand* operand = GetInputOperand<HalOperand>(operation, inputIndex, model);
958 if (!operand)
959 {
960 return armnn::DataLayout::NHWC;
961 }
962
963 if (!IsBool(*operand))
964 {
965 return armnn::DataLayout::NHWC;
966 }
967
968 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand, model, data);
969 if (!valueAddress)
970 {
971 return armnn::DataLayout::NHWC;
972 }
973
974 if (*(static_cast<const bool*>(valueAddress)))
975 {
976 return armnn::DataLayout::NCHW;
977 }
978 else
979 {
980 return armnn::DataLayout::NHWC;
981 }
982}
983
984template<typename HalOperand, typename HalOperation, typename HalModel>
985bool SetupAndTrackLayerOutputSlot(const HalOperation& operation,
986 uint32_t outputIndex,
987 armnn::IConnectableLayer& layer,
988 const HalModel& model,
989 ConversionData& data)
990{
991 return SetupAndTrackLayerOutputSlot<HalOperand>(operation, outputIndex, layer, outputIndex, model, data);
992}
993
994template<typename HalOperand, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +0100995bool ConvertToActivation(const HalOperation& operation,
996 const char* operationName,
997 const armnn::ActivationDescriptor& activationDesc,
998 const HalModel& model,
999 ConversionData& data)
1000{
Mike Kellyb5fdf382019-06-11 16:35:25 +01001001 LayerInputHandle input = ConvertToLayerInputHandle<HalOperand>(operation, 0, model, data);
arovir01b0717b52018-09-05 17:03:25 +01001002 if (!input.IsValid())
1003 {
1004 return Fail("%s: Input 0 is invalid", operationName);
1005 }
1006
Mike Kellyb5fdf382019-06-11 16:35:25 +01001007 const HalOperand* outputOperand = GetOutputOperand<HalOperand>(operation, 0, model);
arovir01b0717b52018-09-05 17:03:25 +01001008 if (!outputOperand)
1009 {
1010 return false;
1011 }
1012 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +01001013 if (!IsLayerSupportedForAnyBackend(__func__,
1014 armnn::IsActivationSupported,
1015 data.m_Backends,
1016 input.GetTensorInfo(),
1017 outInfo,
1018 activationDesc))
arovir01b0717b52018-09-05 17:03:25 +01001019 {
1020 return false;
1021 }
1022
1023 armnn::IConnectableLayer* layer = data.m_Network->AddActivationLayer(activationDesc);
1024 BOOST_ASSERT(layer != nullptr);
1025 input.Connect(layer->GetInputSlot(0));
1026
Mike Kellyb5fdf382019-06-11 16:35:25 +01001027 return SetupAndTrackLayerOutputSlot<HalOperand>(operation, 0, *layer, model, data);
arovir01b0717b52018-09-05 17:03:25 +01001028}
1029
Mike Kellyb5fdf382019-06-11 16:35:25 +01001030template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
arovir01b0717b52018-09-05 17:03:25 +01001031bool ConvertPooling2d(const HalOperation& operation,
1032 const char* operationName,
1033 armnn::PoolingAlgorithm poolType,
1034 const HalModel& model,
1035 ConversionData& data)
1036{
Mike Kellyb5fdf382019-06-11 16:35:25 +01001037 LayerInputHandle input = ConvertToLayerInputHandle<HalOperand>(operation, 0, model, data);
arovir01b0717b52018-09-05 17:03:25 +01001038 if (!input.IsValid())
1039 {
1040 return Fail("%s: Could not read input 0", operationName);
1041 }
1042
Mike Kellyb5fdf382019-06-11 16:35:25 +01001043 const HalOperand* output = GetOutputOperand<HalOperand>(operation, 0, model);
arovir01b0717b52018-09-05 17:03:25 +01001044 if (!output)
1045 {
1046 return Fail("%s: Could not read output 0", __func__);
1047 }
1048
1049 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1050 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1051
arovir01b0717b52018-09-05 17:03:25 +01001052 armnn::Pooling2dDescriptor desc;
1053 desc.m_PoolType = poolType;
1054 desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor;
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001055 desc.m_DataLayout = armnn::DataLayout::NHWC;
arovir01b0717b52018-09-05 17:03:25 +01001056
1057 ActivationFn activation;
1058
1059 if (operation.inputs.size() == 7)
1060 {
1061 // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
1062 android::nn::PaddingScheme scheme;
Mike Kellyb5fdf382019-06-11 16:35:25 +01001063 if (!GetInputPaddingScheme<HalOperand, HalOperandType>(operation, 1, scheme, model, data)
1064 || !GetInputScalar<HalOperand, HalOperandType>(operation, 2, HalOperandType::INT32, desc.m_StrideX, model,
1065 data)
1066 || !GetInputScalar<HalOperand, HalOperandType>(operation, 3, HalOperandType::INT32, desc.m_StrideY, model,
1067 data)
1068 || !GetInputScalar<HalOperand, HalOperandType>(operation, 4, HalOperandType::INT32, desc.m_PoolWidth, model,
1069 data)
1070 || !GetInputScalar<HalOperand, HalOperandType>(operation, 5, HalOperandType::INT32, desc.m_PoolHeight,
1071 model, data)
1072 || !GetInputActivationFunction<HalOperand, HalOperandType>(operation, 6, activation, model, data))
arovir01b0717b52018-09-05 17:03:25 +01001073 {
1074 return Fail("%s: Operation has invalid inputs", operationName);
1075 }
1076
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001077 const unsigned int inputWidth = inputInfo.GetShape()[2];
1078 const unsigned int inputHeight = inputInfo.GetShape()[1];
arovir01b0717b52018-09-05 17:03:25 +01001079
1080 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
1081 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
1082 }
1083 else
1084 {
1085 // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
Mike Kellyb5fdf382019-06-11 16:35:25 +01001086 if (!GetInputScalar<HalOperand, HalOperandType>(operation, 1, HalOperandType::INT32, desc.m_PadLeft, model,
1087 data)
1088 || !GetInputScalar<HalOperand, HalOperandType>(operation, 2, HalOperandType::INT32, desc.m_PadRight, model,
1089 data)
1090 || !GetInputScalar<HalOperand, HalOperandType>(operation, 3, HalOperandType::INT32, desc.m_PadTop, model,
1091 data)
1092 || !GetInputScalar<HalOperand, HalOperandType>(operation, 4, HalOperandType::INT32, desc.m_PadBottom, model,
1093 data)
1094 || !GetInputScalar<HalOperand, HalOperandType>(operation, 5, HalOperandType::INT32, desc.m_StrideX, model,
1095 data)
1096 || !GetInputScalar<HalOperand, HalOperandType>(operation, 6, HalOperandType::INT32, desc.m_StrideY, model,
1097 data)
1098 || !GetInputScalar<HalOperand, HalOperandType>(operation, 7, HalOperandType::INT32, desc.m_PoolWidth, model,
1099 data)
1100 || !GetInputScalar<HalOperand, HalOperandType>(operation, 8, HalOperandType::INT32, desc.m_PoolHeight,
1101 model, data)
1102 || !GetInputActivationFunction<HalOperand, HalOperandType>(operation, 9, activation, model, data))
arovir01b0717b52018-09-05 17:03:25 +01001103 {
1104 return Fail("%s: Operation has invalid inputs", operationName);
1105 }
1106 }
1107
Nattapat Chaimanowongd5fd9762019-04-04 13:33:10 +01001108 if (!IsLayerSupportedForAnyBackend(__func__,
1109 armnn::IsPooling2dSupported,
1110 data.m_Backends,
1111 inputInfo,
1112 outputInfo,
1113 desc))
arovir01b0717b52018-09-05 17:03:25 +01001114 {
Éanna Ó Catháin3d1059c2018-10-11 15:53:04 +01001115 return false;
arovir01b0717b52018-09-05 17:03:25 +01001116 }
arovir01b0717b52018-09-05 17:03:25 +01001117
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001118 armnn::IConnectableLayer* pooling2dLayer = data.m_Network->AddPooling2dLayer(desc);
1119 if (!pooling2dLayer)
arovir01b0717b52018-09-05 17:03:25 +01001120 {
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001121 return Fail("%s: AddPooling2dLayer failed", __func__);
arovir01b0717b52018-09-05 17:03:25 +01001122 }
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001123
1124 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activation, pooling2dLayer, data);
1125 if (!endLayer)
arovir01b0717b52018-09-05 17:03:25 +01001126 {
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001127 return Fail("%s: ProcessActivation failed", __func__);
arovir01b0717b52018-09-05 17:03:25 +01001128 }
Matteo Martincigh39fc5472018-10-26 16:39:28 +01001129
1130 input.Connect(pooling2dLayer->GetInputSlot(0));
1131
Mike Kellyb5fdf382019-06-11 16:35:25 +01001132 return SetupAndTrackLayerOutputSlot<HalOperand>(operation, 0, *endLayer, model, data);
1133}
1134
1135template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
1136bool ConvertConv2d(const HalOperation& operation, const HalModel& model, ConversionData& data)
1137{
1138 LayerInputHandle input = ConvertToLayerInputHandle<HalOperand>(operation, 0, model, data);
1139 if (!input.IsValid())
1140 {
1141 return Fail("%s: Operation has invalid inputs", __func__);
1142 }
1143
1144 const HalOperand* output = GetOutputOperand<HalOperand>(operation, 0, model);
1145 if (!output)
1146 {
1147 return Fail("%s: Could not read output 0", __func__);
1148 }
1149
1150 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1151 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1152
1153 // ArmNN does not currently support non-fixed weights or bias
1154 const ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin<HalOperand>(operation, 1, model, data);
1155 const ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin<HalOperand>(operation, 2, model, data);
1156
1157 if (!weightsPin.IsValid() || !biasPin.IsValid())
1158 {
1159 return Fail("%s: Operation has invalid inputs", __func__);
1160 }
1161
1162 armnn::ConstTensor weights = weightsPin.GetConstTensor();
1163 armnn::ConstTensor bias = biasPin.GetConstTensor();
1164 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), inputInfo);
1165
1166 armnn::Convolution2dDescriptor desc;
1167 desc.m_DataLayout = armnn::DataLayout::NHWC;
1168 ActivationFn activation;
1169
1170 if (operation.inputs.size() >= 10)
1171 {
1172 if (!GetInputScalar<HalOperand, HalOperandType>(operation, 3, HalOperandType::INT32, desc.m_PadLeft, model,
1173 data)
1174 || !GetInputScalar<HalOperand, HalOperandType>(operation, 4, HalOperandType::INT32, desc.m_PadRight, model,
1175 data)
1176 || !GetInputScalar<HalOperand, HalOperandType>(operation, 5, HalOperandType::INT32, desc.m_PadTop, model,
1177 data)
1178 || !GetInputScalar<HalOperand, HalOperandType>(operation, 6, HalOperandType::INT32, desc.m_PadBottom, model,
1179 data)
1180 || !GetInputScalar<HalOperand, HalOperandType>(operation, 7, HalOperandType::INT32, desc.m_StrideX, model,
1181 data)
1182 || !GetInputScalar<HalOperand, HalOperandType>(operation, 8, HalOperandType::INT32, desc.m_StrideY, model,
1183 data)
1184 || !GetInputActivationFunction<HalOperand, HalOperandType>(operation, 9, activation, model, data))
1185 {
1186 return Fail("%s: Operation has invalid inputs", __func__);
1187 }
1188 desc.m_DataLayout = OptionalDataLayout<HalOperand>(operation, 10, model, data);
1189 }
1190 else if (operation.inputs.size() >= 7)
1191 {
1192 android::nn::PaddingScheme paddingScheme;
1193 if (!GetInputPaddingScheme<HalOperand, HalOperandType>(operation, 3, paddingScheme, model, data)
1194 || !GetInputScalar<HalOperand, HalOperandType>(operation, 4, HalOperandType::INT32, desc.m_StrideX,
1195 model, data)
1196 || !GetInputScalar<HalOperand, HalOperandType>(operation, 5, HalOperandType::INT32, desc.m_StrideY, model,
1197 data)
1198 || !GetInputActivationFunction<HalOperand, HalOperandType>(operation, 6, activation, model, data))
1199 {
1200 return Fail("%s: Operation has invalid inputs", __func__);
1201 }
1202
1203 const uint32_t kernelX = weights.GetShape()[2];
1204 const uint32_t kernelY = weights.GetShape()[1];
1205 const uint32_t inputX = inputInfo.GetShape()[2];
1206 const uint32_t inputY = inputInfo.GetShape()[1];
1207
1208 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1209 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1210
1211 desc.m_DataLayout = OptionalDataLayout<HalOperand>(operation, 7, model, data);
1212 }
1213 else
1214 {
1215 return Fail("%s: Unsupported number of operation inputs", __func__);
1216 }
1217
1218 desc.m_BiasEnabled = true;
1219 armnn::Optional<armnn::TensorInfo> biases(bias.GetInfo());
1220
1221 if (!IsLayerSupportedForAnyBackend(__func__,
1222 armnn::IsConvolution2dSupported,
1223 data.m_Backends,
1224 inputInfo,
1225 outputInfo,
1226 desc,
1227 weights.GetInfo(),
1228 biases))
1229 {
1230 return false;
1231 }
1232
1233 armnn::IConnectableLayer* startLayer =
1234 data.m_Network->AddConvolution2dLayer(desc, weights, armnn::Optional<armnn::ConstTensor>(bias));
1235
1236 if (!startLayer)
1237 {
1238 return Fail("%s: AddConvolution2dLayer failed", __func__);
1239 }
1240
1241 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activation, startLayer, data);
1242
1243 if (!endLayer)
1244 {
1245 return Fail("%s: ProcessActivation failed", __func__);
1246 }
1247
1248 input.Connect(startLayer->GetInputSlot(0));
1249
1250 return SetupAndTrackLayerOutputSlot<HalOperand>(operation, 0, *endLayer, model, data);
1251}
1252
1253template<typename HalOperand, typename HalOperandType, typename HalOperation, typename HalModel>
1254bool ConvertDepthwiseConv2d(const HalOperation& operation, const HalModel& model, ConversionData& data)
1255{
1256 LayerInputHandle input = ConvertToLayerInputHandle<HalOperand>(operation, 0, model, data);
1257
1258 if (!input.IsValid())
1259 {
1260 return Fail("%s: Operation has invalid inputs", __func__);
1261 }
1262
1263 const HalOperand* output = GetOutputOperand<HalOperand>(operation, 0, model);
1264
1265 if (!output)
1266 {
1267 return Fail("%s: Could not read output 0", __func__);
1268 }
1269
1270 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1271 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1272
1273 // ArmNN does not currently support non-fixed weights or bias
1274
1275 // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
1276 const HalOperand* weightsOperand = GetInputOperand<HalOperand>(operation, 1, model);
1277
1278 if (weightsOperand == nullptr)
1279 {
1280 return Fail("%s: Operand is invalid", __func__);
1281 }
1282 armnn::DepthwiseConvolution2dDescriptor desc;
1283 desc.m_DataLayout = armnn::DataLayout::NHWC;
1284
1285 // Look ahead to find the optional DataLayout, if present
1286 if (operation.inputs.size() >= 12)
1287 {
1288 desc.m_DataLayout = OptionalDataLayout<HalOperand>(operation, 11, model, data);
1289 }
1290 else if (operation.inputs.size() >= 9)
1291 {
1292 desc.m_DataLayout = OptionalDataLayout<HalOperand>(operation, 8, model, data);
1293 }
1294
1295 armnnUtils::DataLayoutIndexed dataLayoutIndexed(desc.m_DataLayout);
1296 unsigned int channelsIndex = dataLayoutIndexed.GetChannelsIndex();
1297 unsigned int widthIndex = dataLayoutIndexed.GetWidthIndex();
1298 unsigned int heightIndex = dataLayoutIndexed.GetHeightIndex();
1299
1300 // Reinterpret weight data as [ H, W, I, M ]
1301 armnn::TensorShape weightsShape({ weightsOperand->dimensions[1],
1302 weightsOperand->dimensions[2],
1303 inputInfo.GetShape()[channelsIndex],
1304 weightsOperand->dimensions[3] / inputInfo.GetShape()[channelsIndex] });
1305
1306 // Swizzle weight data [ H, W, I, M ] -> [ M, I, H, W ]
1307 const armnn::PermutationVector HWIMToMIHW = { 2U, 3U, 1U, 0U };
1308
1309 const ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin<HalOperand>(operation, 1, model, data,
1310 HWIMToMIHW, &weightsShape);
1311
1312 // Bias is a 1D tensor
1313 const ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin<HalOperand>(operation, 2, model, data);
1314
1315 if (!weightsPin.IsValid() || !biasPin.IsValid())
1316 {
1317 return Fail("%s: Operation has invalid inputs", __func__);
1318 }
1319
1320 armnn::ConstTensor weights = weightsPin.GetConstTensor();
1321 armnn::ConstTensor bias = biasPin.GetConstTensor();
1322 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), inputInfo);
1323
1324 ActivationFn activation;
1325
1326 if (operation.inputs.size() >= 11)
1327 {
1328 if (!GetInputScalar<HalOperand, HalOperandType>(operation, 3, HalOperandType::INT32, desc.m_PadLeft, model,
1329 data)
1330 || !GetInputScalar<HalOperand, HalOperandType>(operation, 4, HalOperandType::INT32, desc.m_PadRight, model,
1331 data)
1332 || !GetInputScalar<HalOperand, HalOperandType>(operation, 5, HalOperandType::INT32, desc.m_PadTop, model,
1333 data)
1334 || !GetInputScalar<HalOperand, HalOperandType>(operation, 6, HalOperandType::INT32, desc.m_PadBottom, model,
1335 data)
1336 || !GetInputScalar<HalOperand, HalOperandType>(operation, 7, HalOperandType::INT32, desc.m_StrideX, model,
1337 data)
1338 || !GetInputScalar<HalOperand, HalOperandType>(operation, 8, HalOperandType::INT32, desc.m_StrideY, model,
1339 data)
Aron Virginas-Tar07c7c9a2019-06-12 14:03:35 +01001340 || !GetInputActivationFunction<HalOperand, HalOperandType>(operation, 10, activation, model, data)
1341 || !GetOptionalConvolutionDilationParams<HalOperand, HalOperandType>(operation, 12, desc, model, data))
Mike Kellyb5fdf382019-06-11 16:35:25 +01001342 {
1343 return Fail("%s: Operation has invalid inputs", __func__);
1344 }
1345 }
1346 else if (operation.inputs.size() >= 8)
1347 {
1348 android::nn::PaddingScheme paddingScheme;
1349 if (!GetInputPaddingScheme<HalOperand, HalOperandType>(operation, 3, paddingScheme, model, data)
1350 || !GetInputScalar<HalOperand, HalOperandType>(operation, 4, HalOperandType::INT32, desc.m_StrideX, model,
1351 data)
1352 || !GetInputScalar<HalOperand, HalOperandType>(operation, 5, HalOperandType::INT32, desc.m_StrideY, model,
1353 data)
Aron Virginas-Tar07c7c9a2019-06-12 14:03:35 +01001354 || !GetInputActivationFunction<HalOperand, HalOperandType>(operation, 7, activation, model, data)
1355 || !GetOptionalConvolutionDilationParams<HalOperand, HalOperandType>(operation, 9, desc, model, data))
Mike Kellyb5fdf382019-06-11 16:35:25 +01001356 {
1357 return Fail("%s: Operation has invalid inputs", __func__);
1358 }
1359
1360 const uint32_t kernelX = weights.GetShape()[3];
1361 const uint32_t kernelY = weights.GetShape()[2];
1362 const uint32_t inputX = inputInfo.GetShape()[widthIndex];
1363 const uint32_t inputY = inputInfo.GetShape()[heightIndex];
1364
1365 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1366 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1367 }
1368 else
1369 {
1370 return Fail("%s: Unsupported number of operation inputs", __func__);
1371 }
1372
1373 desc.m_BiasEnabled = true;
1374 armnn::Optional<armnn::TensorInfo> biases(bias.GetInfo());
1375
1376 if (!IsLayerSupportedForAnyBackend(__func__,
1377 armnn::IsDepthwiseConvolutionSupported,
1378 data.m_Backends,
1379 inputInfo,
1380 outputInfo,
1381 desc,
1382 weights.GetInfo(),
1383 biases))
1384 {
1385 return false;
1386 }
1387
1388 armnn::IConnectableLayer* startLayer =
1389 data.m_Network->AddDepthwiseConvolution2dLayer(desc, weights, armnn::Optional<armnn::ConstTensor>(bias));
1390 if (!startLayer)
1391 {
1392 return Fail("%s: AddDepthwiseConvolution2dLayer failed", __func__);
1393 }
1394
1395 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activation, startLayer, data);
1396 if (!endLayer)
1397 {
1398 return Fail("%s: ProcessActivation failed", __func__);
1399 }
1400
1401 input.Connect(startLayer->GetInputSlot(0));
1402
1403 return SetupAndTrackLayerOutputSlot<HalOperand>(operation, 0, *endLayer, model, data);
arovir01b0717b52018-09-05 17:03:25 +01001404}
1405
saoste01b8471482018-10-10 09:44:51 +01001406} // namespace armnn_driver