blob: 4a7b4014b3ef5d196c97783500a9391e1f8cde96 [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>
arovir013b0a2da2018-08-29 10:16:58 +010021#include <boost/optional.hpp>
telsoa015307bc12018-03-09 13:51:08 +000022
telsoa01ce3e84a2018-08-31 09:31:35 +010023using namespace android::hardware;
24
surmeh0149b9e102018-05-17 14:11:25 +010025namespace armnn_driver
26{
kevmay01bc5f7842018-08-30 12:34:39 +010027
surmeh0149b9e102018-05-17 14:11:25 +010028class LayerInputHandle
29{
30public:
31 LayerInputHandle()
32 : m_OutputSlot(nullptr)
33 , m_Valid(false)
34 {}
35
36 LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo)
37 : m_OutputSlot(outputSlot)
38 , m_Valid(valid)
39 , m_TensorInfo(tensorInfo)
40 {}
41
42 bool IsValid() const { return m_Valid; }
43 void Connect(armnn::IInputSlot& inputSlot)
44 {
45 assert(IsValid());
46
47 if (m_OutputSlot)
48 {
49 m_OutputSlot->Connect(inputSlot);
50 }
51 }
52 const armnn::TensorInfo& GetTensorInfo() const { return m_TensorInfo; }
53
54private:
55 armnn::IOutputSlot* m_OutputSlot;
56 bool m_Valid;
57 armnn::TensorInfo m_TensorInfo;
58};
kevmay01bc5f7842018-08-30 12:34:39 +010059
60} // namespace armnn_driver
surmeh0149b9e102018-05-17 14:11:25 +010061
telsoa015307bc12018-03-09 13:51:08 +000062namespace
63{
kevmay01bc5f7842018-08-30 12:34:39 +010064
telsoa015307bc12018-03-09 13:51:08 +000065using namespace armnn_driver;
66using namespace android::nn;
67
68// Convenience function to log the reason for failing to convert a model.
69// @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
70template<class... Args>
71static bool Fail(const char* formatStr, Args&&... args)
72{
73 ALOGD(formatStr, std::forward<Args>(args)...);
74 return false;
75}
76
77// Convenience function to call an Is*Supported function and log caller name together with reason for lack of support.
78// Called as: IsLayerSupported(__func__, Is*Supported, a, b, c, d, e)
79template<typename IsLayerSupportedFunc, typename ... Args>
80bool IsLayerSupported(const char* funcName, IsLayerSupportedFunc f, Args&&... args)
81{
82 std::vector<char> unsupportedReason(1024+1);
83 bool isSupported = f(std::forward<Args>(args)..., unsupportedReason.data(), unsupportedReason.size()-1);
84 if(isSupported)
85 {
86 return true;
87 }
88 else
89 {
90 std::string sUnsupportedReason(unsupportedReason.data());
91 if (sUnsupportedReason.size() > 0)
92 {
93 ALOGD("%s: not supported by armnn: %s", funcName, sUnsupportedReason.c_str());
94 } else
95 {
96 ALOGD("%s: not supported by armnn", funcName);
97 }
98 return false;
99 }
100}
101
102armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
103{
104 return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
105}
106
107inline bool IsOperandTypeSupportedForTensors(OperandType type)
108{
109 return type == OperandType::TENSOR_FLOAT32 ||
110 type == OperandType::TENSOR_QUANT8_ASYMM ||
111 type == OperandType::TENSOR_INT32;
112}
113
telsoa01ce3e84a2018-08-31 09:31:35 +0100114void BroadcastTensor(LayerInputHandle& input0, LayerInputHandle& input1, armnn::IConnectableLayer* startLayer,
115 armnn::INetwork& network)
116{
117 BOOST_ASSERT(startLayer != nullptr);
118 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
119 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
120
121 if (inputTensorInfo0.GetNumDimensions() != inputTensorInfo1.GetNumDimensions())
122 {
123 // If the number of dimensions do not match then we need to add degenerate dimensions
124 // to the "smaller" tensor using a reshape:
125 // Small Big
126 // | |
127 // Reshape |
128 // \ /
129 // Add
130 bool input0IsBigger = inputTensorInfo0.GetNumDimensions() > inputTensorInfo1.GetNumDimensions();
131
132 LayerInputHandle& smallTensorHandle = input0IsBigger ? input1 : input0;
133 const armnn::TensorInfo& smallTensorDims = smallTensorHandle.GetTensorInfo();
134
135 LayerInputHandle& bigTensorHandle = input0IsBigger ? input0 : input1;
136 const armnn::TensorInfo& bigTensorDims = bigTensorHandle.GetTensorInfo();
137
138 const unsigned int bigTensorDimsNumber = bigTensorDims.GetNumDimensions();
139 std::vector<unsigned int> reshapedDims(bigTensorDimsNumber, 1);
140 unsigned int sizeDifference = bigTensorDimsNumber - smallTensorDims.GetNumDimensions();
141 for (unsigned i = sizeDifference; i < bigTensorDimsNumber; ++i)
142 {
143 reshapedDims[i] = smallTensorDims.GetShape()[i-sizeDifference];
144 }
145 armnn::TensorInfo reshapedInfo = smallTensorDims;
146 reshapedInfo.SetShape(armnn::TensorShape{ static_cast<unsigned int>(reshapedDims.size()),
147 reshapedDims.data() });
148
149 armnn::ReshapeDescriptor reshapeDesc;
150 reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
151 armnn::IConnectableLayer* const reshapeLayer = network.AddReshapeLayer(reshapeDesc);
152 smallTensorHandle.Connect(reshapeLayer->GetInputSlot(0));
153 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
154
155 // Connect the outputs from new reshape and original input layer
156 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
157 bigTensorHandle.Connect(startLayer->GetInputSlot(1));
158 }
159 else
160 {
161 input0.Connect(startLayer->GetInputSlot(0));
162 input1.Connect(startLayer->GetInputSlot(1));
163 }
164}
165
telsoa015307bc12018-03-09 13:51:08 +0000166void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
167 android::nn::PaddingScheme scheme)
168{
169 int32_t padHead;
170 int32_t padTail;
171 calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
172 outPadHead = boost::numeric_cast<uint32_t>(padHead);
173 outPadTail = boost::numeric_cast<uint32_t>(padTail);
174}
175
telsoa015307bc12018-03-09 13:51:08 +0000176Shape GetOperandShape(const Operand& operand)
177{
178 Shape shape;
179 shape.type = operand.type;
180 shape.dimensions = operand.dimensions;
181 shape.scale = operand.scale;
182 shape.offset = operand.zeroPoint;
183 return shape;
184}
185
186// ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
187// what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
188// we accept some tolerance. We don't want to ArmNN itself to accept these inconsistencies as it is up to the user
189// (us, in this case) to ensure they match.
190void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
191 const armnn::TensorInfo& weightInfo, const armnn::TensorInfo& inputInfo)
192{
193 const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
194 if (biasInfo.GetQuantizationScale() != expectedBiasScale)
195 {
196 boost::math::fpc::close_at_tolerance<float> comparer(boost::math::fpc::percent_tolerance(1.0f));
197 if (comparer(biasInfo.GetQuantizationScale(), expectedBiasScale))
198 {
199 ALOGW("Bias quantization scale has been modified to match input*weights");
200 biasInfo.SetQuantizationScale(expectedBiasScale);
201 }
202 }
203}
204
telsoa01ce3e84a2018-08-31 09:31:35 +0100205// 4D Tensor Permutations
206const armnn::PermutationVector IdentityPermutation4D({ 0U, 1U, 2U, 3U });
telsoa015307bc12018-03-09 13:51:08 +0000207const armnn::PermutationVector NHWCToArmNN({ 0U, 2U, 3U, 1U });
surmeh0149b9e102018-05-17 14:11:25 +0100208const armnn::PermutationVector ArmNNToNHWC({ 0U, 3U, 1U, 2U });
209const armnn::PermutationVector SwapDim1And2({ 0U, 2U, 1U, 3U });
telsoa015307bc12018-03-09 13:51:08 +0000210
telsoa01ce3e84a2018-08-31 09:31:35 +0100211// 3D Permutation Vectors
212const armnn::PermutationVector IdentityPermutation3D({ 0U, 1U, 2U });
213const armnn::PermutationVector RotateTensorLeft({ 2U, 0U, 1U });
214const armnn::PermutationVector RotateTensorRight({ 1U, 2U, 0U });
215
kevmay01bc5f7842018-08-30 12:34:39 +0100216template<typename OSlot>
telsoa015307bc12018-03-09 13:51:08 +0000217armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input,
218 const armnn::PermutationVector& mappings)
219{
220 // Add swizzle layer
221 armnn::IConnectableLayer* const layer = network.AddPermuteLayer(mappings);
222
223 assert(layer != nullptr);
224
telsoa01ce3e84a2018-08-31 09:31:35 +0100225 // Connect input to swizzle layer
telsoa015307bc12018-03-09 13:51:08 +0000226 input.Connect(layer->GetInputSlot(0));
227
228 // Setup swizzled output
229 const armnn::TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mappings);
230 layer->GetOutputSlot(0).SetTensorInfo(outInfo);
231
232 return *layer;
233}
234
telsoa01ce3e84a2018-08-31 09:31:35 +0100235void SwizzleIn(armnn::INetwork& network, LayerInputHandle& input, armnn::IConnectableLayer& layer, unsigned int index)
telsoa015307bc12018-03-09 13:51:08 +0000236{
telsoa015307bc12018-03-09 13:51:08 +0000237 // Add swizzle layer
238 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, input, NHWCToArmNN);
telsoa015307bc12018-03-09 13:51:08 +0000239 // Connect swizzled input to layer
telsoa01ce3e84a2018-08-31 09:31:35 +0100240 swizzleLayer.GetOutputSlot(0).Connect(layer.GetInputSlot(index));
241}
telsoa015307bc12018-03-09 13:51:08 +0000242
telsoa01ce3e84a2018-08-31 09:31:35 +0100243armnn::IConnectableLayer& DeswizzleOut(armnn::INetwork& network, armnn::IConnectableLayer& layer, unsigned int index)
244{
telsoa015307bc12018-03-09 13:51:08 +0000245 // Add deswizzle layer
telsoa01ce3e84a2018-08-31 09:31:35 +0100246 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(network, layer.GetOutputSlot(index), ArmNNToNHWC);
telsoa015307bc12018-03-09 13:51:08 +0000247 return deswizzleLayer;
248}
249
telsoa01ce3e84a2018-08-31 09:31:35 +0100250// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly
251armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network,
252 LayerInputHandle& input,
253 armnn::IConnectableLayer& firstLayer,
254 armnn::IConnectableLayer& lastLayer)
255{
256 SwizzleIn(network, input, firstLayer, 0);
257 return DeswizzleOut(network, lastLayer, 0);
258}
259
260// only suitable for input/output slot index 0, for other slots, use SwizzleIn and DeswizzleOut directly
telsoa015307bc12018-03-09 13:51:08 +0000261armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input,
262 armnn::IConnectableLayer& layer)
263{
264 return SwizzleInDeswizzleOut(network, input, layer, layer);
265}
surmeh0149b9e102018-05-17 14:11:25 +0100266
267bool ValidateConcatOutputShape(const std::vector<armnn::TensorShape> & inputShapes,
268 const armnn::TensorShape & outputShape,
269 uint32_t concatDim)
270{
271 // Validate the output shape is correct given the input shapes (which have just been validated)
272 unsigned int numDimensions = inputShapes[0].GetNumDimensions();
273 if (outputShape.GetNumDimensions() != numDimensions)
274 {
275 return Fail("%s: Output shape has wrong number of dimensions", __func__);
276 }
277
278 unsigned int outputSizeAlongConcatenatedDimension = 0;
279 for (unsigned int i = 0; i < inputShapes.size(); i++)
280 {
281 outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
282 }
283
284 for (unsigned int i = 0; i < numDimensions; ++i)
285 {
286 if (i == concatDim)
287 {
288 if (outputShape[i] != outputSizeAlongConcatenatedDimension)
289 {
290 return Fail(
291 "%s: Invalid output shape for dimension %d (%d != %d)",
292 __func__,
293 i,
294 outputShape[i],
295 outputSizeAlongConcatenatedDimension);
296 }
297 }
298 else
299 {
300 if (outputShape[i] != inputShapes[0][i])
301 {
302 return Fail("%s: Invalid output shape", __func__);
303 }
304 }
305 }
306
307 return true;
308}
309
telsoa01ce3e84a2018-08-31 09:31:35 +0100310bool RequiresReshape(armnn::TensorShape & inputShape)
311{
312 return inputShape.GetNumDimensions() < 3;
313}
314
kevmay01bc5f7842018-08-30 12:34:39 +0100315template<typename OSlot>
telsoa01ce3e84a2018-08-31 09:31:35 +0100316armnn::IConnectableLayer& AddReshapeLayer(armnn::INetwork& network, OSlot& inputLayer,
317 armnn::TensorInfo reshapeInfo)
318{
319 armnn::ReshapeDescriptor reshapeDescriptor;
320 reshapeDescriptor.m_TargetShape = reshapeInfo.GetShape();
321
322 armnn::IConnectableLayer* reshapeLayer = network.AddReshapeLayer(reshapeDescriptor);
323 assert(reshapeLayer != nullptr);
324
325 // Attach the input layer to the reshape layer
326 inputLayer.Connect(reshapeLayer->GetInputSlot(0));
327 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapeInfo);
328
329 return *reshapeLayer;
330}
331
surmeh0149b9e102018-05-17 14:11:25 +0100332void SwizzleInputs(armnn::INetwork& network,
333 std::vector<LayerInputHandle>& inputs,
334 std::vector<armnn::TensorShape>& inputShapes,
335 const armnn::PermutationVector& mapping)
336{
telsoa01ce3e84a2018-08-31 09:31:35 +0100337 if (!mapping.IsEqual(IdentityPermutation4D))
surmeh0149b9e102018-05-17 14:11:25 +0100338 {
339 size_t nInputs = inputs.size();
340 for (size_t i=0; i<nInputs; ++i)
341 {
342 // add swizzle layer
343 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, inputs[i], mapping);
344 auto& outputSlot = swizzleLayer.GetOutputSlot(0);
345 auto& outputInfo = outputSlot.GetTensorInfo();
346 // replace inputs with the swizzled ones
347 inputs[i] = LayerInputHandle(true, &outputSlot, outputInfo);
348 inputShapes[i] = inputs[i].GetTensorInfo().GetShape();
349 }
350 }
351}
352
telsoa01ce3e84a2018-08-31 09:31:35 +0100353void CreatePermutationParameters(const unsigned int numberOfDimensions,
354 int32_t & concatDimension,
355 std::pair<armnn::PermutationVector, armnn::PermutationVector> & permutationPair)
356{
357 assert(numberOfDimensions >= 3);
358
359 // ArmNN uses Compute Library subtensors to perform concatenation
360 // This only works when concatenating along dimension 0 or 1 for a 4-D tensor,
361 // or along dimension 0 for a 3-D tensor.
362 if (numberOfDimensions == 4)
363 {
364 if (concatDimension == 3)
365 {
366 concatDimension = 1;
367 permutationPair = std::make_pair(NHWCToArmNN, ArmNNToNHWC);
368 }
369 else if (concatDimension == 2)
370 {
371 concatDimension = 1;
372 permutationPair = std::make_pair(SwapDim1And2, SwapDim1And2);
373 }
374 else
375 {
376 permutationPair = std::make_pair(IdentityPermutation4D, IdentityPermutation4D);
377 }
378
379 }
380 else if (numberOfDimensions == 3)
381 {
382 if (concatDimension == 2)
383 {
384 concatDimension = 0;
385 permutationPair = std::make_pair(RotateTensorRight, RotateTensorLeft);
386 }
387 else if (concatDimension == 1)
388 {
389 concatDimension = 0;
390 permutationPair = std::make_pair(RotateTensorLeft, RotateTensorRight);
391 }
392 else
393 {
394 permutationPair = std::make_pair(IdentityPermutation3D, IdentityPermutation3D);
395 }
396 }
397}
398
kevmay01bc5f7842018-08-30 12:34:39 +0100399} // anonymous namespace
telsoa015307bc12018-03-09 13:51:08 +0000400
401namespace armnn_driver
402{
403
404class ConstTensorPin
405{
406public:
407 // Creates an invalid tensor pin (can be used to signal errors)
telsoa01ce3e84a2018-08-31 09:31:35 +0100408 // The optional flag can be set to indicate the tensor values were missing, but it was otherwise valid
409 ConstTensorPin(bool optional = false) : m_Optional(optional) {}
telsoa015307bc12018-03-09 13:51:08 +0000410
411 // @param tensorInfo TensorInfo associated with the tensor.
412 // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
413 // the model being converted.
414 // @param numBytes Number of bytes for the tensor data.
415 ConstTensorPin(const armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
416 const armnn::PermutationVector& mappings)
417 {
418 boost::ignore_unused(numBytes);
419 assert(tensorInfo.GetNumBytes() == numBytes);
420
421 const bool needsSwizzling = (mappings.GetSize() > 0);
422 if (needsSwizzling)
423 {
424 m_SwizzledTensorData.resize(tensorInfo.GetNumBytes());
425 SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings);
426
427 m_ConstTensor = armnn::ConstTensor(armnnUtils::Permuted(tensorInfo, mappings), m_SwizzledTensorData.data());
428 }
429 else
430 {
431 m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart);
432 }
433 }
434
435 ConstTensorPin(const ConstTensorPin& other) = delete;
436 ConstTensorPin(ConstTensorPin&& other) = default;
437
438 bool IsValid() const { return m_ConstTensor.GetMemoryArea() != nullptr; }
telsoa01ce3e84a2018-08-31 09:31:35 +0100439 bool IsOptional() const { return m_Optional; }
telsoa015307bc12018-03-09 13:51:08 +0000440 const armnn::ConstTensor& GetConstTensor() const { return m_ConstTensor; }
telsoa01ce3e84a2018-08-31 09:31:35 +0100441 const armnn::ConstTensor* GetConstTensorPtr() const
442 {
443 if (IsValid() && m_ConstTensor.GetNumElements() > 0)
444 {
445 return &m_ConstTensor;
446 }
447 // tensor is either invalid, or has no elements (indicating an optional tensor that was not provided)
448 return nullptr;
449 }
telsoa015307bc12018-03-09 13:51:08 +0000450
451private:
452 armnn::ConstTensor m_ConstTensor;
453 // Owned memory for swizzled tensor data, only required if the tensor needed
454 // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
455 // the pools associated with the model being converted.
456 std::vector<uint8_t> m_SwizzledTensorData;
telsoa01ce3e84a2018-08-31 09:31:35 +0100457 // optional flag to indicate that an invalid tensor pin is not an error, but the optional values were not given
458 bool m_Optional;
telsoa015307bc12018-03-09 13:51:08 +0000459};
460
kevmay01bc5f7842018-08-30 12:34:39 +0100461template<typename HalVersion>
462ModelToINetworkConverter<HalVersion>::ModelToINetworkConverter(armnn::Compute compute,
463 const HalModel& model,
telsoa015307bc12018-03-09 13:51:08 +0000464 const std::set<unsigned int>& forcedUnsupportedOperations)
465 : m_Compute(compute)
466 , m_Model(model)
467 , m_ForcedUnsupportedOperations(forcedUnsupportedOperations)
468 , m_Network(nullptr, nullptr)
469 , m_ConversionResult(ConversionResult::Success)
470{
471 try
472 {
473 Convert();
474 }
475 catch (armnn::Exception& e)
476 {
477 m_ConversionResult = ConversionResult::UnsupportedFeature;
478 ALOGE("%s: Unexpected exception: %s", __func__, e.what());
479 assert(false);
480 }
481}
482
kevmay01bc5f7842018-08-30 12:34:39 +0100483template<typename HalVersion>
484void ModelToINetworkConverter<HalVersion>::Convert()
telsoa015307bc12018-03-09 13:51:08 +0000485{
kevmay01bc5f7842018-08-30 12:34:39 +0100486 using Model = typename HalVersion::Model;
487 ALOGV("ModelToINetworkConverter::Convert(): %s", GetModelSummary<Model>(m_Model).c_str());
telsoa015307bc12018-03-09 13:51:08 +0000488
489 // map the memory pool into shared pointers
490 m_MemPools.clear();
491 if (!setRunTimePoolInfosFromHidlMemories(&m_MemPools, m_Model.pools))
492 {
493 Fail("%s: Setting of run time pool infos from Hidl Memories has failed.", __func__);
494 m_ConversionResult = ConversionResult::ErrorMappingPools;
495 return;
496 }
497
498 uint32_t totalPoolSize = 0;
499 for (auto&& pool : m_Model.pools)
500 {
501 totalPoolSize += pool.size();
502 }
503
504 // Create armnn::INetwork
505 m_Network = armnn::INetwork::Create();
506
507 // add operations to it
508 // track which layer outputs each operand
509 m_OutputSlotForOperand = std::vector<armnn::IOutputSlot*>(m_Model.operands.size(), nullptr);
510
511 try
512 {
513 for (uint32_t i = 0; i < m_Model.inputIndexes.size(); i++)
514 {
515 // inputs in android nn are represented by operands
516 uint32_t inputIndex = m_Model.inputIndexes[i];
517 const Operand& operand = m_Model.operands[inputIndex];
518 const armnn::TensorInfo& tensor = GetTensorInfoForOperand(operand);
519 armnn::IConnectableLayer* layer = m_Network->AddInputLayer(i);
520
521 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
522 outputSlot.SetTensorInfo(GetTensorInfoForOperand(operand));
523
524 // store for later layers
525 m_OutputSlotForOperand[inputIndex] = &outputSlot;
526 }
527 }
528 catch (UnsupportedOperand& e)
529 {
530 Fail("%s: Operand type %s not supported in ArmnnDriver", __func__, toString(e.m_type).c_str());
531 m_ConversionResult = ConversionResult::UnsupportedFeature;
532 }
533 catch (const armnn::InvalidArgumentException& e)
534 {
535 Fail("%s: Failed to convert input operand to TensorShape: %s", __func__, e.what());
536 m_ConversionResult = ConversionResult::UnsupportedFeature;
537 }
538
539 for (uint32_t operationIdx = 0; operationIdx < m_Model.operations.size(); operationIdx++)
540 {
541 const auto& operation = m_Model.operations[operationIdx];
542
543 bool ok = true;
544 if (m_ForcedUnsupportedOperations.find(operationIdx) != m_ForcedUnsupportedOperations.end())
545 {
546 Fail("%s: Operation at index %i has been forced to be unsupported.", __func__, operationIdx);
547 ok = false;
548 }
549
550 if (ok)
551 {
552 try
553 {
554 ok = ConvertOperation(operation);
555 }
556 catch (UnsupportedOperand& e)
557 {
558 Fail("%s: Operand type %s not supported in ArmnnDriver", __func__, toString(e.m_type).c_str());
559 ok = false;
560 }
561 catch (const armnn::InvalidArgumentException& e)
562 {
563 Fail("%s: Failed to convert operation in %s", __func__, e.what());
564 ok = false;
565 }
566 }
567
568 // Store whether this operation was successfully converted.
569 m_OperationSupported.emplace(operationIdx, ok);
570
571 // Any single operation failing will fail the entire conversion.
572 // We still need to continue and check the other ones.
573 if (!ok)
574 {
575 m_ConversionResult = ConversionResult::UnsupportedFeature;
576 }
577 }
578 try
579 {
580 if (m_ConversionResult == ConversionResult::Success)
581 {
582 for (uint32_t i = 0; i < m_Model.outputIndexes.size(); i++)
583 {
584 // outputs in android nn are represented by operands
585 uint32_t outputIndex = m_Model.outputIndexes[i];
586 const Operand& operand = m_Model.operands[outputIndex];
587 const armnn::TensorInfo& tensor = GetTensorInfoForOperand(operand);
588 armnn::IConnectableLayer* layer = m_Network->AddOutputLayer(i);
589
590 assert(m_OutputSlotForOperand[outputIndex]);
591 m_OutputSlotForOperand[outputIndex]->Connect(layer->GetInputSlot(0));
592 }
593 }
594 }
595 catch (const armnn::InvalidArgumentException& e)
596 {
597 Fail("%s: Failed to convert output operand to TensorShape: %s", __func__, e.what());
598 m_ConversionResult = ConversionResult::UnsupportedFeature;
599 }
600}
601
kevmay01bc5f7842018-08-30 12:34:39 +0100602template<typename HalVersion>
603bool ModelToINetworkConverter<HalVersion>::ConvertOperation(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000604{
605 switch (operation.type)
606 {
telsoa01ce3e84a2018-08-31 09:31:35 +0100607 case neuralnetworks::V1_0::OperationType::ADD:
608 return ConvertAdd(operation);
609 case neuralnetworks::V1_0::OperationType::AVERAGE_POOL_2D:
610 return ConvertAveragePool2d(operation);
611 case neuralnetworks::V1_0::OperationType::CONCATENATION:
612 return ConvertConcatenation(operation);
613 case neuralnetworks::V1_0::OperationType::CONV_2D:
614 return ConvertConv2d(operation);
615 case neuralnetworks::V1_0::OperationType::DEPTHWISE_CONV_2D:
616 return ConvertDepthwiseConv2d(operation);
617 case neuralnetworks::V1_0::OperationType::FLOOR:
618 return ConvertFloor(operation);
619 case neuralnetworks::V1_0::OperationType::FULLY_CONNECTED:
620 return ConvertFullyConnected(operation);
621 case neuralnetworks::V1_0::OperationType::LOCAL_RESPONSE_NORMALIZATION:
622 return ConvertLocalResponseNormalization(operation);
623 case neuralnetworks::V1_0::OperationType::LOGISTIC:
624 return ConvertLogistic(operation);
625 case neuralnetworks::V1_0::OperationType::LSTM:
626 return ConvertLstm(operation);
627 case neuralnetworks::V1_0::OperationType::L2_NORMALIZATION:
628 return ConvertL2Normalization(operation);
629 case neuralnetworks::V1_0::OperationType::L2_POOL_2D:
630 return ConvertL2Pool2d(operation);
631 case neuralnetworks::V1_0::OperationType::MAX_POOL_2D:
632 return ConvertMaxPool2d(operation);
633 case neuralnetworks::V1_0::OperationType::MUL:
634 return ConvertMul(operation);
635 case neuralnetworks::V1_0::OperationType::RELU:
636 return ConvertReLu(operation);
637 case neuralnetworks::V1_0::OperationType::RELU1:
638 return ConvertReLu1(operation);
639 case neuralnetworks::V1_0::OperationType::RELU6:
640 return ConvertReLu6(operation);
641 case neuralnetworks::V1_0::OperationType::SOFTMAX:
642 return ConvertSoftmax(operation);
643 case neuralnetworks::V1_0::OperationType::TANH:
644 return ConvertTanH(operation);
645 case neuralnetworks::V1_0::OperationType::RESHAPE:
646 return ConvertReshape(operation);
647 case neuralnetworks::V1_0::OperationType::RESIZE_BILINEAR:
648 return ConvertResizeBilinear(operation);
649 default:
650 return Fail("%s: Operation type %s not supported in ArmnnDriver",
651 __func__, toString(operation.type).c_str());
telsoa015307bc12018-03-09 13:51:08 +0000652 }
653}
654
kevmay01bc5f7842018-08-30 12:34:39 +0100655#if defined(ARMNN_ANDROID_NN_V1_1)
656template<typename HalVersion>
657bool ModelToINetworkConverter<HalVersion>::ConvertOperation(const neuralnetworks::V1_1::Operation& operation)
658{
659 if (compliantWithV1_0(operation))
660 {
661 neuralnetworks::V1_0::Operation v1Operation = convertToV1_0(operation);
662 return ConvertOperation(v1Operation);
663 }
664 else
665 {
666 switch (operation.type)
667 {
668 // TODO: provide cases for converting the new v1.1 operations
669 default:
670 return Fail("%s: Operation type %s not supported in ArmnnDriver",
671 __func__, toString(operation.type).c_str());
672 }
673 }
674}
675#endif
676
677template<typename HalVersion>
678bool ModelToINetworkConverter<HalVersion>::ConvertAdd(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000679{
680 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0);
681 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1);
682
683 if (!input0.IsValid() || !input1.IsValid())
684 {
685 return Fail("%s: Operation has invalid inputs", __func__);
686 }
687
telsoa01ce3e84a2018-08-31 09:31:35 +0100688 // The FuseActivation parameter is always the input index 2
689 // and it should be optional
telsoa015307bc12018-03-09 13:51:08 +0000690 ActivationFn activationFunction;
telsoa01ce3e84a2018-08-31 09:31:35 +0100691 if (!GetOptionalInputActivation(operation, 2, activationFunction))
telsoa015307bc12018-03-09 13:51:08 +0000692 {
693 return Fail("%s: Operation has invalid inputs", __func__);
694 }
695
696 const Operand* outputOperand = GetOutputOperand(operation, 0);
697 if (!outputOperand)
698 {
699 return false;
700 }
701
702 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
703
704 if (!IsLayerSupported(__func__,
705 armnn::IsAdditionSupported,
706 m_Compute,
707 input0.GetTensorInfo(),
708 input1.GetTensorInfo(),
709 outInfo))
710 {
711 return false;
712 }
713
714 armnn::IConnectableLayer* const startLayer = m_Network->AddAdditionLayer();
715 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer);
716
717 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
718 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
719
720 if (endLayer != nullptr)
721 {
telsoa01ce3e84a2018-08-31 09:31:35 +0100722 BroadcastTensor(input0, input1, startLayer, *m_Network);
telsoa015307bc12018-03-09 13:51:08 +0000723 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
724 }
725 else
726 {
727 return Fail("%s: ProcessActivation failed", __func__);
728 }
729}
730
kevmay01bc5f7842018-08-30 12:34:39 +0100731template<typename HalVersion>
732bool ModelToINetworkConverter<HalVersion>::ConvertAveragePool2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000733{
734 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Average);
735}
736
kevmay01bc5f7842018-08-30 12:34:39 +0100737template<typename HalVersion>
738bool ModelToINetworkConverter<HalVersion>::ConvertConcatenation(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000739{
740 // The first N (0..N-1) inputs are tensors. The Nth input is the concatenation axis.
741 if (operation.inputs.size() <= 1)
742 {
743 return Fail("%s: Operation has insufficient arguments", __func__);
744 }
745
746 // Get inputs and outputs
747 const std::size_t numInputTensors = operation.inputs.size() - 1;
748
surmeh0149b9e102018-05-17 14:11:25 +0100749 int32_t concatDim;
750 if (!GetInputScalar(operation, numInputTensors, OperandType::INT32, concatDim))
751 {
752 return Fail("%s: Operation has invalid inputs", __func__);
753 }
754
755 const Operand* const outputOperand = GetOutputOperand(operation, 0);
756 if (!outputOperand)
757 {
758 return Fail("%s: Operation has no outputs", __func__);
759 }
760
telsoa01ce3e84a2018-08-31 09:31:35 +0100761
surmeh0149b9e102018-05-17 14:11:25 +0100762 armnn::TensorInfo outputInfo = GetTensorInfoForOperand(*outputOperand);
763 armnn::TensorShape outputShape = outputInfo.GetShape();
764
765 //
766 // handle negative concat dims along the lines of tensorflow as described here:
767 // https://www.tensorflow.org/api_docs/python/tf/concat
768 // "negative axis refers to axis + rank(values)-th dimension"
769 //
770 if (concatDim < 0)
771 {
772 concatDim += outputShape.GetNumDimensions();
773 }
774
775 if (concatDim >= static_cast<int32_t>(outputShape.GetNumDimensions()) || concatDim < 0)
776 {
777 return Fail("%s: Operation has invalid concat axis: %d", __func__, concatDim);
778 }
779
telsoa015307bc12018-03-09 13:51:08 +0000780 std::vector<LayerInputHandle> inputHandles;
781 std::vector<armnn::TensorShape> inputShapes;
782
783 inputHandles.reserve(numInputTensors);
784 inputShapes.reserve(numInputTensors);
785
telsoa01ce3e84a2018-08-31 09:31:35 +0100786 bool inputsHaveBeenReshaped = false;
787 unsigned int tensorDimensionsAdded = 0;
788
telsoa015307bc12018-03-09 13:51:08 +0000789 for (uint32_t i = 0; i < numInputTensors; ++i)
790 {
791 const Operand* const operand = GetInputOperand(operation, i);
792 if (!operand)
793 {
794 return Fail("%s: Operation has invalid inputs", __func__);
795 }
796
telsoa01ce3e84a2018-08-31 09:31:35 +0100797 armnn::TensorShape operandShape = GetTensorShapeForOperand(*operand);
798 LayerInputHandle operandInputHandle = ConvertToLayerInputHandle(operation, i);
surmeh0149b9e102018-05-17 14:11:25 +0100799
telsoa01ce3e84a2018-08-31 09:31:35 +0100800 if (operandShape.GetNumDimensions() == 0)
801 {
802 return Fail("%s: Operands with rank 0 are not supported", __func__);
803 }
804
805 if (RequiresReshape(operandShape))
806 {
807 inputsHaveBeenReshaped = true;
808
809 armnn::TensorInfo reshapeInfo = operandInputHandle.GetTensorInfo();
810
811 // Expand the tensor to three dimensions
812 if (operandShape.GetNumDimensions() == 2)
813 {
814 reshapeInfo.SetShape(armnn::TensorShape({1, operandShape[0], operandShape[1]}));
815 tensorDimensionsAdded = 1;
816 }
817 else
818 {
819 reshapeInfo.SetShape(armnn::TensorShape({1, 1, operandShape[0]}));
820 tensorDimensionsAdded = 2;
821 }
822
823 armnn::IConnectableLayer& newReshape = AddReshapeLayer(
824 *m_Network,
825 operandInputHandle,
826 reshapeInfo
827 );
828
829 // Point to the reshape operation rather then the input operation
830 operandShape = reshapeInfo.GetShape();
831 operandInputHandle = LayerInputHandle(true, &newReshape.GetOutputSlot(0), reshapeInfo);
832 }
833
834 inputShapes.emplace_back(operandShape);
835 inputHandles.emplace_back(operandInputHandle);
surmeh0149b9e102018-05-17 14:11:25 +0100836
telsoa015307bc12018-03-09 13:51:08 +0000837 if (!inputHandles.back().IsValid())
838 {
839 return Fail("%s: Operation has invalid inputs", __func__);
840 }
841 }
842
843 assert(inputShapes.size() == inputHandles.size());
844
telsoa01ce3e84a2018-08-31 09:31:35 +0100845 if (inputsHaveBeenReshaped)
846 {
847 // Adjust the concatenation dimension by the amount of dimensions added (if any)
848 concatDim += tensorDimensionsAdded;
849
850 // Add extra dimensions to the output shape to reflect the addition of the reshape layers
851 if (tensorDimensionsAdded == 1)
852 {
853 outputShape = armnn::TensorShape({1, outputShape[0], outputShape[1]});
854 }
855 else if (tensorDimensionsAdded == 2)
856 {
857 outputShape = armnn::TensorShape({1, 1, outputShape[0], outputShape[1]});
858 }
859 }
860
861 // Get the pair of permutations required for the concatenation
862 std::pair<armnn::PermutationVector, armnn::PermutationVector> permutationPair =
863 std::make_pair(IdentityPermutation4D, IdentityPermutation4D);
864
865 CreatePermutationParameters(inputShapes[0].GetNumDimensions(), concatDim, permutationPair);
866
867 outputShape = armnnUtils::Permuted(outputShape, permutationPair.first);
868 outputInfo.SetShape(outputShape);
869
surmeh0149b9e102018-05-17 14:11:25 +0100870 // this is no-op for identity swizzles, otherwise it replaces both
871 // the handles and shapes with the swizzled layer output handles and shapes
telsoa01ce3e84a2018-08-31 09:31:35 +0100872 SwizzleInputs(*m_Network, inputHandles, inputShapes, permutationPair.first);
telsoa015307bc12018-03-09 13:51:08 +0000873
874 // Create an armnn merger layer descriptor - this will also perform validation on the input shapes
875 armnn::OriginsDescriptor mergerDescriptor;
876 try
877 {
surmeh0149b9e102018-05-17 14:11:25 +0100878 // The merger descriptor is always created across the only supported concat
879 // dimension, which is 0 or 1
880 mergerDescriptor =
881 armnn::CreateMergerDescriptorForConcatenation(
882 inputShapes.begin(), inputShapes.end(), concatDim);
telsoa015307bc12018-03-09 13:51:08 +0000883 }
884 catch (const armnn::Exception& error)
885 {
886 return Fail("%s: Error preparing merger descriptor. %s", __func__, error.what());
887 }
888
surmeh0149b9e102018-05-17 14:11:25 +0100889 // Validate the output shape is correct given the input shapes based on the
890 // only valid concat dimension which is 0 or 1
891 if (!ValidateConcatOutputShape(inputShapes, outputShape, concatDim))
telsoa015307bc12018-03-09 13:51:08 +0000892 {
surmeh0149b9e102018-05-17 14:11:25 +0100893 return Fail("%s: Error validating the output shape for concat", __func__);
telsoa015307bc12018-03-09 13:51:08 +0000894 }
895
896 std::vector<const armnn::TensorInfo*> inputTensorInfos;
897 std::transform(inputHandles.begin(), inputHandles.end(), std::back_inserter(inputTensorInfos),
898 [](const LayerInputHandle& h) -> const armnn::TensorInfo*{ return &h.GetTensorInfo(); });
899 if (!IsLayerSupported(__func__,
900 armnn::IsMergerSupported,
901 m_Compute,
902 inputTensorInfos,
903 mergerDescriptor))
904 {
905 return false;
906 }
907
908 armnn::IConnectableLayer* layer = m_Network->AddMergerLayer(mergerDescriptor);
909 assert(layer != nullptr);
surmeh0149b9e102018-05-17 14:11:25 +0100910 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
telsoa015307bc12018-03-09 13:51:08 +0000911
912 // Connect inputs to the layer
913 const int numInputSlots = layer->GetNumInputSlots();
914 assert(static_cast<std::size_t>(numInputSlots) == inputHandles.size());
915 for (int i = 0; i < numInputSlots; ++i)
916 {
surmeh0149b9e102018-05-17 14:11:25 +0100917 // connect the input directly to the merge (concat) layer
telsoa015307bc12018-03-09 13:51:08 +0000918 inputHandles[static_cast<unsigned int>(i)].Connect(layer->GetInputSlot(i));
919 }
920
telsoa01ce3e84a2018-08-31 09:31:35 +0100921 // Add permutation layer and connect the output to it, the permutation becomes the output layer
922 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(*m_Network,
923 layer->GetOutputSlot(0),
924 permutationPair.second);
925 layer = &deswizzleLayer;
926
927 if (inputsHaveBeenReshaped)
surmeh0149b9e102018-05-17 14:11:25 +0100928 {
telsoa01ce3e84a2018-08-31 09:31:35 +0100929 armnn::TensorInfo afterConcatInfo = layer->GetOutputSlot(0).GetTensorInfo();
930
931 // Undo the reshape knowing the amount of dimensions added
932 if (tensorDimensionsAdded == 1)
933 {
934 afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[1],
935 afterConcatInfo.GetShape()[2] }));
936 }
937 else if (tensorDimensionsAdded == 2)
938 {
939 afterConcatInfo.SetShape(armnn::TensorShape({ afterConcatInfo.GetShape()[2],
940 afterConcatInfo.GetShape()[3] }));
941 }
942
943 layer = &AddReshapeLayer(
944 *m_Network,
945 layer->GetOutputSlot(0),
946 afterConcatInfo
947 );
surmeh0149b9e102018-05-17 14:11:25 +0100948 }
949
telsoa015307bc12018-03-09 13:51:08 +0000950 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
951}
952
kevmay01bc5f7842018-08-30 12:34:39 +0100953template<typename HalVersion>
954bool ModelToINetworkConverter<HalVersion>::ConvertConv2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +0000955{
956 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
957 if (!input.IsValid())
958 {
959 return Fail("%s: Operation has invalid inputs", __func__);
960 }
961
962 const Operand* output = GetOutputOperand(operation, 0);
963 if (!output)
964 {
965 return Fail("%s: Could not read output 0", __func__);
966 }
967
968 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
969 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
970
971 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
972 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
973
974 // ArmNN does not currently support non-fixed weights or bias
975 const ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, NHWCToArmNN);
976 const ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2);
977
978 if (!weightsPin.IsValid() || !biasPin.IsValid())
979 {
980 return Fail("%s: Operation has invalid inputs", __func__);
981 }
982
983 armnn::ConstTensor weights = weightsPin.GetConstTensor();
984 armnn::ConstTensor bias = biasPin.GetConstTensor();
985 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), swizzledInputInfo);
986
987 armnn::Convolution2dDescriptor desc;
988 ActivationFn activation;
989
990 if (operation.inputs.size() == 10)
991 {
992 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft) ||
993 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight) ||
994 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop) ||
995 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom) ||
996 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX) ||
997 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY) ||
998 !GetInputActivationFunction(operation, 9, activation))
999 {
1000 return Fail("%s: Operation has invalid inputs", __func__);
1001 }
1002 }
1003 else if (operation.inputs.size() == 7)
1004 {
1005 android::nn::PaddingScheme paddingScheme;
1006
1007 if (!GetInputPaddingScheme(operation, 3, paddingScheme) ||
1008 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX) ||
1009 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY) ||
1010 !GetInputActivationFunction(operation, 6, activation))
1011 {
1012 return Fail("%s: Operation has invalid inputs", __func__);
1013 }
1014
1015 const uint32_t kernelX = weights.GetShape()[3];
1016 const uint32_t kernelY = weights.GetShape()[2];
1017 const uint32_t inputX = swizzledInputInfo.GetShape()[3];
1018 const uint32_t inputY = swizzledInputInfo.GetShape()[2];
1019
1020 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1021 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1022 }
1023 else
1024 {
1025 return Fail("%s: Unsupported number of operation inputs", __func__);
1026 }
1027
arovir013b0a2da2018-08-29 10:16:58 +01001028 desc.m_BiasEnabled = true;
1029 auto biases = boost::make_optional(bias.GetInfo());
telsoa015307bc12018-03-09 13:51:08 +00001030
1031 if (!IsLayerSupported(__func__,
1032 armnn::IsConvolution2dSupported,
1033 m_Compute,
1034 swizzledInputInfo,
surmeh0149b9e102018-05-17 14:11:25 +01001035 swizzledOutputInfo,
telsoa015307bc12018-03-09 13:51:08 +00001036 desc,
surmeh0149b9e102018-05-17 14:11:25 +01001037 weights.GetInfo(),
arovir013b0a2da2018-08-29 10:16:58 +01001038 biases))
telsoa015307bc12018-03-09 13:51:08 +00001039 {
1040 return false;
1041 }
1042
1043 armnn::IConnectableLayer* startLayer = m_Network->AddConvolution2dLayer(desc, weights, bias);
1044 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
1045
1046 if (endLayer != nullptr)
1047 {
1048 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
1049 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1050 }
1051 else
1052 {
1053 return Fail("%s: ProcessActivation failed", __func__);
1054 }
1055}
1056
kevmay01bc5f7842018-08-30 12:34:39 +01001057template<typename HalVersion>
1058bool ModelToINetworkConverter<HalVersion>::ConvertDepthwiseConv2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001059{
1060 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1061 if (!input.IsValid())
1062 {
1063 return Fail("%s: Operation has invalid inputs", __func__);
1064 }
1065
1066 const Operand* output = GetOutputOperand(operation, 0);
1067 if (!output)
1068 {
1069 return Fail("%s: Could not read output 0", __func__);
1070 }
1071
1072 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1073 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1074
1075 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1076 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1077
1078 // ArmNN does not currently support non-fixed weights or bias
1079
1080 // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
1081 // but in ArmNN it needs to be [ M, I, H, W ]
1082 const Operand* weightsOperand = GetInputOperand(operation, 1);
1083
1084 if (weightsOperand == nullptr)
1085 {
1086 return Fail("%s: Operand is invalid", __func__);
1087 }
1088
1089 // Reinterpret weight data as [ H, W, I, M ]
1090 armnn::TensorShape weightsShape({ weightsOperand->dimensions[1], weightsOperand->dimensions[2],
1091 inputInfo.GetShape()[3],
1092 weightsOperand->dimensions[3] / inputInfo.GetShape()[3] });
1093
1094 // Swizzle weight data [ H, W, I, M ] -> [ M, I, H, W ]
1095 const armnn::PermutationVector HWIMToMIHW = { 2U, 3U, 1U, 0U };
1096 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, HWIMToMIHW, &weightsShape);
1097
1098 // Bias is a 1D tensor
1099 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2);
1100
1101 if (!weightsPin.IsValid() || !biasPin.IsValid())
1102 {
1103 return Fail("%s: Operation has invalid inputs", __func__);
1104 }
1105
1106 armnn::ConstTensor weights = weightsPin.GetConstTensor();
1107 armnn::ConstTensor bias = biasPin.GetConstTensor();
1108 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), swizzledInputInfo);
1109
1110 armnn::DepthwiseConvolution2dDescriptor desc;
1111 ActivationFn activation;
1112
1113 if (operation.inputs.size() == 11)
1114 {
1115 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft) ||
1116 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight) ||
1117 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop) ||
1118 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom) ||
1119 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX) ||
1120 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY) ||
1121 !GetInputActivationFunction(operation, 10, activation))
1122 {
1123 return Fail("%s: Operation has invalid inputs", __func__);
1124 }
1125 }
1126 else if (operation.inputs.size() == 8)
1127 {
1128 android::nn::PaddingScheme paddingScheme;
1129
1130 if (!GetInputPaddingScheme(operation, 3, paddingScheme) ||
1131 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX) ||
1132 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY) ||
1133 !GetInputActivationFunction(operation, 7, activation))
1134 {
1135 return Fail("%s: Operation has invalid inputs", __func__);
1136 }
1137
1138 const uint32_t kernelX = weights.GetShape()[3];
1139 const uint32_t kernelY = weights.GetShape()[2];
1140 const uint32_t inputX = swizzledInputInfo.GetShape()[3];
1141 const uint32_t inputY = swizzledInputInfo.GetShape()[2];
1142
1143 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
1144 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
1145 }
1146 else
1147 {
1148 return Fail("%s: Unsupported number of operation inputs", __func__);
1149 }
1150
1151 desc.m_BiasEnabled = true;
arovir013b0a2da2018-08-29 10:16:58 +01001152 auto biases = boost::make_optional(bias.GetInfo());
telsoa015307bc12018-03-09 13:51:08 +00001153
1154 if (!IsLayerSupported(__func__,
1155 armnn::IsDepthwiseConvolutionSupported,
1156 m_Compute,
1157 swizzledInputInfo,
telsoa01ce3e84a2018-08-31 09:31:35 +01001158 swizzledOutputInfo,
telsoa015307bc12018-03-09 13:51:08 +00001159 desc,
telsoa01ce3e84a2018-08-31 09:31:35 +01001160 weights.GetInfo(),
arovir013b0a2da2018-08-29 10:16:58 +01001161 biases))
telsoa015307bc12018-03-09 13:51:08 +00001162 {
1163 return false;
1164 }
1165
1166 armnn::IConnectableLayer* startLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, bias);
1167 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
1168
1169 if (endLayer != nullptr)
1170 {
1171 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
1172 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1173 }
1174 else
1175 {
1176 return Fail("%s: ProcessActivation failed", __func__);
1177 }
1178}
1179
kevmay01bc5f7842018-08-30 12:34:39 +01001180template<typename HalVersion>
1181bool ModelToINetworkConverter<HalVersion>::ConvertFloor(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001182{
1183 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1184 if (!input.IsValid())
1185 {
1186 return Fail("%s: Operation has invalid inputs", __func__);
1187 }
1188
1189 const Operand* const outputOperand = GetOutputOperand(operation, 0);
1190 if (!outputOperand)
1191 {
1192 return Fail("%s: Operation has invalid outputs", __func__);
1193 }
1194
1195 if (!IsLayerSupported(__func__,
1196 armnn::IsFloorSupported,
1197 m_Compute,
1198 input.GetTensorInfo(),
1199 GetTensorInfoForOperand(*outputOperand)))
1200 {
1201 return false;
1202 }
1203
1204 armnn::IConnectableLayer* layer = m_Network->AddFloorLayer();
1205 assert(layer != nullptr);
1206 input.Connect(layer->GetInputSlot(0));
1207
1208 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1209}
1210
kevmay01bc5f7842018-08-30 12:34:39 +01001211template<typename HalVersion>
1212bool ModelToINetworkConverter<HalVersion>::ConvertFullyConnected(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001213{
1214 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1215 if (!input.IsValid())
1216 {
1217 return Fail("%s: Operation has invalid inputs", __func__);
1218 }
1219
1220 const Operand* output = GetOutputOperand(operation, 0);
1221 if (!output)
1222 {
1223 return Fail("%s: Could not read output 0", __func__);
1224 }
1225
1226 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1227 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1228
telsoa015307bc12018-03-09 13:51:08 +00001229 // ArmNN does not currently support non-fixed weights or bias
1230 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1); // 2D
1231 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2); // 1D
1232
1233 if (!weightsPin.IsValid() || !biasPin.IsValid())
1234 {
1235 return Fail("%s: Operation has invalid inputs", __func__);
1236 }
1237
telsoa015307bc12018-03-09 13:51:08 +00001238 armnn::ConstTensor weights = weightsPin.GetConstTensor();
1239 armnn::ConstTensor bias = biasPin.GetConstTensor();
telsoa01ce3e84a2018-08-31 09:31:35 +01001240
1241 armnn::TensorInfo reshapedInfo = inputInfo;
1242 if (inputInfo.GetNumDimensions() > 2U)
1243 {
1244 unsigned int dim0 = inputInfo.GetShape()[0];
1245 unsigned int dim1 = inputInfo.GetShape()[1];
1246
1247 for (unsigned int i = 2U; i < inputInfo.GetNumDimensions(); ++i)
1248 {
1249 dim1 *= inputInfo.GetShape()[i];
1250 }
1251
1252 unsigned int divisor = weights.GetInfo().GetShape()[1] / dim1;
1253 if(dim0 % divisor != 0)
1254 {
1255 return Fail("%s: Failed to deduce tensor shape", __func__);
1256 }
1257
1258 reshapedInfo.SetShape(armnn::TensorShape({dim0 / divisor, dim1 * divisor}));
1259 }
1260
1261 // ensuring that the bias value is within 1% of the weights input (small float differences can exist)
telsoa015307bc12018-03-09 13:51:08 +00001262 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), reshapedInfo);
1263
1264 ActivationFn activationFunction;
1265 if (!GetInputActivationFunction(operation, 3, activationFunction))
1266 {
1267 return Fail("%s: Operation has invalid inputs", __func__);
1268 }
1269
1270 armnn::FullyConnectedDescriptor desc;
1271 desc.m_TransposeWeightMatrix = true;
1272 desc.m_BiasEnabled = true;
1273
1274 if (!IsLayerSupported(__func__,
1275 armnn::IsFullyConnectedSupported,
1276 m_Compute,
telsoa01ce3e84a2018-08-31 09:31:35 +01001277 inputInfo,
1278 outputInfo,
1279 weights.GetInfo(),
1280 bias.GetInfo(),
telsoa015307bc12018-03-09 13:51:08 +00001281 desc))
1282 {
1283 return false;
1284 }
1285
1286 armnn::IConnectableLayer* startLayer = m_Network->AddFullyConnectedLayer(desc, weights, bias);
1287 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activationFunction, startLayer);
1288
1289 if (endLayer != nullptr)
1290 {
1291 if (inputInfo.GetNumDimensions() > 2U)
1292 {
1293 armnn::ReshapeDescriptor reshapeDescriptor;
1294 reshapeDescriptor.m_TargetShape = reshapedInfo.GetShape();
1295
1296 armnn::IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(reshapeDescriptor);
1297 assert(reshapeLayer != nullptr);
1298 input.Connect(reshapeLayer->GetInputSlot(0));
1299 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
1300 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
1301 }
1302 else
1303 {
1304 input.Connect(startLayer->GetInputSlot(0));
1305 }
1306
1307 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
1308 }
1309 else
1310 {
1311 return Fail("%s: ProcessActivation failed", __func__);
1312 }
1313}
1314
kevmay01bc5f7842018-08-30 12:34:39 +01001315template<typename HalVersion>
1316bool ModelToINetworkConverter<HalVersion>::ConvertLocalResponseNormalization(
1317 const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001318{
1319 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1320 if (!input.IsValid())
1321 {
1322 return Fail("%s: Operation has invalid inputs", __func__);
1323 }
1324
1325 const Operand* output = GetOutputOperand(operation, 0);
1326 if (!output)
1327 {
1328 return Fail("%s: Could not read output 0", __func__);
1329 }
1330
1331 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1332 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1333
1334 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1335 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1336
1337 armnn::NormalizationDescriptor descriptor;
1338
1339 descriptor.m_NormChannelType = armnn::NormalizationAlgorithmChannel::Across;
1340 descriptor.m_NormMethodType = armnn::NormalizationAlgorithmMethod::LocalBrightness;
1341
1342 if (!input.IsValid() ||
1343 !GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_NormSize) ||
1344 !GetInputFloat32(operation, 2, descriptor.m_K) ||
1345 !GetInputFloat32(operation, 3, descriptor.m_Alpha) ||
1346 !GetInputFloat32(operation, 4, descriptor.m_Beta))
1347 {
1348 return Fail("%s: Operation has invalid inputs", __func__);
1349 }
1350
1351 // ArmNN expects normSize to be the full size of the normalization
1352 // window rather than the radius as in AndroidNN.
1353 descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize);
1354
1355 if (!IsLayerSupported(__func__,
1356 armnn::IsNormalizationSupported,
1357 m_Compute,
1358 swizzledInputInfo,
1359 swizzledOutputInfo,
1360 descriptor))
1361 {
1362 return false;
1363 }
1364
1365
1366 armnn::IConnectableLayer* layer = m_Network->AddNormalizationLayer(descriptor);
1367 assert(layer != nullptr);
1368 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1369
1370 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1371
1372 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1373}
1374
kevmay01bc5f7842018-08-30 12:34:39 +01001375template<typename HalVersion>
1376bool ModelToINetworkConverter<HalVersion>::ConvertLogistic(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001377{
1378 armnn::ActivationDescriptor desc;
surmeh0149b9e102018-05-17 14:11:25 +01001379 desc.m_Function = armnn::ActivationFunction::Sigmoid;
telsoa015307bc12018-03-09 13:51:08 +00001380
1381 return ConvertToActivation(operation, __func__, desc);
1382}
1383
kevmay01bc5f7842018-08-30 12:34:39 +01001384template<typename HalVersion>
1385bool ModelToINetworkConverter<HalVersion>::ConvertL2Normalization(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001386{
1387 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1388 if (!input.IsValid())
1389 {
1390 return Fail("%s: Operation has invalid inputs", __func__);
1391 }
1392
1393 const Operand* output = GetOutputOperand(operation, 0);
1394 if (!output)
1395 {
1396 return Fail("%s: Could not read output 0", __func__);
1397 }
1398
1399 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1400 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1401
1402 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1403 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1404
1405 if (!IsLayerSupported(__func__,
1406 armnn::IsL2NormalizationSupported,
1407 m_Compute,
telsoa01ce3e84a2018-08-31 09:31:35 +01001408 swizzledInputInfo,
1409 swizzledOutputInfo))
telsoa015307bc12018-03-09 13:51:08 +00001410 {
1411 return false;
1412 }
1413
1414 armnn::IConnectableLayer* layer = m_Network->AddL2NormalizationLayer();
1415 assert(layer != nullptr);
1416 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1417
1418 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1419
1420 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1421}
1422
kevmay01bc5f7842018-08-30 12:34:39 +01001423template<typename HalVersion>
1424bool ModelToINetworkConverter<HalVersion>::ConvertL2Pool2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001425{
1426 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::L2);
1427}
1428
kevmay01bc5f7842018-08-30 12:34:39 +01001429template<typename HalVersion>
1430bool ModelToINetworkConverter<HalVersion>::ConvertMaxPool2d(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001431{
1432 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Max);
1433}
1434
kevmay01bc5f7842018-08-30 12:34:39 +01001435template<typename HalVersion>
1436bool ModelToINetworkConverter<HalVersion>::ConvertMul(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001437{
1438 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0);
1439 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1);
1440
1441 if (!input0.IsValid() || !input1.IsValid())
1442 {
1443 return Fail("%s: Operation has invalid inputs", __func__);
1444 }
1445
telsoa01ce3e84a2018-08-31 09:31:35 +01001446 // The FuseActivation parameter is always the input index 2
1447 // and it should be optional
telsoa015307bc12018-03-09 13:51:08 +00001448 ActivationFn activationFunction;
telsoa01ce3e84a2018-08-31 09:31:35 +01001449 if (!GetOptionalInputActivation(operation, 2, activationFunction))
telsoa015307bc12018-03-09 13:51:08 +00001450 {
1451 return Fail("%s: Operation has invalid inputs", __func__);
1452 }
1453
telsoa015307bc12018-03-09 13:51:08 +00001454 const Operand* outputOperand = GetOutputOperand(operation, 0);
1455
1456 if (outputOperand == nullptr)
1457 {
1458 return false;
1459 }
1460
1461 const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
1462
telsoa01ce3e84a2018-08-31 09:31:35 +01001463 if (!IsLayerSupported(__func__,
1464 armnn::IsMultiplicationSupported,
1465 m_Compute,
1466 input0.GetTensorInfo(),
1467 input1.GetTensorInfo(),
1468 outInfo))
1469 {
1470 return false;
1471 }
1472
telsoa015307bc12018-03-09 13:51:08 +00001473 armnn::IConnectableLayer* const startLayer = m_Network->AddMultiplicationLayer();
1474 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer);
1475
telsoa01ce3e84a2018-08-31 09:31:35 +01001476 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
1477 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
1478
telsoa015307bc12018-03-09 13:51:08 +00001479 if (endLayer != nullptr)
1480 {
telsoa01ce3e84a2018-08-31 09:31:35 +01001481 BroadcastTensor(input0, input1, startLayer, *m_Network);
telsoa015307bc12018-03-09 13:51:08 +00001482 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
1483 }
1484 else
1485 {
1486 return Fail("%s: ProcessActivation failed", __func__);
1487 }
1488}
1489
kevmay01bc5f7842018-08-30 12:34:39 +01001490template<typename HalVersion>
1491bool ModelToINetworkConverter<HalVersion>::ConvertReLu(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001492{
1493 armnn::ActivationDescriptor desc;
1494 desc.m_Function = armnn::ActivationFunction::ReLu;
1495
1496 return ConvertToActivation(operation, __func__, desc);
1497}
1498
kevmay01bc5f7842018-08-30 12:34:39 +01001499template<typename HalVersion>
1500bool ModelToINetworkConverter<HalVersion>::ConvertReLu1(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001501{
1502 armnn::ActivationDescriptor desc;
1503 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1504 desc.m_A = 1.0f;
1505 desc.m_B = -1.0f;
1506
1507 return ConvertToActivation(operation, __func__, desc);
1508}
1509
kevmay01bc5f7842018-08-30 12:34:39 +01001510template<typename HalVersion>
1511bool ModelToINetworkConverter<HalVersion>::ConvertReLu6(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001512{
1513 armnn::ActivationDescriptor desc;
1514 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1515 desc.m_A = 6.0f;
1516
1517 return ConvertToActivation(operation, __func__, desc);
1518}
1519
kevmay01bc5f7842018-08-30 12:34:39 +01001520template<typename HalVersion>
1521bool ModelToINetworkConverter<HalVersion>::ConvertSoftmax(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001522{
1523 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1524 if (!input.IsValid())
1525 {
1526 return Fail("%s: Operation has invalid inputs", __func__);
1527 }
1528
telsoa01ce3e84a2018-08-31 09:31:35 +01001529 const Operand* outputOperand = GetOutputOperand(operation, 0);
1530 if (!outputOperand)
1531 {
1532 return Fail("%s: Operation has no outputs", __func__);
1533 }
1534
1535 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
1536
telsoa015307bc12018-03-09 13:51:08 +00001537 armnn::SoftmaxDescriptor desc;
1538 if (!GetInputFloat32(operation, 1, desc.m_Beta))
1539 {
1540 return Fail("%s: Operation has invalid inputs", __func__);
1541 }
1542
1543 if (!IsLayerSupported(__func__,
1544 armnn::IsSoftmaxSupported,
1545 m_Compute,
1546 input.GetTensorInfo(),
telsoa01ce3e84a2018-08-31 09:31:35 +01001547 outInfo,
telsoa015307bc12018-03-09 13:51:08 +00001548 desc))
1549 {
1550 return false;
1551 }
1552
1553 armnn::IConnectableLayer* layer = m_Network->AddSoftmaxLayer(desc);
1554 assert(layer != nullptr);
1555 input.Connect(layer->GetInputSlot(0));
1556
1557 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1558}
1559
kevmay01bc5f7842018-08-30 12:34:39 +01001560template<typename HalVersion>
1561bool ModelToINetworkConverter<HalVersion>::ConvertTanH(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001562{
1563 armnn::ActivationDescriptor desc;
1564 desc.m_Function = armnn::ActivationFunction::TanH;
1565 desc.m_A = 1.0f; // android nn does not support tanH parameters
1566 desc.m_B = 1.0f; // set to 1.0f for unity scaling
1567
1568 return ConvertToActivation(operation, __func__, desc);
1569}
1570
kevmay01bc5f7842018-08-30 12:34:39 +01001571template<typename HalVersion>
1572bool ModelToINetworkConverter<HalVersion>::ConvertReshape(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001573{
1574 const Operand* inputOperand = GetInputOperand(operation, 0);
1575 const Operand* requestedShapeOperand = GetInputOperand(operation, 1);
1576 const Operand* outputOperand = GetOutputOperand(operation, 0);
1577
1578 if (inputOperand == nullptr
1579 || requestedShapeOperand == nullptr
1580 || outputOperand == nullptr)
1581 {
1582 return Fail("%s: Operation has invalid inputs", __func__);
1583 }
1584
1585
1586 if (requestedShapeOperand->dimensions.size() != 1)
1587 {
1588 return Fail("%s: Input 1 expected to be one-dimensional (found %i dimensions)",
1589 __func__, requestedShapeOperand->dimensions.size());
1590 }
1591
1592 std::vector<int32_t> targetDimensions;
1593 if (!GetTensorInt32Values(*requestedShapeOperand, targetDimensions))
1594 {
1595 return Fail("%s: Could not read values of input 1", __func__);
1596 }
1597
1598 const Shape inputOperandShape = GetOperandShape(*inputOperand);
1599
1600 Shape requestedShape;
1601 // targetDimensions may contain special values (e.g. -1). reshapePrepare() is an AndroidNN provided utility
1602 // function that resolves these values into a fully specified tensor shape.
1603 if (!reshapePrepare(inputOperandShape, targetDimensions.data(), targetDimensions.size(), &requestedShape))
1604 {
1605 return Fail("%s: Failed to resolve the requested shape", __func__);
1606 }
1607
1608 const Shape outputOperandShape = GetOperandShape(*outputOperand);
1609 if (!SameShape(requestedShape, outputOperandShape))
1610 {
1611 return Fail("%s: Shape of output operand does not match resolved requested shape", __func__);
1612 }
1613
1614 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1615 if (!input.IsValid())
1616 {
1617 return Fail("%s: Could not read input 0", __func__);
1618 }
1619
1620 if (!IsLayerSupported(__func__,
1621 armnn::IsReshapeSupported,
1622 m_Compute,
1623 input.GetTensorInfo()))
1624 {
1625 return false;
1626 }
1627
1628
1629 armnn::ReshapeDescriptor reshapeDescriptor;
1630 reshapeDescriptor.m_TargetShape = armnn::TensorShape(requestedShape.dimensions.size(),
1631 requestedShape.dimensions.data());
1632
1633 armnn::IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDescriptor);
1634 assert(layer != nullptr);
1635 input.Connect(layer->GetInputSlot(0));
1636
1637 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1638}
1639
kevmay01bc5f7842018-08-30 12:34:39 +01001640template<typename HalVersion>
1641bool ModelToINetworkConverter<HalVersion>::ConvertResizeBilinear(const neuralnetworks::V1_0::Operation& operation)
telsoa015307bc12018-03-09 13:51:08 +00001642{
1643 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1644 if (!input.IsValid())
1645 {
1646 return Fail("%s: Could not read input 0", __func__);
1647 }
1648
1649 const Operand* output = GetOutputOperand(operation, 0);
1650 if (!output)
1651 {
1652 return Fail("%s: Could not read output 0", __func__);
1653 }
1654
1655 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1656 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1657
1658 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1659 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1660
1661 if (!IsLayerSupported(__func__,
1662 armnn::IsResizeBilinearSupported,
1663 m_Compute,
1664 swizzledInputInfo))
1665 {
1666 return false;
1667 }
1668
1669 armnn::ResizeBilinearDescriptor desc;
1670
1671 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_TargetHeight)
1672 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_TargetWidth))
1673 {
1674 return Fail("%s: Operation has invalid inputs", __func__);
1675 }
1676
1677 armnn::IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc);
1678 assert(layer != nullptr);
1679 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1680
1681 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1682
1683 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1684
1685}
1686
kevmay01bc5f7842018-08-30 12:34:39 +01001687template<typename HalVersion>
1688bool ModelToINetworkConverter<HalVersion>::ConvertLstm(const neuralnetworks::V1_0::Operation& operation)
telsoa01ce3e84a2018-08-31 09:31:35 +01001689{
1690 // Inputs:
1691 // 00: The input: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, input_size], where
1692 // “batch_size” corresponds to the batching dimension, and “input_size” is the size of the input.
1693 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1694 if (!input.IsValid())
1695 {
1696 return Fail("%s: Could not read input 0: input", __func__);
1697 }
1698 // 18: The output state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
1699 LayerInputHandle outputStateIn = ConvertToLayerInputHandle(operation, 18);
1700 if (!outputStateIn.IsValid())
1701 {
1702 return Fail("%s: Could not read input 18: outputStateIn", __func__);
1703 }
1704 // 19: The cell state: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
1705 LayerInputHandle cellStateIn = ConvertToLayerInputHandle(operation, 19);
1706 if (!cellStateIn.IsValid())
1707 {
1708 return Fail("%s: Could not read input 19: cellStateIn", __func__);
1709 }
1710
1711 // Get the mandatory input tensors:
1712 // 02: The input-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1713 // [num_units, input_size].
1714 const ConstTensorPin inputToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 2);
1715 // 03: The input-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units, input_size].
1716 const ConstTensorPin inputToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 3);
1717 // 04: The input-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1718 // [num_units, input_size].
1719 const ConstTensorPin inputToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 4);
1720 // 06: The recurrent-to-forget weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1721 // [num_units, output_size].
1722 const ConstTensorPin recurrentToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 6);
1723 // 07: The recurrent-to-cell weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1724 // [num_units, output_size].
1725 const ConstTensorPin recurrentToCellWeightsPin = ConvertOperationInputToConstTensorPin(operation, 7);
1726 // 08: The recurrent-to-output weights: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1727 // [num_units, output_size].
1728 const ConstTensorPin recurrentToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 8);
1729 // 13: The forget gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1730 const ConstTensorPin forgetGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 13);
1731 // 14: The cell bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1732 const ConstTensorPin cellBiasPin = ConvertOperationInputToConstTensorPin(operation, 14);
1733 // 15: The output gate bias: A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1734 const ConstTensorPin outputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 15);
1735
1736 if (!inputToForgetWeightsPin.IsValid() ||
1737 !inputToCellWeightsPin.IsValid() ||
1738 !inputToOutputWeightsPin.IsValid() ||
1739 !recurrentToForgetWeightsPin.IsValid() ||
1740 !recurrentToCellWeightsPin.IsValid() ||
1741 !recurrentToOutputWeightsPin.IsValid() ||
1742 !forgetGateBiasPin.IsValid() ||
1743 !cellBiasPin.IsValid() ||
1744 !outputGateBiasPin.IsValid())
1745 {
1746 return Fail("%s: Operation has invalid tensor inputs", __func__);
1747 }
1748
1749 // Get the optional input tensors:
1750 // 01: The input-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1751 // [num_units, input_size], where “num_units” corresponds to the number of cell units.
1752 const ConstTensorPin inputToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 1);
1753 // 05: The recurrent-to-input weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1754 // [num_units, output_size], where “output_size” corresponds to either the number of cell units (i.e.,
1755 // “num_units”), or the second dimension of the “projection_weights”, if defined.
1756 const ConstTensorPin recurrentToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 5);
1757 // 09: The cell-to-input weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1758 const ConstTensorPin cellToInputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 9);
1759 // 10: The cell-to-forget weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1760 const ConstTensorPin cellToForgetWeightsPin = ConvertOperationInputToConstTensorPin(operation, 10);
1761 // 11: The cell-to-output weights: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1762 const ConstTensorPin cellToOutputWeightsPin = ConvertOperationInputToConstTensorPin(operation, 11);
1763 // 12: The input gate bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [num_units].
1764 const ConstTensorPin inputGateBiasPin = ConvertOperationInputToConstTensorPin(operation, 12);
1765 // 16: The projection weights: Optional. A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape
1766 // [output_size, num_units].
1767 const ConstTensorPin projectionWeightsPin = ConvertOperationInputToConstTensorPin(operation, 16);
1768 // 17: The projection bias: Optional. A 1-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [output_size].
1769 const ConstTensorPin projectionBiasPin = ConvertOperationInputToConstTensorPin(operation, 17);
1770
1771 if ((!inputToInputWeightsPin.IsValid() && !inputToInputWeightsPin.IsOptional()) ||
1772 (!recurrentToInputWeightsPin.IsValid() && !recurrentToInputWeightsPin.IsOptional()) ||
1773 (!cellToInputWeightsPin.IsValid() && !cellToInputWeightsPin.IsOptional()) ||
1774 (!cellToForgetWeightsPin.IsValid() && !cellToForgetWeightsPin.IsOptional()) ||
1775 (!cellToOutputWeightsPin.IsValid() && !cellToOutputWeightsPin.IsOptional()) ||
1776 (!inputGateBiasPin.IsValid() && !inputGateBiasPin.IsOptional()) ||
1777 (!projectionWeightsPin.IsValid() && !projectionWeightsPin.IsOptional()) ||
1778 (!projectionBiasPin.IsValid() && !projectionBiasPin.IsOptional()))
1779 {
1780 return Fail("%s: Operation has invalid tensor inputs", __func__);
1781 }
1782
1783 // Get the mandatory input scalars (actually 1-D tensors of size 1):
1784 // 20: The activation function: A value indicating the activation function:
1785 // 0: None; 1: Relu; 3: Relu6; 4: Tanh; 6: Sigmoid.
1786 // 21: The clipping threshold: for the cell state, such that values are bound within [-cell_clip, cell_clip].
1787 // If set to 0.0 then clipping is disabled.
1788 // 22: The clipping threshold: for the output from the projection layer, such that values are bound within
1789 // [-proj_clip, proj_clip]. If set to 0.0 then clipping is disabled.
1790 ActivationFn activation;
1791 float cellClip;
1792 float projClip;
1793 if (!GetInputActivationFunctionFromTensor(operation, 20, activation) ||
1794 !GetInputScalar(operation, 21, OperandType::FLOAT32, cellClip) ||
1795 !GetInputScalar(operation, 22, OperandType::FLOAT32, projClip))
1796 {
1797 return Fail("%s: Operation has invalid scalar inputs", __func__);
1798 }
1799
1800 // Outputs:
1801 // 00: The scratch buffer: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units * 4] with
1802 // CIFG, or [batch_size, num_units * 3] without CIFG.
1803 const Operand* scratchBuffer = GetOutputOperand(operation, 0);
1804 if (!scratchBuffer)
1805 {
1806 return Fail("%s: Could not read output 0: scratchBuffer", __func__);
1807 }
1808 // 01: The output state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size].
1809 const Operand* outputStateOut = GetOutputOperand(operation, 1);
1810 if (!outputStateOut)
1811 {
1812 return Fail("%s: Could not read output 1: outputStateOut", __func__);
1813 }
1814 // 02: The cell state (out): A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, num_units].
1815 const Operand* cellStateOut = GetOutputOperand(operation, 2);
1816 if (!cellStateOut)
1817 {
1818 return Fail("%s: Could not read output 2: cellStateOut", __func__);
1819 }
1820 // 03: The output: A 2-D tensor of ANEURALNETWORKS_TENSOR_FLOAT32, of shape [batch_size, output_size]. This is
1821 // effectively the same as the current “output state (out)” value.
1822 const Operand* output = GetOutputOperand(operation, 3);
1823 if (!output)
1824 {
1825 return Fail("%s: Could not read output 3: output", __func__);
1826 }
1827
1828 // set the params structure for the AddLstmLayer call
1829 armnn::LstmInputParams params;
1830 params.m_InputToInputWeights = inputToInputWeightsPin.GetConstTensorPtr();
1831 params.m_InputToForgetWeights = inputToForgetWeightsPin.GetConstTensorPtr();
1832 params.m_InputToCellWeights = inputToCellWeightsPin.GetConstTensorPtr();
1833 params.m_InputToOutputWeights = inputToOutputWeightsPin.GetConstTensorPtr();
1834 params.m_RecurrentToInputWeights = recurrentToInputWeightsPin.GetConstTensorPtr();
1835 params.m_RecurrentToForgetWeights = recurrentToForgetWeightsPin.GetConstTensorPtr();
1836 params.m_RecurrentToCellWeights = recurrentToCellWeightsPin.GetConstTensorPtr();
1837 params.m_RecurrentToOutputWeights = recurrentToOutputWeightsPin.GetConstTensorPtr();
1838 params.m_CellToInputWeights = cellToInputWeightsPin.GetConstTensorPtr();
1839 params.m_CellToForgetWeights = cellToForgetWeightsPin.GetConstTensorPtr();
1840 params.m_CellToOutputWeights = cellToOutputWeightsPin.GetConstTensorPtr();
1841 params.m_InputGateBias = inputGateBiasPin.GetConstTensorPtr();
1842 params.m_ForgetGateBias = forgetGateBiasPin.GetConstTensorPtr();
1843 params.m_CellBias = cellBiasPin.GetConstTensorPtr();
1844 params.m_OutputGateBias = outputGateBiasPin.GetConstTensorPtr();
1845 params.m_ProjectionWeights = projectionWeightsPin.GetConstTensorPtr();
1846 params.m_ProjectionBias = projectionBiasPin.GetConstTensorPtr();
1847
1848 // set the layer descriptor
1849 armnn::LstmDescriptor desc;
1850 desc.m_ActivationFunc = activation;
1851 desc.m_ClippingThresCell = cellClip;
1852 desc.m_ClippingThresProj = projClip;
1853 desc.m_CifgEnabled = (params.m_InputToInputWeights == nullptr ||
1854 params.m_RecurrentToInputWeights == nullptr ||
1855 params.m_InputGateBias == nullptr);
1856 desc.m_PeepholeEnabled = (params.m_CellToForgetWeights != nullptr ||
1857 params.m_CellToOutputWeights != nullptr);
1858 desc.m_ProjectionEnabled = (params.m_ProjectionWeights != nullptr);
1859
1860 // validate the optional input groups
1861 if (desc.m_CifgEnabled &&
1862 (params.m_InputToInputWeights != nullptr ||
1863 params.m_RecurrentToInputWeights != nullptr ||
1864 params.m_InputGateBias != nullptr))
1865 {
1866 return Fail("%s: All, or none, of input-to-input weights, recurrent-to-input weights,"
1867 " and input gate bias must be provided", __func__);
1868 }
1869
1870 if (!desc.m_ProjectionEnabled && params.m_ProjectionBias != nullptr)
1871 {
1872 return Fail("%s: projection bias should not be provided without projection weights", __func__);
1873 }
1874
1875 if (desc.m_PeepholeEnabled &&
1876 (params.m_CellToForgetWeights == nullptr ||
1877 params.m_CellToOutputWeights == nullptr ||
1878 (!desc.m_CifgEnabled && params.m_CellToInputWeights == nullptr)))
1879 {
1880 return Fail("%s: All, or none, of cell-to-forget weights and cell-to-output weights must be provided"
1881 " and, if CIFG is not enabled, cell-to-input weights must also be provided", __func__);
1882 }
1883
1884 // Check if the layer is supported
1885 // Inputs
1886 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1887 const armnn::TensorInfo& outputStateInInfo = outputStateIn.GetTensorInfo();
1888 const armnn::TensorInfo& cellStateInInfo = cellStateIn.GetTensorInfo();
1889
1890 // Outputs
1891 const armnn::TensorInfo& scratchBufferInfo = GetTensorInfoForOperand(*scratchBuffer);
1892 const armnn::TensorInfo& outputStateOutInfo = GetTensorInfoForOperand(*outputStateOut);
1893 const armnn::TensorInfo& cellStateOutInfo = GetTensorInfoForOperand(*cellStateOut);
1894 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1895
1896 // Basic parameters
1897 const armnn::TensorInfo& inputToForgetWeights = params.m_InputToForgetWeights->GetInfo();
1898 const armnn::TensorInfo& inputToCellWeights = params.m_InputToCellWeights->GetInfo();
1899 const armnn::TensorInfo& inputToOutputWeights = params.m_InputToOutputWeights->GetInfo();
1900 const armnn::TensorInfo& recurrentToForgetWeights = params.m_RecurrentToForgetWeights->GetInfo();
1901 const armnn::TensorInfo& recurrentToCellWeights = params.m_RecurrentToCellWeights->GetInfo();
1902 const armnn::TensorInfo& recurrentToOutputWeights = params.m_RecurrentToOutputWeights->GetInfo();
1903 const armnn::TensorInfo& forgetGateBias = params.m_ForgetGateBias->GetInfo();
1904 const armnn::TensorInfo& cellBias = params.m_CellBias->GetInfo();
1905 const armnn::TensorInfo& outputGateBias = params.m_OutputGateBias->GetInfo();
1906
1907 //Optional parameters
1908 const armnn::TensorInfo* inputToInputWeights = nullptr;
1909 const armnn::TensorInfo* recurrentToInputWeights = nullptr;
1910 const armnn::TensorInfo* cellToInputWeights = nullptr;
1911 const armnn::TensorInfo* inputGateBias = nullptr;
1912 const armnn::TensorInfo* projectionWeights = nullptr;
1913 const armnn::TensorInfo* projectionBias = nullptr;
1914 const armnn::TensorInfo* cellToForgetWeights = nullptr;
1915 const armnn::TensorInfo* cellToOutputWeights = nullptr;
1916
1917 if(!desc.m_CifgEnabled)
1918 {
1919 inputToInputWeights = &(params.m_InputToInputWeights->GetInfo());
1920 recurrentToInputWeights = &(params.m_RecurrentToInputWeights->GetInfo());
1921 if (params.m_CellToInputWeights != nullptr)
1922 {
1923 cellToInputWeights = &(params.m_CellToInputWeights->GetInfo());
1924 }
1925 inputGateBias = &(params.m_InputGateBias->GetInfo());
1926 }
1927
1928 if(desc.m_ProjectionEnabled)
1929 {
1930 projectionWeights = &(params.m_ProjectionWeights->GetInfo());
1931 if (params.m_ProjectionBias != nullptr)
1932 {
1933 projectionBias = &(params.m_ProjectionBias->GetInfo());
1934 }
1935 }
1936
1937 if(desc.m_PeepholeEnabled)
1938 {
1939 cellToForgetWeights = &(params.m_CellToForgetWeights->GetInfo());
1940 cellToOutputWeights = &(params.m_CellToOutputWeights->GetInfo());
1941 }
1942
1943 if (!IsLayerSupported(__func__,
1944 armnn::IsLstmSupported,
1945 m_Compute,
1946 inputInfo,
1947 outputStateInInfo,
1948 cellStateInInfo,
1949 scratchBufferInfo,
1950 outputStateOutInfo,
1951 cellStateOutInfo,
1952 outputInfo,
1953 desc,
1954 inputToForgetWeights,
1955 inputToCellWeights,
1956 inputToOutputWeights,
1957 recurrentToForgetWeights,
1958 recurrentToCellWeights,
1959 recurrentToOutputWeights,
1960 forgetGateBias,
1961 cellBias,
1962 outputGateBias,
1963 inputToInputWeights,
1964 recurrentToInputWeights,
1965 cellToInputWeights,
1966 inputGateBias,
1967 projectionWeights,
1968 projectionBias,
1969 cellToForgetWeights,
1970 cellToOutputWeights))
1971 {
1972 return false;
1973 }
1974
1975 // Add the layer
1976 armnn::IConnectableLayer* layer = m_Network->AddLstmLayer(desc, params, "Lstm");
1977
1978 input.Connect(layer->GetInputSlot(0));
1979 outputStateIn.Connect(layer->GetInputSlot(1));
1980 cellStateIn.Connect(layer->GetInputSlot(2));
1981
1982 return (SetupAndTrackLayerOutputSlot(operation, 0, *layer, 0) &&
1983 SetupAndTrackLayerOutputSlot(operation, 1, *layer, 1) &&
1984 SetupAndTrackLayerOutputSlot(operation, 2, *layer, 2) &&
1985 SetupAndTrackLayerOutputSlot(operation, 3, *layer, 3));
1986}
1987
kevmay01bc5f7842018-08-30 12:34:39 +01001988template<typename HalVersion>
1989bool ModelToINetworkConverter<HalVersion>::ConvertToActivation(const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00001990 const char* operationName,
1991 const armnn::ActivationDescriptor& activationDesc)
1992{
1993 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1994 if (!input.IsValid())
1995 {
1996 return Fail("%s: Input 0 is invalid", operationName);
1997 }
1998
telsoa01ce3e84a2018-08-31 09:31:35 +01001999 const Operand* outputOperand = GetOutputOperand(operation, 0);
2000 if (!outputOperand)
2001 {
2002 return false;
2003 }
2004 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
telsoa015307bc12018-03-09 13:51:08 +00002005 if (!IsLayerSupported(__func__,
2006 armnn::IsActivationSupported,
2007 m_Compute,
2008 input.GetTensorInfo(),
telsoa01ce3e84a2018-08-31 09:31:35 +01002009 outInfo,
telsoa015307bc12018-03-09 13:51:08 +00002010 activationDesc))
2011 {
2012 return false;
2013 }
2014
2015 armnn::IConnectableLayer* layer = m_Network->AddActivationLayer(activationDesc);
2016 assert(layer != nullptr);
2017 input.Connect(layer->GetInputSlot(0));
2018
2019 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
2020}
2021
kevmay01bc5f7842018-08-30 12:34:39 +01002022template<typename HalVersion>
2023bool ModelToINetworkConverter<HalVersion>::ConvertPooling2d(const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00002024 const char* operationName,
2025 armnn::PoolingAlgorithm poolType)
2026{
2027 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
2028 if (!input.IsValid())
2029 {
2030 return Fail("%s: Could not read input 0", operationName);
2031 }
2032
2033 const Operand* output = GetOutputOperand(operation, 0);
2034 if (!output)
2035 {
2036 return Fail("%s: Could not read output 0", __func__);
2037 }
2038
2039 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
2040 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
2041
2042 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
2043 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
2044
2045 armnn::Pooling2dDescriptor desc;
2046 desc.m_PoolType = poolType;
2047 desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor;
2048
2049 ActivationFn activation;
2050
2051 if (operation.inputs.size() == 7)
2052 {
2053 // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
2054 android::nn::PaddingScheme scheme;
2055
2056 if ( !GetInputPaddingScheme(operation, 1, scheme)
2057 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX)
2058 || !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY)
2059 || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth)
2060 || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight)
2061 || !GetInputActivationFunction(operation, 6, activation))
2062 {
2063 return Fail("%s: Operation has invalid inputs", operationName);
2064 }
2065
2066 const unsigned int inputWidth = swizzledInputInfo.GetShape()[3];
2067 const unsigned int inputHeight = swizzledInputInfo.GetShape()[2];
2068
2069 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
2070 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
2071 }
2072 else
2073 {
2074 // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
2075 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft)
2076 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight)
2077 || !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop)
2078 || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom)
2079 || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX)
2080 || !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY)
2081 || !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth)
2082 || !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight)
2083 || !GetInputActivationFunction(operation, 9, activation))
2084 {
2085 return Fail("%s: Operation has invalid inputs", operationName);
2086 }
2087 }
2088
2089 // ArmNN does not accept a pool size of 1, but the ArmNN driver is expected to cope.
2090 // This is mapped to a trivial splitter instead.
2091 armnn::IConnectableLayer* startLayer = nullptr;
2092 if (desc.m_PoolWidth != 1 || desc.m_PoolHeight != 1)
2093 {
2094 if (!IsLayerSupported(__func__,
2095 armnn::IsPooling2dSupported,
2096 m_Compute,
2097 swizzledInputInfo,
2098 swizzledOutputInfo,
2099 desc))
2100 {
2101 return false;
2102 }
2103
2104 startLayer = m_Network->AddPooling2dLayer(desc);
2105 }
2106 else
2107 {
2108 const unsigned int numDims = swizzledOutputInfo.GetNumDimensions();
2109
2110 armnn::ViewsDescriptor viewsDesc(1, numDims);
2111
2112 for (unsigned int i = 0; i < numDims; ++i)
2113 {
2114 viewsDesc.SetViewOriginCoord(0, i, 0);
2115 viewsDesc.SetViewSize(0, i, swizzledOutputInfo.GetShape()[i]);
2116 }
2117
2118 if (!IsLayerSupported(__func__,
2119 armnn::IsSplitterSupported,
2120 m_Compute,
2121 swizzledInputInfo,
2122 viewsDesc))
2123 {
2124 return false;
2125 }
2126
2127 startLayer = m_Network->AddSplitterLayer(viewsDesc);
2128 }
2129
2130 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
2131
2132 if (endLayer != nullptr)
2133 {
2134 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
2135 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
2136 }
2137 else
2138 {
2139 return Fail("%s: ProcessActivation failed", operationName);
2140 }
2141}
2142
kevmay01bc5f7842018-08-30 12:34:39 +01002143template<typename HalVersion>
2144const void* ModelToINetworkConverter<HalVersion>::GetOperandValueReadOnlyAddress(const Operand& operand) const
telsoa015307bc12018-03-09 13:51:08 +00002145{
2146 const void* valueStart = nullptr;
2147
2148 switch (operand.lifetime)
2149 {
2150 case OperandLifeTime::CONSTANT_COPY:
2151 {
2152 // Constant found in model.operandValues
2153 valueStart = &m_Model.operandValues[operand.location.offset];
2154 break;
2155 }
2156 case OperandLifeTime::CONSTANT_REFERENCE:
2157 {
2158 // Constant specified via a Memory object
2159 valueStart = GetMemoryFromPool(operand.location, m_MemPools);
2160 break;
2161 }
2162 default:
2163 {
2164 // Unsupported/invalid (e.g. can't get value of an input to the model)
2165 Fail("%s: unsupported/invalid operand lifetime: %s",
2166 __func__, toString(operand.lifetime).c_str());
2167 valueStart = nullptr;
2168 }
2169 }
2170
2171 return valueStart;
2172}
2173
kevmay01bc5f7842018-08-30 12:34:39 +01002174template<typename HalVersion>
2175const Operand* ModelToINetworkConverter<HalVersion>::GetInputOperand(const neuralnetworks::V1_0::Operation& operation,
2176 uint32_t inputIndex) const
telsoa015307bc12018-03-09 13:51:08 +00002177{
2178 if (inputIndex >= operation.inputs.size())
2179 {
2180 Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
2181 return nullptr;
2182 }
2183
2184 assert(operation.inputs[inputIndex] < m_Model.operands.size()); // Model should have been validated beforehand
2185 return &m_Model.operands[operation.inputs[inputIndex]];
2186}
2187
kevmay01bc5f7842018-08-30 12:34:39 +01002188template<typename HalVersion>
2189const Operand* ModelToINetworkConverter<HalVersion>::GetOutputOperand(const neuralnetworks::V1_0::Operation& operation,
2190 uint32_t outputIndex) const
telsoa015307bc12018-03-09 13:51:08 +00002191{
2192 if (outputIndex >= operation.outputs.size())
2193 {
2194 Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
2195 return nullptr;
2196 }
2197
2198 assert(operation.outputs[outputIndex] < m_Model.operands.size()); // Model should have been validated beforehand
2199 return &m_Model.operands[operation.outputs[outputIndex]];
2200}
2201
kevmay01bc5f7842018-08-30 12:34:39 +01002202template<typename HalVersion>
telsoa015307bc12018-03-09 13:51:08 +00002203template<typename T>
kevmay01bc5f7842018-08-30 12:34:39 +01002204bool ModelToINetworkConverter<HalVersion>::GetInputScalar(const neuralnetworks::V1_0::Operation& operation,
2205 uint32_t inputIndex,
telsoa015307bc12018-03-09 13:51:08 +00002206 OperandType type, T& outValue) const
2207{
2208 const Operand* operand = GetInputOperand(operation, inputIndex);
2209 if (!operand)
2210 {
2211 return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
2212 }
2213
2214 if (operand->type != type)
2215 {
2216 return Fail("%s: unexpected operand type: %s (should be %s)",
2217 __func__, toString(operand->type).c_str(), toString(type).c_str());
2218 }
2219
2220 if (operand->location.length != sizeof(T))
2221 {
2222 return Fail("%s: incorrect operand location length: %i (should be %i)",
2223 __func__, operand->location.length, sizeof(T));
2224 }
2225
2226 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand);
2227 if (!valueAddress)
2228 {
2229 return Fail("%s: failed to get address for operand", __func__);
2230 }
2231
2232 outValue = *(static_cast<const T*>(valueAddress));
2233 return true;
2234}
2235
kevmay01bc5f7842018-08-30 12:34:39 +01002236template<typename HalVersion>
2237bool ModelToINetworkConverter<HalVersion>::GetInputInt32(const neuralnetworks::V1_0::Operation& operation,
surmeh01deb3bdb2018-07-05 12:06:04 +01002238 uint32_t inputIndex, int32_t& outValue) const
telsoa015307bc12018-03-09 13:51:08 +00002239{
2240 return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue);
2241}
2242
kevmay01bc5f7842018-08-30 12:34:39 +01002243template<typename HalVersion>
2244bool ModelToINetworkConverter<HalVersion>::GetInputFloat32(const neuralnetworks::V1_0::Operation& operation,
surmeh01deb3bdb2018-07-05 12:06:04 +01002245 uint32_t inputIndex, float& outValue) const
telsoa015307bc12018-03-09 13:51:08 +00002246{
2247 return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue);
2248}
2249
kevmay01bc5f7842018-08-30 12:34:39 +01002250template<typename HalVersion>
2251bool ModelToINetworkConverter<HalVersion>::GetInputActivationFunctionImpl(
2252 const neuralnetworks::V1_0::Operation& operation,
2253 uint32_t inputIndex,
2254 OperandType type,
2255 ActivationFn& outActivationFunction) const
telsoa015307bc12018-03-09 13:51:08 +00002256{
telsoa01ce3e84a2018-08-31 09:31:35 +01002257 if (type != OperandType::INT32 && type != OperandType::TENSOR_INT32)
2258 {
2259 return Fail("%s: unexpected operand type: %s (should be %s or %s)",
2260 __func__,
2261 toString(type).c_str(),
2262 toString(OperandType::INT32).c_str(),
2263 toString(OperandType::TENSOR_INT32).c_str());
2264 }
2265
telsoa015307bc12018-03-09 13:51:08 +00002266 int32_t activationFunctionAsInt;
telsoa01ce3e84a2018-08-31 09:31:35 +01002267 if (!GetInputScalar(operation, inputIndex, type, activationFunctionAsInt))
telsoa015307bc12018-03-09 13:51:08 +00002268 {
2269 return Fail("%s: failed to get activation input value", __func__);
2270 }
telsoa015307bc12018-03-09 13:51:08 +00002271 outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
2272 return true;
2273}
2274
kevmay01bc5f7842018-08-30 12:34:39 +01002275template<typename HalVersion>
2276bool ModelToINetworkConverter<HalVersion>::GetInputActivationFunction(
2277 const neuralnetworks::V1_0::Operation& operation,
2278 uint32_t inputIndex,
2279 ActivationFn& outActivationFunction) const
telsoa01ce3e84a2018-08-31 09:31:35 +01002280{
2281 return GetInputActivationFunctionImpl(operation, inputIndex, OperandType::INT32, outActivationFunction);
2282}
2283
kevmay01bc5f7842018-08-30 12:34:39 +01002284template<typename HalVersion>
2285bool ModelToINetworkConverter<HalVersion>::GetInputActivationFunctionFromTensor(
2286 const neuralnetworks::V1_0::Operation& operation,
2287 uint32_t inputIndex,
2288 ActivationFn& outActivationFunction) const
telsoa01ce3e84a2018-08-31 09:31:35 +01002289{
2290 // This only accepts a 1-D tensor of size 1
2291 return GetInputActivationFunctionImpl(operation, inputIndex, OperandType::INT32, outActivationFunction);
2292}
2293
kevmay01bc5f7842018-08-30 12:34:39 +01002294template<typename HalVersion>
2295bool ModelToINetworkConverter<HalVersion>::GetOptionalInputActivation(const neuralnetworks::V1_0::Operation& operation,
2296 uint32_t inputIndex,
2297 ActivationFn& activationFunction) const
telsoa01ce3e84a2018-08-31 09:31:35 +01002298{
2299 if (operation.inputs.size() <= inputIndex)
2300 {
2301 activationFunction = ActivationFn::kActivationNone;
2302 }
2303 else
2304 {
2305 if (!GetInputActivationFunction(operation, inputIndex, activationFunction))
2306 {
2307 return Fail("%s: Operation has invalid inputs", __func__);
2308 }
2309 }
2310 return true;
2311}
2312
kevmay01bc5f7842018-08-30 12:34:39 +01002313template<typename HalVersion>
2314bool ModelToINetworkConverter<HalVersion>::GetInputPaddingScheme(const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00002315 uint32_t inputIndex,
2316 android::nn::PaddingScheme& outPaddingScheme) const
2317{
2318 int32_t paddingSchemeAsInt;
2319 if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt))
2320 {
2321 return Fail("%s: failed to get padding scheme input value", __func__);
2322 }
2323
2324 outPaddingScheme = static_cast<android::nn::PaddingScheme>(paddingSchemeAsInt);
2325 return true;
2326}
2327
kevmay01bc5f7842018-08-30 12:34:39 +01002328template<typename HalVersion>
2329LayerInputHandle ModelToINetworkConverter<HalVersion>::ConvertToLayerInputHandle(
telsoa01ce3e84a2018-08-31 09:31:35 +01002330 const neuralnetworks::V1_0::Operation& operation,
telsoa015307bc12018-03-09 13:51:08 +00002331 uint32_t inputIndex)
2332{
2333 const Operand* operand = GetInputOperand(operation, inputIndex);
2334 if (!operand)
2335 {
2336 Fail("%s: failed to get input operand %i", __func__, inputIndex);
2337 return LayerInputHandle();
2338 }
2339
2340 if (!IsOperandTypeSupportedForTensors(operand->type))
2341 {
2342 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand->type).c_str());
2343 return LayerInputHandle();
2344 }
2345
2346 armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
2347
2348 switch (operand->lifetime)
2349 {
2350 case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
2351 case OperandLifeTime::MODEL_INPUT:
2352 {
2353 // The tensor is either an operand internal to the model, or a model input.
2354 // It can be associated with an ArmNN output slot for an existing layer.
2355
2356 // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
2357 const uint32_t operandIndex = operation.inputs[inputIndex];
2358 return LayerInputHandle(true, m_OutputSlotForOperand[operandIndex], operandTensorInfo);
2359 break;
2360 }
2361 case OperandLifeTime::CONSTANT_COPY:
2362 case OperandLifeTime::CONSTANT_REFERENCE:
2363 {
2364 // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
2365 ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand);
2366 if (tensorPin.IsValid())
2367 {
2368 if (!IsLayerSupported(__func__,
2369 armnn::IsConstantSupported,
2370 m_Compute,
2371 tensorPin.GetConstTensor().GetInfo()))
2372 {
2373 return LayerInputHandle();
2374 }
2375
2376 armnn::IConnectableLayer* constantLayer = m_Network->AddConstantLayer(tensorPin.GetConstTensor());
2377 armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
2378 outputSlot.SetTensorInfo(tensorPin.GetConstTensor().GetInfo());
2379
2380 return LayerInputHandle(true, &outputSlot, operandTensorInfo);
2381 }
2382 else
2383 {
2384 Fail("%s: invalid operand tensor", __func__);
2385 return LayerInputHandle();
2386 }
2387 break;
2388 }
2389 default:
2390 {
2391 // Unsupported lifetime for an input tensor
2392 Fail("%s: unsupported lifetime for input tensor: %s",
2393 __func__, toString(operand->lifetime).c_str());
2394 return LayerInputHandle();
2395 }
2396 }
2397}
2398
kevmay01bc5f7842018-08-30 12:34:39 +01002399template<typename HalVersion>
2400ConstTensorPin ModelToINetworkConverter<HalVersion>::ConvertOperationInputToConstTensorPin(
telsoa01ce3e84a2018-08-31 09:31:35 +01002401 const neuralnetworks::V1_0::Operation& operation,
2402 uint32_t inputIndex, const armnn::PermutationVector& dimensionMappings,
2403 const armnn::TensorShape* overrideTensorShape, bool optional)
telsoa015307bc12018-03-09 13:51:08 +00002404{
2405 const Operand* operand = GetInputOperand(operation, inputIndex);
2406 if (!operand)
2407 {
telsoa01ce3e84a2018-08-31 09:31:35 +01002408 Fail("%s: failed to get input operand: index=%u", __func__, inputIndex);
telsoa015307bc12018-03-09 13:51:08 +00002409 return ConstTensorPin();
2410 }
telsoa01ce3e84a2018-08-31 09:31:35 +01002411 return ConvertOperandToConstTensorPin(*operand, dimensionMappings, overrideTensorShape, optional);
telsoa015307bc12018-03-09 13:51:08 +00002412}
2413
kevmay01bc5f7842018-08-30 12:34:39 +01002414template<typename HalVersion>
2415ConstTensorPin ModelToINetworkConverter<HalVersion>::ConvertOperandToConstTensorPin(const Operand& operand,
telsoa01ce3e84a2018-08-31 09:31:35 +01002416 const armnn::PermutationVector& dimensionMappings, const armnn::TensorShape* overrideTensorShape, bool optional)
telsoa015307bc12018-03-09 13:51:08 +00002417{
2418 if (!IsOperandTypeSupportedForTensors(operand.type))
2419 {
2420 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand.type).c_str());
2421 return ConstTensorPin();
2422 }
2423
2424 if (operand.lifetime != OperandLifeTime::CONSTANT_COPY && operand.lifetime != OperandLifeTime::CONSTANT_REFERENCE)
2425 {
2426 Fail("%s: invalid operand lifetime: %s", __func__, toString(operand.lifetime).c_str());
2427 return ConstTensorPin();
2428 }
2429
2430 const void* const valueStart = GetOperandValueReadOnlyAddress(operand);
2431 if (!valueStart)
2432 {
telsoa01ce3e84a2018-08-31 09:31:35 +01002433 if (optional)
2434 {
2435 // optional tensor with no values is not really an error; return it as invalid, but marked as optional
2436 return ConstTensorPin(true);
2437 }
2438 // mandatory tensor with no values
telsoa015307bc12018-03-09 13:51:08 +00002439 Fail("%s: failed to get operand address", __func__);
2440 return ConstTensorPin();
2441 }
2442
2443 armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
2444 if (overrideTensorShape != nullptr)
2445 {
2446 tensorInfo.SetShape(*overrideTensorShape);
2447 }
2448 return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
2449}
2450
kevmay01bc5f7842018-08-30 12:34:39 +01002451template<typename HalVersion>
2452bool ModelToINetworkConverter<HalVersion>::GetTensorInt32Values(const Operand& operand,
2453 std::vector<int32_t>& outValues) const
telsoa015307bc12018-03-09 13:51:08 +00002454{
2455 if (operand.type != OperandType::TENSOR_INT32)
2456 {
2457 return Fail("%s: invalid operand type: %s", __func__, toString(operand.type).c_str());
2458 }
2459
2460 const void* startAddress = GetOperandValueReadOnlyAddress(operand);
2461 if (!startAddress)
2462 {
2463 return Fail("%s: failed to get operand address", __func__, operand.type);
2464 }
2465
2466 // Check number of bytes is sensible
2467 const uint32_t numBytes = operand.location.length;
2468 if (numBytes % sizeof(int32_t) != 0)
2469 {
2470 return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
2471 __func__, numBytes, sizeof(int32_t));
2472 }
2473
2474 outValues.resize(numBytes / sizeof(int32_t));
2475 memcpy(outValues.data(), startAddress, numBytes);
2476 return true;
2477}
2478
2479// Creates an ArmNN activation layer and connects it to the given layer, if the
2480// passed in AndroidNN activation function requires so.
2481// @return The end layer of the sequence of layers built for the given AndroidNN
2482// activation function or nullptr if an error occurred (e.g. unsupported activation).
2483// Note that the end layer matches the input layer if no activation is required
2484// (the sequence of layers has length 1).
kevmay01bc5f7842018-08-30 12:34:39 +01002485template<typename HalVersion>
2486armnn::IConnectableLayer* ModelToINetworkConverter<HalVersion>::ProcessActivation(const armnn::TensorInfo& tensorInfo,
telsoa015307bc12018-03-09 13:51:08 +00002487 ActivationFn activation, armnn::IConnectableLayer* prevLayer)
2488{
2489 assert(prevLayer->GetNumOutputSlots() == 1);
2490
2491 prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2492
2493 armnn::IConnectableLayer* activationLayer = prevLayer;
2494
2495 if (activation != ActivationFn::kActivationNone)
2496 {
2497 armnn::ActivationDescriptor activationDesc;
2498 switch (activation)
2499 {
2500 case ActivationFn::kActivationRelu:
2501 {
2502 activationDesc.m_Function = armnn::ActivationFunction::ReLu;
2503 break;
2504 }
2505 case ActivationFn::kActivationRelu1:
2506 {
2507 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
2508 activationDesc.m_A = 1.0f;
2509 activationDesc.m_B = -1.0f;
2510 break;
2511 }
2512 case ActivationFn::kActivationRelu6:
2513 {
2514 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
2515 activationDesc.m_A = 6.0f;
2516 break;
2517 }
2518 case ActivationFn::kActivationSigmoid:
2519 {
2520 activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
2521 break;
2522 }
2523 case ActivationFn::kActivationTanh:
2524 {
2525 activationDesc.m_Function = armnn::ActivationFunction::TanH;
2526 activationDesc.m_A = 1.0f;
2527 activationDesc.m_B = 1.0f;
2528 break;
2529 }
2530 default:
2531 {
2532 Fail("%s: Invalid activation enum value %i", __func__, activation);
2533 return nullptr;
2534 }
2535 }
2536
2537 if (!IsLayerSupported(__func__, armnn::IsActivationSupported, m_Compute,
telsoa01ce3e84a2018-08-31 09:31:35 +01002538 prevLayer->GetOutputSlot(0).GetTensorInfo(), tensorInfo, activationDesc))
telsoa015307bc12018-03-09 13:51:08 +00002539 {
2540 return nullptr;
2541 }
2542
2543 activationLayer = m_Network->AddActivationLayer(activationDesc);
2544
2545 prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0));
2546 activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2547 }
2548
2549 return activationLayer;
2550}
2551
kevmay01bc5f7842018-08-30 12:34:39 +01002552template<typename HalVersion>
2553bool ModelToINetworkConverter<HalVersion>::SetupAndTrackLayerOutputSlot(
2554 const neuralnetworks::V1_0::Operation& operation,
2555 uint32_t operationOutputIndex,
2556 armnn::IConnectableLayer& layer,
2557 uint32_t layerOutputIndex)
telsoa015307bc12018-03-09 13:51:08 +00002558{
telsoa01ce3e84a2018-08-31 09:31:35 +01002559 const Operand* outputOperand = GetOutputOperand(operation, operationOutputIndex);
telsoa015307bc12018-03-09 13:51:08 +00002560
telsoa01ce3e84a2018-08-31 09:31:35 +01002561 if ((outputOperand == nullptr) || (operationOutputIndex >= layer.GetNumOutputSlots()))
telsoa015307bc12018-03-09 13:51:08 +00002562 {
2563 return false;
2564 }
2565
telsoa01ce3e84a2018-08-31 09:31:35 +01002566 armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(layerOutputIndex);
telsoa015307bc12018-03-09 13:51:08 +00002567
telsoa01ce3e84a2018-08-31 09:31:35 +01002568 const uint32_t operandIndex = operation.outputs[operationOutputIndex];
telsoa015307bc12018-03-09 13:51:08 +00002569 m_OutputSlotForOperand[operandIndex] = &outputSlot;
2570
2571 outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
2572
2573 return true;
2574}
2575
kevmay01bc5f7842018-08-30 12:34:39 +01002576template<typename HalVersion>
2577bool ModelToINetworkConverter<HalVersion>::SetupAndTrackLayerOutputSlot(
2578 const neuralnetworks::V1_0::Operation& operation,
2579 uint32_t outputIndex,
2580 armnn::IConnectableLayer& layer)
telsoa01ce3e84a2018-08-31 09:31:35 +01002581{
2582 return SetupAndTrackLayerOutputSlot(operation, outputIndex, layer, outputIndex);
2583}
2584
kevmay01bc5f7842018-08-30 12:34:39 +01002585template<typename HalVersion>
2586bool ModelToINetworkConverter<HalVersion>::IsOperationSupported(uint32_t operationIndex) const
telsoa015307bc12018-03-09 13:51:08 +00002587{
2588 std::map<uint32_t, bool>::const_iterator it = m_OperationSupported.find(operationIndex);
2589 assert(it != m_OperationSupported.end());
2590 return it->second;
2591}
2592
kevmay01bc5f7842018-08-30 12:34:39 +01002593template class ModelToINetworkConverter<HalVersion_1_0>;
telsoa015307bc12018-03-09 13:51:08 +00002594
kevmay01bc5f7842018-08-30 12:34:39 +01002595#if defined(ARMNN_ANDROID_NN_V1_1)
2596template class ModelToINetworkConverter<HalVersion_1_1>;
2597#endif
2598
2599} // armnn_driver