blob: 68ebef001dd93fbff546dcd6fe0d8b1a9a70a02b [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"
9#include "OperationsUtils.h"
10
11#include <armnn/LayerSupport.hpp>
12#include <Permute.hpp>
13
14#include <log/log.h>
15#include <cassert>
16
17#include <boost/format.hpp>
18#include <boost/core/ignore_unused.hpp>
19#include <boost/test/tools/floating_point_comparison.hpp>
20#include <boost/cast.hpp>
21
22namespace
23{
24using namespace armnn_driver;
25using namespace android::nn;
26
27// Convenience function to log the reason for failing to convert a model.
28// @return Always returns false (so that it can be used by callers as a quick way to signal an error and return)
29template<class... Args>
30static bool Fail(const char* formatStr, Args&&... args)
31{
32 ALOGD(formatStr, std::forward<Args>(args)...);
33 return false;
34}
35
36// Convenience function to call an Is*Supported function and log caller name together with reason for lack of support.
37// Called as: IsLayerSupported(__func__, Is*Supported, a, b, c, d, e)
38template<typename IsLayerSupportedFunc, typename ... Args>
39bool IsLayerSupported(const char* funcName, IsLayerSupportedFunc f, Args&&... args)
40{
41 std::vector<char> unsupportedReason(1024+1);
42 bool isSupported = f(std::forward<Args>(args)..., unsupportedReason.data(), unsupportedReason.size()-1);
43 if(isSupported)
44 {
45 return true;
46 }
47 else
48 {
49 std::string sUnsupportedReason(unsupportedReason.data());
50 if (sUnsupportedReason.size() > 0)
51 {
52 ALOGD("%s: not supported by armnn: %s", funcName, sUnsupportedReason.c_str());
53 } else
54 {
55 ALOGD("%s: not supported by armnn", funcName);
56 }
57 return false;
58 }
59}
60
61armnn::TensorShape GetTensorShapeForOperand(const Operand& operand)
62{
63 return armnn::TensorShape(operand.dimensions.size(), operand.dimensions.data());
64}
65
66inline bool IsOperandTypeSupportedForTensors(OperandType type)
67{
68 return type == OperandType::TENSOR_FLOAT32 ||
69 type == OperandType::TENSOR_QUANT8_ASYMM ||
70 type == OperandType::TENSOR_INT32;
71}
72
73void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
74 android::nn::PaddingScheme scheme)
75{
76 int32_t padHead;
77 int32_t padTail;
78 calculateExplicitPadding(input, stride, kernel, scheme, &padHead, &padTail);
79 outPadHead = boost::numeric_cast<uint32_t>(padHead);
80 outPadTail = boost::numeric_cast<uint32_t>(padTail);
81}
82
83bool ValidateBroadcast(const Model& model, const Operation& operation, uint32_t numInputs)
84{
85 assert(operation.inputs.size() > 0); // This should have been validated by the caller
86 // validateModel() has been called already so we know the operation.inputs indexes are valid within model.operands.
87 const Operand& firstInput = model.operands[operation.inputs[0]];
88
89 // We don't support broadcasting yet - we require all input operands to have the same shape
90 for (uint32_t i = 1; i < numInputs; ++i)
91 {
92 const Operand& otherInput = model.operands[operation.inputs[i]];
93
94 if (firstInput.dimensions.size() != otherInput.dimensions.size())
95 {
96 return Fail("%s: Broadcasting not supported (Input 0 dims: %i Input %i dims: %i)",
97 __func__, firstInput.dimensions.size(), i, otherInput.dimensions.size());
98 }
99
100 for (unsigned int d = 0; d < firstInput.dimensions.size(); ++d)
101 {
102 if (firstInput.dimensions[d] != otherInput.dimensions[d])
103 {
104 return Fail("%s: Broadcasting not supported (Dimension %i size mismatch. "
105 "Input 0: %i Input %i: %i)",
106 __func__, d, firstInput.dimensions[d], i, otherInput.dimensions[d]);
107 }
108 }
109 }
110
111 return true;
112}
113
114Shape GetOperandShape(const Operand& operand)
115{
116 Shape shape;
117 shape.type = operand.type;
118 shape.dimensions = operand.dimensions;
119 shape.scale = operand.scale;
120 shape.offset = operand.zeroPoint;
121 return shape;
122}
123
124// ArmNN requires the bias scale to be equal to the product of the weight and input scales, which is also
125// what AndroidNN requires. However for some of the AndroidNN tests the values don't exactly match so
126// we accept some tolerance. We don't want to ArmNN itself to accept these inconsistencies as it is up to the user
127// (us, in this case) to ensure they match.
128void SanitizeBiasQuantizationScale(armnn::TensorInfo& biasInfo,
129 const armnn::TensorInfo& weightInfo, const armnn::TensorInfo& inputInfo)
130{
131 const float expectedBiasScale = weightInfo.GetQuantizationScale() * inputInfo.GetQuantizationScale();
132 if (biasInfo.GetQuantizationScale() != expectedBiasScale)
133 {
134 boost::math::fpc::close_at_tolerance<float> comparer(boost::math::fpc::percent_tolerance(1.0f));
135 if (comparer(biasInfo.GetQuantizationScale(), expectedBiasScale))
136 {
137 ALOGW("Bias quantization scale has been modified to match input*weights");
138 biasInfo.SetQuantizationScale(expectedBiasScale);
139 }
140 }
141}
142
143const armnn::PermutationVector NHWCToArmNN({ 0U, 2U, 3U, 1U });
144
145template <typename OSlot>
146armnn::IConnectableLayer& AddPermuteLayer(armnn::INetwork& network, OSlot& input,
147 const armnn::PermutationVector& mappings)
148{
149 // Add swizzle layer
150 armnn::IConnectableLayer* const layer = network.AddPermuteLayer(mappings);
151
152 assert(layer != nullptr);
153
154 // Connect intput to swizzle layer
155 input.Connect(layer->GetInputSlot(0));
156
157 // Setup swizzled output
158 const armnn::TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mappings);
159 layer->GetOutputSlot(0).SetTensorInfo(outInfo);
160
161 return *layer;
162}
163
164armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input,
165 armnn::IConnectableLayer& firstLayer,
166 armnn::IConnectableLayer& lastLayer)
167{
168 static const armnn::PermutationVector ArmNNToNHWC({ 0U, 3U, 1U, 2U });
169
170 // Add swizzle layer
171 armnn::IConnectableLayer& swizzleLayer = AddPermuteLayer(network, input, NHWCToArmNN);
172
173 // Connect swizzled input to layer
174 swizzleLayer.GetOutputSlot(0).Connect(firstLayer.GetInputSlot(0));
175
176 // Add deswizzle layer
177 armnn::IConnectableLayer& deswizzleLayer = AddPermuteLayer(network, lastLayer.GetOutputSlot(0), ArmNNToNHWC);
178
179 return deswizzleLayer;
180}
181
182armnn::IConnectableLayer& SwizzleInDeswizzleOut(armnn::INetwork& network, LayerInputHandle& input,
183 armnn::IConnectableLayer& layer)
184{
185 return SwizzleInDeswizzleOut(network, input, layer, layer);
186}
187} // namespace
188
189namespace armnn_driver
190{
191
192class ConstTensorPin
193{
194public:
195 // Creates an invalid tensor pin (can be used to signal errors)
196 ConstTensorPin() {}
197
198 // @param tensorInfo TensorInfo associated with the tensor.
199 // @param valueStart Start address of tensor data. Belongs to one of the memory pools associated with
200 // the model being converted.
201 // @param numBytes Number of bytes for the tensor data.
202 ConstTensorPin(const armnn::TensorInfo& tensorInfo, const void* valueStart, uint32_t numBytes,
203 const armnn::PermutationVector& mappings)
204 {
205 boost::ignore_unused(numBytes);
206 assert(tensorInfo.GetNumBytes() == numBytes);
207
208 const bool needsSwizzling = (mappings.GetSize() > 0);
209 if (needsSwizzling)
210 {
211 m_SwizzledTensorData.resize(tensorInfo.GetNumBytes());
212 SwizzleAndroidNn4dTensorToArmNn(tensorInfo, valueStart, m_SwizzledTensorData.data(), mappings);
213
214 m_ConstTensor = armnn::ConstTensor(armnnUtils::Permuted(tensorInfo, mappings), m_SwizzledTensorData.data());
215 }
216 else
217 {
218 m_ConstTensor = armnn::ConstTensor(tensorInfo, valueStart);
219 }
220 }
221
222 ConstTensorPin(const ConstTensorPin& other) = delete;
223 ConstTensorPin(ConstTensorPin&& other) = default;
224
225 bool IsValid() const { return m_ConstTensor.GetMemoryArea() != nullptr; }
226 const armnn::ConstTensor& GetConstTensor() const { return m_ConstTensor; }
227
228private:
229 armnn::ConstTensor m_ConstTensor;
230 // Owned memory for swizzled tensor data, only required if the tensor needed
231 // swizzling. Otherwise, @ref m_ConstTensor will reference memory from one of
232 // the pools associated with the model being converted.
233 std::vector<uint8_t> m_SwizzledTensorData;
234};
235
236ModelToINetworkConverter::ModelToINetworkConverter(armnn::Compute compute, const Model& model,
237 const std::set<unsigned int>& forcedUnsupportedOperations)
238 : m_Compute(compute)
239 , m_Model(model)
240 , m_ForcedUnsupportedOperations(forcedUnsupportedOperations)
241 , m_Network(nullptr, nullptr)
242 , m_ConversionResult(ConversionResult::Success)
243{
244 try
245 {
246 Convert();
247 }
248 catch (armnn::Exception& e)
249 {
250 m_ConversionResult = ConversionResult::UnsupportedFeature;
251 ALOGE("%s: Unexpected exception: %s", __func__, e.what());
252 assert(false);
253 }
254}
255
256void ModelToINetworkConverter::Convert()
257{
258 ALOGV("ModelToINetworkConverter::Convert(): %s", GetModelSummary(m_Model).c_str());
259
260 // map the memory pool into shared pointers
261 m_MemPools.clear();
262 if (!setRunTimePoolInfosFromHidlMemories(&m_MemPools, m_Model.pools))
263 {
264 Fail("%s: Setting of run time pool infos from Hidl Memories has failed.", __func__);
265 m_ConversionResult = ConversionResult::ErrorMappingPools;
266 return;
267 }
268
269 uint32_t totalPoolSize = 0;
270 for (auto&& pool : m_Model.pools)
271 {
272 totalPoolSize += pool.size();
273 }
274
275 // Create armnn::INetwork
276 m_Network = armnn::INetwork::Create();
277
278 // add operations to it
279 // track which layer outputs each operand
280 m_OutputSlotForOperand = std::vector<armnn::IOutputSlot*>(m_Model.operands.size(), nullptr);
281
282 try
283 {
284 for (uint32_t i = 0; i < m_Model.inputIndexes.size(); i++)
285 {
286 // inputs in android nn are represented by operands
287 uint32_t inputIndex = m_Model.inputIndexes[i];
288 const Operand& operand = m_Model.operands[inputIndex];
289 const armnn::TensorInfo& tensor = GetTensorInfoForOperand(operand);
290 armnn::IConnectableLayer* layer = m_Network->AddInputLayer(i);
291
292 armnn::IOutputSlot& outputSlot = layer->GetOutputSlot(0);
293 outputSlot.SetTensorInfo(GetTensorInfoForOperand(operand));
294
295 // store for later layers
296 m_OutputSlotForOperand[inputIndex] = &outputSlot;
297 }
298 }
299 catch (UnsupportedOperand& e)
300 {
301 Fail("%s: Operand type %s not supported in ArmnnDriver", __func__, toString(e.m_type).c_str());
302 m_ConversionResult = ConversionResult::UnsupportedFeature;
303 }
304 catch (const armnn::InvalidArgumentException& e)
305 {
306 Fail("%s: Failed to convert input operand to TensorShape: %s", __func__, e.what());
307 m_ConversionResult = ConversionResult::UnsupportedFeature;
308 }
309
310 for (uint32_t operationIdx = 0; operationIdx < m_Model.operations.size(); operationIdx++)
311 {
312 const auto& operation = m_Model.operations[operationIdx];
313
314 bool ok = true;
315 if (m_ForcedUnsupportedOperations.find(operationIdx) != m_ForcedUnsupportedOperations.end())
316 {
317 Fail("%s: Operation at index %i has been forced to be unsupported.", __func__, operationIdx);
318 ok = false;
319 }
320
321 if (ok)
322 {
323 try
324 {
325 ok = ConvertOperation(operation);
326 }
327 catch (UnsupportedOperand& e)
328 {
329 Fail("%s: Operand type %s not supported in ArmnnDriver", __func__, toString(e.m_type).c_str());
330 ok = false;
331 }
332 catch (const armnn::InvalidArgumentException& e)
333 {
334 Fail("%s: Failed to convert operation in %s", __func__, e.what());
335 ok = false;
336 }
337 }
338
339 // Store whether this operation was successfully converted.
340 m_OperationSupported.emplace(operationIdx, ok);
341
342 // Any single operation failing will fail the entire conversion.
343 // We still need to continue and check the other ones.
344 if (!ok)
345 {
346 m_ConversionResult = ConversionResult::UnsupportedFeature;
347 }
348 }
349 try
350 {
351 if (m_ConversionResult == ConversionResult::Success)
352 {
353 for (uint32_t i = 0; i < m_Model.outputIndexes.size(); i++)
354 {
355 // outputs in android nn are represented by operands
356 uint32_t outputIndex = m_Model.outputIndexes[i];
357 const Operand& operand = m_Model.operands[outputIndex];
358 const armnn::TensorInfo& tensor = GetTensorInfoForOperand(operand);
359 armnn::IConnectableLayer* layer = m_Network->AddOutputLayer(i);
360
361 assert(m_OutputSlotForOperand[outputIndex]);
362 m_OutputSlotForOperand[outputIndex]->Connect(layer->GetInputSlot(0));
363 }
364 }
365 }
366 catch (const armnn::InvalidArgumentException& e)
367 {
368 Fail("%s: Failed to convert output operand to TensorShape: %s", __func__, e.what());
369 m_ConversionResult = ConversionResult::UnsupportedFeature;
370 }
371}
372
373bool ModelToINetworkConverter::ConvertOperation(const Operation& operation)
374{
375 switch (operation.type)
376 {
377 case OperationType::ADD: return ConvertAdd(operation);
378 case OperationType::AVERAGE_POOL_2D: return ConvertAveragePool2d(operation);
379 case OperationType::CONCATENATION: return ConvertConcatenation(operation);
380 case OperationType::CONV_2D: return ConvertConv2d(operation);
381 case OperationType::DEPTHWISE_CONV_2D: return ConvertDepthwiseConv2d(operation);
382 case OperationType::FLOOR: return ConvertFloor(operation);
383 case OperationType::FULLY_CONNECTED: return ConvertFullyConnected(operation);
384 case OperationType::LOCAL_RESPONSE_NORMALIZATION: return ConvertLocalResponseNormalization(operation);
385 case OperationType::LOGISTIC: return ConvertLogistic(operation);
386 case OperationType::L2_NORMALIZATION: return ConvertL2Normalization(operation);
387 case OperationType::L2_POOL_2D: return ConvertL2Pool2d(operation);
388 case OperationType::MAX_POOL_2D: return ConvertMaxPool2d(operation);
389 case OperationType::MUL: return ConvertMul(operation);
390 case OperationType::RELU: return ConvertReLu(operation);
391 case OperationType::RELU1: return ConvertReLu1(operation);
392 case OperationType::RELU6: return ConvertReLu6(operation);
393 case OperationType::SOFTMAX: return ConvertSoftmax(operation);
394 case OperationType::TANH: return ConvertTanH(operation);
395 case OperationType::RESHAPE: return ConvertReshape(operation);
396 case OperationType::RESIZE_BILINEAR: return ConvertResizeBilinear(operation);
397 default: return Fail("%s: Operation type %s not supported in ArmnnDriver",
398 __func__, toString(operation.type).c_str());
399 }
400}
401
402class LayerInputHandle
403{
404public:
405 LayerInputHandle()
406 : m_OutputSlot(nullptr)
407 , m_Valid(false)
408 {}
409
410 LayerInputHandle(bool valid, armnn::IOutputSlot* outputSlot, armnn::TensorInfo tensorInfo)
411 : m_OutputSlot(outputSlot)
412 , m_Valid(valid)
413 , m_TensorInfo(tensorInfo)
414 {}
415
416 bool IsValid() const { return m_Valid; }
417 void Connect(armnn::IInputSlot& inputSlot)
418 {
419 assert(IsValid());
420
421 if (m_OutputSlot)
422 {
423 m_OutputSlot->Connect(inputSlot);
424 }
425 }
426 const armnn::TensorInfo& GetTensorInfo() const { return m_TensorInfo; }
427
428private:
429 armnn::IOutputSlot* m_OutputSlot;
430 bool m_Valid;
431 armnn::TensorInfo m_TensorInfo;
432};
433
434bool ModelToINetworkConverter::ConvertAdd(const Operation& operation)
435{
436 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0);
437 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1);
438
439 if (!input0.IsValid() || !input1.IsValid())
440 {
441 return Fail("%s: Operation has invalid inputs", __func__);
442 }
443
444 ActivationFn activationFunction;
445 if (!GetInputActivationFunction(operation, 2, activationFunction))
446 {
447 return Fail("%s: Operation has invalid inputs", __func__);
448 }
449
450 const Operand* outputOperand = GetOutputOperand(operation, 0);
451 if (!outputOperand)
452 {
453 return false;
454 }
455
456 const armnn::TensorInfo outInfo = GetTensorInfoForOperand(*outputOperand);
457
458 if (!IsLayerSupported(__func__,
459 armnn::IsAdditionSupported,
460 m_Compute,
461 input0.GetTensorInfo(),
462 input1.GetTensorInfo(),
463 outInfo))
464 {
465 return false;
466 }
467
468 armnn::IConnectableLayer* const startLayer = m_Network->AddAdditionLayer();
469 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer);
470
471 const armnn::TensorInfo& inputTensorInfo0 = input0.GetTensorInfo();
472 const armnn::TensorInfo& inputTensorInfo1 = input1.GetTensorInfo();
473
474 if (endLayer != nullptr)
475 {
476 // If the number of dimensions do not match then we need to add degenerate dimensions
477 // to the "smaller" tensor using a reshape:
478 // Small Big
479 // | |
480 // Reshape |
481 // \ /
482 // Add
483 if (inputTensorInfo0.GetNumDimensions() != inputTensorInfo1.GetNumDimensions())
484 {
485 bool input0IsBigger = inputTensorInfo0.GetNumDimensions() > inputTensorInfo1.GetNumDimensions();
486
487 LayerInputHandle& smallTensorHandle = input0IsBigger ? input1 : input0;
488 const armnn::TensorInfo& smallTensorDims = smallTensorHandle.GetTensorInfo();
489
490 LayerInputHandle& bigTensorHandle = input0IsBigger ? input0 : input1;
491 const armnn::TensorInfo& bigTensorDims = bigTensorHandle.GetTensorInfo();
492
493 std::vector<unsigned int> reshapedDims(bigTensorDims.GetNumDimensions(), 1);
494 unsigned int sizeDifference = bigTensorDims.GetNumDimensions() - smallTensorDims.GetNumDimensions();
495 for (unsigned i = sizeDifference; i < bigTensorDims.GetNumDimensions(); ++i)
496 {
497 reshapedDims[i] = smallTensorDims.GetShape()[i-sizeDifference];
498 }
499 armnn::TensorInfo reshapedInfo = smallTensorDims;
500 reshapedInfo.SetShape(armnn::TensorShape{ static_cast<unsigned int>(reshapedDims.size()),
501 reshapedDims.data() });
502
503 armnn::ReshapeDescriptor reshapeDesc;
504 reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
505 armnn::IConnectableLayer* const reshapeLayer = m_Network->AddReshapeLayer(reshapeDesc);
506 smallTensorHandle.Connect(reshapeLayer->GetInputSlot(0));
507 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
508
509 // Connect the outputs from new reshape and original input layer
510 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
511 bigTensorHandle.Connect(startLayer->GetInputSlot(1));
512 }
513 else
514 {
515 input0.Connect(startLayer->GetInputSlot(0));
516 input1.Connect(startLayer->GetInputSlot(1));
517 }
518
519 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
520 }
521 else
522 {
523 return Fail("%s: ProcessActivation failed", __func__);
524 }
525}
526
527bool ModelToINetworkConverter::ConvertAveragePool2d(const Operation& operation)
528{
529 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Average);
530}
531
532bool ModelToINetworkConverter::ConvertConcatenation(const Operation& operation)
533{
534 // The first N (0..N-1) inputs are tensors. The Nth input is the concatenation axis.
535 if (operation.inputs.size() <= 1)
536 {
537 return Fail("%s: Operation has insufficient arguments", __func__);
538 }
539
540 // Get inputs and outputs
541 const std::size_t numInputTensors = operation.inputs.size() - 1;
542
543 std::vector<LayerInputHandle> inputHandles;
544 std::vector<armnn::TensorShape> inputShapes;
545
546 inputHandles.reserve(numInputTensors);
547 inputShapes.reserve(numInputTensors);
548
549 for (uint32_t i = 0; i < numInputTensors; ++i)
550 {
551 const Operand* const operand = GetInputOperand(operation, i);
552 if (!operand)
553 {
554 return Fail("%s: Operation has invalid inputs", __func__);
555 }
556
557 inputShapes.emplace_back(GetTensorShapeForOperand(*operand));
558 inputHandles.emplace_back(ConvertToLayerInputHandle(operation, i));
559 if (!inputHandles.back().IsValid())
560 {
561 return Fail("%s: Operation has invalid inputs", __func__);
562 }
563 }
564
565 assert(inputShapes.size() == inputHandles.size());
566
567 uint32_t concatDim;
568 if (!GetInputScalar(operation, numInputTensors, OperandType::INT32, concatDim))
569 {
570 return Fail("%s: Operation has invalid inputs", __func__);
571 }
572
573 const Operand* const outputOperand = GetOutputOperand(operation, 0);
574 if (!outputOperand)
575 {
576 return Fail("%s: Operation has no outputs", __func__);
577 }
578 const armnn::TensorShape outputShape = GetTensorShapeForOperand(*outputOperand);
579
580 // Create an armnn merger layer descriptor - this will also perform validation on the input shapes
581 armnn::OriginsDescriptor mergerDescriptor;
582 try
583 {
584 mergerDescriptor = armnn::CreateMergerDescriptorForConcatenation(inputShapes.begin(), inputShapes.end(),
585 concatDim);
586 }
587 catch (const armnn::Exception& error)
588 {
589 return Fail("%s: Error preparing merger descriptor. %s", __func__, error.what());
590 }
591
592 // Validate the output shape is correct given the input shapes (which have just been validated)
593 unsigned int numDimensions = inputShapes[0].GetNumDimensions();
594 if (outputShape.GetNumDimensions() != numDimensions)
595 {
596 return Fail("%s: Output shape has wrong number of dimensions", __func__);
597 }
598
599 unsigned int outputSizeAlongConcatenatedDimension = 0;
600 for (unsigned int i = 0; i < inputShapes.size(); i++)
601 {
602 outputSizeAlongConcatenatedDimension += inputShapes[i][concatDim];
603 }
604
605 for (unsigned int i = 0; i < numDimensions; ++i)
606 {
607 if (i == concatDim)
608 {
609 if (outputShape[i] != outputSizeAlongConcatenatedDimension)
610 {
611 return Fail("%s: Invalid output shape", __func__);
612 }
613 }
614 else
615 {
616 if (outputShape[i] != inputShapes[0][i])
617 {
618 return Fail("%s: Invalid output shape", __func__);
619 }
620 }
621 }
622
623 std::vector<const armnn::TensorInfo*> inputTensorInfos;
624 std::transform(inputHandles.begin(), inputHandles.end(), std::back_inserter(inputTensorInfos),
625 [](const LayerInputHandle& h) -> const armnn::TensorInfo*{ return &h.GetTensorInfo(); });
626 if (!IsLayerSupported(__func__,
627 armnn::IsMergerSupported,
628 m_Compute,
629 inputTensorInfos,
630 mergerDescriptor))
631 {
632 return false;
633 }
634
635 armnn::IConnectableLayer* layer = m_Network->AddMergerLayer(mergerDescriptor);
636 assert(layer != nullptr);
637
638 // Connect inputs to the layer
639 const int numInputSlots = layer->GetNumInputSlots();
640 assert(static_cast<std::size_t>(numInputSlots) == inputHandles.size());
641 for (int i = 0; i < numInputSlots; ++i)
642 {
643 inputHandles[static_cast<unsigned int>(i)].Connect(layer->GetInputSlot(i));
644 }
645
646 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
647}
648
649bool ModelToINetworkConverter::ConvertConv2d(const Operation& operation)
650{
651 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
652 if (!input.IsValid())
653 {
654 return Fail("%s: Operation has invalid inputs", __func__);
655 }
656
657 const Operand* output = GetOutputOperand(operation, 0);
658 if (!output)
659 {
660 return Fail("%s: Could not read output 0", __func__);
661 }
662
663 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
664 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
665
666 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
667 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
668
669 // ArmNN does not currently support non-fixed weights or bias
670 const ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, NHWCToArmNN);
671 const ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2);
672
673 if (!weightsPin.IsValid() || !biasPin.IsValid())
674 {
675 return Fail("%s: Operation has invalid inputs", __func__);
676 }
677
678 armnn::ConstTensor weights = weightsPin.GetConstTensor();
679 armnn::ConstTensor bias = biasPin.GetConstTensor();
680 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), swizzledInputInfo);
681
682 armnn::Convolution2dDescriptor desc;
683 ActivationFn activation;
684
685 if (operation.inputs.size() == 10)
686 {
687 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft) ||
688 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight) ||
689 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop) ||
690 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom) ||
691 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX) ||
692 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY) ||
693 !GetInputActivationFunction(operation, 9, activation))
694 {
695 return Fail("%s: Operation has invalid inputs", __func__);
696 }
697 }
698 else if (operation.inputs.size() == 7)
699 {
700 android::nn::PaddingScheme paddingScheme;
701
702 if (!GetInputPaddingScheme(operation, 3, paddingScheme) ||
703 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX) ||
704 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY) ||
705 !GetInputActivationFunction(operation, 6, activation))
706 {
707 return Fail("%s: Operation has invalid inputs", __func__);
708 }
709
710 const uint32_t kernelX = weights.GetShape()[3];
711 const uint32_t kernelY = weights.GetShape()[2];
712 const uint32_t inputX = swizzledInputInfo.GetShape()[3];
713 const uint32_t inputY = swizzledInputInfo.GetShape()[2];
714
715 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
716 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
717 }
718 else
719 {
720 return Fail("%s: Unsupported number of operation inputs", __func__);
721 }
722
723 desc.m_BiasEnabled = true;
724
725 if (!IsLayerSupported(__func__,
726 armnn::IsConvolution2dSupported,
727 m_Compute,
728 swizzledInputInfo,
729 desc,
730 weights.GetInfo()))
731 {
732 return false;
733 }
734
735 armnn::IConnectableLayer* startLayer = m_Network->AddConvolution2dLayer(desc, weights, bias);
736 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
737
738 if (endLayer != nullptr)
739 {
740 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
741 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
742 }
743 else
744 {
745 return Fail("%s: ProcessActivation failed", __func__);
746 }
747}
748
749bool ModelToINetworkConverter::ConvertDepthwiseConv2d(const Operation& operation)
750{
751 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
752 if (!input.IsValid())
753 {
754 return Fail("%s: Operation has invalid inputs", __func__);
755 }
756
757 const Operand* output = GetOutputOperand(operation, 0);
758 if (!output)
759 {
760 return Fail("%s: Could not read output 0", __func__);
761 }
762
763 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
764 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
765
766 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
767 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
768
769 // ArmNN does not currently support non-fixed weights or bias
770
771 // Find the shape of the weights tensor. In AndroidNN this will be [ 1, H, W, I * M ]
772 // but in ArmNN it needs to be [ M, I, H, W ]
773 const Operand* weightsOperand = GetInputOperand(operation, 1);
774
775 if (weightsOperand == nullptr)
776 {
777 return Fail("%s: Operand is invalid", __func__);
778 }
779
780 // Reinterpret weight data as [ H, W, I, M ]
781 armnn::TensorShape weightsShape({ weightsOperand->dimensions[1], weightsOperand->dimensions[2],
782 inputInfo.GetShape()[3],
783 weightsOperand->dimensions[3] / inputInfo.GetShape()[3] });
784
785 // Swizzle weight data [ H, W, I, M ] -> [ M, I, H, W ]
786 const armnn::PermutationVector HWIMToMIHW = { 2U, 3U, 1U, 0U };
787 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1, HWIMToMIHW, &weightsShape);
788
789 // Bias is a 1D tensor
790 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2);
791
792 if (!weightsPin.IsValid() || !biasPin.IsValid())
793 {
794 return Fail("%s: Operation has invalid inputs", __func__);
795 }
796
797 armnn::ConstTensor weights = weightsPin.GetConstTensor();
798 armnn::ConstTensor bias = biasPin.GetConstTensor();
799 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), swizzledInputInfo);
800
801 armnn::DepthwiseConvolution2dDescriptor desc;
802 ActivationFn activation;
803
804 if (operation.inputs.size() == 11)
805 {
806 if (!GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadLeft) ||
807 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadRight) ||
808 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PadTop) ||
809 !GetInputScalar(operation, 6, OperandType::INT32, desc.m_PadBottom) ||
810 !GetInputScalar(operation, 7, OperandType::INT32, desc.m_StrideX) ||
811 !GetInputScalar(operation, 8, OperandType::INT32, desc.m_StrideY) ||
812 !GetInputActivationFunction(operation, 10, activation))
813 {
814 return Fail("%s: Operation has invalid inputs", __func__);
815 }
816 }
817 else if (operation.inputs.size() == 8)
818 {
819 android::nn::PaddingScheme paddingScheme;
820
821 if (!GetInputPaddingScheme(operation, 3, paddingScheme) ||
822 !GetInputScalar(operation, 4, OperandType::INT32, desc.m_StrideX) ||
823 !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideY) ||
824 !GetInputActivationFunction(operation, 7, activation))
825 {
826 return Fail("%s: Operation has invalid inputs", __func__);
827 }
828
829 const uint32_t kernelX = weights.GetShape()[3];
830 const uint32_t kernelY = weights.GetShape()[2];
831 const uint32_t inputX = swizzledInputInfo.GetShape()[3];
832 const uint32_t inputY = swizzledInputInfo.GetShape()[2];
833
834 CalcPadding(inputX, kernelX, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, paddingScheme);
835 CalcPadding(inputY, kernelY, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, paddingScheme);
836 }
837 else
838 {
839 return Fail("%s: Unsupported number of operation inputs", __func__);
840 }
841
842 desc.m_BiasEnabled = true;
843
844 if (!IsLayerSupported(__func__,
845 armnn::IsDepthwiseConvolutionSupported,
846 m_Compute,
847 swizzledInputInfo,
848 desc,
849 weights.GetInfo()))
850 {
851 return false;
852 }
853
854 armnn::IConnectableLayer* startLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, bias);
855 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
856
857 if (endLayer != nullptr)
858 {
859 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
860 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
861 }
862 else
863 {
864 return Fail("%s: ProcessActivation failed", __func__);
865 }
866}
867
868bool ModelToINetworkConverter::ConvertFloor(const Operation& operation)
869{
870 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
871 if (!input.IsValid())
872 {
873 return Fail("%s: Operation has invalid inputs", __func__);
874 }
875
876 const Operand* const outputOperand = GetOutputOperand(operation, 0);
877 if (!outputOperand)
878 {
879 return Fail("%s: Operation has invalid outputs", __func__);
880 }
881
882 if (!IsLayerSupported(__func__,
883 armnn::IsFloorSupported,
884 m_Compute,
885 input.GetTensorInfo(),
886 GetTensorInfoForOperand(*outputOperand)))
887 {
888 return false;
889 }
890
891 armnn::IConnectableLayer* layer = m_Network->AddFloorLayer();
892 assert(layer != nullptr);
893 input.Connect(layer->GetInputSlot(0));
894
895 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
896}
897
898bool ModelToINetworkConverter::ConvertFullyConnected(const Operation& operation)
899{
900 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
901 if (!input.IsValid())
902 {
903 return Fail("%s: Operation has invalid inputs", __func__);
904 }
905
906 const Operand* output = GetOutputOperand(operation, 0);
907 if (!output)
908 {
909 return Fail("%s: Could not read output 0", __func__);
910 }
911
912 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
913 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
914
915 armnn::TensorInfo reshapedInfo = inputInfo;
916
917 if (inputInfo.GetNumDimensions() > 2U)
918 {
919 unsigned int dim1 = inputInfo.GetShape()[1];
920 for (unsigned int i = 2U; i < inputInfo.GetNumDimensions(); ++i)
921 {
922 dim1 *= inputInfo.GetShape()[i];
923 }
924 reshapedInfo.SetShape(armnn::TensorShape({inputInfo.GetShape()[0], dim1}));
925 }
926
927 // ArmNN does not currently support non-fixed weights or bias
928 ConstTensorPin weightsPin = ConvertOperationInputToConstTensorPin(operation, 1); // 2D
929 ConstTensorPin biasPin = ConvertOperationInputToConstTensorPin(operation, 2); // 1D
930
931 if (!weightsPin.IsValid() || !biasPin.IsValid())
932 {
933 return Fail("%s: Operation has invalid inputs", __func__);
934 }
935
936 // ensuring that the bias value is within 1% of the weights input (small float differences can exist)
937 armnn::ConstTensor weights = weightsPin.GetConstTensor();
938 armnn::ConstTensor bias = biasPin.GetConstTensor();
939 SanitizeBiasQuantizationScale(bias.GetInfo(), weights.GetInfo(), reshapedInfo);
940
941 ActivationFn activationFunction;
942 if (!GetInputActivationFunction(operation, 3, activationFunction))
943 {
944 return Fail("%s: Operation has invalid inputs", __func__);
945 }
946
947 armnn::FullyConnectedDescriptor desc;
948 desc.m_TransposeWeightMatrix = true;
949 desc.m_BiasEnabled = true;
950
951 if (!IsLayerSupported(__func__,
952 armnn::IsFullyConnectedSupported,
953 m_Compute,
954 reshapedInfo,
955 desc))
956 {
957 return false;
958 }
959
960 armnn::IConnectableLayer* startLayer = m_Network->AddFullyConnectedLayer(desc, weights, bias);
961 armnn::IConnectableLayer* endLayer = ProcessActivation(outputInfo, activationFunction, startLayer);
962
963 if (endLayer != nullptr)
964 {
965 if (inputInfo.GetNumDimensions() > 2U)
966 {
967 armnn::ReshapeDescriptor reshapeDescriptor;
968 reshapeDescriptor.m_TargetShape = reshapedInfo.GetShape();
969
970 armnn::IConnectableLayer* reshapeLayer = m_Network->AddReshapeLayer(reshapeDescriptor);
971 assert(reshapeLayer != nullptr);
972 input.Connect(reshapeLayer->GetInputSlot(0));
973 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
974 reshapeLayer->GetOutputSlot(0).Connect(startLayer->GetInputSlot(0));
975 }
976 else
977 {
978 input.Connect(startLayer->GetInputSlot(0));
979 }
980
981 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
982 }
983 else
984 {
985 return Fail("%s: ProcessActivation failed", __func__);
986 }
987}
988
989bool ModelToINetworkConverter::ConvertLocalResponseNormalization(const Operation& operation)
990{
991 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
992 if (!input.IsValid())
993 {
994 return Fail("%s: Operation has invalid inputs", __func__);
995 }
996
997 const Operand* output = GetOutputOperand(operation, 0);
998 if (!output)
999 {
1000 return Fail("%s: Could not read output 0", __func__);
1001 }
1002
1003 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1004 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1005
1006 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1007 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1008
1009 armnn::NormalizationDescriptor descriptor;
1010
1011 descriptor.m_NormChannelType = armnn::NormalizationAlgorithmChannel::Across;
1012 descriptor.m_NormMethodType = armnn::NormalizationAlgorithmMethod::LocalBrightness;
1013
1014 if (!input.IsValid() ||
1015 !GetInputScalar(operation, 1, OperandType::INT32, descriptor.m_NormSize) ||
1016 !GetInputFloat32(operation, 2, descriptor.m_K) ||
1017 !GetInputFloat32(operation, 3, descriptor.m_Alpha) ||
1018 !GetInputFloat32(operation, 4, descriptor.m_Beta))
1019 {
1020 return Fail("%s: Operation has invalid inputs", __func__);
1021 }
1022
1023 // ArmNN expects normSize to be the full size of the normalization
1024 // window rather than the radius as in AndroidNN.
1025 descriptor.m_NormSize = 1 + (2 * descriptor.m_NormSize);
1026
1027 if (!IsLayerSupported(__func__,
1028 armnn::IsNormalizationSupported,
1029 m_Compute,
1030 swizzledInputInfo,
1031 swizzledOutputInfo,
1032 descriptor))
1033 {
1034 return false;
1035 }
1036
1037
1038 armnn::IConnectableLayer* layer = m_Network->AddNormalizationLayer(descriptor);
1039 assert(layer != nullptr);
1040 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1041
1042 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1043
1044 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1045}
1046
1047bool ModelToINetworkConverter::ConvertLogistic(const Operation& operation)
1048{
1049 armnn::ActivationDescriptor desc;
1050 desc.m_Function == armnn::ActivationFunction::Sigmoid;
1051
1052 return ConvertToActivation(operation, __func__, desc);
1053}
1054
1055bool ModelToINetworkConverter::ConvertL2Normalization(const Operation& operation)
1056{
1057 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1058 if (!input.IsValid())
1059 {
1060 return Fail("%s: Operation has invalid inputs", __func__);
1061 }
1062
1063 const Operand* output = GetOutputOperand(operation, 0);
1064 if (!output)
1065 {
1066 return Fail("%s: Could not read output 0", __func__);
1067 }
1068
1069 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1070 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1071
1072 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1073 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1074
1075 if (!IsLayerSupported(__func__,
1076 armnn::IsL2NormalizationSupported,
1077 m_Compute,
1078 swizzledInputInfo))
1079 {
1080 return false;
1081 }
1082
1083 armnn::IConnectableLayer* layer = m_Network->AddL2NormalizationLayer();
1084 assert(layer != nullptr);
1085 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1086
1087 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1088
1089 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1090}
1091
1092bool ModelToINetworkConverter::ConvertL2Pool2d(const Operation& operation)
1093{
1094 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::L2);
1095}
1096
1097bool ModelToINetworkConverter::ConvertMaxPool2d(const Operation& operation)
1098{
1099 return ConvertPooling2d(operation, __func__, armnn::PoolingAlgorithm::Max);
1100}
1101
1102bool ModelToINetworkConverter::ConvertMul(const Operation& operation)
1103{
1104 LayerInputHandle input0 = ConvertToLayerInputHandle(operation, 0);
1105 LayerInputHandle input1 = ConvertToLayerInputHandle(operation, 1);
1106
1107 if (!input0.IsValid() || !input1.IsValid())
1108 {
1109 return Fail("%s: Operation has invalid inputs", __func__);
1110 }
1111
1112 ActivationFn activationFunction;
1113 if (!GetInputActivationFunction(operation, 2, activationFunction))
1114 {
1115 return Fail("%s: Operation has invalid inputs", __func__);
1116 }
1117
1118 if (!ValidateBroadcast(m_Model, operation, 2u))
1119 {
1120 return Fail("%s is invalid due to broadcasting", __func__);
1121 }
1122
1123 if (!IsLayerSupported(__func__,
1124 armnn::IsMultiplicationSupported,
1125 m_Compute,
1126 input0.GetTensorInfo(),
1127 input1.GetTensorInfo()))
1128 {
1129 return false;
1130 }
1131
1132 const Operand* outputOperand = GetOutputOperand(operation, 0);
1133
1134 if (outputOperand == nullptr)
1135 {
1136 return false;
1137 }
1138
1139 const armnn::TensorInfo& outInfo = GetTensorInfoForOperand(*outputOperand);
1140
1141 armnn::IConnectableLayer* const startLayer = m_Network->AddMultiplicationLayer();
1142 armnn::IConnectableLayer* const endLayer = ProcessActivation(outInfo, activationFunction, startLayer);
1143
1144 if (endLayer != nullptr)
1145 {
1146 input0.Connect(startLayer->GetInputSlot(0));
1147 input1.Connect(startLayer->GetInputSlot(1));
1148
1149 return SetupAndTrackLayerOutputSlot(operation, 0, *endLayer);
1150 }
1151 else
1152 {
1153 return Fail("%s: ProcessActivation failed", __func__);
1154 }
1155}
1156
1157bool ModelToINetworkConverter::ConvertReLu(const Operation& operation)
1158{
1159 armnn::ActivationDescriptor desc;
1160 desc.m_Function = armnn::ActivationFunction::ReLu;
1161
1162 return ConvertToActivation(operation, __func__, desc);
1163}
1164
1165bool ModelToINetworkConverter::ConvertReLu1(const Operation& operation)
1166{
1167 armnn::ActivationDescriptor desc;
1168 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1169 desc.m_A = 1.0f;
1170 desc.m_B = -1.0f;
1171
1172 return ConvertToActivation(operation, __func__, desc);
1173}
1174
1175bool ModelToINetworkConverter::ConvertReLu6(const Operation& operation)
1176{
1177 armnn::ActivationDescriptor desc;
1178 desc.m_Function = armnn::ActivationFunction::BoundedReLu;
1179 desc.m_A = 6.0f;
1180
1181 return ConvertToActivation(operation, __func__, desc);
1182}
1183
1184bool ModelToINetworkConverter::ConvertSoftmax(const Operation& operation)
1185{
1186 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1187 if (!input.IsValid())
1188 {
1189 return Fail("%s: Operation has invalid inputs", __func__);
1190 }
1191
1192 armnn::SoftmaxDescriptor desc;
1193 if (!GetInputFloat32(operation, 1, desc.m_Beta))
1194 {
1195 return Fail("%s: Operation has invalid inputs", __func__);
1196 }
1197
1198 if (!IsLayerSupported(__func__,
1199 armnn::IsSoftmaxSupported,
1200 m_Compute,
1201 input.GetTensorInfo(),
1202 desc))
1203 {
1204 return false;
1205 }
1206
1207 armnn::IConnectableLayer* layer = m_Network->AddSoftmaxLayer(desc);
1208 assert(layer != nullptr);
1209 input.Connect(layer->GetInputSlot(0));
1210
1211 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1212}
1213
1214bool ModelToINetworkConverter::ConvertTanH(const Operation& operation)
1215{
1216 armnn::ActivationDescriptor desc;
1217 desc.m_Function = armnn::ActivationFunction::TanH;
1218 desc.m_A = 1.0f; // android nn does not support tanH parameters
1219 desc.m_B = 1.0f; // set to 1.0f for unity scaling
1220
1221 return ConvertToActivation(operation, __func__, desc);
1222}
1223
1224bool ModelToINetworkConverter::ConvertReshape(const Operation& operation)
1225{
1226 const Operand* inputOperand = GetInputOperand(operation, 0);
1227 const Operand* requestedShapeOperand = GetInputOperand(operation, 1);
1228 const Operand* outputOperand = GetOutputOperand(operation, 0);
1229
1230 if (inputOperand == nullptr
1231 || requestedShapeOperand == nullptr
1232 || outputOperand == nullptr)
1233 {
1234 return Fail("%s: Operation has invalid inputs", __func__);
1235 }
1236
1237
1238 if (requestedShapeOperand->dimensions.size() != 1)
1239 {
1240 return Fail("%s: Input 1 expected to be one-dimensional (found %i dimensions)",
1241 __func__, requestedShapeOperand->dimensions.size());
1242 }
1243
1244 std::vector<int32_t> targetDimensions;
1245 if (!GetTensorInt32Values(*requestedShapeOperand, targetDimensions))
1246 {
1247 return Fail("%s: Could not read values of input 1", __func__);
1248 }
1249
1250 const Shape inputOperandShape = GetOperandShape(*inputOperand);
1251
1252 Shape requestedShape;
1253 // targetDimensions may contain special values (e.g. -1). reshapePrepare() is an AndroidNN provided utility
1254 // function that resolves these values into a fully specified tensor shape.
1255 if (!reshapePrepare(inputOperandShape, targetDimensions.data(), targetDimensions.size(), &requestedShape))
1256 {
1257 return Fail("%s: Failed to resolve the requested shape", __func__);
1258 }
1259
1260 const Shape outputOperandShape = GetOperandShape(*outputOperand);
1261 if (!SameShape(requestedShape, outputOperandShape))
1262 {
1263 return Fail("%s: Shape of output operand does not match resolved requested shape", __func__);
1264 }
1265
1266 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1267 if (!input.IsValid())
1268 {
1269 return Fail("%s: Could not read input 0", __func__);
1270 }
1271
1272 if (!IsLayerSupported(__func__,
1273 armnn::IsReshapeSupported,
1274 m_Compute,
1275 input.GetTensorInfo()))
1276 {
1277 return false;
1278 }
1279
1280
1281 armnn::ReshapeDescriptor reshapeDescriptor;
1282 reshapeDescriptor.m_TargetShape = armnn::TensorShape(requestedShape.dimensions.size(),
1283 requestedShape.dimensions.data());
1284
1285 armnn::IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDescriptor);
1286 assert(layer != nullptr);
1287 input.Connect(layer->GetInputSlot(0));
1288
1289 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1290}
1291
1292bool ModelToINetworkConverter::ConvertResizeBilinear(const Operation& operation)
1293{
1294 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1295 if (!input.IsValid())
1296 {
1297 return Fail("%s: Could not read input 0", __func__);
1298 }
1299
1300 const Operand* output = GetOutputOperand(operation, 0);
1301 if (!output)
1302 {
1303 return Fail("%s: Could not read output 0", __func__);
1304 }
1305
1306 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1307 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1308
1309 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1310 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1311
1312 if (!IsLayerSupported(__func__,
1313 armnn::IsResizeBilinearSupported,
1314 m_Compute,
1315 swizzledInputInfo))
1316 {
1317 return false;
1318 }
1319
1320 armnn::ResizeBilinearDescriptor desc;
1321
1322 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_TargetHeight)
1323 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_TargetWidth))
1324 {
1325 return Fail("%s: Operation has invalid inputs", __func__);
1326 }
1327
1328 armnn::IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc);
1329 assert(layer != nullptr);
1330 layer->GetOutputSlot(0).SetTensorInfo(swizzledOutputInfo);
1331
1332 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *layer);
1333
1334 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1335
1336}
1337
1338bool ModelToINetworkConverter::ConvertToActivation(const Operation& operation,
1339 const char* operationName,
1340 const armnn::ActivationDescriptor& activationDesc)
1341{
1342 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1343 if (!input.IsValid())
1344 {
1345 return Fail("%s: Input 0 is invalid", operationName);
1346 }
1347
1348 if (!IsLayerSupported(__func__,
1349 armnn::IsActivationSupported,
1350 m_Compute,
1351 input.GetTensorInfo(),
1352 activationDesc))
1353 {
1354 return false;
1355 }
1356
1357 armnn::IConnectableLayer* layer = m_Network->AddActivationLayer(activationDesc);
1358 assert(layer != nullptr);
1359 input.Connect(layer->GetInputSlot(0));
1360
1361 return SetupAndTrackLayerOutputSlot(operation, 0, *layer);
1362}
1363
1364bool ModelToINetworkConverter::ConvertPooling2d(const Operation& operation,
1365 const char* operationName,
1366 armnn::PoolingAlgorithm poolType)
1367{
1368 LayerInputHandle input = ConvertToLayerInputHandle(operation, 0);
1369 if (!input.IsValid())
1370 {
1371 return Fail("%s: Could not read input 0", operationName);
1372 }
1373
1374 const Operand* output = GetOutputOperand(operation, 0);
1375 if (!output)
1376 {
1377 return Fail("%s: Could not read output 0", __func__);
1378 }
1379
1380 const armnn::TensorInfo& inputInfo = input.GetTensorInfo();
1381 const armnn::TensorInfo& outputInfo = GetTensorInfoForOperand(*output);
1382
1383 const armnn::TensorInfo swizzledInputInfo = armnnUtils::Permuted(inputInfo, NHWCToArmNN);
1384 const armnn::TensorInfo swizzledOutputInfo = armnnUtils::Permuted(outputInfo, NHWCToArmNN);
1385
1386 armnn::Pooling2dDescriptor desc;
1387 desc.m_PoolType = poolType;
1388 desc.m_OutputShapeRounding = armnn::OutputShapeRounding::Floor;
1389
1390 ActivationFn activation;
1391
1392 if (operation.inputs.size() == 7)
1393 {
1394 // one input, 6 parameters (padding, stridex, stridey, width, height, activation type)
1395 android::nn::PaddingScheme scheme;
1396
1397 if ( !GetInputPaddingScheme(operation, 1, scheme)
1398 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_StrideX)
1399 || !GetInputScalar(operation, 3, OperandType::INT32, desc.m_StrideY)
1400 || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PoolWidth)
1401 || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_PoolHeight)
1402 || !GetInputActivationFunction(operation, 6, activation))
1403 {
1404 return Fail("%s: Operation has invalid inputs", operationName);
1405 }
1406
1407 const unsigned int inputWidth = swizzledInputInfo.GetShape()[3];
1408 const unsigned int inputHeight = swizzledInputInfo.GetShape()[2];
1409
1410 CalcPadding(inputWidth, desc.m_PoolWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, scheme);
1411 CalcPadding(inputHeight, desc.m_PoolHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, scheme);
1412 }
1413 else
1414 {
1415 // one input, 9 parameters (padding l r t b, stridex, stridey, width, height, activation type)
1416 if ( !GetInputScalar(operation, 1, OperandType::INT32, desc.m_PadLeft)
1417 || !GetInputScalar(operation, 2, OperandType::INT32, desc.m_PadRight)
1418 || !GetInputScalar(operation, 3, OperandType::INT32, desc.m_PadTop)
1419 || !GetInputScalar(operation, 4, OperandType::INT32, desc.m_PadBottom)
1420 || !GetInputScalar(operation, 5, OperandType::INT32, desc.m_StrideX)
1421 || !GetInputScalar(operation, 6, OperandType::INT32, desc.m_StrideY)
1422 || !GetInputScalar(operation, 7, OperandType::INT32, desc.m_PoolWidth)
1423 || !GetInputScalar(operation, 8, OperandType::INT32, desc.m_PoolHeight)
1424 || !GetInputActivationFunction(operation, 9, activation))
1425 {
1426 return Fail("%s: Operation has invalid inputs", operationName);
1427 }
1428 }
1429
1430 // ArmNN does not accept a pool size of 1, but the ArmNN driver is expected to cope.
1431 // This is mapped to a trivial splitter instead.
1432 armnn::IConnectableLayer* startLayer = nullptr;
1433 if (desc.m_PoolWidth != 1 || desc.m_PoolHeight != 1)
1434 {
1435 if (!IsLayerSupported(__func__,
1436 armnn::IsPooling2dSupported,
1437 m_Compute,
1438 swizzledInputInfo,
1439 swizzledOutputInfo,
1440 desc))
1441 {
1442 return false;
1443 }
1444
1445 startLayer = m_Network->AddPooling2dLayer(desc);
1446 }
1447 else
1448 {
1449 const unsigned int numDims = swizzledOutputInfo.GetNumDimensions();
1450
1451 armnn::ViewsDescriptor viewsDesc(1, numDims);
1452
1453 for (unsigned int i = 0; i < numDims; ++i)
1454 {
1455 viewsDesc.SetViewOriginCoord(0, i, 0);
1456 viewsDesc.SetViewSize(0, i, swizzledOutputInfo.GetShape()[i]);
1457 }
1458
1459 if (!IsLayerSupported(__func__,
1460 armnn::IsSplitterSupported,
1461 m_Compute,
1462 swizzledInputInfo,
1463 viewsDesc))
1464 {
1465 return false;
1466 }
1467
1468 startLayer = m_Network->AddSplitterLayer(viewsDesc);
1469 }
1470
1471 armnn::IConnectableLayer* endLayer = ProcessActivation(swizzledOutputInfo, activation, startLayer);
1472
1473 if (endLayer != nullptr)
1474 {
1475 armnn::IConnectableLayer& outSwizzleLayer = SwizzleInDeswizzleOut(*m_Network, input, *startLayer, *endLayer);
1476 return SetupAndTrackLayerOutputSlot(operation, 0, outSwizzleLayer);
1477 }
1478 else
1479 {
1480 return Fail("%s: ProcessActivation failed", operationName);
1481 }
1482}
1483
1484const void* ModelToINetworkConverter::GetOperandValueReadOnlyAddress(const Operand& operand) const
1485{
1486 const void* valueStart = nullptr;
1487
1488 switch (operand.lifetime)
1489 {
1490 case OperandLifeTime::CONSTANT_COPY:
1491 {
1492 // Constant found in model.operandValues
1493 valueStart = &m_Model.operandValues[operand.location.offset];
1494 break;
1495 }
1496 case OperandLifeTime::CONSTANT_REFERENCE:
1497 {
1498 // Constant specified via a Memory object
1499 valueStart = GetMemoryFromPool(operand.location, m_MemPools);
1500 break;
1501 }
1502 default:
1503 {
1504 // Unsupported/invalid (e.g. can't get value of an input to the model)
1505 Fail("%s: unsupported/invalid operand lifetime: %s",
1506 __func__, toString(operand.lifetime).c_str());
1507 valueStart = nullptr;
1508 }
1509 }
1510
1511 return valueStart;
1512}
1513
1514const Operand* ModelToINetworkConverter::GetInputOperand(const Operation& operation, uint32_t inputIndex) const
1515{
1516 if (inputIndex >= operation.inputs.size())
1517 {
1518 Fail("%s: invalid input index: %i out of %i", __func__, inputIndex, operation.inputs.size());
1519 return nullptr;
1520 }
1521
1522 assert(operation.inputs[inputIndex] < m_Model.operands.size()); // Model should have been validated beforehand
1523 return &m_Model.operands[operation.inputs[inputIndex]];
1524}
1525
1526const Operand* ModelToINetworkConverter::GetOutputOperand(const Operation& operation, uint32_t outputIndex) const
1527{
1528 if (outputIndex >= operation.outputs.size())
1529 {
1530 Fail("%s: invalid output index: %i out of %i", __func__, outputIndex, operation.outputs.size());
1531 return nullptr;
1532 }
1533
1534 assert(operation.outputs[outputIndex] < m_Model.operands.size()); // Model should have been validated beforehand
1535 return &m_Model.operands[operation.outputs[outputIndex]];
1536}
1537
1538template<typename T>
1539bool ModelToINetworkConverter::GetInputScalar(const Operation& operation, uint32_t inputIndex,
1540 OperandType type, T& outValue) const
1541{
1542 const Operand* operand = GetInputOperand(operation, inputIndex);
1543 if (!operand)
1544 {
1545 return Fail("%s: invalid input operand at index %i", __func__, inputIndex);
1546 }
1547
1548 if (operand->type != type)
1549 {
1550 return Fail("%s: unexpected operand type: %s (should be %s)",
1551 __func__, toString(operand->type).c_str(), toString(type).c_str());
1552 }
1553
1554 if (operand->location.length != sizeof(T))
1555 {
1556 return Fail("%s: incorrect operand location length: %i (should be %i)",
1557 __func__, operand->location.length, sizeof(T));
1558 }
1559
1560 const void* valueAddress = GetOperandValueReadOnlyAddress(*operand);
1561 if (!valueAddress)
1562 {
1563 return Fail("%s: failed to get address for operand", __func__);
1564 }
1565
1566 outValue = *(static_cast<const T*>(valueAddress));
1567 return true;
1568}
1569
1570bool ModelToINetworkConverter::GetInputInt32(const Operation& operation, uint32_t inputIndex, int32_t& outValue) const
1571{
1572 return GetInputScalar(operation, inputIndex, OperandType::INT32, outValue);
1573}
1574
1575bool ModelToINetworkConverter::GetInputFloat32(const Operation& operation, uint32_t inputIndex, float& outValue) const
1576{
1577 return GetInputScalar(operation, inputIndex, OperandType::FLOAT32, outValue);
1578}
1579
1580bool ModelToINetworkConverter::GetInputActivationFunction(const Operation& operation,
1581 uint32_t inputIndex,
1582 ActivationFn& outActivationFunction) const
1583{
1584 int32_t activationFunctionAsInt;
1585 if (!GetInputInt32(operation, inputIndex, activationFunctionAsInt))
1586 {
1587 return Fail("%s: failed to get activation input value", __func__);
1588 }
1589
1590 outActivationFunction = static_cast<ActivationFn>(activationFunctionAsInt);
1591 return true;
1592}
1593
1594bool ModelToINetworkConverter::GetInputPaddingScheme(const Operation& operation,
1595 uint32_t inputIndex,
1596 android::nn::PaddingScheme& outPaddingScheme) const
1597{
1598 int32_t paddingSchemeAsInt;
1599 if (!GetInputInt32(operation, inputIndex, paddingSchemeAsInt))
1600 {
1601 return Fail("%s: failed to get padding scheme input value", __func__);
1602 }
1603
1604 outPaddingScheme = static_cast<android::nn::PaddingScheme>(paddingSchemeAsInt);
1605 return true;
1606}
1607
1608LayerInputHandle ModelToINetworkConverter::ConvertToLayerInputHandle(
1609 const Operation& operation,
1610 uint32_t inputIndex)
1611{
1612 const Operand* operand = GetInputOperand(operation, inputIndex);
1613 if (!operand)
1614 {
1615 Fail("%s: failed to get input operand %i", __func__, inputIndex);
1616 return LayerInputHandle();
1617 }
1618
1619 if (!IsOperandTypeSupportedForTensors(operand->type))
1620 {
1621 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand->type).c_str());
1622 return LayerInputHandle();
1623 }
1624
1625 armnn::TensorInfo operandTensorInfo = GetTensorInfoForOperand(*operand);
1626
1627 switch (operand->lifetime)
1628 {
1629 case OperandLifeTime::TEMPORARY_VARIABLE: // intentional fallthrough
1630 case OperandLifeTime::MODEL_INPUT:
1631 {
1632 // The tensor is either an operand internal to the model, or a model input.
1633 // It can be associated with an ArmNN output slot for an existing layer.
1634
1635 // m_OutputSlotForOperand[...] can be nullptr if the previous layer could not be converted
1636 const uint32_t operandIndex = operation.inputs[inputIndex];
1637 return LayerInputHandle(true, m_OutputSlotForOperand[operandIndex], operandTensorInfo);
1638 break;
1639 }
1640 case OperandLifeTime::CONSTANT_COPY:
1641 case OperandLifeTime::CONSTANT_REFERENCE:
1642 {
1643 // The tensor has an already known constant value, and can be converted into an ArmNN Constant layer.
1644 ConstTensorPin tensorPin = ConvertOperandToConstTensorPin(*operand);
1645 if (tensorPin.IsValid())
1646 {
1647 if (!IsLayerSupported(__func__,
1648 armnn::IsConstantSupported,
1649 m_Compute,
1650 tensorPin.GetConstTensor().GetInfo()))
1651 {
1652 return LayerInputHandle();
1653 }
1654
1655 armnn::IConnectableLayer* constantLayer = m_Network->AddConstantLayer(tensorPin.GetConstTensor());
1656 armnn::IOutputSlot& outputSlot = constantLayer->GetOutputSlot(0);
1657 outputSlot.SetTensorInfo(tensorPin.GetConstTensor().GetInfo());
1658
1659 return LayerInputHandle(true, &outputSlot, operandTensorInfo);
1660 }
1661 else
1662 {
1663 Fail("%s: invalid operand tensor", __func__);
1664 return LayerInputHandle();
1665 }
1666 break;
1667 }
1668 default:
1669 {
1670 // Unsupported lifetime for an input tensor
1671 Fail("%s: unsupported lifetime for input tensor: %s",
1672 __func__, toString(operand->lifetime).c_str());
1673 return LayerInputHandle();
1674 }
1675 }
1676}
1677
1678ConstTensorPin ModelToINetworkConverter::ConvertOperationInputToConstTensorPin(const Operation& operation,
1679 uint32_t inputIndex, const armnn::PermutationVector& dimensionMappings,
1680 const armnn::TensorShape* overrideTensorShape)
1681{
1682 const Operand* operand = GetInputOperand(operation, inputIndex);
1683 if (!operand)
1684 {
1685 Fail("%s: failed to get input operand", __func__);
1686 return ConstTensorPin();
1687 }
1688
1689 return ConvertOperandToConstTensorPin(*operand, dimensionMappings, overrideTensorShape);
1690}
1691
1692ConstTensorPin ModelToINetworkConverter::ConvertOperandToConstTensorPin(const Operand& operand,
1693 const armnn::PermutationVector& dimensionMappings, const armnn::TensorShape* overrideTensorShape)
1694{
1695 if (!IsOperandTypeSupportedForTensors(operand.type))
1696 {
1697 Fail("%s: unsupported operand type for tensor %s", __func__, toString(operand.type).c_str());
1698 return ConstTensorPin();
1699 }
1700
1701 if (operand.lifetime != OperandLifeTime::CONSTANT_COPY && operand.lifetime != OperandLifeTime::CONSTANT_REFERENCE)
1702 {
1703 Fail("%s: invalid operand lifetime: %s", __func__, toString(operand.lifetime).c_str());
1704 return ConstTensorPin();
1705 }
1706
1707 const void* const valueStart = GetOperandValueReadOnlyAddress(operand);
1708 if (!valueStart)
1709 {
1710 Fail("%s: failed to get operand address", __func__);
1711 return ConstTensorPin();
1712 }
1713
1714 armnn::TensorInfo tensorInfo = GetTensorInfoForOperand(operand);
1715 if (overrideTensorShape != nullptr)
1716 {
1717 tensorInfo.SetShape(*overrideTensorShape);
1718 }
1719 return ConstTensorPin(tensorInfo, valueStart, operand.location.length, dimensionMappings);
1720}
1721
1722bool ModelToINetworkConverter::GetTensorInt32Values(const Operand& operand, std::vector<int32_t>& outValues) const
1723{
1724 if (operand.type != OperandType::TENSOR_INT32)
1725 {
1726 return Fail("%s: invalid operand type: %s", __func__, toString(operand.type).c_str());
1727 }
1728
1729 const void* startAddress = GetOperandValueReadOnlyAddress(operand);
1730 if (!startAddress)
1731 {
1732 return Fail("%s: failed to get operand address", __func__, operand.type);
1733 }
1734
1735 // Check number of bytes is sensible
1736 const uint32_t numBytes = operand.location.length;
1737 if (numBytes % sizeof(int32_t) != 0)
1738 {
1739 return Fail("%s: invalid number of bytes: %i, expected to be a multiple of %i",
1740 __func__, numBytes, sizeof(int32_t));
1741 }
1742
1743 outValues.resize(numBytes / sizeof(int32_t));
1744 memcpy(outValues.data(), startAddress, numBytes);
1745 return true;
1746}
1747
1748// Creates an ArmNN activation layer and connects it to the given layer, if the
1749// passed in AndroidNN activation function requires so.
1750// @return The end layer of the sequence of layers built for the given AndroidNN
1751// activation function or nullptr if an error occurred (e.g. unsupported activation).
1752// Note that the end layer matches the input layer if no activation is required
1753// (the sequence of layers has length 1).
1754armnn::IConnectableLayer* ModelToINetworkConverter::ProcessActivation(const armnn::TensorInfo& tensorInfo,
1755 ActivationFn activation, armnn::IConnectableLayer* prevLayer)
1756{
1757 assert(prevLayer->GetNumOutputSlots() == 1);
1758
1759 prevLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1760
1761 armnn::IConnectableLayer* activationLayer = prevLayer;
1762
1763 if (activation != ActivationFn::kActivationNone)
1764 {
1765 armnn::ActivationDescriptor activationDesc;
1766 switch (activation)
1767 {
1768 case ActivationFn::kActivationRelu:
1769 {
1770 activationDesc.m_Function = armnn::ActivationFunction::ReLu;
1771 break;
1772 }
1773 case ActivationFn::kActivationRelu1:
1774 {
1775 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
1776 activationDesc.m_A = 1.0f;
1777 activationDesc.m_B = -1.0f;
1778 break;
1779 }
1780 case ActivationFn::kActivationRelu6:
1781 {
1782 activationDesc.m_Function = armnn::ActivationFunction::BoundedReLu;
1783 activationDesc.m_A = 6.0f;
1784 break;
1785 }
1786 case ActivationFn::kActivationSigmoid:
1787 {
1788 activationDesc.m_Function = armnn::ActivationFunction::Sigmoid;
1789 break;
1790 }
1791 case ActivationFn::kActivationTanh:
1792 {
1793 activationDesc.m_Function = armnn::ActivationFunction::TanH;
1794 activationDesc.m_A = 1.0f;
1795 activationDesc.m_B = 1.0f;
1796 break;
1797 }
1798 default:
1799 {
1800 Fail("%s: Invalid activation enum value %i", __func__, activation);
1801 return nullptr;
1802 }
1803 }
1804
1805 if (!IsLayerSupported(__func__, armnn::IsActivationSupported, m_Compute,
1806 prevLayer->GetOutputSlot(0).GetTensorInfo(), activationDesc))
1807 {
1808 return nullptr;
1809 }
1810
1811 activationLayer = m_Network->AddActivationLayer(activationDesc);
1812
1813 prevLayer->GetOutputSlot(0).Connect(activationLayer->GetInputSlot(0));
1814 activationLayer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1815 }
1816
1817 return activationLayer;
1818}
1819
1820bool ModelToINetworkConverter::SetupAndTrackLayerOutputSlot(const Operation& operation, uint32_t outputIndex,
1821 armnn::IConnectableLayer& layer)
1822{
1823 const Operand* outputOperand = GetOutputOperand(operation, outputIndex);
1824
1825 if ((outputOperand == nullptr) || (outputIndex >= layer.GetNumOutputSlots()))
1826 {
1827 return false;
1828 }
1829
1830 armnn::IOutputSlot& outputSlot = layer.GetOutputSlot(outputIndex);
1831
1832 const uint32_t operandIndex = operation.outputs[outputIndex];
1833 m_OutputSlotForOperand[operandIndex] = &outputSlot;
1834
1835 outputSlot.SetTensorInfo(GetTensorInfoForOperand(*outputOperand));
1836
1837 return true;
1838}
1839
1840bool ModelToINetworkConverter::IsOperationSupported(uint32_t operationIndex) const
1841{
1842 std::map<uint32_t, bool>::const_iterator it = m_OperationSupported.find(operationIndex);
1843 assert(it != m_OperationSupported.end());
1844 return it->second;
1845}
1846
1847
1848} // armnn_driver