blob: 3da56ef802f39c8ba5f3e4c83fa24aa84bb93dec [file] [log] [blame]
telsoa015307bc12018-03-09 13:51:08 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// See LICENSE file in the project root for full license information.
4//
5
6#define LOG_TAG "ArmnnDriver"
7
8#include "ModelToINetworkConverter.hpp"
telsoa01ce3e84a2018-08-31 09:31:35 +01009#include <OperationsUtils.h>
telsoa015307bc12018-03-09 13:51:08 +000010
11#include <armnn/LayerSupport.hpp>
12#include <Permute.hpp>
13
14#include <log/log.h>
15#include <cassert>
16
17#include <boost/format.hpp>
18#include <boost/core/ignore_unused.hpp>
19#include <boost/test/tools/floating_point_comparison.hpp>
20#include <boost/cast.hpp>
21
telsoa01ce3e84a2018-08-31 09:31:35 +010022using namespace android::hardware;
23
surmeh0149b9e102018-05-17 14:11:25 +010024namespace armnn_driver
25{
26class LayerInputHandle
27{
28public:
29 LayerInputHandle()
30 : m_OutputSlot(nullptr)
31 , m_Valid(false)
32 {}
33
34 LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo)
35 : m_OutputSlot(outputSlot)
36 , m_Valid(valid)
37 , m_TensorInfo(tensorInfo)
38 {}
39
40 bool IsValid() const { return m_Valid; }
41 void Connect(armnn::IInputSlot& inputSlot)
42 {
43 assert(IsValid());
44
45 if (m_OutputSlot)
46 {
47 m_OutputSlot->Connect(inputSlot);
48 }
49 }
50 const armnn::TensorInfo& GetTensorInfo() const { return m_TensorInfo; }
51
52private:
53 armnn::IOutputSlot* m_OutputSlot;
54 bool m_Valid;
55 armnn::TensorInfo m_TensorInfo;
56};
57} // armnn_driver
58
telsoa015307bc12018-03-09 13:51:08 +000059namespace
60{
61using namespace armnn_driver;
62using namespace android::nn;
63
64// Convenience function to log the reason for failing to convert a model.
65// @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
66template<class... Args>
67static bool Fail(const char* formatStr, Args&&... args)
68{
69 ALOGD(formatStr, std::forward<Args>(args)...);
70 return false;
71}
72
73// Convenience function to call an Is*Supported function and log caller name together with reason for lack of support.
74// Called as: IsLayerSupported(__func__, Is*Supported, a, b, c, d, e)
75template<typename IsLayerSupportedFunc, typename ... Args>
76bool IsLayerSupported(const char* funcName, IsLayerSupportedFunc f, Args&&... args)
77{
78 std::vector<char> unsupportedReason(1024+1);
79 bool isSupported = f(std::forward<Args>(args)..., unsupportedReason.data(), unsupportedReason.size()-1);
80 if(isSupported)
81 {
82 return true;
83 }
84 else
85 {
86 std::string sUnsupportedReason(unsupportedReason.data());
87 if (sUnsupportedReason.size() > 0)
88 {
89 ALOGD("%s: not supported by armnn: %s", funcName, sUnsupportedReason.c_str());
90 } else
91 {
92 ALOGD("%s: not supported by armnn", funcName);
93 }
94 return false;
95 }
96}
97
98armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
99{
100 return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
101}
102
103inline bool IsOperandTypeSupportedForTensors(OperandType type)
104{
105 return type == OperandType::TENSOR_FLOAT32 ||
106 type == OperandType::TENSOR_QUANT8_ASYMM ||
107 type == OperandType::TENSOR_INT32;
108}
109
telsoa01ce3e84a2018-08-31 09:31:35 +0100110void BroadcastTensor(LayerInputHandle& input0, LayerInputHandle& input1, armnn::IConnectableLayer* startLayer,
111 armnn::INetwork& network)
112{
113 BOOST_ASSERT(startLayer != nullptr);
114 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
115 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
116
117 if (inputTensorInfo0.GetNumDimensions() != inputTensorInfo1.GetNumDimensions())
118 {
119 // If the number of dimensions do not match then we need to add degenerate dimensions
120 // to the "smaller" tensor using a reshape:
121 // Small Big
122 // | |
123 // Reshape |
124 // \ /
125 // Add
126 bool input0IsBigger = inputTensorInfo0.GetNumDimensions() > inputTensorInfo1.GetNumDimensions();
127
128 LayerInputHandle& smallTensorHandle = input0IsBigger ? input1 : input0;
129 const armnn::TensorInfo& smallTensorDims = smallTensorHandle.GetTensorInfo();
130
131 LayerInputHandle& bigTensorHandle = input0IsBigger ? input0 : input1;
132 const armnn::TensorInfo& bigTensorDims = bigTensorHandle.GetTensorInfo();
133
134 const unsigned int bigTensorDimsNumber = bigTensorDims.GetNumDimensions();
135 std::vector<unsigned int> reshapedDims(bigTensorDimsNumber, 1);
136 unsigned int sizeDifference = bigTensorDimsNumber - smallTensorDims.GetNumDimensions();
137 for (unsigned i = sizeDifference; i < bigTensorDimsNumber; ++i)
138 {
139 reshapedDims[i] = smallTensorDims.GetShape()[i-sizeDifference];
140 }
141 armnn::TensorInfo reshapedInfo = smallTensorDims;
142 reshapedInfo.SetShape(armnn::TensorShape{ static_cast<unsigned int>(reshapedDims.size()),
143 reshapedDims.data() });
144
145 armnn::ReshapeDescriptor reshapeDesc;
146 reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
147 armnn::IConnectableLayer* const reshapeLayer = network.AddReshapeLayer(reshapeDesc);
148 smallTensorHandle.Connect(reshapeLayer->GetInputSlot(0));
149 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
150
151 // Connect the outputs from new reshape and original input layer
152 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
153 bigTensorHandle.Connect(startLayer->GetInputSlot(1));
154 }
155 else
156 {
157 input0.Connect(startLayer->GetInputSlot(0));
158 input1.Connect(startLayer->GetInputSlot(1));
159 }
160}
161
telsoa015307bc12018-03-09 13:51:08 +0000162void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
163 android::nn::PaddingScheme scheme)
164{
165 int32_t padHead;
166 int32_t padTail;
167 calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
168 outPadHead = boost::numeric_cast<uint32_t>(padHead);
169 outPadTail = boost::numeric_cast<uint32_t>(padTail);
170}
171
telsoa015307bc12018-03-09 13:51:08 +0000172Shape GetOperandShape(const Operand& operand)
173{
174 Shape shape;
175 shape.type = operand.type;
176 shape.dimensions = operand.dimensions;
177 shape.scale = operand.scale;
178 shape.offset = operand.zeroPoint;
179 return shape;
180}
181
182// ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
183// what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
184// we accept some tolerance. We don't want to ArmNN itself to accept these inconsistencies as it is up to the user
185// (us, in this case) to ensure they match.
186void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
187 const armnn::TensorInfo& weightInfo, const armnn::TensorInfo& inputInfo)
188{
189 const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
190 if (biasInfo.GetQuantizationScale() != expectedBiasScale)
191 {
192 boost::math::fpc::close_at_tolerance<float> comparer(boost::math::fpc::percent_tolerance(1.0f));
193 if (comparer(biasInfo.GetQuantizationScale(), expectedBiasScale))
194 {
195 ALOGW("Bias quantization scale has been modified to match input*weights");
196 biasInfo.SetQuantizationScale(expectedBiasScale);
197 }
198 }
199}
200
telsoa01ce3e84a2018-08-31 09:31:35 +0100201// 4D Tensor Permutations
202const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U });
telsoa015307bc12018-03-09 13:51:08 +0000203const armnn::PermutationVector NHWCToArmNN({ 0U, 2U, 3U, 1U });
surmeh0149b9e102018-05-17 14:11:25 +0100204const armnn::PermutationVector ArmNNToNHWC({ 0U, 3U, 1U, 2U });
205const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U });
telsoa015307bc12018-03-09 13:51:08 +0000206
telsoa01ce3e84a2018-08-31 09:31:35 +0100207// 3D Permutation Vectors
208const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U });
209const armnn::PermutationVector RotateTensorLeft({ 2U, 0U, 1U });
210const armnn::PermutationVector RotateTensorRight({ 1U, 2U, 0U });
211
telsoa015307bc12018-03-09 13:51:08 +0000212template <typename OSlot>
213armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input,
214 const armnn::PermutationVector& mappings)
215{
216 // Add swizzle layer
217 armnn::IConnectableLayer* const layer = network.AddPermuteLayer(mappings);
218
219 assert(layer != nullptr);
220
telsoa01ce3e84a2018-08-31 09:31:35 +0100221 // Connect input to swizzle layer
telsoa015307bc12018-03-09 13:51:08 +0000222 input.Connect(layer->GetInputSlot(0));
223
224 // Setup swizzled output
225 const armnn::TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mappings);
226 layer->GetOutputSlot(0).SetTensorInfo(outInfo);
227
228 return *layer;
229}
230
telsoa01ce3e84a2018-08-31 09:31:35 +0100231void SwizzleIn(armnn::INetwork& network, LayerInputHandle& input, armnn::IConnectableLayer& layer, unsigned int index)
telsoa015307bc12018-03-09 13:51:08 +0000232{
telsoa015307bc12018-03-09 13:51:08 +0000233 // Add swizzle layer
234 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, input, NHWCToArmNN);
telsoa015307bc12018-03-09 13:51:08 +0000235 // Connect swizzled input to layer
telsoa01ce3e84a2018-08-31 09:31:35 +0100236 swizzleLayer.GetOutputSlot(0).Connect(layer.GetInputSlot(index));
237}
telsoa015307bc12018-03-09 13:51:08 +0000238
telsoa01ce3e84a2018-08-31 09:31:35 +0100239armnn::IConnectableLayer& DeswizzleOut(armnn::INetwork& network, armnn::IConnectableLayer& layer, unsigned int index)
240{
telsoa015307bc12018-03-09 13:51:08 +0000241 // Add deswizzle layer
telsoa01ce3e84a2018-08-31 09:31:35 +0100242 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(network, layer.GetOutputSlot(index), ArmNNToNHWC);
telsoa015307bc12018-03-09 13:51:08 +0000243 return deswizzleLayer;
244}
245
telsoa01ce3e84a2018-08-31 09:31:35 +0100246// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly
247armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network,
248 LayerInputHandle& input,
249 armnn::IConnectableLayer& firstLayer,
250 armnn::IConnectableLayer& lastLayer)
251{
252 SwizzleIn(network, input, firstLayer, 0);
253 return DeswizzleOut(network, lastLayer, 0);
254}
255
256// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly
telsoa015307bc12018-03-09 13:51:08 +0000257armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input,
258 armnn::IConnectableLayer& layer)
259{
260 return SwizzleInDeswizzleOut(network, input, layer, layer);
261}
surmeh0149b9e102018-05-17 14:11:25 +0100262
263bool ValidateConcatOutputShape(const std::vector<armnn::TensorShape> & inputShapes,
264 const armnn::TensorShape & outputShape,
265 uint32_t concatDim)
266{
267 // Validate the output shape is correct given the input shapes (which have just been validated)
268 unsigned int numDimensions = inputShapes[0].GetNumDimensions();
269 if (outputShape.GetNumDimensions() != numDimensions)
270 {
271 return Fail("%s: Output shape has wrong number of dimensions", __func__);
272 }
273
274 unsigned int outputSizeAlongConcatenatedDimension = 0;
275 for (unsigned int i = 0; i < inputShapes.size(); i++)
276 {
277 outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
278 }
279
280 for (unsigned int i = 0; i < numDimensions; ++i)
281 {
282 if (i == concatDim)
283 {
284 if (outputShape[i] != outputSizeAlongConcatenatedDimension)
285 {
286 return Fail(
287 "%s: Invalid output shape for dimension %d (%d != %d)",
288 __func__,
289 i,
290 outputShape[i],
291 outputSizeAlongConcatenatedDimension);
292 }
293 }
294 else
295 {
296 if (outputShape[i] != inputShapes[0][i])
297 {
298 return Fail("%s: Invalid output shape", __func__);
299 }
300 }
301 }
302
303 return true;
304}
305
telsoa01ce3e84a2018-08-31 09:31:35 +0100306bool RequiresReshape(armnn::TensorShape & inputShape)
307{
308 return inputShape.GetNumDimensions() < 3;
309}
310
311template <typename OSlot>
312armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network, OSlot& inputLayer,
313 armnn::TensorInfo reshapeInfo)
314{
315 armnn::ReshapeDescriptor reshapeDescriptor;
316 reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
317
318 armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor);
319 assert(reshapeLayer != nullptr);
320
321 // Attach the input layer to the reshape layer
322 inputLayer.Connect(reshapeLayer->GetInputSlot(0));
323 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo);
324
325 return *reshapeLayer;
326}
327
surmeh0149b9e102018-05-17 14:11:25 +0100328void SwizzleInputs(armnn::INetwork& network,
329 std::vector<LayerInputHandle>& inputs,
330 std::vector<armnn::TensorShape>& inputShapes,
331 const armnn::PermutationVector& mapping)
332{
telsoa01ce3e84a2018-08-31 09:31:35 +0100333 if (!mapping.IsEqual(IdentityPermutation4D))
surmeh0149b9e102018-05-17 14:11:25 +0100334 {
335 size_t nInputs = inputs.size();
336 for (size_t i=0; i<nInputs; ++i)
337 {
338 // add swizzle layer
339 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, inputs[i], mapping);
340 auto& outputSlot = swizzleLayer.GetOutputSlot(0);
341 auto& outputInfo = outputSlot.GetTensorInfo();
342 // replace inputs with the swizzled ones
343 inputs[i] = LayerInputHandle(true, &outputSlot, outputInfo);
344 inputShapes[i] = inputs[i].GetTensorInfo().GetShape();
345 }
346 }
347}
348
telsoa01ce3e84a2018-08-31 09:31:35 +0100349void CreatePermutationParameters(const unsigned int numberOfDimensions,
350 int32_t & concatDimension,
351 std::pair<armnn::PermutationVector, armnn::PermutationVector> & permutationPair)
352{
353 assert(numberOfDimensions >= 3);
354
355 // ArmNN uses Compute Library subtensors to perform concatenation
356 // This only works when concatenating along dimension 0 or 1 for a 4-D tensor,
357 // or along dimension 0 for a 3-D tensor.
358 if (numberOfDimensions == 4)
359 {
360 if (concatDimension == 3)
361 {
362 concatDimension = 1;
363 permutationPair = std::make_pair(NHWCToArmNN, ArmNNToNHWC);
364 }
365 else if (concatDimension == 2)
366 {
367 concatDimension = 1;
368 permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2);
369 }
370 else
371 {
372 permutationPair = std::make_pair(IdentityPermutation4D, IdentityPermutation4D);
373 }
374
375 }
376 else if (numberOfDimensions == 3)
377 {
378 if (concatDimension == 2)
379 {
380 concatDimension = 0;
381 permutationPair = std::make_pair(RotateTensorRight, RotateTensorLeft);
382 }
383 else if (concatDimension == 1)
384 {
385 concatDimension = 0;
386 permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight);
387 }
388 else
389 {
390 permutationPair = std::make_pair(IdentityPermutation3D, IdentityPermutation3D);
391 }
392 }
393}
394
395
telsoa015307bc12018-03-09 13:51:08 +0000396} // namespace
397
398namespace armnn_driver
399{
400
401class ConstTensorPin
402{
403public:
404 // Creates an invalid tensor pin (can be used to signal errors)
telsoa01ce3e84a2018-08-31 09:31:35 +0100405 // The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid
406 ConstTensorPin(bool optional = false) : m_Optional(optional) {}
telsoa015307bc12018-03-09 13:51:08 +0000407
408 // @param tensorInfo TensorInfo associated with the tensor.
409 // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
410 // the model being converted.
411 // @param numBytes Number of bytes for the tensor data.
412 ConstTensorPin(const armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
413 const armnn::PermutationVector& mappings)
414 {
415 boost::ignore_unused(numBytes);
416 assert(tensorInfo.GetNumBytes() == numBytes);
417
418 const bool needsSwizzling = (mappings.GetSize() > 0);
419 if (needsSwizzling)
420 {
421 m_SwizzledTensorData.resize(tensorInfo.GetNumBytes());
422 SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings);
423
424 m_ConstTensor = armnn::ConstTensor(armnnUtils::Permuted(tensorInfo, mappings), m_SwizzledTensorData.data());
425 }
426 else
427 {
428 m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart);
429 }
430 }
431
432 ConstTensorPin(const ConstTensorPin& other) = delete;
433 ConstTensorPin(ConstTensorPin&& other) = default;
434
435 bool IsValid() const { return m_ConstTensor.GetMemoryArea() != nullptr; }
telsoa01ce3e84a2018-08-31 09:31:35 +0100436 bool IsOptional() const { return m_Optional; }
telsoa015307bc12018-03-09 13:51:08 +0000437 const armnn::ConstTensor& GetConstTensor() const { return m_ConstTensor; }
telsoa01ce3e84a2018-08-31 09:31:35 +0100438 const armnn::ConstTensor* GetConstTensorPtr() const
439 {
440 if (IsValid() && m_ConstTensor.GetNumElements() > 0)
441 {
442 return &m_ConstTensor;
443 }
444 // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided)
445 return nullptr;
446 }
telsoa015307bc12018-03-09 13:51:08 +0000447
448private:
449 armnn::ConstTensor m_ConstTensor;
450 // Owned memory for swizzled tensor data, only required if the tensor needed
451 // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
452 // the pools associated with the model being converted.
453 std::vector<uint8_t> m_SwizzledTensorData;
telsoa01ce3e84a2018-08-31 09:31:35 +0100454 // optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given
455 bool m_Optional;
telsoa015307bc12018-03-09 13:51:08 +0000456};
457
telsoa01ce3e84a2018-08-31 09:31:35 +0100458ModelToINetworkConverter::ModelToINetworkConverter(armnn::Compute compute,
459 const neuralnetworks::V1_0::Model& model,
telsoa015307bc12018-03-09 13:51:08 +0000460 const std::set<unsigned int>& forcedUnsupportedOperations)
461 : m_Compute(compute)
462 , m_Model(model)
463 , m_ForcedUnsupportedOperations(forcedUnsupportedOperations)
464 , m_Network(nullptr, nullptr)
465 , m_ConversionResult(ConversionResult::Success)
466{
467 try
468 {
469 Convert();
470 }
471 catch (armnn::Exception& e)
472 {
473 m_ConversionResult = ConversionResult::UnsupportedFeature;
474 ALOGE("%s: Unexpected exception: %s", __func__, e.what());
475 assert(false);
476 }
477}
478
479void ModelToINetworkConverter::Convert()
480{
481 ALOGV("ModelToINetworkConverter::Convert(): %s", GetModelSummary(m_Model).c_str());
482
483 // map the memory pool into shared pointers
484 m_MemPools.clear();
485 if (!setRunTimePoolInfosFromHidlMemories(&m_MemPools, m_Model.pools))
486 {
487 Fail("%s: Setting of run time pool infos from Hidl Memories has failed.", __func__);
488 m_ConversionResult = ConversionResult::ErrorMappingPools;
489 return;
490 }
491
492 uint32_t totalPoolSize = 0;
493 for (auto&& pool : m_Model.pools)
494 {
495 totalPoolSize += pool.size();
496 }
497
498 // Create armnn::INetwork
499 m_Network = armnn::INetwork::Create();
500
501 // add operations to it
502 // track which layer outputs each operand
503 m_OutputSlotForOperand = std::vector<armnn::IOutputSlot*>(m_Model.operands.size(), nullptr);
504
505 try
506 {
507 for (uint32_t i = 0; i < m_Model.inputIndexes.size(); i++)
508 {
509 // inputs in android nn are represented by operands
510 uint32_t inputIndex = m_Model.inputIndexes[i];
511 const Operand& operand = m_Model.operands[inputIndex];
512 const armnn::TensorInfo& tensor = GetTensorInfoForOperand(operand);
513 armnn::IConnectableLayer* layer = m_Network->AddInputLayer(i);
514
515 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
516 outputSlot.SetTensorInfo(GetTensorInfoForOperand(operand));
517
518 // store for later layers
519 m_OutputSlotForOperand[inputIndex] = &outputSlot;
520 }
521 }
522 catch (UnsupportedOperand& e)
523 {
524 Fail("%s: Operand type %s not supported in ArmnnDriver", __func__, toString(e.m_type).c_str());
525 m_ConversionResult = ConversionResult::UnsupportedFeature;
526 }
527 catch (const armnn::InvalidArgumentException& e)
528 {
529 Fail("%s: Failed to convert input operand to TensorShape: %s", __func__, e.what());
530 m_ConversionResult = ConversionResult::UnsupportedFeature;
531 }
532
533 for (uint32_t operationIdx = 0; operationIdx < m_Model.operations.size(); operationIdx++)
534 {
535 const auto& operation = m_Model.operations[operationIdx];
536
537 bool ok = true;
538 if (m_ForcedUnsupportedOperations.find(operationIdx) != m_ForcedUnsupportedOperations.end())
539 {
540 Fail("%s: Operation at index %i has been forced to be unsupported.", __func__, operationIdx);
541 ok = false;
542 }
543
544 if (ok)
545 {
546 try
547 {
548 ok = ConvertOperation(operation);
549 }
550 catch (UnsupportedOperand& e)
551 {
552 Fail("%s: Operand type %s not supported in ArmnnDriver", __func__, toString(e.m_type).c_str());
553 ok = false;
554 }
555 catch (const armnn::InvalidArgumentException& e)
556 {
557 Fail("%s: Failed to convert operation in %s", __func__, e.what());
558 ok = false;
559 }
560 }
561
562 // Store whether this operation was successfully converted.
563 m_OperationSupported.emplace(operationIdx, ok);
564
565 // Any single operation failing will fail the entire conversion.
566 // We still need to continue and check the other ones.
567 if (!ok)
568 {
569 m_ConversionResult = ConversionResult::UnsupportedFeature;
570 }
571 }
572 try
573 {
574 if (m_ConversionResult == ConversionResult::Success)
575 {
576 for (uint32_t i = 0; i < m_Model.outputIndexes.size(); i++)
577 {
578 // outputs in android nn are represented by operands
579 uint32_t outputIndex = m_Model.outputIndexes[i];
580 const Operand& operand = m_Model.operands[outputIndex];
581 const armnn::TensorInfo& tensor = GetTensorInfoForOperand(operand);
582 armnn::IConnectableLayer* layer = m_Network->AddOutputLayer(i);
583
584 assert(m_OutputSlotForOperand[outputIndex]);
585 m_OutputSlotForOperand[outputIndex]->Connect(layer->GetInputSlot(0));
586 }
587 }
588 }
589 catch (const armnn::InvalidArgumentException& e)
590 {
591 Fail("%s: Failed to convert output operand to TensorShape: %s", __func__, e.what());
592 m_ConversionResult = ConversionResult::UnsupportedFeature;
593 }
594}
595
telsoa01ce3e84a2018-08-31 09:31:35 +0100596bool ModelToINetworkConverter::ConvertOperation(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000597{
598 switch (operation.type)
599 {
telsoa01ce3e84a2018-08-31 09:31:35 +0100600 case neuralnetworks::V1_0::OperationType::ADD:
601 return ConvertAdd(operation);
602 case neuralnetworks::V1_0::OperationType::AVERAGE_POOL_2D:
603 return ConvertAveragePool2d(operation);
604 case neuralnetworks::V1_0::OperationType::CONCATENATION:
605 return ConvertConcatenation(operation);
606 case neuralnetworks::V1_0::OperationType::CONV_2D:
607 return ConvertConv2d(operation);
608 case neuralnetworks::V1_0::OperationType::DEPTHWISE_CONV_2D:
609 return ConvertDepthwiseConv2d(operation);
610 case neuralnetworks::V1_0::OperationType::FLOOR:
611 return ConvertFloor(operation);
612 case neuralnetworks::V1_0::OperationType::FULLY_CONNECTED:
613 return ConvertFullyConnected(operation);
614 case neuralnetworks::V1_0::OperationType::LOCAL_RESPONSE_NORMALIZATION:
615 return ConvertLocalResponseNormalization(operation);
616 case neuralnetworks::V1_0::OperationType::LOGISTIC:
617 return ConvertLogistic(operation);
618 case neuralnetworks::V1_0::OperationType::LSTM:
619 return ConvertLstm(operation);
620 case neuralnetworks::V1_0::OperationType::L2_NORMALIZATION:
621 return ConvertL2Normalization(operation);
622 case neuralnetworks::V1_0::OperationType::L2_POOL_2D:
623 return ConvertL2Pool2d(operation);
624 case neuralnetworks::V1_0::OperationType::MAX_POOL_2D:
625 return ConvertMaxPool2d(operation);
626 case neuralnetworks::V1_0::OperationType::MUL:
627 return ConvertMul(operation);
628 case neuralnetworks::V1_0::OperationType::RELU:
629 return ConvertReLu(operation);
630 case neuralnetworks::V1_0::OperationType::RELU1:
631 return ConvertReLu1(operation);
632 case neuralnetworks::V1_0::OperationType::RELU6:
633 return ConvertReLu6(operation);
634 case neuralnetworks::V1_0::OperationType::SOFTMAX:
635 return ConvertSoftmax(operation);
636 case neuralnetworks::V1_0::OperationType::TANH:
637 return ConvertTanH(operation);
638 case neuralnetworks::V1_0::OperationType::RESHAPE:
639 return ConvertReshape(operation);
640 case neuralnetworks::V1_0::OperationType::RESIZE_BILINEAR:
641 return ConvertResizeBilinear(operation);
642 default:
643 return Fail("%s: Operation type %s not supported in ArmnnDriver",
644 __func__, toString(operation.type).c_str());
telsoa015307bc12018-03-09 13:51:08 +0000645 }
646}
647
telsoa01ce3e84a2018-08-31 09:31:35 +0100648bool ModelToINetworkConverter::ConvertAdd(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000649{
650 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0);
651 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1);
652
653 if (!input0.IsValid() || !input1.IsValid())
654 {
655 return Fail("%s: Operation has invalid inputs", __func__);
656 }
657
telsoa01ce3e84a2018-08-31 09:31:35 +0100658 // The FuseActivation parameter is always the input index 2
659 // and it should be optional
telsoa015307bc12018-03-09 13:51:08 +0000660 ActivationFn activationFunction;
telsoa01ce3e84a2018-08-31 09:31:35 +0100661 if (!GetOptionalInputActivation(operation, 2, activationFunction))
telsoa015307bc12018-03-09 13:51:08 +0000662 {
663 return Fail("%s: Operation has invalid inputs", __func__);
664 }
665
666 const Operand* outputOperand = GetOutputOperand(operation, 0);
667 if (!outputOperand)
668 {
669 return false;
670 }
671
672 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
673
674 if (!IsLayerSupported(__func__,
675 armnn::IsAdditionSupported,
676 m_Compute,
677 input0.GetTensorInfo(),
678 input1.GetTensorInfo(),
679 outInfo))
680 {
681 return false;
682 }
683
684 armnn::IConnectableLayer* const startLayer = m_Network->AddAdditionLayer();
685 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer);
686
687 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
688 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
689
690 if (endLayer != nullptr)
691 {
telsoa01ce3e84a2018-08-31 09:31:35 +0100692 BroadcastTensor(input0, input1, startLayer, *m_Network);
telsoa015307bc12018-03-09 13:51:08 +0000693 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
694 }
695 else
696 {
697 return Fail("%s: ProcessActivation failed", __func__);
698 }
699}
700
telsoa01ce3e84a2018-08-31 09:31:35 +0100701bool ModelToINetworkConverter::ConvertAveragePool2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000702{
703 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Average);
704}
705
telsoa01ce3e84a2018-08-31 09:31:35 +0100706bool ModelToINetworkConverter::ConvertConcatenation(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000707{
708 // The first N (0..N-1) inputs are tensors. The Nth input is the concatenation axis.
709 if (operation.inputs.size() <= 1)
710 {
711 return Fail("%s: Operation has insufficient arguments", __func__);
712 }
713
714 // Get inputs and outputs
715 const std::size_t numInputTensors = operation.inputs.size() - 1;
716
surmeh0149b9e102018-05-17 14:11:25 +0100717 int32_t concatDim;
718 if (!GetInputScalar(operation, numInputTensors, OperandType::INT32, concatDim))
719 {
720 return Fail("%s: Operation has invalid inputs", __func__);
721 }
722
723 const Operand* const outputOperand = GetOutputOperand(operation, 0);
724 if (!outputOperand)
725 {
726 return Fail("%s: Operation has no outputs", __func__);
727 }
728
telsoa01ce3e84a2018-08-31 09:31:35 +0100729
surmeh0149b9e102018-05-17 14:11:25 +0100730 armnn::TensorInfo outputInfo = GetTensorInfoForOperand(*outputOperand);
731 armnn::TensorShape outputShape = outputInfo.GetShape();
732
733 //
734 // handle negative concat dims along the lines of tensorflow as described here:
735 // https://www.tensorflow.org/api_docs/python/tf/concat
736 // "negative axis refers to axis + rank(values)-th dimension"
737 //
738 if (concatDim < 0)
739 {
740 concatDim += outputShape.GetNumDimensions();
741 }
742
743 if (concatDim >= static_cast<int32_t>(outputShape.GetNumDimensions()) || concatDim < 0)
744 {
745 return Fail("%s: Operation has invalid concat axis: %d", __func__, concatDim);
746 }
747
telsoa015307bc12018-03-09 13:51:08 +0000748 std::vector<LayerInputHandle> inputHandles;
749 std::vector<armnn::TensorShape> inputShapes;
750
751 inputHandles.reserve(numInputTensors);
752 inputShapes.reserve(numInputTensors);
753
telsoa01ce3e84a2018-08-31 09:31:35 +0100754 bool inputsHaveBeenReshaped = false;
755 unsigned int tensorDimensionsAdded = 0;
756
telsoa015307bc12018-03-09 13:51:08 +0000757 for (uint32_t i = 0; i < numInputTensors; ++i)
758 {
759 const Operand* const operand = GetInputOperand(operation, i);
760 if (!operand)
761 {
762 return Fail("%s: Operation has invalid inputs", __func__);
763 }
764
telsoa01ce3e84a2018-08-31 09:31:35 +0100765 armnn::TensorShape operandShape = GetTensorShapeForOperand(*operand);
766 LayerInputHandle operandInputHandle = ConvertToLayerInputHandle(operation, i);
surmeh0149b9e102018-05-17 14:11:25 +0100767
telsoa01ce3e84a2018-08-31 09:31:35 +0100768 if (operandShape.GetNumDimensions() == 0)
769 {
770 return Fail("%s: Operands with rank 0 are not supported", __func__);
771 }
772
773 if (RequiresReshape(operandShape))
774 {
775 inputsHaveBeenReshaped = true;
776
777 armnn::TensorInfo reshapeInfo = operandInputHandle.GetTensorInfo();
778
779 // Expand the tensor to three dimensions
780 if (operandShape.GetNumDimensions() == 2)
781 {
782 reshapeInfo.SetShape(armnn::TensorShape({1, operandShape[0], operandShape[1]}));
783 tensorDimensionsAdded = 1;
784 }
785 else
786 {
787 reshapeInfo.SetShape(armnn::TensorShape({1, 1, operandShape[0]}));
788 tensorDimensionsAdded = 2;
789 }
790
791 armnn::IConnectableLayer& newReshape = AddReshapeLayer(
792 *m_Network,
793 operandInputHandle,
794 reshapeInfo
795 );
796
797 // Point to the reshape operation rather then the input operation
798 operandShape = reshapeInfo.GetShape();
799 operandInputHandle = LayerInputHandle(true, &newReshape.GetOutputSlot(0), reshapeInfo);
800 }
801
802 inputShapes.emplace_back(operandShape);
803 inputHandles.emplace_back(operandInputHandle);
surmeh0149b9e102018-05-17 14:11:25 +0100804
telsoa015307bc12018-03-09 13:51:08 +0000805 if (!inputHandles.back().IsValid())
806 {
807 return Fail("%s: Operation has invalid inputs", __func__);
808 }
809 }
810
811 assert(inputShapes.size() == inputHandles.size());
812
telsoa01ce3e84a2018-08-31 09:31:35 +0100813 if (inputsHaveBeenReshaped)
814 {
815 // Adjust the concatenation dimension by the amount of dimensions added (if any)
816 concatDim += tensorDimensionsAdded;
817
818 // Add extra dimensions to the output shape to reflect the addition of the reshape layers
819 if (tensorDimensionsAdded == 1)
820 {
821 outputShape = armnn::TensorShape({1, outputShape[0], outputShape[1]});
822 }
823 else if (tensorDimensionsAdded == 2)
824 {
825 outputShape = armnn::TensorShape({1, 1, outputShape[0], outputShape[1]});
826 }
827 }
828
829 // Get the pair of permutations required for the concatenation
830 std::pair<armnn::PermutationVector, armnn::PermutationVector> permutationPair =
831 std::make_pair(IdentityPermutation4D, IdentityPermutation4D);
832
833 CreatePermutationParameters(inputShapes[0].GetNumDimensions(), concatDim, permutationPair);
834
835 outputShape = armnnUtils::Permuted(outputShape, permutationPair.first);
836 outputInfo.SetShape(outputShape);
837
surmeh0149b9e102018-05-17 14:11:25 +0100838 // this is no-op for identity swizzles, otherwise it replaces both
839 // the handles and shapes with the swizzled layer output handles and shapes
telsoa01ce3e84a2018-08-31 09:31:35 +0100840 SwizzleInputs(*m_Network, inputHandles, inputShapes, permutationPair.first);
telsoa015307bc12018-03-09 13:51:08 +0000841
842 // Create an armnn merger layer descriptor - this will also perform validation on the input shapes
843 armnn::OriginsDescriptor mergerDescriptor;
844 try
845 {
surmeh0149b9e102018-05-17 14:11:25 +0100846 // The merger descriptor is always created across the only supported concat
847 // dimension, which is 0 or 1
848 mergerDescriptor =
849 armnn::CreateMergerDescriptorForConcatenation(
850 inputShapes.begin(), inputShapes.end(), concatDim);
telsoa015307bc12018-03-09 13:51:08 +0000851 }
852 catch (const armnn::Exception& error)
853 {
854 return Fail("%s: Error preparing merger descriptor. %s", __func__, error.what());
855 }
856
surmeh0149b9e102018-05-17 14:11:25 +0100857 // Validate the output shape is correct given the input shapes based on the
858 // only valid concat dimension which is 0 or 1
859 if (!ValidateConcatOutputShape(inputShapes, outputShape, concatDim))
telsoa015307bc12018-03-09 13:51:08 +0000860 {
surmeh0149b9e102018-05-17 14:11:25 +0100861 return Fail("%s: Error validating the output shape for concat", __func__);
telsoa015307bc12018-03-09 13:51:08 +0000862 }
863
864 std::vector<const armnn::TensorInfo*> inputTensorInfos;
865 std::transform(inputHandles.begin(), inputHandles.end(), std::back_inserter(inputTensorInfos),
866 [](const LayerInputHandle& h) -> const armnn::TensorInfo*{ return &h.GetTensorInfo(); });
867 if (!IsLayerSupported(__func__,
868 armnn::IsMergerSupported,
869 m_Compute,
870 inputTensorInfos,
871 mergerDescriptor))
872 {
873 return false;
874 }
875
876 armnn::IConnectableLayer* layer = m_Network->AddMergerLayer(mergerDescriptor);
877 assert(layer != nullptr);
surmeh0149b9e102018-05-17 14:11:25 +0100878 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
telsoa015307bc12018-03-09 13:51:08 +0000879
880 // Connect inputs to the layer
881 const int numInputSlots = layer->GetNumInputSlots();
882 assert(static_cast<std::size_t>(numInputSlots) == inputHandles.size());
883 for (int i = 0; i < numInputSlots; ++i)
884 {
surmeh0149b9e102018-05-17 14:11:25 +0100885 // connect the input directly to the merge (concat) layer
telsoa015307bc12018-03-09 13:51:08 +0000886 inputHandles[static_cast<unsigned int>(i)].Connect(layer->GetInputSlot(i));
887 }
888
telsoa01ce3e84a2018-08-31 09:31:35 +0100889 // Add permutation layer and connect the output to it, the permutation becomes the output layer
890 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(*m_Network,
891 layer->GetOutputSlot(0),
892 permutationPair.second);
893 layer = &deswizzleLayer;
894
895 if (inputsHaveBeenReshaped)
surmeh0149b9e102018-05-17 14:11:25 +0100896 {
telsoa01ce3e84a2018-08-31 09:31:35 +0100897 armnn::TensorInfo afterConcatInfo = layer->GetOutputSlot(0).GetTensorInfo();
898
899 // Undo the reshape knowing the amount of dimensions added
900 if (tensorDimensionsAdded == 1)
901 {
902 afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[1],
903 afterConcatInfo.GetShape()[2] }));
904 }
905 else if (tensorDimensionsAdded == 2)
906 {
907 afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[2],
908 afterConcatInfo.GetShape()[3] }));
909 }
910
911 layer = &AddReshapeLayer(
912 *m_Network,
913 layer->GetOutputSlot(0),
914 afterConcatInfo
915 );
surmeh0149b9e102018-05-17 14:11:25 +0100916 }
917
telsoa015307bc12018-03-09 13:51:08 +0000918 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
919}
920
telsoa01ce3e84a2018-08-31 09:31:35 +0100921bool ModelToINetworkConverter::ConvertConv2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000922{
923 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
924 if (!input.IsValid())
925 {
926 return Fail("%s: Operation has invalid inputs", __func__);
927 }
928
929 const Operand* output = GetOutputOperand(operation, 0);
930 if (!output)
931 {
932 return Fail("%s: Could not read output 0", __func__);
933 }
934
935 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
936 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
937
938 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
939 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
940
941 // ArmNN does not currently support non-fixed weights or bias
942 const ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, NHWCToArmNN);
943 const ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2);
944
945 if (!weightsPin.IsValid() || !biasPin.IsValid())
946 {
947 return Fail("%s: Operation has invalid inputs", __func__);
948 }
949
950 armnn::ConstTensor weights = weightsPin.GetConstTensor();
951 armnn::ConstTensor bias = biasPin.GetConstTensor();
952 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), swizzledInputInfo);
953
954 armnn::Convolution2dDescriptor desc;
955 ActivationFn activation;
956
957 if (operation.inputs.size() == 10)
958 {
959 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft) ||
960 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight) ||
961 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop) ||
962 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom) ||
963 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX) ||
964 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY) ||
965 !GetInputActivationFunction(operation, 9, activation))
966 {
967 return Fail("%s: Operation has invalid inputs", __func__);
968 }
969 }
970 else if (operation.inputs.size() == 7)
971 {
972 android::nn::PaddingScheme paddingScheme;
973
974 if (!GetInputPaddingScheme(operation, 3, paddingScheme) ||
975 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX) ||
976 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY) ||
977 !GetInputActivationFunction(operation, 6, activation))
978 {
979 return Fail("%s: Operation has invalid inputs", __func__);
980 }
981
982 const uint32_t kernelX = weights.GetShape()[3];
983 const uint32_t kernelY = weights.GetShape()[2];
984 const uint32_t inputX = swizzledInputInfo.GetShape()[3];
985 const uint32_t inputY = swizzledInputInfo.GetShape()[2];
986
987 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
988 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
989 }
990 else
991 {
992 return Fail("%s: Unsupported number of operation inputs", __func__);
993 }
994
995 desc.m_BiasEnabled = true;
996
997 if (!IsLayerSupported(__func__,
998 armnn::IsConvolution2dSupported,
999 m_Compute,
1000 swizzledInputInfo,
surmeh0149b9e102018-05-17 14:11:25 +01001001 swizzledOutputInfo,
telsoa015307bc12018-03-09 13:51:08 +00001002 desc,
surmeh0149b9e102018-05-17 14:11:25 +01001003 weights.GetInfo(),
1004 bias.GetInfo()))
telsoa015307bc12018-03-09 13:51:08 +00001005 {
1006 return false;
1007 }
1008
1009 armnn::IConnectableLayer* startLayer = m_Network->AddConvolution2dLayer(desc, weights, bias);
1010 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
1011
1012 if (endLayer != nullptr)
1013 {
1014 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
1015 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1016 }
1017 else
1018 {
1019 return Fail("%s: ProcessActivation failed", __func__);
1020 }
1021}
1022
telsoa01ce3e84a2018-08-31 09:31:35 +01001023bool ModelToINetworkConverter::ConvertDepthwiseConv2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001024{
1025 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1026 if (!input.IsValid())
1027 {
1028 return Fail("%s: Operation has invalid inputs", __func__);
1029 }
1030
1031 const Operand* output = GetOutputOperand(operation, 0);
1032 if (!output)
1033 {
1034 return Fail("%s: Could not read output 0", __func__);
1035 }
1036
1037 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1038 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1039
1040 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1041 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1042
1043 // ArmNN does not currently support non-fixed weights or bias
1044
1045 // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
1046 // but in ArmNN it needs to be [ M, I, H, W ]
1047 const Operand* weightsOperand = GetInputOperand(operation, 1);
1048
1049 if (weightsOperand == nullptr)
1050 {
1051 return Fail("%s: Operand is invalid", __func__);
1052 }
1053
1054 // Reinterpret weight data as [ H, W, I, M ]
1055 armnn::TensorShape weightsShape({ weightsOperand->dimensions[1], weightsOperand->dimensions[2],
1056 inputInfo.GetShape()[3],
1057 weightsOperand->dimensions[3] / inputInfo.GetShape()[3] });
1058
1059 // Swizzle weight data [ H, W, I, M ] -> [ M, I, H, W ]
1060 const armnn::PermutationVector HWIMToMIHW = { 2U, 3U, 1U, 0U };
1061 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, HWIMToMIHW, &weightsShape);
1062
1063 // Bias is a 1D tensor
1064 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2);
1065
1066 if (!weightsPin.IsValid() || !biasPin.IsValid())
1067 {
1068 return Fail("%s: Operation has invalid inputs", __func__);
1069 }
1070
1071 armnn::ConstTensor weights = weightsPin.GetConstTensor();
1072 armnn::ConstTensor bias = biasPin.GetConstTensor();
1073 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), swizzledInputInfo);
1074
1075 armnn::DepthwiseConvolution2dDescriptor desc;
1076 ActivationFn activation;
1077
1078 if (operation.inputs.size() == 11)
1079 {
1080 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft) ||
1081 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight) ||
1082 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop) ||
1083 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom) ||
1084 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX) ||
1085 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY) ||
1086 !GetInputActivationFunction(operation, 10, activation))
1087 {
1088 return Fail("%s: Operation has invalid inputs", __func__);
1089 }
1090 }
1091 else if (operation.inputs.size() == 8)
1092 {
1093 android::nn::PaddingScheme paddingScheme;
1094
1095 if (!GetInputPaddingScheme(operation, 3, paddingScheme) ||
1096 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX) ||
1097 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY) ||
1098 !GetInputActivationFunction(operation, 7, activation))
1099 {
1100 return Fail("%s: Operation has invalid inputs", __func__);
1101 }
1102
1103 const uint32_t kernelX = weights.GetShape()[3];
1104 const uint32_t kernelY = weights.GetShape()[2];
1105 const uint32_t inputX = swizzledInputInfo.GetShape()[3];
1106 const uint32_t inputY = swizzledInputInfo.GetShape()[2];
1107
1108 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1109 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1110 }
1111 else
1112 {
1113 return Fail("%s: Unsupported number of operation inputs", __func__);
1114 }
1115
1116 desc.m_BiasEnabled = true;
1117
1118 if (!IsLayerSupported(__func__,
1119 armnn::IsDepthwiseConvolutionSupported,
1120 m_Compute,
1121 swizzledInputInfo,
telsoa01ce3e84a2018-08-31 09:31:35 +01001122 swizzledOutputInfo,
telsoa015307bc12018-03-09 13:51:08 +00001123 desc,
telsoa01ce3e84a2018-08-31 09:31:35 +01001124 weights.GetInfo(),
1125 bias.GetInfo()))
telsoa015307bc12018-03-09 13:51:08 +00001126 {
1127 return false;
1128 }
1129
1130 armnn::IConnectableLayer* startLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, bias);
1131 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
1132
1133 if (endLayer != nullptr)
1134 {
1135 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
1136 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1137 }
1138 else
1139 {
1140 return Fail("%s: ProcessActivation failed", __func__);
1141 }
1142}
1143
telsoa01ce3e84a2018-08-31 09:31:35 +01001144bool ModelToINetworkConverter::ConvertFloor(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001145{
1146 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1147 if (!input.IsValid())
1148 {
1149 return Fail("%s: Operation has invalid inputs", __func__);
1150 }
1151
1152 const Operand* const outputOperand = GetOutputOperand(operation, 0);
1153 if (!outputOperand)
1154 {
1155 return Fail("%s: Operation has invalid outputs", __func__);
1156 }
1157
1158 if (!IsLayerSupported(__func__,
1159 armnn::IsFloorSupported,
1160 m_Compute,
1161 input.GetTensorInfo(),
1162 GetTensorInfoForOperand(*outputOperand)))
1163 {
1164 return false;
1165 }
1166
1167 armnn::IConnectableLayer* layer = m_Network->AddFloorLayer();
1168 assert(layer != nullptr);
1169 input.Connect(layer->GetInputSlot(0));
1170
1171 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1172}
1173
telsoa01ce3e84a2018-08-31 09:31:35 +01001174bool ModelToINetworkConverter::ConvertFullyConnected(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001175{
1176 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1177 if (!input.IsValid())
1178 {
1179 return Fail("%s: Operation has invalid inputs", __func__);
1180 }
1181
1182 const Operand* output = GetOutputOperand(operation, 0);
1183 if (!output)
1184 {
1185 return Fail("%s: Could not read output 0", __func__);
1186 }
1187
1188 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1189 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1190
telsoa015307bc12018-03-09 13:51:08 +00001191 // ArmNN does not currently support non-fixed weights or bias
1192 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1); // 2D
1193 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2); // 1D
1194
1195 if (!weightsPin.IsValid() || !biasPin.IsValid())
1196 {
1197 return Fail("%s: Operation has invalid inputs", __func__);
1198 }
1199
telsoa015307bc12018-03-09 13:51:08 +00001200 armnn::ConstTensor weights = weightsPin.GetConstTensor();
1201 armnn::ConstTensor bias = biasPin.GetConstTensor();
telsoa01ce3e84a2018-08-31 09:31:35 +01001202
1203 armnn::TensorInfo reshapedInfo = inputInfo;
1204 if (inputInfo.GetNumDimensions() > 2U)
1205 {
1206 unsigned int dim0 = inputInfo.GetShape()[0];
1207 unsigned int dim1 = inputInfo.GetShape()[1];
1208
1209 for (unsigned int i = 2U; i < inputInfo.GetNumDimensions(); ++i)
1210 {
1211 dim1 *= inputInfo.GetShape()[i];
1212 }
1213
1214 unsigned int divisor = weights.GetInfo().GetShape()[1] / dim1;
1215 if(dim0 % divisor != 0)
1216 {
1217 return Fail("%s: Failed to deduce tensor shape", __func__);
1218 }
1219
1220 reshapedInfo.SetShape(armnn::TensorShape({dim0 / divisor, dim1 * divisor}));
1221 }
1222
1223 // ensuring that the bias value is within 1% of the weights input (small float differences can exist)
telsoa015307bc12018-03-09 13:51:08 +00001224 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), reshapedInfo);
1225
1226 ActivationFn activationFunction;
1227 if (!GetInputActivationFunction(operation, 3, activationFunction))
1228 {
1229 return Fail("%s: Operation has invalid inputs", __func__);
1230 }
1231
1232 armnn::FullyConnectedDescriptor desc;
1233 desc.m_TransposeWeightMatrix = true;
1234 desc.m_BiasEnabled = true;
1235
1236 if (!IsLayerSupported(__func__,
1237 armnn::IsFullyConnectedSupported,
1238 m_Compute,
telsoa01ce3e84a2018-08-31 09:31:35 +01001239 inputInfo,
1240 outputInfo,
1241 weights.GetInfo(),
1242 bias.GetInfo(),
telsoa015307bc12018-03-09 13:51:08 +00001243 desc))
1244 {
1245 return false;
1246 }
1247
1248 armnn::IConnectableLayer* startLayer = m_Network->AddFullyConnectedLayer(desc, weights, bias);
1249 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activationFunction, startLayer);
1250
1251 if (endLayer != nullptr)
1252 {
1253 if (inputInfo.GetNumDimensions() > 2U)
1254 {
1255 armnn::ReshapeDescriptor reshapeDescriptor;
1256 reshapeDescriptor.m_TargetShape = reshapedInfo.GetShape();
1257
1258 armnn::IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(reshapeDescriptor);
1259 assert(reshapeLayer != nullptr);
1260 input.Connect(reshapeLayer->GetInputSlot(0));
1261 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
1262 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
1263 }
1264 else
1265 {
1266 input.Connect(startLayer->GetInputSlot(0));
1267 }
1268
1269 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
1270 }
1271 else
1272 {
1273 return Fail("%s: ProcessActivation failed", __func__);
1274 }
1275}
1276
telsoa01ce3e84a2018-08-31 09:31:35 +01001277bool ModelToINetworkConverter::ConvertLocalResponseNormalization(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001278{
1279 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1280 if (!input.IsValid())
1281 {
1282 return Fail("%s: Operation has invalid inputs", __func__);
1283 }
1284
1285 const Operand* output = GetOutputOperand(operation, 0);
1286 if (!output)
1287 {
1288 return Fail("%s: Could not read output 0", __func__);
1289 }
1290
1291 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1292 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1293
1294 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1295 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1296
1297 armnn::NormalizationDescriptor descriptor;
1298
1299 descriptor.m_NormChannelType = armnn::NormalizationAlgorithmChannel::Across;
1300 descriptor.m_NormMethodType = armnn::NormalizationAlgorithmMethod::LocalBrightness;
1301
1302 if (!input.IsValid() ||
1303 !GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_NormSize) ||
1304 !GetInputFloat32(operation, 2, descriptor.m_K) ||
1305 !GetInputFloat32(operation, 3, descriptor.m_Alpha) ||
1306 !GetInputFloat32(operation, 4, descriptor.m_Beta))
1307 {
1308 return Fail("%s: Operation has invalid inputs", __func__);
1309 }
1310
1311 // ArmNN expects normSize to be the full size of the normalization
1312 // window rather than the radius as in AndroidNN.
1313 descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize);
1314
1315 if (!IsLayerSupported(__func__,
1316 armnn::IsNormalizationSupported,
1317 m_Compute,
1318 swizzledInputInfo,
1319 swizzledOutputInfo,
1320 descriptor))
1321 {
1322 return false;
1323 }
1324
1325
1326 armnn::IConnectableLayer* layer = m_Network->AddNormalizationLayer(descriptor);
1327 assert(layer != nullptr);
1328 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1329
1330 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1331
1332 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1333}
1334
telsoa01ce3e84a2018-08-31 09:31:35 +01001335bool ModelToINetworkConverter::ConvertLogistic(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001336{
1337 armnn::ActivationDescriptor desc;
surmeh0149b9e102018-05-17 14:11:25 +01001338 desc.m_Function = armnn::ActivationFunction::Sigmoid;
telsoa015307bc12018-03-09 13:51:08 +00001339
1340 return ConvertToActivation(operation, __func__, desc);
1341}
1342
telsoa01ce3e84a2018-08-31 09:31:35 +01001343bool ModelToINetworkConverter::ConvertL2Normalization(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001344{
1345 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1346 if (!input.IsValid())
1347 {
1348 return Fail("%s: Operation has invalid inputs", __func__);
1349 }
1350
1351 const Operand* output = GetOutputOperand(operation, 0);
1352 if (!output)
1353 {
1354 return Fail("%s: Could not read output 0", __func__);
1355 }
1356
1357 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1358 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1359
1360 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1361 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1362
1363 if (!IsLayerSupported(__func__,
1364 armnn::IsL2NormalizationSupported,
1365 m_Compute,
telsoa01ce3e84a2018-08-31 09:31:35 +01001366 swizzledInputInfo,
1367 swizzledOutputInfo))
telsoa015307bc12018-03-09 13:51:08 +00001368 {
1369 return false;
1370 }
1371
1372 armnn::IConnectableLayer* layer = m_Network->AddL2NormalizationLayer();
1373 assert(layer != nullptr);
1374 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1375
1376 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1377
1378 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1379}
1380
telsoa01ce3e84a2018-08-31 09:31:35 +01001381bool ModelToINetworkConverter::ConvertL2Pool2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001382{
1383 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::L2);
1384}
1385
telsoa01ce3e84a2018-08-31 09:31:35 +01001386bool ModelToINetworkConverter::ConvertMaxPool2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001387{
1388 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Max);
1389}
1390
telsoa01ce3e84a2018-08-31 09:31:35 +01001391bool ModelToINetworkConverter::ConvertMul(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001392{
1393 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0);
1394 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1);
1395
1396 if (!input0.IsValid() || !input1.IsValid())
1397 {
1398 return Fail("%s: Operation has invalid inputs", __func__);
1399 }
1400
telsoa01ce3e84a2018-08-31 09:31:35 +01001401 // The FuseActivation parameter is always the input index 2
1402 // and it should be optional
telsoa015307bc12018-03-09 13:51:08 +00001403 ActivationFn activationFunction;
telsoa01ce3e84a2018-08-31 09:31:35 +01001404 if (!GetOptionalInputActivation(operation, 2, activationFunction))
telsoa015307bc12018-03-09 13:51:08 +00001405 {
1406 return Fail("%s: Operation has invalid inputs", __func__);
1407 }
1408
telsoa015307bc12018-03-09 13:51:08 +00001409 const Operand* outputOperand = GetOutputOperand(operation, 0);
1410
1411 if (outputOperand == nullptr)
1412 {
1413 return false;
1414 }
1415
1416 const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
1417
telsoa01ce3e84a2018-08-31 09:31:35 +01001418 if (!IsLayerSupported(__func__,
1419 armnn::IsMultiplicationSupported,
1420 m_Compute,
1421 input0.GetTensorInfo(),
1422 input1.GetTensorInfo(),
1423 outInfo))
1424 {
1425 return false;
1426 }
1427
telsoa015307bc12018-03-09 13:51:08 +00001428 armnn::IConnectableLayer* const startLayer = m_Network->AddMultiplicationLayer();
1429 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer);
1430
telsoa01ce3e84a2018-08-31 09:31:35 +01001431 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
1432 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
1433
telsoa015307bc12018-03-09 13:51:08 +00001434 if (endLayer != nullptr)
1435 {
telsoa01ce3e84a2018-08-31 09:31:35 +01001436 BroadcastTensor(input0, input1, startLayer, *m_Network);
telsoa015307bc12018-03-09 13:51:08 +00001437 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
1438 }
1439 else
1440 {
1441 return Fail("%s: ProcessActivation failed", __func__);
1442 }
1443}
1444
telsoa01ce3e84a2018-08-31 09:31:35 +01001445bool ModelToINetworkConverter::ConvertReLu(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001446{
1447 armnn::ActivationDescriptor desc;
1448 desc.m_Function = armnn::ActivationFunction::ReLu;
1449
1450 return ConvertToActivation(operation, __func__, desc);
1451}
1452
telsoa01ce3e84a2018-08-31 09:31:35 +01001453bool ModelToINetworkConverter::ConvertReLu1(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001454{
1455 armnn::ActivationDescriptor desc;
1456 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1457 desc.m_A = 1.0f;
1458 desc.m_B = -1.0f;
1459
1460 return ConvertToActivation(operation, __func__, desc);
1461}
1462
telsoa01ce3e84a2018-08-31 09:31:35 +01001463bool ModelToINetworkConverter::ConvertReLu6(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001464{
1465 armnn::ActivationDescriptor desc;
1466 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1467 desc.m_A = 6.0f;
1468
1469 return ConvertToActivation(operation, __func__, desc);
1470}
1471
telsoa01ce3e84a2018-08-31 09:31:35 +01001472bool ModelToINetworkConverter::ConvertSoftmax(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001473{
1474 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1475 if (!input.IsValid())
1476 {
1477 return Fail("%s: Operation has invalid inputs", __func__);
1478 }
1479
telsoa01ce3e84a2018-08-31 09:31:35 +01001480 const Operand* outputOperand = GetOutputOperand(operation, 0);
1481 if (!outputOperand)
1482 {
1483 return Fail("%s: Operation has no outputs", __func__);
1484 }
1485
1486 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
1487
telsoa015307bc12018-03-09 13:51:08 +00001488 armnn::SoftmaxDescriptor desc;
1489 if (!GetInputFloat32(operation, 1, desc.m_Beta))
1490 {
1491 return Fail("%s: Operation has invalid inputs", __func__);
1492 }
1493
1494 if (!IsLayerSupported(__func__,
1495 armnn::IsSoftmaxSupported,
1496 m_Compute,
1497 input.GetTensorInfo(),
telsoa01ce3e84a2018-08-31 09:31:35 +01001498 outInfo,
telsoa015307bc12018-03-09 13:51:08 +00001499 desc))
1500 {
1501 return false;
1502 }
1503
1504 armnn::IConnectableLayer* layer = m_Network->AddSoftmaxLayer(desc);
1505 assert(layer != nullptr);
1506 input.Connect(layer->GetInputSlot(0));
1507
1508 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1509}
1510
telsoa01ce3e84a2018-08-31 09:31:35 +01001511bool ModelToINetworkConverter::ConvertTanH(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001512{
1513 armnn::ActivationDescriptor desc;
1514 desc.m_Function = armnn::ActivationFunction::TanH;
1515 desc.m_A = 1.0f; // android nn does not support tanH parameters
1516 desc.m_B = 1.0f; // set to 1.0f for unity scaling
1517
1518 return ConvertToActivation(operation, __func__, desc);
1519}
1520
telsoa01ce3e84a2018-08-31 09:31:35 +01001521bool ModelToINetworkConverter::ConvertReshape(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001522{
1523 const Operand* inputOperand = GetInputOperand(operation, 0);
1524 const Operand* requestedShapeOperand = GetInputOperand(operation, 1);
1525 const Operand* outputOperand = GetOutputOperand(operation, 0);
1526
1527 if (inputOperand == nullptr
1528 || requestedShapeOperand == nullptr
1529 || outputOperand == nullptr)
1530 {
1531 return Fail("%s: Operation has invalid inputs", __func__);
1532 }
1533
1534
1535 if (requestedShapeOperand->dimensions.size() != 1)
1536 {
1537 return Fail("%s: Input 1 expected to be one-dimensional (found %i dimensions)",
1538 __func__, requestedShapeOperand->dimensions.size());
1539 }
1540
1541 std::vector<int32_t> targetDimensions;
1542 if (!GetTensorInt32Values(*requestedShapeOperand, targetDimensions))
1543 {
1544 return Fail("%s: Could not read values of input 1", __func__);
1545 }
1546
1547 const Shape inputOperandShape = GetOperandShape(*inputOperand);
1548
1549 Shape requestedShape;
1550 // targetDimensions may contain special values (e.g. -1). reshapePrepare() is an AndroidNN provided utility
1551 // function that resolves these values into a fully specified tensor shape.
1552 if (!reshapePrepare(inputOperandShape, targetDimensions.data(), targetDimensions.size(), &requestedShape))
1553 {
1554 return Fail("%s: Failed to resolve the requested shape", __func__);
1555 }
1556
1557 const Shape outputOperandShape = GetOperandShape(*outputOperand);
1558 if (!SameShape(requestedShape, outputOperandShape))
1559 {
1560 return Fail("%s: Shape of output operand does not match resolved requested shape", __func__);
1561 }
1562
1563 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1564 if (!input.IsValid())
1565 {
1566 return Fail("%s: Could not read input 0", __func__);
1567 }
1568
1569 if (!IsLayerSupported(__func__,
1570 armnn::IsReshapeSupported,
1571 m_Compute,
1572 input.GetTensorInfo()))
1573 {
1574 return false;
1575 }
1576
1577
1578 armnn::ReshapeDescriptor reshapeDescriptor;
1579 reshapeDescriptor.m_TargetShape = armnn::TensorShape(requestedShape.dimensions.size(),
1580 requestedShape.dimensions.data());
1581
1582 armnn::IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDescriptor);
1583 assert(layer != nullptr);
1584 input.Connect(layer->GetInputSlot(0));
1585
1586 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1587}
1588
telsoa01ce3e84a2018-08-31 09:31:35 +01001589bool ModelToINetworkConverter::ConvertResizeBilinear(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001590{
1591 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1592 if (!input.IsValid())
1593 {
1594 return Fail("%s: Could not read input 0", __func__);
1595 }
1596
1597 const Operand* output = GetOutputOperand(operation, 0);
1598 if (!output)
1599 {
1600 return Fail("%s: Could not read output 0", __func__);
1601 }
1602
1603 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1604 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1605
1606 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1607 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1608
1609 if (!IsLayerSupported(__func__,
1610 armnn::IsResizeBilinearSupported,
1611 m_Compute,
1612 swizzledInputInfo))
1613 {
1614 return false;
1615 }
1616
1617 armnn::ResizeBilinearDescriptor desc;
1618
1619 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_TargetHeight)
1620 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_TargetWidth))
1621 {
1622 return Fail("%s: Operation has invalid inputs", __func__);
1623 }
1624
1625 armnn::IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc);
1626 assert(layer != nullptr);
1627 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1628
1629 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1630
1631 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1632
1633}
1634
telsoa01ce3e84a2018-08-31 09:31:35 +01001635bool ModelToINetworkConverter::ConvertLstm(const neuralnetworks::V1_0::Operation& operation)
1636{
1637 // Inputs:
1638 // 00: The input: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, input_size], where
1639 // “batch_size” corresponds to the batching dimension, and “input_size” is the size of the input.
1640 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1641 if (!input.IsValid())
1642 {
1643 return Fail("%s: Could not read input 0: input", __func__);
1644 }
1645 // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
1646 LayerInputHandle outputStateIn = ConvertToLayerInputHandle(operation, 18);
1647 if (!outputStateIn.IsValid())
1648 {
1649 return Fail("%s: Could not read input 18: outputStateIn", __func__);
1650 }
1651 // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
1652 LayerInputHandle cellStateIn = ConvertToLayerInputHandle(operation, 19);
1653 if (!cellStateIn.IsValid())
1654 {
1655 return Fail("%s: Could not read input 19: cellStateIn", __func__);
1656 }
1657
1658 // Get the mandatory input tensors:
1659 // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1660 // [num_units, input_size].
1661 const ConstTensorPin inputToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 2);
1662 // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units, input_size].
1663 const ConstTensorPin inputToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 3);
1664 // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1665 // [num_units, input_size].
1666 const ConstTensorPin inputToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 4);
1667 // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1668 // [num_units, output_size].
1669 const ConstTensorPin recurrentToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 6);
1670 // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1671 // [num_units, output_size].
1672 const ConstTensorPin recurrentToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 7);
1673 // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1674 // [num_units, output_size].
1675 const ConstTensorPin recurrentToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 8);
1676 // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1677 const ConstTensorPin forgetGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 13);
1678 // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1679 const ConstTensorPin cellBiasPin = ConvertOperationInputToConstTensorPin(operation, 14);
1680 // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1681 const ConstTensorPin outputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 15);
1682
1683 if (!inputToForgetWeightsPin.IsValid() ||
1684 !inputToCellWeightsPin.IsValid() ||
1685 !inputToOutputWeightsPin.IsValid() ||
1686 !recurrentToForgetWeightsPin.IsValid() ||
1687 !recurrentToCellWeightsPin.IsValid() ||
1688 !recurrentToOutputWeightsPin.IsValid() ||
1689 !forgetGateBiasPin.IsValid() ||
1690 !cellBiasPin.IsValid() ||
1691 !outputGateBiasPin.IsValid())
1692 {
1693 return Fail("%s: Operation has invalid tensor inputs", __func__);
1694 }
1695
1696 // Get the optional input tensors:
1697 // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1698 // [num_units, input_size], where “num_units” corresponds to the number of cell units.
1699 const ConstTensorPin inputToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 1);
1700 // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1701 // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
1702 // “num_units”), or the second dimension of the “projection_weights”, if defined.
1703 const ConstTensorPin recurrentToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 5);
1704 // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1705 const ConstTensorPin cellToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 9);
1706 // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1707 const ConstTensorPin cellToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 10);
1708 // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1709 const ConstTensorPin cellToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 11);
1710 // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1711 const ConstTensorPin inputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 12);
1712 // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1713 // [output_size, num_units].
1714 const ConstTensorPin projectionWeightsPin = ConvertOperationInputToConstTensorPin(operation, 16);
1715 // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [output_size].
1716 const ConstTensorPin projectionBiasPin = ConvertOperationInputToConstTensorPin(operation, 17);
1717
1718 if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional()) ||
1719 (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional()) ||
1720 (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional()) ||
1721 (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional()) ||
1722 (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional()) ||
1723 (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional()) ||
1724 (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional()) ||
1725 (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
1726 {
1727 return Fail("%s: Operation has invalid tensor inputs", __func__);
1728 }
1729
1730 // Get the mandatory input scalars (actually 1-D tensors of size 1):
1731 // 20: The activation function: A value indicating the activation function:
1732 // 0: None; 1: Relu; 3: Relu6; 4: Tanh; 6: Sigmoid.
1733 // 21: The clipping threshold: for the cell state, such that values are bound within [-cell_clip, cell_clip].
1734 // If set to 0.0 then clipping is disabled.
1735 // 22: The clipping threshold: for the output from the projection layer, such that values are bound within
1736 // [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
1737 ActivationFn activation;
1738 float cellClip;
1739 float projClip;
1740 if (!GetInputActivationFunctionFromTensor(operation, 20, activation) ||
1741 !GetInputScalar(operation, 21, OperandType::FLOAT32, cellClip) ||
1742 !GetInputScalar(operation, 22, OperandType::FLOAT32, projClip))
1743 {
1744 return Fail("%s: Operation has invalid scalar inputs", __func__);
1745 }
1746
1747 // Outputs:
1748 // 00: The scratch buffer: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units * 4] with
1749 // CIFG, or [batch_size, num_units * 3] without CIFG.
1750 const Operand* scratchBuffer = GetOutputOperand(operation, 0);
1751 if (!scratchBuffer)
1752 {
1753 return Fail("%s: Could not read output 0: scratchBuffer", __func__);
1754 }
1755 // 01: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
1756 const Operand* outputStateOut = GetOutputOperand(operation, 1);
1757 if (!outputStateOut)
1758 {
1759 return Fail("%s: Could not read output 1: outputStateOut", __func__);
1760 }
1761 // 02: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
1762 const Operand* cellStateOut = GetOutputOperand(operation, 2);
1763 if (!cellStateOut)
1764 {
1765 return Fail("%s: Could not read output 2: cellStateOut", __func__);
1766 }
1767 // 03: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. This is
1768 // effectively the same as the current “output state (out)” value.
1769 const Operand* output = GetOutputOperand(operation, 3);
1770 if (!output)
1771 {
1772 return Fail("%s: Could not read output 3: output", __func__);
1773 }
1774
1775 // set the params structure for the AddLstmLayer call
1776 armnn::LstmInputParams params;
1777 params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
1778 params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
1779 params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
1780 params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
1781 params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
1782 params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
1783 params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
1784 params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
1785 params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
1786 params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
1787 params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
1788 params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
1789 params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
1790 params.m_CellBias = cellBiasPin.GetConstTensorPtr();
1791 params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
1792 params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
1793 params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
1794
1795 // set the layer descriptor
1796 armnn::LstmDescriptor desc;
1797 desc.m_ActivationFunc = activation;
1798 desc.m_ClippingThresCell = cellClip;
1799 desc.m_ClippingThresProj = projClip;
1800 desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
1801 params.m_RecurrentToInputWeights == nullptr ||
1802 params.m_InputGateBias == nullptr);
1803 desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
1804 params.m_CellToOutputWeights != nullptr);
1805 desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
1806
1807 // validate the optional input groups
1808 if (desc.m_CifgEnabled &&
1809 (params.m_InputToInputWeights != nullptr ||
1810 params.m_RecurrentToInputWeights != nullptr ||
1811 params.m_InputGateBias != nullptr))
1812 {
1813 return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
1814 " and input gate bias must be provided", __func__);
1815 }
1816
1817 if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
1818 {
1819 return Fail("%s: projection bias should not be provided without projection weights", __func__);
1820 }
1821
1822 if (desc.m_PeepholeEnabled &&
1823 (params.m_CellToForgetWeights == nullptr ||
1824 params.m_CellToOutputWeights == nullptr ||
1825 (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
1826 {
1827 return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
1828 " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
1829 }
1830
1831 // Check if the layer is supported
1832 // Inputs
1833 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1834 const armnn::TensorInfo& outputStateInInfo = outputStateIn.GetTensorInfo();
1835 const armnn::TensorInfo& cellStateInInfo = cellStateIn.GetTensorInfo();
1836
1837 // Outputs
1838 const armnn::TensorInfo& scratchBufferInfo = GetTensorInfoForOperand(*scratchBuffer);
1839 const armnn::TensorInfo& outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
1840 const armnn::TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
1841 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1842
1843 // Basic parameters
1844 const armnn::TensorInfo& inputToForgetWeights = params.m_InputToForgetWeights->GetInfo();
1845 const armnn::TensorInfo& inputToCellWeights = params.m_InputToCellWeights->GetInfo();
1846 const armnn::TensorInfo& inputToOutputWeights = params.m_InputToOutputWeights->GetInfo();
1847 const armnn::TensorInfo& recurrentToForgetWeights = params.m_RecurrentToForgetWeights->GetInfo();
1848 const armnn::TensorInfo& recurrentToCellWeights = params.m_RecurrentToCellWeights->GetInfo();
1849 const armnn::TensorInfo& recurrentToOutputWeights = params.m_RecurrentToOutputWeights->GetInfo();
1850 const armnn::TensorInfo& forgetGateBias = params.m_ForgetGateBias->GetInfo();
1851 const armnn::TensorInfo& cellBias = params.m_CellBias->GetInfo();
1852 const armnn::TensorInfo& outputGateBias = params.m_OutputGateBias->GetInfo();
1853
1854 //Optional parameters
1855 const armnn::TensorInfo* inputToInputWeights = nullptr;
1856 const armnn::TensorInfo* recurrentToInputWeights = nullptr;
1857 const armnn::TensorInfo* cellToInputWeights = nullptr;
1858 const armnn::TensorInfo* inputGateBias = nullptr;
1859 const armnn::TensorInfo* projectionWeights = nullptr;
1860 const armnn::TensorInfo* projectionBias = nullptr;
1861 const armnn::TensorInfo* cellToForgetWeights = nullptr;
1862 const armnn::TensorInfo* cellToOutputWeights = nullptr;
1863
1864 if(!desc.m_CifgEnabled)
1865 {
1866 inputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
1867 recurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo());
1868 if (params.m_CellToInputWeights != nullptr)
1869 {
1870 cellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
1871 }
1872 inputGateBias = &(params.m_InputGateBias->GetInfo());
1873 }
1874
1875 if(desc.m_ProjectionEnabled)
1876 {
1877 projectionWeights = &(params.m_ProjectionWeights->GetInfo());
1878 if (params.m_ProjectionBias != nullptr)
1879 {
1880 projectionBias = &(params.m_ProjectionBias->GetInfo());
1881 }
1882 }
1883
1884 if(desc.m_PeepholeEnabled)
1885 {
1886 cellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
1887 cellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
1888 }
1889
1890 if (!IsLayerSupported(__func__,
1891 armnn::IsLstmSupported,
1892 m_Compute,
1893 inputInfo,
1894 outputStateInInfo,
1895 cellStateInInfo,
1896 scratchBufferInfo,
1897 outputStateOutInfo,
1898 cellStateOutInfo,
1899 outputInfo,
1900 desc,
1901 inputToForgetWeights,
1902 inputToCellWeights,
1903 inputToOutputWeights,
1904 recurrentToForgetWeights,
1905 recurrentToCellWeights,
1906 recurrentToOutputWeights,
1907 forgetGateBias,
1908 cellBias,
1909 outputGateBias,
1910 inputToInputWeights,
1911 recurrentToInputWeights,
1912 cellToInputWeights,
1913 inputGateBias,
1914 projectionWeights,
1915 projectionBias,
1916 cellToForgetWeights,
1917 cellToOutputWeights))
1918 {
1919 return false;
1920 }
1921
1922 // Add the layer
1923 armnn::IConnectableLayer* layer = m_Network->AddLstmLayer(desc, params, "Lstm");
1924
1925 input.Connect(layer->GetInputSlot(0));
1926 outputStateIn.Connect(layer->GetInputSlot(1));
1927 cellStateIn.Connect(layer->GetInputSlot(2));
1928
1929 return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0) &&
1930 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1) &&
1931 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2) &&
1932 SetupAndTrackLayerOutputSlot(operation, 3, *layer, 3));
1933}
1934
1935bool ModelToINetworkConverter::ConvertToActivation(const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00001936 const char* operationName,
1937 const armnn::ActivationDescriptor& activationDesc)
1938{
1939 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1940 if (!input.IsValid())
1941 {
1942 return Fail("%s: Input 0 is invalid", operationName);
1943 }
1944
telsoa01ce3e84a2018-08-31 09:31:35 +01001945 const Operand* outputOperand = GetOutputOperand(operation, 0);
1946 if (!outputOperand)
1947 {
1948 return false;
1949 }
1950 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
telsoa015307bc12018-03-09 13:51:08 +00001951 if (!IsLayerSupported(__func__,
1952 armnn::IsActivationSupported,
1953 m_Compute,
1954 input.GetTensorInfo(),
telsoa01ce3e84a2018-08-31 09:31:35 +01001955 outInfo,
telsoa015307bc12018-03-09 13:51:08 +00001956 activationDesc))
1957 {
1958 return false;
1959 }
1960
1961 armnn::IConnectableLayer* layer = m_Network->AddActivationLayer(activationDesc);
1962 assert(layer != nullptr);
1963 input.Connect(layer->GetInputSlot(0));
1964
1965 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1966}
1967
telsoa01ce3e84a2018-08-31 09:31:35 +01001968bool ModelToINetworkConverter::ConvertPooling2d(const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00001969 const char* operationName,
1970 armnn::PoolingAlgorithm poolType)
1971{
1972 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1973 if (!input.IsValid())
1974 {
1975 return Fail("%s: Could not read input 0", operationName);
1976 }
1977
1978 const Operand* output = GetOutputOperand(operation, 0);
1979 if (!output)
1980 {
1981 return Fail("%s: Could not read output 0", __func__);
1982 }
1983
1984 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1985 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1986
1987 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1988 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1989
1990 armnn::Pooling2dDescriptor desc;
1991 desc.m_PoolType = poolType;
1992 desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor;
1993
1994 ActivationFn activation;
1995
1996 if (operation.inputs.size() == 7)
1997 {
1998 // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
1999 android::nn::PaddingScheme scheme;
2000
2001 if ( !GetInputPaddingScheme(operation, 1, scheme)
2002 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX)
2003 || !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY)
2004 || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth)
2005 || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight)
2006 || !GetInputActivationFunction(operation, 6, activation))
2007 {
2008 return Fail("%s: Operation has invalid inputs", operationName);
2009 }
2010
2011 const unsigned int inputWidth = swizzledInputInfo.GetShape()[3];
2012 const unsigned int inputHeight = swizzledInputInfo.GetShape()[2];
2013
2014 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
2015 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
2016 }
2017 else
2018 {
2019 // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
2020 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft)
2021 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight)
2022 || !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop)
2023 || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom)
2024 || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX)
2025 || !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY)
2026 || !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth)
2027 || !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight)
2028 || !GetInputActivationFunction(operation, 9, activation))
2029 {
2030 return Fail("%s: Operation has invalid inputs", operationName);
2031 }
2032 }
2033
2034 // ArmNN does not accept a pool size of 1, but the ArmNN driver is expected to cope.
2035 // This is mapped to a trivial splitter instead.
2036 armnn::IConnectableLayer* startLayer = nullptr;
2037 if (desc.m_PoolWidth != 1 || desc.m_PoolHeight != 1)
2038 {
2039 if (!IsLayerSupported(__func__,
2040 armnn::IsPooling2dSupported,
2041 m_Compute,
2042 swizzledInputInfo,
2043 swizzledOutputInfo,
2044 desc))
2045 {
2046 return false;
2047 }
2048
2049 startLayer = m_Network->AddPooling2dLayer(desc);
2050 }
2051 else
2052 {
2053 const unsigned int numDims = swizzledOutputInfo.GetNumDimensions();
2054
2055 armnn::ViewsDescriptor viewsDesc(1, numDims);
2056
2057 for (unsigned int i = 0; i < numDims; ++i)
2058 {
2059 viewsDesc.SetViewOriginCoord(0, i, 0);
2060 viewsDesc.SetViewSize(0, i, swizzledOutputInfo.GetShape()[i]);
2061 }
2062
2063 if (!IsLayerSupported(__func__,
2064 armnn::IsSplitterSupported,
2065 m_Compute,
2066 swizzledInputInfo,
2067 viewsDesc))
2068 {
2069 return false;
2070 }
2071
2072 startLayer = m_Network->AddSplitterLayer(viewsDesc);
2073 }
2074
2075 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
2076
2077 if (endLayer != nullptr)
2078 {
2079 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
2080 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
2081 }
2082 else
2083 {
2084 return Fail("%s: ProcessActivation failed", operationName);
2085 }
2086}
2087
2088const void* ModelToINetworkConverter::GetOperandValueReadOnlyAddress(const Operand& operand) const
2089{
2090 const void* valueStart = nullptr;
2091
2092 switch (operand.lifetime)
2093 {
2094 case OperandLifeTime::CONSTANT_COPY:
2095 {
2096 // Constant found in model.operandValues
2097 valueStart = &m_Model.operandValues[operand.location.offset];
2098 break;
2099 }
2100 case OperandLifeTime::CONSTANT_REFERENCE:
2101 {
2102 // Constant specified via a Memory object
2103 valueStart = GetMemoryFromPool(operand.location, m_MemPools);
2104 break;
2105 }
2106 default:
2107 {
2108 // Unsupported/invalid (e.g. can't get value of an input to the model)
2109 Fail("%s: unsupported/invalid operand lifetime: %s",
2110 __func__, toString(operand.lifetime).c_str());
2111 valueStart = nullptr;
2112 }
2113 }
2114
2115 return valueStart;
2116}
2117
telsoa01ce3e84a2018-08-31 09:31:35 +01002118const Operand* ModelToINetworkConverter::GetInputOperand(const neuralnetworks::V1_0::Operation& operation,
2119 uint32_t inputIndex) const
telsoa015307bc12018-03-09 13:51:08 +00002120{
2121 if (inputIndex >= operation.inputs.size())
2122 {
2123 Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
2124 return nullptr;
2125 }
2126
2127 assert(operation.inputs[inputIndex] < m_Model.operands.size()); // Model should have been validated beforehand
2128 return &m_Model.operands[operation.inputs[inputIndex]];
2129}
2130
telsoa01ce3e84a2018-08-31 09:31:35 +01002131const Operand* ModelToINetworkConverter::GetOutputOperand(const neuralnetworks::V1_0::Operation& operation,
2132 uint32_t outputIndex) const
telsoa015307bc12018-03-09 13:51:08 +00002133{
2134 if (outputIndex >= operation.outputs.size())
2135 {
2136 Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
2137 return nullptr;
2138 }
2139
2140 assert(operation.outputs[outputIndex] < m_Model.operands.size()); // Model should have been validated beforehand
2141 return &m_Model.operands[operation.outputs[outputIndex]];
2142}
2143
2144template<typename T>
telsoa01ce3e84a2018-08-31 09:31:35 +01002145bool ModelToINetworkConverter::GetInputScalar(const neuralnetworks::V1_0::Operation& operation, uint32_t inputIndex,
telsoa015307bc12018-03-09 13:51:08 +00002146 OperandType type, T& outValue) const
2147{
2148 const Operand* operand = GetInputOperand(operation, inputIndex);
2149 if (!operand)
2150 {
2151 return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
2152 }
2153
2154 if (operand->type != type)
2155 {
2156 return Fail("%s: unexpected operand type: %s (should be %s)",
2157 __func__, toString(operand->type).c_str(), toString(type).c_str());
2158 }
2159
2160 if (operand->location.length != sizeof(T))
2161 {
2162 return Fail("%s: incorrect operand location length: %i (should be %i)",
2163 __func__, operand->location.length, sizeof(T));
2164 }
2165
2166 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand);
2167 if (!valueAddress)
2168 {
2169 return Fail("%s: failed to get address for operand", __func__);
2170 }
2171
2172 outValue = *(static_cast<const T*>(valueAddress));
2173 return true;
2174}
2175
telsoa01ce3e84a2018-08-31 09:31:35 +01002176bool ModelToINetworkConverter::GetInputInt32(const neuralnetworks::V1_0::Operation& operation,
surmeh01deb3bdb2018-07-05 12:06:04 +01002177 uint32_t inputIndex, int32_t& outValue) const
telsoa015307bc12018-03-09 13:51:08 +00002178{
2179 return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue);
2180}
2181
telsoa01ce3e84a2018-08-31 09:31:35 +01002182bool ModelToINetworkConverter::GetInputFloat32(const neuralnetworks::V1_0::Operation& operation,
surmeh01deb3bdb2018-07-05 12:06:04 +01002183 uint32_t inputIndex, float& outValue) const
telsoa015307bc12018-03-09 13:51:08 +00002184{
2185 return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue);
2186}
2187
telsoa01ce3e84a2018-08-31 09:31:35 +01002188bool ModelToINetworkConverter::GetInputActivationFunctionImpl(const neuralnetworks::V1_0::Operation& operation,
2189 uint32_t inputIndex,
2190 OperandType type,
2191 ActivationFn& outActivationFunction) const
telsoa015307bc12018-03-09 13:51:08 +00002192{
telsoa01ce3e84a2018-08-31 09:31:35 +01002193 if (type != OperandType::INT32 && type != OperandType::TENSOR_INT32)
2194 {
2195 return Fail("%s: unexpected operand type: %s (should be %s or %s)",
2196 __func__,
2197 toString(type).c_str(),
2198 toString(OperandType::INT32).c_str(),
2199 toString(OperandType::TENSOR_INT32).c_str());
2200 }
2201
telsoa015307bc12018-03-09 13:51:08 +00002202 int32_t activationFunctionAsInt;
telsoa01ce3e84a2018-08-31 09:31:35 +01002203 if (!GetInputScalar(operation, inputIndex, type, activationFunctionAsInt))
telsoa015307bc12018-03-09 13:51:08 +00002204 {
2205 return Fail("%s: failed to get activation input value", __func__);
2206 }
telsoa015307bc12018-03-09 13:51:08 +00002207 outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
2208 return true;
2209}
2210
telsoa01ce3e84a2018-08-31 09:31:35 +01002211bool ModelToINetworkConverter::GetInputActivationFunction(const neuralnetworks::V1_0::Operation& operation,
2212 uint32_t inputIndex,
2213 ActivationFn& outActivationFunction) const
2214{
2215 return GetInputActivationFunctionImpl(operation, inputIndex, OperandType::INT32, outActivationFunction);
2216}
2217
2218bool ModelToINetworkConverter::GetInputActivationFunctionFromTensor(const neuralnetworks::V1_0::Operation& operation,
2219 uint32_t inputIndex,
2220 ActivationFn& outActivationFunction) const
2221{
2222 // This only accepts a 1-D tensor of size 1
2223 return GetInputActivationFunctionImpl(operation, inputIndex, OperandType::INT32, outActivationFunction);
2224}
2225
2226bool ModelToINetworkConverter::GetOptionalInputActivation(const neuralnetworks::V1_0::Operation& operation,
2227 uint32_t inputIndex,
2228 ActivationFn& activationFunction) const
2229{
2230 if (operation.inputs.size() <= inputIndex)
2231 {
2232 activationFunction = ActivationFn::kActivationNone;
2233 }
2234 else
2235 {
2236 if (!GetInputActivationFunction(operation, inputIndex, activationFunction))
2237 {
2238 return Fail("%s: Operation has invalid inputs", __func__);
2239 }
2240 }
2241 return true;
2242}
2243
2244bool ModelToINetworkConverter::GetInputPaddingScheme(const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00002245 uint32_t inputIndex,
2246 android::nn::PaddingScheme& outPaddingScheme) const
2247{
2248 int32_t paddingSchemeAsInt;
2249 if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt))
2250 {
2251 return Fail("%s: failed to get padding scheme input value", __func__);
2252 }
2253
2254 outPaddingScheme = static_cast<android::nn::PaddingScheme>(paddingSchemeAsInt);
2255 return true;
2256}
2257
2258LayerInputHandle ModelToINetworkConverter::ConvertToLayerInputHandle(
telsoa01ce3e84a2018-08-31 09:31:35 +01002259 const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00002260 uint32_t inputIndex)
2261{
2262 const Operand* operand = GetInputOperand(operation, inputIndex);
2263 if (!operand)
2264 {
2265 Fail("%s: failed to get input operand %i", __func__, inputIndex);
2266 return LayerInputHandle();
2267 }
2268
2269 if (!IsOperandTypeSupportedForTensors(operand->type))
2270 {
2271 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand->type).c_str());
2272 return LayerInputHandle();
2273 }
2274
2275 armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
2276
2277 switch (operand->lifetime)
2278 {
2279 case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
2280 case OperandLifeTime::MODEL_INPUT:
2281 {
2282 // The tensor is either an operand internal to the model, or a model input.
2283 // It can be associated with an ArmNN output slot for an existing layer.
2284
2285 // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
2286 const uint32_t operandIndex = operation.inputs[inputIndex];
2287 return LayerInputHandle(true, m_OutputSlotForOperand[operandIndex], operandTensorInfo);
2288 break;
2289 }
2290 case OperandLifeTime::CONSTANT_COPY:
2291 case OperandLifeTime::CONSTANT_REFERENCE:
2292 {
2293 // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
2294 ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand);
2295 if (tensorPin.IsValid())
2296 {
2297 if (!IsLayerSupported(__func__,
2298 armnn::IsConstantSupported,
2299 m_Compute,
2300 tensorPin.GetConstTensor().GetInfo()))
2301 {
2302 return LayerInputHandle();
2303 }
2304
2305 armnn::IConnectableLayer* constantLayer = m_Network->AddConstantLayer(tensorPin.GetConstTensor());
2306 armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
2307 outputSlot.SetTensorInfo(tensorPin.GetConstTensor().GetInfo());
2308
2309 return LayerInputHandle(true, &outputSlot, operandTensorInfo);
2310 }
2311 else
2312 {
2313 Fail("%s: invalid operand tensor", __func__);
2314 return LayerInputHandle();
2315 }
2316 break;
2317 }
2318 default:
2319 {
2320 // Unsupported lifetime for an input tensor
2321 Fail("%s: unsupported lifetime for input tensor: %s",
2322 __func__, toString(operand->lifetime).c_str());
2323 return LayerInputHandle();
2324 }
2325 }
2326}
2327
telsoa01ce3e84a2018-08-31 09:31:35 +01002328ConstTensorPin ModelToINetworkConverter::ConvertOperationInputToConstTensorPin(
2329 const neuralnetworks::V1_0::Operation& operation,
2330 uint32_t inputIndex, const armnn::PermutationVector& dimensionMappings,
2331 const armnn::TensorShape* overrideTensorShape, bool optional)
telsoa015307bc12018-03-09 13:51:08 +00002332{
2333 const Operand* operand = GetInputOperand(operation, inputIndex);
2334 if (!operand)
2335 {
telsoa01ce3e84a2018-08-31 09:31:35 +01002336 Fail("%s: failed to get input operand: index=%u", __func__, inputIndex);
telsoa015307bc12018-03-09 13:51:08 +00002337 return ConstTensorPin();
2338 }
telsoa01ce3e84a2018-08-31 09:31:35 +01002339 return ConvertOperandToConstTensorPin(*operand, dimensionMappings, overrideTensorShape, optional);
telsoa015307bc12018-03-09 13:51:08 +00002340}
2341
2342ConstTensorPin ModelToINetworkConverter::ConvertOperandToConstTensorPin(const Operand& operand,
telsoa01ce3e84a2018-08-31 09:31:35 +01002343 const armnn::PermutationVector& dimensionMappings, const armnn::TensorShape* overrideTensorShape, bool optional)
telsoa015307bc12018-03-09 13:51:08 +00002344{
2345 if (!IsOperandTypeSupportedForTensors(operand.type))
2346 {
2347 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand.type).c_str());
2348 return ConstTensorPin();
2349 }
2350
2351 if (operand.lifetime != OperandLifeTime::CONSTANT_COPY && operand.lifetime != OperandLifeTime::CONSTANT_REFERENCE)
2352 {
2353 Fail("%s: invalid operand lifetime: %s", __func__, toString(operand.lifetime).c_str());
2354 return ConstTensorPin();
2355 }
2356
2357 const void* const valueStart = GetOperandValueReadOnlyAddress(operand);
2358 if (!valueStart)
2359 {
telsoa01ce3e84a2018-08-31 09:31:35 +01002360 if (optional)
2361 {
2362 // optional tensor with no values is not really an error; return it as invalid, but marked as optional
2363 return ConstTensorPin(true);
2364 }
2365 // mandatory tensor with no values
telsoa015307bc12018-03-09 13:51:08 +00002366 Fail("%s: failed to get operand address", __func__);
2367 return ConstTensorPin();
2368 }
2369
2370 armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
2371 if (overrideTensorShape != nullptr)
2372 {
2373 tensorInfo.SetShape(*overrideTensorShape);
2374 }
2375 return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
2376}
2377
2378bool ModelToINetworkConverter::GetTensorInt32Values(const Operand& operand, std::vector<int32_t>& outValues) const
2379{
2380 if (operand.type != OperandType::TENSOR_INT32)
2381 {
2382 return Fail("%s: invalid operand type: %s", __func__, toString(operand.type).c_str());
2383 }
2384
2385 const void* startAddress = GetOperandValueReadOnlyAddress(operand);
2386 if (!startAddress)
2387 {
2388 return Fail("%s: failed to get operand address", __func__, operand.type);
2389 }
2390
2391 // Check number of bytes is sensible
2392 const uint32_t numBytes = operand.location.length;
2393 if (numBytes % sizeof(int32_t) != 0)
2394 {
2395 return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
2396 __func__, numBytes, sizeof(int32_t));
2397 }
2398
2399 outValues.resize(numBytes / sizeof(int32_t));
2400 memcpy(outValues.data(), startAddress, numBytes);
2401 return true;
2402}
2403
2404// Creates an ArmNN activation layer and connects it to the given layer, if the
2405// passed in AndroidNN activation function requires so.
2406// @return The end layer of the sequence of layers built for the given AndroidNN
2407// activation function or nullptr if an error occurred (e.g. unsupported activation).
2408// Note that the end layer matches the input layer if no activation is required
2409// (the sequence of layers has length 1).
2410armnn::IConnectableLayer* ModelToINetworkConverter::ProcessActivation(const armnn::TensorInfo& tensorInfo,
2411 ActivationFn activation, armnn::IConnectableLayer* prevLayer)
2412{
2413 assert(prevLayer->GetNumOutputSlots() == 1);
2414
2415 prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2416
2417 armnn::IConnectableLayer* activationLayer = prevLayer;
2418
2419 if (activation != ActivationFn::kActivationNone)
2420 {
2421 armnn::ActivationDescriptor activationDesc;
2422 switch (activation)
2423 {
2424 case ActivationFn::kActivationRelu:
2425 {
2426 activationDesc.m_Function = armnn::ActivationFunction::ReLu;
2427 break;
2428 }
2429 case ActivationFn::kActivationRelu1:
2430 {
2431 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
2432 activationDesc.m_A = 1.0f;
2433 activationDesc.m_B = -1.0f;
2434 break;
2435 }
2436 case ActivationFn::kActivationRelu6:
2437 {
2438 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
2439 activationDesc.m_A = 6.0f;
2440 break;
2441 }
2442 case ActivationFn::kActivationSigmoid:
2443 {
2444 activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
2445 break;
2446 }
2447 case ActivationFn::kActivationTanh:
2448 {
2449 activationDesc.m_Function = armnn::ActivationFunction::TanH;
2450 activationDesc.m_A = 1.0f;
2451 activationDesc.m_B = 1.0f;
2452 break;
2453 }
2454 default:
2455 {
2456 Fail("%s: Invalid activation enum value %i", __func__, activation);
2457 return nullptr;
2458 }
2459 }
2460
2461 if (!IsLayerSupported(__func__, armnn::IsActivationSupported, m_Compute,
telsoa01ce3e84a2018-08-31 09:31:35 +01002462 prevLayer->GetOutputSlot(0).GetTensorInfo(), tensorInfo, activationDesc))
telsoa015307bc12018-03-09 13:51:08 +00002463 {
2464 return nullptr;
2465 }
2466
2467 activationLayer = m_Network->AddActivationLayer(activationDesc);
2468
2469 prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0));
2470 activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2471 }
2472
2473 return activationLayer;
2474}
2475
telsoa01ce3e84a2018-08-31 09:31:35 +01002476bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const neuralnetworks::V1_0::Operation& operation,
2477 uint32_t operationOutputIndex,
2478 armnn::IConnectableLayer& layer,
2479 uint32_t layerOutputIndex)
telsoa015307bc12018-03-09 13:51:08 +00002480{
telsoa01ce3e84a2018-08-31 09:31:35 +01002481 const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex);
telsoa015307bc12018-03-09 13:51:08 +00002482
telsoa01ce3e84a2018-08-31 09:31:35 +01002483 if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots()))
telsoa015307bc12018-03-09 13:51:08 +00002484 {
2485 return false;
2486 }
2487
telsoa01ce3e84a2018-08-31 09:31:35 +01002488 armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex);
telsoa015307bc12018-03-09 13:51:08 +00002489
telsoa01ce3e84a2018-08-31 09:31:35 +01002490 const uint32_t operandIndex = operation.outputs[operationOutputIndex];
telsoa015307bc12018-03-09 13:51:08 +00002491 m_OutputSlotForOperand[operandIndex] = &outputSlot;
2492
2493 outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
2494
2495 return true;
2496}
2497
telsoa01ce3e84a2018-08-31 09:31:35 +01002498bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const neuralnetworks::V1_0::Operation& operation,
2499 uint32_t outputIndex,
2500 armnn::IConnectableLayer& layer)
2501{
2502 return SetupAndTrackLayerOutputSlot(operation, outputIndex, layer, outputIndex);
2503}
2504
telsoa015307bc12018-03-09 13:51:08 +00002505bool ModelToINetworkConverter::IsOperationSupported(uint32_t operationIndex) const
2506{
2507 std::map<uint32_t, bool>::const_iterator it = m_OperationSupported.find(operationIndex);
2508 assert(it != m_OperationSupported.end());
2509 return it->second;
2510}
2511
2512
surmeh0149b9e102018-05-17 14:11:25 +01002513} // armnn_driver