IVGCVSW-2467 Remove GetDataType<T> function
[platform/upstream/armnn.git] / src / armnnTfParser / TfParser.cpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5
6 #include "TfParser.hpp"
7
8 #include <armnn/TypesUtils.hpp>
9 #include <armnn/Descriptors.hpp>
10
11 #include <GraphTopologicalSort.hpp>
12 #include <ParserHelper.hpp>
13 #include <Permute.hpp>
14 #include <DataLayoutIndexed.hpp>
15
16 #include <google/protobuf/io/zero_copy_stream_impl.h>
17 #include <google/protobuf/text_format.h>
18
19 #include "tensorflow/core/framework/graph.pb.h"
20
21 #include <boost/format.hpp>
22 #include <boost/core/ignore_unused.hpp>
23 #include <boost/polymorphic_cast.hpp>
24
25 #include <numeric>
26
27 using namespace armnnUtils;
28 using namespace armnn;
29
30 namespace armnnTfParser
31 {
32 namespace
33 {
34
35 const PermutationVector NHWCToArmNN = { 0, 2, 3, 1 };
36 const PermutationVector ArmNNToNHWC = { 0, 3, 1, 2 };
37
38
39 template <typename Callable>
40 void ReadMandatoryNodeAttributeImpl(const tensorflow::NodeDef& nodeDef,
41     const std::string& attribName,
42     tensorflow::AttrValue::ValueCase expectedValueCase,
43     Callable callable)
44 {
45     auto iter = nodeDef.attr().find(attribName);
46     if (iter != nodeDef.attr().end())
47     {
48         const auto& attrValue = iter->second;
49         if (attrValue.value_case() == expectedValueCase)
50         {
51             callable(attrValue);
52         }
53         else
54         {
55             throw ParseException(
56                 boost::str(
57                     boost::format(
58                         "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, "
59                         "but found %4% instead %5%")
60                         % attribName
61                         % nodeDef.name()
62                         % static_cast<int>(expectedValueCase)
63                         % static_cast<int>(attrValue.value_case())
64                         % CHECK_LOCATION().AsString()));
65         }
66     }
67     else
68     {
69         throw ParseException(
70             boost::str(
71                 boost::format(
72                     "Could not find required attribute %1% in node %2% %3%")
73                     % attribName
74                     % nodeDef.name()
75                     % CHECK_LOCATION().AsString()));
76     }
77 }
78
79 template <typename Callable>
80 void ReadOptionalNodeAttributeImpl(const tensorflow::NodeDef& nodeDef,
81     const std::string& attribName,
82     tensorflow::AttrValue::ValueCase expectedValueCase,
83     Callable callable)
84 {
85     auto iter = nodeDef.attr().find(attribName);
86     if (iter != nodeDef.attr().end())
87     {
88         const auto& attrValue = iter->second;
89         if (attrValue.value_case() == expectedValueCase)
90         {
91             callable(attrValue);
92         }
93         else
94         {
95             throw ParseException(
96                 boost::str(
97                     boost::format(
98                         "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, "
99                         "but found %4% instead %5%")
100                         % attribName
101                         % nodeDef.name()
102                         % static_cast<int>(expectedValueCase)
103                         % static_cast<int>(attrValue.value_case())
104                         % CHECK_LOCATION().AsString()));
105         }
106     }
107 }
108
109 float ReadMandatoryNodeFloatAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
110 {
111     float attribValue = 0.0f;
112     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kF,
113         [&attribValue](const tensorflow::AttrValue& attrValue)
114     {
115         attribValue = attrValue.f();
116     });
117     return attribValue;
118 }
119
120 int32_t ReadMandatoryNodeInt32Attribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
121 {
122     int32_t attribValue = 0u;
123     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kI,
124                                    [&attribValue](const tensorflow::AttrValue& attrValue)
125                                    {
126                                        attribValue = static_cast<int32_t>(attrValue.i());
127                                    });
128     return attribValue;
129 }
130
131 bool ReadMandatoryNodeBoolAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
132 {
133     bool attribValue = false;
134     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kB,
135                                    [&attribValue](const tensorflow::AttrValue& attrValue)
136                                    {
137                                        attribValue = static_cast<bool>(attrValue.b());
138                                    });
139     return attribValue;
140 }
141
142 uint32_t ReadMandatoryNodeUint32Attribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
143 {
144     uint32_t attribValue = 0u;
145     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kI,
146         [&attribValue](const tensorflow::AttrValue& attrValue)
147     {
148         attribValue = static_cast<uint32_t>(attrValue.i());
149     });
150     return attribValue;
151 }
152
153 std::string ReadMandatoryNodeStringAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
154 {
155     std::string attribValue = "";
156     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kS,
157         [&attribValue](const tensorflow::AttrValue& attrValue)
158     {
159         attribValue = attrValue.s();
160     });
161     return attribValue;
162 }
163
164 std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef,
165     const std::string& name)
166 {
167     std::vector<uint32_t> attriList;
168     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList,
169         [&attriList](const tensorflow::AttrValue& attrValue)
170     {
171         for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum)
172         {
173             attriList.push_back(static_cast<uint32_t>(attrValue.list().i(attriNum)));
174         }
175     });
176
177     return attriList;
178 }
179
180 std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef,
181     const std::string& name)
182 {
183     std::vector<uint32_t> attriList;
184     ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList,
185         [&attriList](const tensorflow::AttrValue& attrValue)
186     {
187         for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum)
188         {
189             attriList.push_back(static_cast<uint32_t>(attrValue.list().i(attriNum)));
190         }
191     });
192
193     return attriList;
194 }
195
196 bool ReadOptionalNodeBoolAttribute(const tensorflow::NodeDef& nodeDef,
197     const std::string& name,
198     bool defaultValue = false)
199 {
200     bool attribValue = defaultValue;
201     ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kB,
202         [&attribValue](const tensorflow::AttrValue& attrValue)
203     {
204         attribValue = attrValue.b();
205     });
206     return attribValue;
207 }
208
209 tensorflow::DataType ReadMandatoryNodeTypeAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
210 {
211     tensorflow::DataType attribValue = tensorflow::DT_INVALID;
212     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kType,
213         [&attribValue](const tensorflow::AttrValue& attrValue)
214     {
215         attribValue = attrValue.type();
216     });
217     return attribValue;
218 }
219
220 TensorInfo PrepareReshape(const TensorInfo& input, const std::vector<int32_t>& targetDims)
221 {
222     std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
223     const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
224
225     if (stretchDim != targetDims.end())
226     {
227         if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
228         {
229             throw ParseException(
230                 boost::str(
231                     boost::format(
232                         "At most one component of shape can be -1 %1%")
233                         % CHECK_LOCATION().AsString()));
234         }
235
236         auto targetNumElements =
237             boost::numeric_cast<unsigned int>(
238                 std::accumulate(targetDims.begin(), targetDims.end(), -1, std::multiplies<int32_t>()));
239         auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
240         outDims[stretchIndex] = input.GetNumElements() / targetNumElements;
241     }
242
243     TensorInfo reshapeInfo = input;
244     reshapeInfo.SetShape(TensorShape{ static_cast<unsigned int>(outDims.size()), outDims.data() });
245
246     return reshapeInfo;
247 }
248
249 // We need the input0Slot to guide the reshape for input1Slot.
250 IOutputSlot* AddBroadcastReshapeLayer(IOutputSlot* input0Slot, IOutputSlot* input1Slot, bool isNHWC,
251                                          INetwork& m_Network, const tensorflow::NodeDef& nodeDef)
252 {
253     const TensorInfo& input1Info = input1Slot->GetTensorInfo();
254     const TensorInfo inputTensorInfo = input0Slot->GetTensorInfo();
255     const unsigned int matchDim = inputTensorInfo.GetNumDimensions() - (isNHWC ? 1 : 3);
256     std::array<unsigned int, MaxNumOfTensorDimensions> reshapedDimensions;
257     std::fill_n(reshapedDimensions.begin(), inputTensorInfo.GetNumDimensions(), 1);
258     reshapedDimensions[matchDim] = input1Info.GetShape()[0];
259
260     armnn::TensorInfo reshapedInfo = input1Info;
261     reshapedInfo.SetShape(TensorShape{ inputTensorInfo.GetNumDimensions(), reshapedDimensions.data() });
262
263     const std::string reshapeLayerName = "reshape_for-" + nodeDef.name();
264     ReshapeDescriptor reshapeDesc;
265     reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
266     IConnectableLayer* const reshapeLayer = m_Network.AddReshapeLayer(reshapeDesc, reshapeLayerName.c_str());
267
268     input1Slot->Connect(reshapeLayer->GetInputSlot(0));
269     reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
270
271     input1Slot = &reshapeLayer->GetOutputSlot(0);
272
273     return input1Slot;
274 }
275
276 OutputId ParseOutputId(const std::string & name)
277 {
278     unsigned int outputNum = 0;
279     size_t colonPos = name.find_last_of(":");
280     if (colonPos != std::string::npos)
281     {
282         int n = std::stoi(name.substr(colonPos+1));
283         if (n<0 || n>100)
284         {
285             throw ParseException(
286                 boost::str(
287                     boost::format(
288                         "Output tensor id is out of range for %1% %2%")
289                         % name
290                         % CHECK_LOCATION().AsString()));
291         }
292         outputNum = static_cast<unsigned int>(n);
293     }
294     return OutputId(name.substr(0,colonPos),outputNum);
295 }
296
297 #define CHECK_DATA_FORMAT(NODE_DEF, FORMAT, NODE_TYPE) \
298     if( FORMAT != "NHWC" && FORMAT != "NCHW" ) \
299     { \
300         throw ParseException( \
301             boost::str( \
302                 boost::format( \
303                     "Unsupported data format %1% passed for %2% node %3%. " \
304                     "Only NHWC and NCHW supported %4%") \
305                     % FORMAT \
306                     % NODE_TYPE \
307                     % NODE_DEF.name() \
308                     % CHECK_LOCATION().AsString())); \
309     }
310
311 #define CHECK_PADDING_TYPE(NODE_DEF, PADDING) \
312     if(PADDING != "SAME" && PADDING != "VALID" ) \
313     { \
314         throw ParseException( \
315             boost::str( \
316                 boost::format( \
317                     "Only 'SAME' and 'VALID' padding supported. Got %1% for %2% %3%") \
318                     % PADDING \
319                     % NODE_DEF.name() \
320                     % CHECK_LOCATION().AsString())); \
321     } \
322
323 } // namespace
324
325 const std::map<std::string, TfParser::OperationParsingFunction> TfParser::ms_OperationNameToParsingFunctions = {
326     { "Const",                 &TfParser::ParseConst },
327     { "Add",                   &TfParser::ParseAdd },
328     { "AddN",                  &TfParser::ParseAddN },
329     { "BiasAdd",               &TfParser::ParseBiasAdd },
330     { "Identity",              &TfParser::ParseIdentity },
331     { "Conv2D",                &TfParser::ParseConv2D },
332     { "DepthwiseConv2dNative", &TfParser::ParseDepthwiseConv2D },
333     { "ExpandDims",            &TfParser::ParseExpandDims },
334     { "FusedBatchNorm",        &TfParser::ParseFusedBatchNorm },
335     { "Greater",               &TfParser::ParseGreater},
336     { "ConcatV2",              &TfParser::ParseConcat },
337     { "LRN",                   &TfParser::ParseLrn },
338     { "MatMul",                &TfParser::ParseMatMul },
339     { "Mean",                  &TfParser::ParseMean },
340     { "Mul",                   &TfParser::ParseMul },
341     { "Placeholder",           &TfParser::ParsePlaceholder },
342     { "RealDiv",               &TfParser::ParseRealDiv },
343     { "Relu",                  &TfParser::ParseRelu },
344     { "Relu6",                 &TfParser::ParseRelu6 },
345     { "Reshape",               &TfParser::ParseReshape },
346     { "ResizeBilinear",        &TfParser::ParseResizeBilinear },
347     { "Rsqrt",                 &TfParser::ParseRsqrt },
348     { "Shape",                 &TfParser::ParseShape },
349     { "Squeeze",               &TfParser::ParseSqueeze },
350     { "Sigmoid",               &TfParser::ParseSigmoid },
351     { "Softmax",               &TfParser::ParseSoftmax },
352     { "Softplus",              &TfParser::ParseSoftplus },
353     { "Split",                 &TfParser::ParseSplit },
354     { "Tanh",                  &TfParser::ParseTanh },
355     { "MaxPool",               &TfParser::ParseMaxPool },
356     { "AvgPool",               &TfParser::ParseAvgPool },
357     { "Maximum",               &TfParser::ParseMaximum },
358     { "Minimum",               &TfParser::ParseMinimum },
359     { "Equal",                 &TfParser::ParseEqual },
360     { "Pad",                   &TfParser::ParsePad },
361     { "Sub",                   &TfParser::ParseSub }
362 };
363
364 const std::list<std::string> TfParser::m_ControlInputs = {
365     "Assert"
366 };
367
368 ITfParser* ITfParser::CreateRaw()
369 {
370     return new TfParser();
371 }
372
373 ITfParserPtr ITfParser::Create()
374 {
375     return ITfParserPtr(CreateRaw(), &ITfParser::Destroy);
376 }
377
378 void ITfParser::Destroy(ITfParser* parser)
379 {
380     delete parser;
381 }
382
383 inline void CalculateSamePadding(uint32_t inputSize, uint32_t stride,
384                                  uint32_t filterSize, bool samePadding,
385                                  uint32_t* paddingFront, uint32_t* paddingBack) {
386     *paddingFront = 0;
387     *paddingBack = 0;
388
389     if (samePadding) {
390         uint32_t outputSize = (inputSize + stride - 1) / stride;
391         uint32_t temp = (outputSize - 1) * stride + filterSize;
392         if (temp > inputSize) {
393             *paddingFront = (temp - inputSize) / 2;
394             *paddingBack = (temp - inputSize) - *paddingFront;
395         }
396     }
397 }
398
399 void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
400                  bool samePadding)
401 {
402     CalculateSamePadding(input, stride, kernel, samePadding, &outPadHead, &outPadTail);
403 }
404
405 /// An Abstract base class which represents a single tensorflow operation (node)
406 /// that has been (potentially partially) converted to Armnn.
407 /// It may not yet have been fully converted into actual Armnn layers.
408 class ParsedTfOperation
409 {
410 public:
411     ParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
412     : m_Parser(parser)
413     , m_Node(node)
414     {
415     }
416
417     virtual ~ParsedTfOperation() {};
418
419     const tensorflow::NodeDef& GetNode() const { return m_Node; }
420
421     /// Gets the ArmNN IOutputSlot corresponding to the given output index of the Tensorflow operation.
422     /// This may result in the creation of Armnn layers if this was deferred (e.g. see ParsedConstTfOperation).
423     virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) = 0;
424
425     /// If this operation is an Identity then this will follow return the 'parent' operation (recursively).
426     virtual ParsedTfOperation* ResolveIdentityOperations()
427     {
428         return this;
429     }
430
431 protected:
432     TfParser* m_Parser;
433     const tensorflow::NodeDef& m_Node;
434 };
435
436 /// An ParsedTfOperation where the Armnn equivalent is a single layer,
437 /// with output slots that correspond directly to the Tf node outputs.
438 class SingleLayerParsedTfOperation : public ParsedTfOperation
439 {
440 public:
441     SingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node, IConnectableLayer* layer)
442     : ParsedTfOperation(parser, node)
443     , m_Layer(layer)
444     {
445     }
446
447     IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
448     {
449         BOOST_ASSERT(m_Layer);
450         // Assumes one-to-one mapping between Tf and armnn output slots.
451         unsigned int armnnOutputSlotIdx = tfOutputIndex;
452         if (armnnOutputSlotIdx >= m_Layer->GetNumOutputSlots())
453         {
454             throw ParseException(
455                 boost::str(
456                     boost::format(
457                         "The requested output slot #%1% "
458                         "for %2% does not exist %3%")
459                         % armnnOutputSlotIdx
460                         % m_Layer->GetName()
461                         % CHECK_LOCATION().AsString()));
462         }
463         return m_Layer->GetOutputSlot(armnnOutputSlotIdx);
464     }
465
466 protected:
467     IConnectableLayer* m_Layer;
468 };
469
470 /// A SingleLayerParsedTfOperation for deferred layer creation.
471 class DeferredSingleLayerParsedTfOperation : public SingleLayerParsedTfOperation
472 {
473 public:
474     DeferredSingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
475     : SingleLayerParsedTfOperation(parser, node, nullptr)
476     {
477     }
478
479     IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
480     {
481         if (!m_Layer)
482         {
483             CreateLayerDeferred();
484         }
485         return SingleLayerParsedTfOperation::ResolveArmnnOutputSlot(tfOutputIndex);
486     }
487
488 private:
489     virtual void CreateLayerDeferred() = 0;
490 };
491
492
493 TfParser::TfParser()
494     : m_Network(nullptr, nullptr)
495 {
496 }
497
498
499 const tensorflow::NodeDef* TfParser::ResolveIdentityNode(const tensorflow::NodeDef* nodeDef)
500 {
501     if (nodeDef->op() != "Identity")
502     {
503         return nodeDef;
504     }
505
506     if (nodeDef->input_size() != 1)
507     {
508         throw ParseException(
509             boost::str(
510                 boost::format(
511                     "Identity node should have a single input! %1% has %2% inputs %3%")
512                     % nodeDef->name()
513                     % nodeDef->input_size()
514                     % CHECK_LOCATION().AsString()));
515     }
516
517     auto it = m_NodesByName.find(nodeDef->input(0));
518     if (it != m_NodesByName.end())
519     {
520         const tensorflow::NodeDef* inputNode = it->second;
521         return ResolveIdentityNode(inputNode);
522     }
523     else
524     {
525         throw ParseException(
526             boost::str(
527                 boost::format(
528                     "Cannot find what the Identity node %1% is linked to! %2%")
529                     % nodeDef->name()
530                     % CHECK_LOCATION().AsString()));
531     }
532 }
533
534 std::vector<OutputOfConstNodeDef>
535 TfParser::GetTfInputNodes(const tensorflow::NodeDef& nodeDef) const
536 {
537     std::vector<OutputOfConstNodeDef> ret;
538
539     if (nodeDef.op() == "Const")
540     {
541         // For some reason const node can have "Control Inputs". We ignore them for now.
542         return ret;
543     }
544
545     ret.reserve(boost::numeric_cast<size_t>(nodeDef.input_size()));
546     for (int j = 0; j < nodeDef.input_size(); ++j)
547     {
548         OutputId outputId = ParseOutputId(nodeDef.input(j));
549
550         if (nodeDef.input(j)[0] == '^') // I couldn't find a better test for control inputs.
551         {
552             // We currently allow Control Input from TensorFlow graph but we ignore them from ArmNN graph.
553             continue;
554         }
555
556         auto inputIt = m_NodesByName.find(outputId.m_IndexedValue);
557         if (inputIt == m_NodesByName.end())
558         {
559             throw ParseException(
560                 boost::str(
561                     boost::format(
562                         "Can't find node '%1%', which is listed as an input of '%2%' %3%")
563                         % nodeDef.input(j)
564                         % nodeDef.name()
565                         % CHECK_LOCATION().AsString()));
566         }
567         ret.push_back(OutputOfConstNodeDef(inputIt->second,outputId.m_Index));
568     }
569
570     return ret;
571 }
572
573 std::vector<OutputOfParsedTfOperation>
574 TfParser::GetInputParsedTfOperationsChecked(const tensorflow::NodeDef& nodeDef,
575                                             std::size_t expectedNumInputs)
576 {
577     // Fetches the tensorflow nodes connected as inputs and validate the size.
578     std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
579     const std::size_t numInputs = nodes.size();
580     if (numInputs != expectedNumInputs)
581     {
582         throw ParseException(
583             boost::str(
584                 boost::format(
585                     "Unexpected number of inputs for node %1%. Expected %2%, found %3% %4%")
586                     % nodeDef.name()
587                     % expectedNumInputs
588                     % numInputs
589                     % CHECK_LOCATION().AsString()));
590     }
591     // Fetches the corresponding ParsedTfOperation operations
592     std::vector<OutputOfParsedTfOperation> result;
593     for (auto&& node : nodes)
594     {
595         auto it = m_ParsedTfOperations.find(node.m_IndexedValue->name());
596         if (it == m_ParsedTfOperations.end())
597         {
598             throw ParseException(
599                 boost::str(
600                     boost::format(
601                         "Node with name '%1%' has not been parsed %2%")
602                         % node.m_IndexedValue->name()
603                         % CHECK_LOCATION().AsString()));
604         }
605         ParsedTfOperation* parsedOp = it->second.get();
606         // Transparently 'skip' any Identity operations. This simplifies the logic inside the ParseXXX() functions.
607         parsedOp = parsedOp->ResolveIdentityOperations();
608         result.push_back(OutputOfParsedTfOperation(parsedOp,node.m_Index));
609     }
610     return result;
611 }
612
613 IConnectableLayer* TfParser::CreateAdditionLayer(
614             const tensorflow::NodeDef& nodeDef,
615             IOutputSlot* input0Slot,
616             IOutputSlot* input1Slot,
617             const std::string& layerName)
618 {
619     const TensorInfo& input0Info = input0Slot->GetTensorInfo();
620     const TensorInfo& input1Info = input1Slot->GetTensorInfo();
621
622     const unsigned int input0Dim = input0Info.GetNumDimensions();
623     const unsigned int input1Dim = input1Info.GetNumDimensions();
624     if (input0Dim != input1Dim)
625     {
626         // broadcasting where input0 and input1 have different number of dimensions
627         // is only supported for 1D and 4D tensors pair
628         if (input0Dim == 1 && input1Dim == 4)
629         {
630             input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, true, *m_Network, nodeDef);
631         }
632         else if (input0Dim == 4 && input1Dim == 1)
633         {
634             input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, true, *m_Network, nodeDef);
635         }
636         else
637         {
638             throw ParseException(
639                     boost::str(
640                             boost::format("Unsupported broadcast configuration for %1% operation %2% %3%")
641                             % layerName
642                             % nodeDef.name()
643                             % CHECK_LOCATION().AsString()));
644         }
645     }
646     IConnectableLayer* const layer = m_Network->AddAdditionLayer(layerName.c_str());
647
648     input0Slot->Connect(layer->GetInputSlot(0));
649     input1Slot->Connect(layer->GetInputSlot(1));
650
651     // Ensure the output tensor has the correct dimensions even if a broadcast has been done
652     TensorInfo outputInfo = input0Slot->GetTensorInfo();
653     std::vector<unsigned int> outputShape;
654
655     const TensorShape& input0Shape = input0Slot->GetTensorInfo().GetShape();
656     const TensorShape& input1Shape = input1Slot->GetTensorInfo().GetShape();
657
658     for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++)
659     {
660         outputShape.push_back(std::max(input0Shape[i], input1Shape[i]));
661     }
662
663     outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data()));
664     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
665
666     return layer;
667 }
668
669 IConnectableLayer* TfParser::CreateAdditionLayer(
670             const tensorflow::NodeDef& nodeDef,
671             IConnectableLayer* layerOne,
672             IConnectableLayer* layerTwo,
673             unsigned int numberOfAddition,
674             unsigned long numberOfLayersToConnect,
675             bool isOdd)
676 {
677     IOutputSlot* input0Slot = &layerOne->GetOutputSlot(0);
678     IOutputSlot* input1Slot = &layerTwo->GetOutputSlot(0);
679     std::string layerName(nodeDef.name());
680     if (isOdd || numberOfLayersToConnect != 2)
681     {
682         // we are not connecting the final layer
683         layerName.append("_addN_").append(std::to_string(numberOfAddition));
684     }
685     return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, layerName);
686 }
687
688 IConnectableLayer* TfParser::CreateAdditionLayer(
689         const tensorflow::NodeDef& nodeDef,
690         const OutputOfParsedTfOperation& opOne,
691         const OutputOfParsedTfOperation& opTwo,
692         unsigned int numberOfAddition)
693 {
694     IOutputSlot* input0Slot = &opOne.m_IndexedValue->ResolveArmnnOutputSlot(opOne.m_Index);
695     IOutputSlot* input1Slot = &opTwo.m_IndexedValue->ResolveArmnnOutputSlot(opTwo.m_Index);
696     std::string layerName(nodeDef.name());
697     layerName.append("_addN_").append(std::to_string(numberOfAddition));
698     return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, layerName);
699 }
700
701 IConnectableLayer* TfParser::CreateAdditionLayer(
702             const tensorflow::NodeDef& nodeDef,
703             const OutputOfParsedTfOperation& op,
704             IConnectableLayer* layer)
705 {
706     IOutputSlot* input0Slot = &op.m_IndexedValue->ResolveArmnnOutputSlot(op.m_Index);
707     IOutputSlot* input1Slot = &layer->GetOutputSlot(0);
708     return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, nodeDef.name());
709 }
710
711 ParsedTfOperationPtr TfParser::ParseAddN(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
712 {
713     uint32_t numberOfInputs = ReadMandatoryNodeUint32Attribute(nodeDef, "N");
714     if (numberOfInputs < 2)
715     {
716         // should never happen
717         throw ParseException(
718                 boost::str(
719                         boost::format(
720                                 "AddN Node with name '%1%' has less than two (%2) inputs %3%")
721                         % nodeDef.name()
722                         % std::to_string(numberOfInputs)
723                         % CHECK_LOCATION().AsString()));
724     }
725     else if (numberOfInputs == 2)
726     {
727         //this is the same as a simple Add operation
728         return AddAdditionLayer(nodeDef, false);
729     }
730     else
731     {
732         // build a binary tree of Add layers and return the final Add as the return from the function
733         // if we have an odd number of inputs then the final Add will consist of a layer connecting to an
734         // OutputOfParsedTfOperation, otherwise it will be two layers being added together
735         std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numberOfInputs);
736         unsigned int numberOfAdditions = 0;
737         std::vector<IConnectableLayer*> layers;
738         // NOTE: at this point we will have a minimum of three inputs
739         for (unsigned int i = 0; i < numberOfInputs; ++i)
740         {
741             // every time i is odd we have two inputs to process.
742             bool onSecondItem = i % 2;
743             if (onSecondItem)
744             {
745                 ++numberOfAdditions;
746                 IConnectableLayer* newLayer = CreateAdditionLayer(
747                         nodeDef, inputs[ i - 1], inputs[i], numberOfAdditions);
748                 layers.push_back(newLayer);
749             }
750         }
751
752         std::vector<IConnectableLayer*> layersToConnect(layers);
753         unsigned long numberOfLayersToConnect = layersToConnect.size();
754         bool isOdd = numberOfInputs % 2;
755
756         while (numberOfLayersToConnect > 1)
757         {
758             layers.clear();
759             for (unsigned long i = 0; i < numberOfLayersToConnect; ++i) {
760                 bool onSecondItem = i % 2;
761                 if (onSecondItem) {
762                     ++numberOfAdditions;
763                     IConnectableLayer* newLayer = CreateAdditionLayer(
764                         nodeDef,
765                         layersToConnect[i - 1],
766                         layersToConnect[i],
767                         numberOfAdditions,
768                         numberOfLayersToConnect,
769                         isOdd);
770                     layers.push_back(newLayer);
771                 }
772             }
773             //OK... need to go again... maybe
774             layersToConnect = layers;
775             numberOfLayersToConnect = layersToConnect.size();
776         }
777         IConnectableLayer* finalLayer = layersToConnect[0];
778         // if we had an odd number of inputs we need to connect the final layer to the
779         // last OutputOfParsedTfOperation in order to create the last Add layer we will
780         // be handing back.
781         if (isOdd)
782         {
783             // connect the final layer to the last op
784             finalLayer = CreateAdditionLayer(nodeDef, inputs[numberOfInputs - 1], finalLayer);
785         }
786         return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, finalLayer);
787     }
788 }
789
790 ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
791 {
792     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
793
794     // If one of the inputs is a MatMul and the other is a const, then we handle both nodes
795     // together as FullyConnected.
796     if (inputs[0].m_IndexedValue->GetNode().op() == "MatMul" &&
797         HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
798     {
799         IConnectableLayer* layer =
800             AddFullyConnectedLayer(inputs[0].m_IndexedValue->GetNode(),
801                                    &nodeDef,nodeDef.name().c_str());
802         return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
803     }
804     else if (HasParsedConstTensor<float>(inputs[0].m_IndexedValue->GetNode().name()) &&
805                                          inputs[1].m_IndexedValue->GetNode().op() == "MatMul")
806     {
807         IConnectableLayer* layer =
808             AddFullyConnectedLayer(inputs[1].m_IndexedValue->GetNode(),
809                                    &nodeDef,nodeDef.name().c_str());
810         return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
811     }
812     else
813     {
814         // Otherwise it's just a regular addition.
815         return AddAdditionLayer(nodeDef);
816     }
817 }
818
819 ParsedTfOperationPtr TfParser::ParseBiasAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
820 {
821     return AddAdditionLayer(nodeDef, true);
822 }
823
824 /// An ParsedTfOperation which forwards to another (used for Identity nodes).
825 class ParsedIdentityTfOperation : public ParsedTfOperation
826 {
827 public:
828     ParsedIdentityTfOperation(TfParser* parser, const tensorflow::NodeDef& node, ParsedTfOperation* representative)
829         : ParsedTfOperation(parser, node)
830         , m_Representative(representative)
831     {
832     }
833
834     virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
835     {
836         BOOST_ASSERT(m_Representative);
837         return m_Representative->ResolveArmnnOutputSlot(tfOutputIndex);
838     }
839
840     virtual ParsedTfOperation* ResolveIdentityOperations() override
841     {
842         return m_Representative->ResolveIdentityOperations();
843     }
844
845 private:
846     ParsedTfOperation* m_Representative;
847 };
848
849 ParsedTfOperationPtr TfParser::ParseIdentity(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
850 {
851     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
852     // Any requests for the output slots of this node should be forwarded to the node connected as input.
853     return std::make_unique<ParsedIdentityTfOperation>(this, nodeDef, inputs[0].m_IndexedValue);
854 }
855
856 /// An ParsedTfOperation for a Const node.
857 /// Creation of the armnn ConstLayer is deferred until it is actually needed, because Const nodes are mostly used
858 /// for weight inputs to MatMul/Conv2D nodes and in these cases armnn doesn't need a ConstLayer.
859 template <typename T>
860 class ParsedConstTfOperation : public DeferredSingleLayerParsedTfOperation
861 {
862 public:
863     ParsedConstTfOperation(TfParser* parser, const tensorflow::NodeDef& node,
864         const T* tensorData, const TensorInfo& tensorInfo)
865         : DeferredSingleLayerParsedTfOperation(parser, node),
866         m_Storage(tensorData, tensorData + tensorInfo.GetNumElements()),
867         m_TensorInfo(tensorInfo)
868     {
869         BOOST_ASSERT(GetDataTypeSize(tensorInfo.GetDataType()) == sizeof(T));
870     }
871
872     void CreateLayerDeferred() override
873     {
874         BOOST_ASSERT(m_Layer == nullptr);
875         m_Layer = m_Parser->m_Network->AddConstantLayer(ConstTensor(m_TensorInfo, m_Storage), m_Node.name().c_str());
876         m_Layer->GetOutputSlot(0).SetTensorInfo(m_TensorInfo);
877     }
878
879     ConstTensor GetConstTensor(std::vector<T>& outputTensorData) const
880     {
881         outputTensorData.resize(m_TensorInfo.GetNumElements());
882
883         memcpy(outputTensorData.data(), m_Storage.data(), m_TensorInfo.GetNumBytes());
884
885         // Updates the result to point to the user provided storage.
886         ConstTensor constTensor(m_TensorInfo, outputTensorData);
887         return constTensor;
888     }
889
890     const T* GetStorage() const
891     {
892         return m_Storage.data();
893     }
894
895     const TensorInfo& GetTensorInfo() const
896     {
897         return m_TensorInfo;
898     }
899
900 private:
901     ///< Manages the lifetime of the tensor data.
902     std::vector<T> m_Storage;
903     ///< Describes the layout of the tensor and points to the data in m_Storage.
904     TensorInfo m_TensorInfo;
905 };
906
907 DataType ConvertTfTensorDataType(const tensorflow::DataType tfDataType,
908                                  const tensorflow::NodeDef& nodeDef)
909 {
910     switch (tfDataType)
911     {
912     case tensorflow::DT_FLOAT:
913         return DataType::Float32;
914         break;
915     case tensorflow::DT_INT32:
916         return DataType::Signed32;
917         break;
918     default:
919         throw ParseException(
920             boost::str(
921                 boost::format(
922                     "Unknown DataType %1% for node %2% %3%")
923                     % tensorflow::DataType_Name(tfDataType)
924                     % nodeDef.name()
925                     % CHECK_LOCATION().AsString()));
926     }
927 }
928
929 struct ParseTfTensorValueList
930 {
931     template<typename DataType>
932     static void Parse(
933         const tensorflow::TensorProto& tfTensor,
934         unsigned int dstElements,
935         std::vector<int8_t>& outputData);
936
937     template <typename DataType>
938     static void ReadData(const void* srcData, unsigned int numSrcElements,
939         std::vector<int8_t>& dstData, unsigned int numDstElements)
940     {
941         // If there are no entries in the list, perform no action.
942         if (numSrcElements == 0)
943         {
944             return;
945         }
946
947         // If no size was provided, use the length of the value list.
948         if (numDstElements == 0)
949         {
950             numDstElements = numSrcElements;
951         }
952
953         // Allocates memory.
954         dstData.resize(std::max(numSrcElements, numDstElements) * sizeof(DataType));
955
956         const DataType* srcTensor = reinterpret_cast<const DataType*>(srcData);
957         DataType* dstTensor = reinterpret_cast<DataType*>(dstData.data());
958
959         // Copies the value list entries into the destination.
960         std::copy(srcTensor, srcTensor + numSrcElements, dstTensor);
961
962         if (numDstElements > numSrcElements)
963         {
964             // Uses the last element in the list to fill the remaining entries.
965             std::fill(dstTensor + numSrcElements, dstTensor + numDstElements, srcTensor[numSrcElements - 1]);
966         }
967     }
968
969 };
970
971 template <>
972 void ParseTfTensorValueList::Parse<float>(const tensorflow::TensorProto& tfTensor,
973     unsigned int dstElements, std::vector<int8_t>& outputData)
974 {
975     ReadData<float>(tfTensor.float_val().data(), static_cast<unsigned int>(tfTensor.float_val_size()),
976         outputData, dstElements);
977 }
978
979 template <>
980 void ParseTfTensorValueList::Parse<int32_t>(const tensorflow::TensorProto& tfTensor,
981     unsigned int dstElements, std::vector<int8_t>& outputData)
982 {
983     ReadData<int32_t>(tfTensor.int_val().data(), static_cast<unsigned int>(tfTensor.int_val_size()),
984         outputData, dstElements);
985 }
986
987 template <template<typename> class OperatorType, typename T = int8_t>
988 struct MakeTfOperation
989 {
990     template<typename DataType, class... Args>
991     inline static std::unique_ptr<OperatorType<DataType>> Parse(TfParser* parser, const tensorflow::NodeDef& node,
992         Args&&... args)
993     {
994         return std::make_unique<OperatorType<DataType>>(parser, node, std::forward<Args>(args)...);
995     }
996 };
997
998 template <>
999 struct MakeTfOperation<ParsedConstTfOperation>
1000 {
1001     template<typename DataType, class... Args>
1002     inline static std::unique_ptr<ParsedConstTfOperation<DataType>> Parse(TfParser* parser,
1003         const tensorflow::NodeDef& node, const std::vector<int8_t>& tensorData, const TensorInfo& tensorInfo)
1004     {
1005         return std::make_unique<ParsedConstTfOperation<DataType>>(parser, node,
1006             reinterpret_cast<const DataType*>(tensorData.data()), tensorInfo);
1007     }
1008 };
1009
1010 template <class FuncType>
1011 struct InvokeParseFunction
1012 {
1013     template<class ResType, class... Args>
1014     inline static ResType Result(DataType dataType, Args&&... args)
1015     {
1016         if (dataType == DataType::Float32)
1017         {
1018             return FuncType::template Parse<float>(std::forward<Args>(args)...);
1019         }
1020         else if (dataType == DataType::Signed32)
1021         {
1022             return FuncType::template Parse<int32_t>(std::forward<Args>(args)...);
1023         }
1024
1025         return ResType();
1026     }
1027
1028     template<class... Args>
1029     inline static void Result(DataType dataType, Args&&... args)
1030     {
1031         if (dataType == DataType::Float32)
1032         {
1033             FuncType::template Parse<float>(std::forward<Args>(args)...);
1034         }
1035         else if (dataType == DataType::Signed32)
1036         {
1037             FuncType::template Parse<int32_t>(std::forward<Args>(args)...);
1038         }
1039     }
1040 };
1041
1042 ParsedTfOperationPtr TfParser::ParseConst(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1043 {
1044     BOOST_ASSERT(nodeDef.op() == "Const");
1045
1046     if (nodeDef.attr().count("value") == 0)
1047     {
1048         throw ParseException(
1049             boost::str(
1050                 boost::format(
1051                     "Value not found for Const node - %1% %2%")
1052                     % nodeDef.name()
1053                     % CHECK_LOCATION().AsString()));
1054     }
1055
1056     const tensorflow::TensorProto& tfTensor = nodeDef.attr().at("value").tensor();
1057     const tensorflow::TensorShapeProto& tfTensorShape = tfTensor.tensor_shape();
1058     const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "dtype");
1059
1060     const auto GetDimensionSize = [](auto& d) { return d.size(); };
1061
1062     std::vector<unsigned int> dimensionSizes;
1063     std::transform(tfTensorShape.dim().begin(), tfTensorShape.dim().end(),
1064         std::back_inserter(dimensionSizes), GetDimensionSize);
1065
1066     // Calculates number of elements.
1067     const DataType dataType = ConvertTfTensorDataType(tfDataType, nodeDef);
1068     unsigned int numElements = 0U;
1069
1070     if (!dimensionSizes.empty())
1071     {
1072         numElements = std::accumulate(dimensionSizes.begin(), dimensionSizes.end(),
1073                                       1U, std::multiplies<unsigned int>());
1074     }
1075
1076     std::vector<int8_t> tensorData;
1077
1078     // Get tensor data from the list of values attribute.
1079     if (tfTensor.tensor_content().empty())
1080     {
1081         InvokeParseFunction<ParseTfTensorValueList>::Result<void>(dataType, tfTensor, numElements, tensorData);
1082
1083         // If the tensor shape is not defined, but there is a value list, then interpret the data as a 1D
1084         // tensor of the provided number of elements.
1085         if (numElements == 0)
1086         {
1087             const unsigned int tfNumElements =
1088                 static_cast<unsigned int>(tensorData.size()) / GetDataTypeSize(dataType);
1089             dimensionSizes.push_back(tfNumElements);
1090         }
1091     }
1092     // Gets tensor data from tensor content attribute.
1093     else
1094     {
1095         tensorData.assign(tfTensor.tensor_content().begin(), tfTensor.tensor_content().end());
1096
1097         // Checks if a tensor shape is defined for the tensor content.
1098         if (numElements == 0)
1099         {
1100             throw ParseException(
1101                 boost::str(
1102                     boost::format(
1103                         "No tensor shape found for Const node - %1% %2%")
1104                         % nodeDef.name()
1105                         % CHECK_LOCATION().AsString()));
1106         }
1107     }
1108
1109     // Const node requires at least a list of values or a content attribute.
1110     if (tensorData.empty())
1111     {
1112         throw ParseException(
1113             boost::str(
1114                 boost::format(
1115                     "No tensor data found for Const node - %1% %2%")
1116                     % nodeDef.name()
1117                     % CHECK_LOCATION().AsString()));
1118     }
1119
1120     const TensorInfo tensorInfo(static_cast<unsigned int>(dimensionSizes.size()),
1121                                 dimensionSizes.data(),
1122                                 dataType);
1123
1124     // If we have a list of values, then the length of the list must be
1125     // less than or equal to the number of elements implied by the shape argument.
1126     if (tensorData.size() > tensorInfo.GetNumBytes())
1127     {
1128         throw ParseException(
1129             boost::str(
1130                 boost::format(
1131                     "Number of elements (%1%) should be less than or equal "
1132                     "to the number of elements implied by the shape argument (%2%) for Const node - %3% %4%")
1133                     % (tensorData.size() / GetDataTypeSize(dataType))
1134                     % tensorInfo.GetNumElements()
1135                     % nodeDef.name()
1136                     % CHECK_LOCATION().AsString()));
1137     }
1138
1139     return InvokeParseFunction<MakeTfOperation<ParsedConstTfOperation>>::Result<ParsedTfOperationPtr>(
1140         dataType, this, nodeDef, tensorData, tensorInfo);
1141 }
1142
1143 template<typename Type>
1144 bool TfParser::HasParsedConstTensor(const std::string & nodeName) const
1145 {
1146     auto it = m_ParsedTfOperations.find(nodeName);
1147     if (it == m_ParsedTfOperations.end())
1148     {
1149         return false;
1150     }
1151     return dynamic_cast<ParsedConstTfOperation<Type>*>(it->second.get()) != nullptr;
1152 }
1153
1154 template<typename Type>
1155 bool TfParser::HasParsedConstTensor(ParsedTfOperation* parsedTfOpPtr) const
1156 {
1157     return dynamic_cast<ParsedConstTfOperation<Type>*>(parsedTfOpPtr) != nullptr;
1158 }
1159
1160 ParsedTfOperationPtr TfParser::ParseConv2D(const tensorflow::NodeDef& nodeDef,
1161     const tensorflow::GraphDef& graphDef)
1162 {
1163     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1164     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1165     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1166
1167     if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1168     {
1169         throw ParseException(
1170             boost::str(
1171                 boost::format(
1172                     "ArmNN only supports Convolution layers with constant weights for %1%, input %2% %3%")
1173                     % nodeDef.name()
1174                     % inputs[1].m_IndexedValue->GetNode().name()
1175                     % CHECK_LOCATION().AsString()));
1176     }
1177     ParsedConstTfOperation<float>* weightNode =
1178         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1179
1180     std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1181     std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1182     std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1183
1184     // Read the dilations, if present - only [1,1,1,1] (the default) is supported.
1185     std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(nodeDef, "dilations");
1186     if (!dilations.empty())
1187     {
1188         for (auto dilation : dilations)
1189         {
1190             if (dilation != 1u)
1191             {
1192                 throw ParseException(
1193                     boost::str(
1194                         boost::format(
1195                             "ArmNN only supports Convolution layers with dilations [1,1,1,1] for %1% %2%")
1196                             % nodeDef.name()
1197                             % CHECK_LOCATION().AsString()));
1198             }
1199         }
1200     }
1201
1202     Convolution2dDescriptor desc;
1203     desc.m_BiasEnabled = false;
1204
1205     CHECK_DATA_FORMAT(nodeDef, dataFormat, "Conv2D");
1206
1207     DataLayout dataLayout = dataFormat == "NHWC" ? DataLayout::NHWC : DataLayout::NCHW;
1208
1209     desc.m_DataLayout = dataLayout;
1210
1211     DataLayoutIndexed dataLayoutIndexed(dataLayout);
1212
1213     desc.m_StrideX = strides[dataLayoutIndexed.GetWidthIndex()];
1214     desc.m_StrideY = strides[dataLayoutIndexed.GetHeightIndex()];
1215
1216     uint32_t inputHeight = inputTensorInfo.GetShape()[dataLayoutIndexed.GetHeightIndex()];
1217     uint32_t inputWidth  = inputTensorInfo.GetShape()[dataLayoutIndexed.GetWidthIndex()];
1218
1219     // Mappings from TensorFlow filter tensors to the ArmNN filter tensors.
1220     // Tensorflow weights are [H, W, In, Out].
1221     // ArmNN weights have to be [Out, H, W, In] when the data layout is NHWC,
1222     // and [Out, In, H, W] when the data layout is NCHW.
1223     PermutationVector permutationVector =
1224             dataLayout == DataLayout::NHWC ?
1225                 std::initializer_list<unsigned int>{ 1, 2, 3, 0 } : // NHWC: [H, W, In, Out] -> [Out, H, W, In]
1226                 std::initializer_list<unsigned int>{ 2, 3, 1, 0 };  // NCHW: [H, W, In, Out] -> [Out, In, H, W]
1227
1228     // Swizzle the tensor using the given permutation vector.
1229     const TensorInfo& weightTensorInfo = weightNode->GetTensorInfo();
1230     const TensorInfo weightTensorSwizzledInfo = armnnUtils::Permuted(weightTensorInfo, permutationVector);
1231
1232     // Swizzles the content of the tensor's permanent storage into a local storage.
1233     std::vector<float> weightTensorSwizzledData(weightTensorInfo.GetNumElements());
1234     armnnUtils::Permute(weightTensorSwizzledInfo.GetShape(), permutationVector,
1235                         weightNode->GetStorage(), weightTensorSwizzledData.data(), sizeof(float));
1236
1237     // Create a weight tensor with the newly swizzled data.
1238     ConstTensor weightTensor(weightTensorSwizzledInfo, weightTensorSwizzledData);
1239
1240     uint32_t weightHeight = weightTensor.GetShape()[dataLayoutIndexed.GetHeightIndex()];
1241     uint32_t weightWidth  = weightTensor.GetShape()[dataLayoutIndexed.GetWidthIndex()];
1242
1243     bool padding = false;
1244     TensorInfo outputInfo;
1245     unsigned int outputHeight = 0;
1246     unsigned int outputWidth = 0;
1247
1248     CHECK_PADDING_TYPE(nodeDef, paddingString);
1249
1250     if (paddingString == "SAME")
1251     {
1252         padding = true;
1253
1254         outputHeight = static_cast<uint32_t>(ceil(static_cast<float>(inputHeight) /
1255                                                   static_cast<float>(desc.m_StrideY)));
1256         outputWidth  = static_cast<uint32_t>(ceil(static_cast<float>(inputWidth) /
1257                                                   static_cast<float>(desc.m_StrideX)));
1258     }
1259     else if (paddingString == "VALID")
1260     {
1261         padding = false;
1262
1263         outputHeight = static_cast<uint32_t>(ceil(static_cast<float>(inputHeight - weightHeight + 1) /
1264                                                   static_cast<float>(desc.m_StrideY)));
1265         outputWidth  = static_cast<uint32_t>(ceil(static_cast<float>(inputWidth - weightWidth + 1) /
1266                                                   static_cast<float>(desc.m_StrideX)));
1267     }
1268
1269     switch (dataLayout)
1270     {
1271     case DataLayout::NHWC:
1272         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1273                                   outputHeight,
1274                                   outputWidth,
1275                                   weightTensor.GetShape()[0] },
1276                                 DataType::Float32);
1277         break;
1278     case DataLayout::NCHW:
1279     default:
1280         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1281                                   weightTensor.GetShape()[0],
1282                                   outputHeight,
1283                                   outputWidth },
1284                                 DataType::Float32);
1285         break;
1286     }
1287
1288     CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding);
1289     CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding);
1290
1291     IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str());
1292     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1293     inputSlot.Connect(layer->GetInputSlot(0));
1294
1295     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1296 }
1297
1298 ParsedTfOperationPtr TfParser::ParseDepthwiseConv2D(const tensorflow::NodeDef& nodeDef,
1299                                                     const tensorflow::GraphDef& graphDef)
1300 {
1301     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1302     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1303     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1304
1305     if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1306     {
1307         throw ParseException(
1308             boost::str(
1309                 boost::format(
1310                     "ArmNN only supports Depthwise Convolution layer with constant weights. "
1311                     "Non const input found %1% for node %2% %3%")
1312                     % inputs[1].m_IndexedValue->GetNode().name()
1313                     % nodeDef.name()
1314                     % CHECK_LOCATION().AsString()));
1315     }
1316
1317     ParsedConstTfOperation<float>* weightNode =
1318         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1319
1320     std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1321     std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1322     std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1323
1324     DepthwiseConvolution2dDescriptor desc;
1325     desc.m_BiasEnabled = false;
1326
1327     CHECK_DATA_FORMAT(nodeDef, dataFormat, "DepthwiseConv2dNative");
1328
1329     DataLayout dataLayout = dataFormat == "NHWC" ? DataLayout::NHWC : DataLayout::NCHW;
1330
1331     desc.m_DataLayout = dataLayout;
1332
1333     DataLayoutIndexed dataLayoutIndexed(dataLayout);
1334
1335     desc.m_StrideX = strides[dataLayoutIndexed.GetWidthIndex()];
1336     desc.m_StrideY = strides[dataLayoutIndexed.GetHeightIndex()];
1337
1338     uint32_t inputHeight = inputTensorInfo.GetShape()[dataLayoutIndexed.GetHeightIndex()];
1339     uint32_t inputWidth  = inputTensorInfo.GetShape()[dataLayoutIndexed.GetWidthIndex()];
1340
1341     // Mappings from TensorFlow filter tensors to the ArmNN filter tensors.
1342     // Tensorflow weights come in the format [H, W, I, M].
1343     // ArmNN weights have to be [M, I, H, W].
1344     PermutationVector permutationVector{ 2, 3, 1, 0 }; // [H, W, I, M] -> [M, I, H, W]
1345
1346     // Swizzle the tensor using the given permutation vector.
1347     const TensorInfo& weightTensorInfo = weightNode->GetTensorInfo();
1348     const TensorInfo weightTensorSwizzledInfo = armnnUtils::Permuted(weightTensorInfo, permutationVector);
1349
1350     // Swizzles the content of the tensor's permanent storage into a local storage.
1351     std::vector<float> weightTensorSwizzledData(weightTensorInfo.GetNumElements());
1352     armnnUtils::Permute(weightTensorSwizzledInfo.GetShape(), permutationVector,
1353                         weightNode->GetStorage(), weightTensorSwizzledData.data(), sizeof(float));
1354
1355     // Create a weight tensor with the newly swizzled data.
1356     ConstTensor weightTensor(weightTensorSwizzledInfo, weightTensorSwizzledData);
1357
1358     uint32_t weightHeight = weightTensor.GetShape()[2];
1359     uint32_t weightWidth  = weightTensor.GetShape()[3];
1360
1361     bool padding = false;
1362     TensorInfo outputInfo;
1363     unsigned int outputHeight = 0;
1364     unsigned int outputWidth = 0;
1365
1366     CHECK_PADDING_TYPE(nodeDef, paddingString);
1367
1368     if (paddingString == "SAME")
1369     {
1370         padding = true;
1371
1372         outputHeight = static_cast<uint32_t>(ceil(static_cast<float>(inputHeight) /
1373                                                   static_cast<float>(desc.m_StrideY)));
1374         outputWidth  = static_cast<uint32_t>(ceil(static_cast<float>(inputWidth) /
1375                                                   static_cast<float>(desc.m_StrideX)));
1376     }
1377     else if (paddingString == "VALID")
1378     {
1379         padding = false;
1380
1381         outputHeight = static_cast<uint32_t>(ceil(static_cast<float>(inputHeight - weightHeight + 1) /
1382                                                   static_cast<float>(desc.m_StrideY)));
1383         outputWidth  = static_cast<uint32_t>(ceil(static_cast<float>(inputWidth - weightWidth + 1) /
1384                                                   static_cast<float>(desc.m_StrideX)));
1385     }
1386
1387     switch (dataLayout)
1388     {
1389         case DataLayout::NHWC:
1390             outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1391                                       outputHeight,
1392                                       outputWidth,
1393                                       weightTensor.GetShape()[0] * weightTensor.GetShape()[1]},
1394                                     DataType::Float32);
1395             break;
1396         case DataLayout::NCHW:
1397         default:
1398             outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1399                                       weightTensor.GetShape()[0] * weightTensor.GetShape()[1],
1400                                       outputHeight,
1401                                       outputWidth },
1402                                     DataType::Float32);
1403             break;
1404     }
1405
1406     CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding);
1407     CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding);
1408
1409     IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str());
1410     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1411     inputSlot.Connect(layer->GetInputSlot(0));
1412
1413     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1414 }
1415
1416 TensorInfo OutputShapeOfExpandDims(const tensorflow::NodeDef& nodeDef, TensorInfo inputTensorInfo)
1417 {
1418     BOOST_ASSERT(nodeDef.op() == "ExpandDims");
1419
1420     if (inputTensorInfo.GetNumDimensions() > 4) {
1421         throw ParseException(
1422                 boost::str(
1423                         boost::format(
1424                                 "Unsupported number of dimensions: %1% for input shape for ExpandDims %2% %3%")
1425                         % inputTensorInfo.GetNumDimensions()
1426                         % nodeDef.name()
1427                         % CHECK_LOCATION().AsString()));
1428     }
1429
1430     std::int32_t expandDim = ReadMandatoryNodeInt32Attribute(nodeDef, "Tdim");
1431
1432     std::int32_t inputDimSize = boost::numeric_cast<int32_t>(inputTensorInfo.GetNumDimensions());
1433     std::vector<uint32_t> outputDims;
1434
1435     // expandDim operation requires: -1-input.dims() <= dim <= input.dims()
1436     if (expandDim >= -1 - inputDimSize && expandDim <= inputDimSize)
1437     {
1438         // add current input shape to outputDims
1439         for (unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); ++i) {
1440             auto currentDimension = inputTensorInfo.GetShape()[i];
1441             outputDims.push_back(currentDimension);
1442         }
1443
1444         // insert a dimension of 1 at index 'expandDim' of inputs shape
1445         if (expandDim >= 0)
1446         {
1447             auto getPosition = std::next(outputDims.begin() + 0, expandDim);
1448             outputDims.insert(getPosition, 1);
1449         }
1450
1451         // if negative number for 'expandDim' then count backwards from the last element
1452         // and insert 1 dimension at index 'expandDim'
1453         if (expandDim < 0)
1454         {
1455             int outputDimSize = boost::numeric_cast<int>(outputDims.size() + 1);
1456             auto getPosition = std::next(outputDims.begin() + outputDimSize, expandDim);
1457             outputDims.insert(getPosition, 1);
1458         }
1459     }
1460     else
1461     {
1462         throw InvalidArgumentException(
1463                 boost::str(
1464                         boost::format(
1465                                 "Cannot expand dimension %1% in input tensor with %2% dimension %3%")
1466                         % expandDim
1467                         % inputDimSize
1468                         % CHECK_LOCATION().AsString()));
1469     }
1470
1471     if (outputDims.size() > 4)
1472     {
1473         throw ParseException(
1474                 boost::str(
1475                         boost::format(
1476                                 "Unsupported number of dimensions: %1% for output shape for ExpandDims %2% %3%")
1477                         % outputDims.size()
1478                         % nodeDef.name()
1479                         % CHECK_LOCATION().AsString()));
1480     }
1481
1482     TensorShape outShape = TensorShape(static_cast<unsigned int>(outputDims.size()),
1483                                        outputDims.data());
1484
1485     TensorInfo outTensorInfo = inputTensorInfo;
1486     outTensorInfo.SetShape(outShape);
1487
1488     return outTensorInfo;
1489 }
1490
1491 ParsedTfOperationPtr TfParser::ParseExpandDims(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1492 {
1493     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1494
1495     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1496     TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1497
1498     TensorInfo outputInfo;
1499     outputInfo = OutputShapeOfExpandDims(nodeDef, inputTensorInfo);
1500
1501     ReshapeDescriptor reshapeDesc;
1502     reshapeDesc.m_TargetShape = outputInfo.GetShape();
1503     IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
1504     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1505     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1506
1507     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1508 }
1509
1510 ParsedTfOperationPtr TfParser::ParseFusedBatchNorm(const tensorflow::NodeDef& nodeDef,
1511                                                    const tensorflow::GraphDef& graphDef)
1512 {
1513     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 5);
1514
1515     if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1516     {
1517         throw ParseException(
1518             boost::str(
1519                 boost::format(
1520                     "ArmNN only supports FusedBatchNormalization layers with constant scale. "
1521                     "Input %1%. Node %2% %3%")
1522                     % inputs[1].m_IndexedValue->GetNode().name()
1523                     % nodeDef.name()
1524                     % CHECK_LOCATION().AsString()));
1525     }
1526     ParsedConstTfOperation<float>* scaleNode =
1527         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1528
1529     if (!HasParsedConstTensor<float>(inputs[2].m_IndexedValue->GetNode().name()))
1530     {
1531         throw ParseException(
1532             boost::str(
1533                 boost::format(
1534                     "ArmNN only supports FusedBatchNormalization layers with constant offset. "
1535                     "Input %1%. Node %2% %3%")
1536                     % inputs[2].m_IndexedValue->GetNode().name()
1537                     % nodeDef.name()
1538                     % CHECK_LOCATION().AsString()));
1539     }
1540     ParsedConstTfOperation<float>* offsetNode =
1541         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[2].m_IndexedValue);
1542
1543     if (!HasParsedConstTensor<float>(inputs[3].m_IndexedValue->GetNode().name()))
1544     {
1545         throw ParseException(
1546             boost::str(
1547                 boost::format(
1548                     "ArmNN only supports FusedBatchNormalization layers with constant mean. "
1549                     "Input %1%. Node %2% %3%")
1550                     % inputs[3].m_IndexedValue->GetNode().name()
1551                     % nodeDef.name()
1552                     % CHECK_LOCATION().AsString()));
1553     }
1554     ParsedConstTfOperation<float>* meanNode =
1555         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[3].m_IndexedValue);
1556
1557     if (!HasParsedConstTensor<float>(inputs[4].m_IndexedValue->GetNode().name()))
1558     {
1559         throw ParseException(
1560             boost::str(
1561                 boost::format(
1562                     "ArmNN only supports FusedBatchNormalization layers with constant variance. "
1563                     "Input %1%. Node %2% %3%")
1564                     % inputs[4].m_IndexedValue->GetNode().name()
1565                     % nodeDef.name()
1566                     % CHECK_LOCATION().AsString()));
1567     }
1568     ParsedConstTfOperation<float>* varianceNode =
1569         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[4].m_IndexedValue);
1570
1571     const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1572
1573     CHECK_DATA_FORMAT(nodeDef, dataFormat, "FusedBatchNorm");
1574
1575     // The descriptor only has the epsilon attribute.
1576     BatchNormalizationDescriptor desc;
1577     desc.m_Eps = ReadMandatoryNodeFloatAttribute(nodeDef, "epsilon");
1578     desc.m_DataLayout = dataFormat == "NHWC" ? DataLayout::NHWC : DataLayout::NCHW;
1579
1580     // Data for the parsed tensor args (scale, offset, mean, variance) must be stored
1581     // locally until the layer is added.
1582     std::vector<float> scaleTensorData;
1583     ConstTensor scaleTensor = scaleNode->GetConstTensor(scaleTensorData);
1584
1585     std::vector<float> offsetTensorData;
1586     ConstTensor offsetTensor = offsetNode->GetConstTensor(offsetTensorData);
1587
1588     std::vector<float> meanTensorData;
1589     ConstTensor meanTensor = meanNode->GetConstTensor(meanTensorData);
1590
1591     std::vector<float> varianceTensorData;
1592     ConstTensor varianceTensor = varianceNode->GetConstTensor(varianceTensorData);
1593
1594     IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1595                                                                      meanTensor,
1596                                                                      varianceTensor,
1597                                                                      offsetTensor,
1598                                                                      scaleTensor,
1599                                                                      nodeDef.name().c_str());
1600
1601     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1602
1603     layer->GetOutputSlot(0).SetTensorInfo(inputSlot.GetTensorInfo());
1604     inputSlot.Connect(layer->GetInputSlot(0));
1605
1606     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1607 }
1608
1609 bool TfParser::IsSupportedLeakyReluPattern(const tensorflow::NodeDef& mulNodeDef,
1610                                            size_t alphaLayerIndex,
1611                                            const OutputOfParsedTfOperation& otherOp,
1612                                            armnn::IOutputSlot** outputOfLeakyRelu,
1613                                            armnn::ActivationDescriptor & desc)
1614 {
1615     const tensorflow::NodeDef& otherNodeDef = otherOp.m_IndexedValue->GetNode();
1616
1617     // Verifying all these assumptions hold:
1618     //
1619     // 1, the mulNodeDef is an elementwise multiplication node "Mul"
1620     // 2, the alphaLayerIndex selects a constant node from the inputs of the "Mul" node
1621     // 3, the inputLayerIndex selects a layer which has the same name as otherNodeDef
1622     //
1623
1624     if (mulNodeDef.op() == "Mul")
1625     {
1626         size_t otherLayerIndex = (alphaLayerIndex == 0 ? 1 : 0);
1627         std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(mulNodeDef, 2);
1628
1629         BOOST_ASSERT(inputs.size() == 2);
1630         BOOST_ASSERT((otherLayerIndex == 0 || alphaLayerIndex == 0));
1631         BOOST_ASSERT((otherLayerIndex == 1 || alphaLayerIndex == 1));
1632         BOOST_ASSERT(((otherLayerIndex + alphaLayerIndex) == 1));
1633
1634         if (inputs[otherLayerIndex].m_IndexedValue->GetNode().name() == otherNodeDef.name())
1635         {
1636             if (HasParsedConstTensor<float>(inputs[alphaLayerIndex].m_IndexedValue->GetNode().name()))
1637             {
1638                 ParsedConstTfOperation<float>* alpha =
1639                     boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(
1640                         inputs[alphaLayerIndex].m_IndexedValue);
1641
1642                 std::vector<float> const_data;
1643                 ConstTensor const_tensor = alpha->GetConstTensor(const_data);
1644
1645                 if (const_data.size() == 1)
1646                 {
1647                     desc.m_Function = ActivationFunction::LeakyReLu;
1648                     desc.m_A = const_data[0];
1649
1650                     *outputOfLeakyRelu = &(otherOp.m_IndexedValue->ResolveArmnnOutputSlot(otherOp.m_Index));
1651                     return true;
1652                 }
1653             }
1654         }
1655     }
1656     return false;
1657 }
1658
1659 ParsedTfOperationPtr TfParser::ParseMaximum(const tensorflow::NodeDef& nodeDef,
1660                                             const tensorflow::GraphDef& graphDef)
1661 {
1662     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1663     if (inputs.size() != 2)
1664     {
1665         throw ParseException(
1666             boost::str(
1667                 boost::format(
1668                     "Maximum expects two inputs!. Got %1% for Node %2% %3%")
1669                 % inputs.size()
1670                 % nodeDef.name()
1671                 % CHECK_LOCATION().AsString()));
1672     }
1673
1674     auto inputNode0 = inputs[0].m_IndexedValue->GetNode();
1675     auto inputNode1 = inputs[1].m_IndexedValue->GetNode();
1676     IOutputSlot* outputOfLeakyRelu = nullptr;
1677
1678     ActivationDescriptor desc;
1679
1680     // A max node may be part of a LeakyRelu, with one input as a multiplication with a scalar constant,
1681     // i.e. one of the four possible scenarios:
1682     //  1, max(mul(a, x), x)
1683     //  2, max(mul(x, a), x)
1684     //  3, max(x, mul(a, x))
1685     //  4, max(x, mul(x, a))
1686     // These are handled by an activation layer.
1687
1688     if (IsSupportedLeakyReluPattern(inputNode0, 0, inputs[1], &outputOfLeakyRelu, desc) ||
1689         IsSupportedLeakyReluPattern(inputNode0, 1, inputs[1], &outputOfLeakyRelu, desc) ||
1690         IsSupportedLeakyReluPattern(inputNode1, 0, inputs[0], &outputOfLeakyRelu, desc) ||
1691         IsSupportedLeakyReluPattern(inputNode1, 1, inputs[0], &outputOfLeakyRelu, desc))
1692     {
1693         BOOST_ASSERT(outputOfLeakyRelu != nullptr);
1694
1695         IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, nodeDef.name().c_str());
1696         outputOfLeakyRelu->Connect(layer->GetInputSlot(0));
1697         layer->GetOutputSlot(0).SetTensorInfo(outputOfLeakyRelu->GetTensorInfo());
1698         return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1699     }
1700     else
1701     {
1702         // Anything else is just a maximum layer.
1703
1704         return AddMaximumLayer(nodeDef);
1705     }
1706 }
1707
1708 std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> TfParser::ProcessElementwiseInputSlots(
1709             const tensorflow::NodeDef& nodeDef, const std::string& layerName)
1710 {
1711     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1712
1713     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1714     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
1715     const unsigned int input0Dim = input0Slot->GetTensorInfo().GetNumDimensions();
1716     const unsigned int input1Dim = input1Slot->GetTensorInfo().GetNumDimensions();
1717
1718     if (input0Dim != input1Dim)
1719     {
1720         // broadcasting where input0 and input1 have different number of dimensions
1721         // is only supported for 1D and 4D tensors pair
1722         if (input0Dim == 1 && input1Dim == 4)
1723         {
1724             input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, true, *m_Network, nodeDef);
1725         }
1726         else if (input0Dim == 4 && input1Dim == 1)
1727         {
1728             input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, true, *m_Network, nodeDef);
1729         }
1730         else
1731         {
1732             throw ParseException(
1733                     boost::str(
1734                             boost::format("Unsupported broadcast configuration for %1% operation %2% %3%")
1735                             % layerName
1736                             % nodeDef.name()
1737                             % CHECK_LOCATION().AsString()));
1738         }
1739     }
1740     return {input0Slot, input1Slot};
1741 }
1742
1743 ParsedTfOperationPtr TfParser::ProcessElementwiseLayer(
1744         IOutputSlot* input0Slot,
1745         IOutputSlot* input1Slot,
1746         IConnectableLayer* const layer,
1747         const tensorflow::NodeDef& nodeDef)
1748 {
1749     input0Slot->Connect(layer->GetInputSlot(0));
1750     input1Slot->Connect(layer->GetInputSlot(1));
1751
1752     TensorInfo outputInfo = input0Slot->GetTensorInfo();
1753     std::vector<unsigned int> outputShape;
1754
1755     const TensorShape& input0Shape = input0Slot->GetTensorInfo().GetShape();
1756     const TensorShape& input1Shape = input1Slot->GetTensorInfo().GetShape();
1757
1758     for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++)
1759     {
1760         outputShape.push_back(std::max(input0Shape[i], input1Shape[i]));
1761     }
1762
1763     outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data()));
1764     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1765
1766     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1767 }
1768
1769 ParsedTfOperationPtr TfParser::ParseGreater(const tensorflow::NodeDef& nodeDef,
1770                                             const tensorflow::GraphDef& graphDef)
1771 {
1772     std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> inputLayers = ProcessElementwiseInputSlots(nodeDef, "Greater");
1773     IOutputSlot* input0Slot = inputLayers.first;
1774     IOutputSlot* input1Slot = inputLayers.second;
1775
1776     IConnectableLayer* const layer = m_Network->AddGreaterLayer(nodeDef.name().c_str());
1777
1778     return ProcessElementwiseLayer(input0Slot, input1Slot, layer, nodeDef);
1779 }
1780
1781 ParsedTfOperationPtr TfParser::ParseEqual(const tensorflow::NodeDef& nodeDef,
1782                                           const tensorflow::GraphDef& graphDef)
1783 {
1784     std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> inputLayers = ProcessElementwiseInputSlots(nodeDef, "Equal");
1785     IOutputSlot* input0Slot = inputLayers.first;
1786     IOutputSlot* input1Slot = inputLayers.second;
1787
1788     IConnectableLayer* const layer = m_Network->AddEqualLayer(nodeDef.name().c_str());
1789
1790     return ProcessElementwiseLayer(input0Slot, input1Slot, layer, nodeDef);
1791 }
1792
1793 ParsedTfOperationPtr TfParser::ParseMinimum(const tensorflow::NodeDef& nodeDef,
1794                                             const tensorflow::GraphDef& graphDef)
1795 {
1796     std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> inputLayers = ProcessElementwiseInputSlots(nodeDef, "Minimum");
1797     IOutputSlot* input0Slot = inputLayers.first;
1798     IOutputSlot* input1Slot = inputLayers.second;
1799
1800     IConnectableLayer* const layer = m_Network->AddMinimumLayer(nodeDef.name().c_str());
1801
1802     return ProcessElementwiseLayer(input0Slot, input1Slot, layer, nodeDef);
1803 }
1804
1805 ParsedTfOperationPtr TfParser::ParseSub(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1806 {
1807     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1808
1809     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1810     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
1811
1812     const TensorInfo& input0Info = input0Slot->GetTensorInfo();
1813     const TensorInfo& input1Info = input1Slot->GetTensorInfo();
1814
1815     if (input0Info.GetNumDimensions() == 1)
1816     {
1817         const bool isNHWC = true;
1818         input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
1819     }
1820
1821     if (input1Info.GetNumDimensions() == 1)
1822     {
1823         const bool isNHWC = true;
1824         input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
1825     }
1826
1827     IConnectableLayer* const layer = m_Network->AddSubtractionLayer(nodeDef.name().c_str());
1828
1829     input0Slot->Connect(layer->GetInputSlot(0));
1830     input1Slot->Connect(layer->GetInputSlot(1));
1831
1832     if (input0Info.GetNumDimensions() == 1)
1833     {
1834         layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
1835     }
1836     else
1837     {
1838         layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
1839     }
1840
1841     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1842 }
1843
1844 unsigned int CheckPaddingTensor(const ConstTensor& paddingTensor,
1845                                 const TensorInfo& inputTensorInfo,
1846                                 const std::string& nodeName)
1847 {
1848     unsigned int rank = paddingTensor.GetShape()[0];
1849     unsigned int expectedRank = inputTensorInfo.GetNumDimensions();
1850     if (rank != expectedRank)
1851     {
1852         throw ParseException(
1853                 boost::str(
1854                         boost::format(
1855                                 "Expected the padding tensor to be of rank %1 not %2 on Node %3 %4.")
1856                         % expectedRank
1857                         % rank
1858                         % nodeName
1859                         % CHECK_LOCATION().AsString()));
1860     }
1861     unsigned int second = paddingTensor.GetShape()[1];
1862     if (second != 2)
1863     {
1864         throw ParseException(
1865                 boost::str(
1866                         boost::format(
1867                                 "Expected the padding tensor to be of dimensions [%1, 2] not [%1, %2] on Node %3 %4.")
1868                         % rank
1869                         % second
1870                         % nodeName
1871                         % CHECK_LOCATION().AsString()));
1872     }
1873     return rank;
1874 }
1875
1876 TensorInfo CalculatePaddedOutputTensorInfo(const TensorInfo& inputTensorInfo,
1877                                            const std::vector<std::pair<unsigned int, unsigned int>>& padList)
1878 {
1879     unsigned int numDims = inputTensorInfo.GetNumDimensions();
1880     std::vector<unsigned int> outDims;
1881     for (unsigned int i = 0; i < numDims; ++i)
1882     {
1883         unsigned int dimSize = inputTensorInfo.GetShape()[i];
1884         const std::pair<unsigned int, unsigned int>& dimPadding = padList[i];
1885         dimSize += dimPadding.first;
1886         dimSize += dimPadding.second;
1887         outDims.push_back(dimSize);
1888     }
1889     TensorInfo paddedTensorInfo = inputTensorInfo;
1890     unsigned int outDimsSize = static_cast<unsigned int>(outDims.size());
1891     paddedTensorInfo.SetShape(TensorShape{ outDimsSize, outDims.data() });
1892     return paddedTensorInfo;
1893 }
1894
1895 ParsedTfOperationPtr TfParser::ParsePad(const tensorflow::NodeDef& nodeDef,
1896                                         const tensorflow::GraphDef& graphDef)
1897 {
1898     // input consists of:
1899     // input[0] the tensor which will be padded
1900     // input[1] the tensor holding the padding values
1901     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1902     IOutputSlot& previousLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1903     TensorInfo inputTensorInfo = previousLayerOutputSlot.GetTensorInfo();
1904     if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue))
1905     {
1906         throw ParseException(
1907                 boost::str(
1908                         boost::format(
1909                                 "ArmNN only supports Pad with constant padding. "
1910                                 "Input %1%. Node %2% %3%")
1911                         % inputs[1].m_IndexedValue->GetNode().name()
1912                         % nodeDef.name()
1913                         % CHECK_LOCATION().AsString()));
1914
1915     }
1916     ParsedConstTfOperation<int32_t>* paddingTensorOp =
1917             boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
1918
1919     std::vector<int32_t> paddingTensorData;
1920     ConstTensor paddingTensor = paddingTensorOp->GetConstTensor(paddingTensorData);
1921     // paddings is an integer tensor with shape [n, 2], where n is the rank of tensor
1922     // and should match the rank of the input tensor that is being padded.
1923     // For each dimension D of input, paddings[D, 0] indicates how many values to add
1924     // before the contents of tensor in that dimension, and paddings[D, 1] indicates how
1925     // many values to add after the contents of tensor in that dimension
1926     // This needs to be translated into a padList for ACL
1927     std::vector<std::pair<unsigned int, unsigned int>> padList;
1928     unsigned int rank = CheckPaddingTensor(paddingTensor, inputTensorInfo, nodeDef.name());
1929     for (unsigned int i = 0; i < rank; ++i)
1930     {
1931         std::pair<unsigned int, unsigned int> paddingForDim;
1932         for (unsigned int j = 0; j < 2; j++)
1933         {
1934             unsigned int index = (i * 2) + j;
1935             int paddingAmount = paddingTensorData[index];
1936             // make sure we can cast to an unsigned value
1937             if (paddingAmount < 0)
1938             {
1939                 throw ParseException(
1940                         boost::str(
1941                                 boost::format(
1942                                         "Negative amount %1 specified at [%2, %3] of padding tensor on Node %4 %5.")
1943                                 % paddingAmount
1944                                 % i
1945                                 % j
1946                                 % nodeDef.name()
1947                                 % CHECK_LOCATION().AsString()));
1948             }
1949             if (j == 0)
1950             {
1951                 paddingForDim.first = static_cast<unsigned int>(paddingAmount);
1952             }
1953             else
1954             {
1955                 paddingForDim.second = static_cast<unsigned int>(paddingAmount);
1956             }
1957         }
1958         padList.push_back(paddingForDim);
1959     }
1960     PadDescriptor padDescriptor(padList);
1961     IConnectableLayer* layer = m_Network->AddPadLayer(padDescriptor, nodeDef.name().c_str());
1962     previousLayerOutputSlot.Connect(layer->GetInputSlot(0));
1963     // Use the padding to calculate the new output tensor shape
1964     TensorInfo outputTensorInfo = CalculatePaddedOutputTensorInfo(inputTensorInfo, padList);
1965     layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1966     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1967 }
1968
1969 ParsedTfOperationPtr TfParser::ParseConcat(const tensorflow::NodeDef& nodeDef,
1970                                            const tensorflow::GraphDef& graphDef)
1971 {
1972     std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
1973
1974     // In tensorflow, we have the last input of the Concat layer as the axis for concatenation.
1975     unsigned int numInputs = static_cast<unsigned int>(nodes.size());
1976
1977     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
1978
1979     // The last input is the axis for concatenation.
1980     if (!HasParsedConstTensor<int32_t>(inputs[numInputs - 1].m_IndexedValue->GetNode().name()))
1981     {
1982         throw ParseException(
1983             boost::str(
1984                 boost::format(
1985                     "ArmNN only supports Concat with constant axis. "
1986                     "Input %1%. Node %2% %3%")
1987                     % inputs[numInputs - 1].m_IndexedValue->GetNode().name()
1988                     % nodeDef.name()
1989                     % CHECK_LOCATION().AsString()));
1990     }
1991     ParsedConstTfOperation<int32_t>* shapeNode =
1992             boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[numInputs - 1].m_IndexedValue);
1993
1994     // Get the axis tensor data
1995     std::vector<int32_t> axisTensorData;
1996     shapeNode->GetConstTensor(axisTensorData);
1997
1998     // This concatDim indicates the data format: 3 is the NHWC, 1 is the NCHW.
1999     const unsigned int concatDim = static_cast<unsigned int>(axisTensorData[0]);
2000
2001     // Armnn supports concatenation along the channel dimension for data formats NHWC and NCHW.
2002     if (concatDim == 0 || concatDim == 2)
2003     {
2004         throw ParseException(
2005             boost::str(
2006                 boost::format(
2007                     "Dimension %1% for concatenation is not supported by Armnn. "
2008                     "Node %2% %3%")
2009                     % concatDim
2010                     % nodeDef.name()
2011                     % CHECK_LOCATION().AsString()));
2012     }
2013
2014     unsigned int numConcatViews = numInputs - 1;
2015     OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numConcatViews), MaxNumOfTensorDimensions);
2016     concatDescriptor.SetConcatAxis(concatDim);
2017     TensorShape mergeDims(MaxNumOfTensorDimensions);
2018     unsigned int mergeDim = 0;
2019     for (unsigned int viewIndex = 0; viewIndex < numConcatViews; ++viewIndex)
2020     {
2021         // Need to double check whether it should be
2022         IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
2023         TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2024
2025         // Double check dimensions of the tensors
2026         if (inputTensorInfo.GetNumDimensions() != MaxNumOfTensorDimensions)
2027         {
2028             throw armnn::ParseException(
2029                 boost::str(
2030                     boost::format(
2031                         "The number of dimensions: %1% for input tensors of the "
2032                         "concatenation op should be %2% %3%")
2033                     % inputTensorInfo.GetNumDimensions()
2034                     % MaxNumOfTensorDimensions
2035                     % CHECK_LOCATION().AsString()));
2036         }
2037
2038         // Copy the input tensor shape to mergeDimSizes and initialize the view origin coordinates for the current input
2039         mergeDims = inputTensorInfo.GetShape();
2040         unsigned int* viewOrigin = const_cast<unsigned int*>(concatDescriptor.GetViewOrigin(viewIndex));
2041         std::fill(viewOrigin, viewOrigin + MaxNumOfTensorDimensions, 0);
2042
2043         // Update the view origin coordinates and the merge dimension value
2044         concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
2045         mergeDim += mergeDims[concatDim];
2046     }
2047
2048     // Update the output shape
2049     mergeDims[concatDim] = mergeDim;
2050     armnn::IConnectableLayer *layer = m_Network->AddMergerLayer(concatDescriptor, nodeDef.name().c_str());
2051
2052     layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(mergeDims, DataType::Float32));
2053
2054     for (unsigned int viewIndex = 0; viewIndex < numConcatViews; ++viewIndex)
2055     {
2056         IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
2057         inputSlot.Connect(layer->GetInputSlot(viewIndex));
2058     }
2059
2060     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2061 }
2062
2063 ParsedTfOperationPtr TfParser::ParseShape(const tensorflow::NodeDef& nodeDef,
2064     const tensorflow::GraphDef& graphDef)
2065 {
2066     // Note: the Shape layer is handled in a special way, because:
2067     //        1. ARMNN doesn't support int32 tensors which it outputs.
2068     //        2. ARMNN works with statically shaped tensors which are known at parse time.
2069     //        3. because of 1. and 2. we treat the output of Shape as a temporary const int32
2070     //           tensor which may be used as an input to other ops, most likely a Reshape.
2071
2072     const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "out_type");
2073     if (tfDataType != tensorflow::DT_INT32)
2074     {
2075         throw ParseException(
2076             boost::str(
2077                 boost::format(
2078                     "Armnn only supports DT_INT32 as out_type. Got %1% for Node %2% %3%")
2079                     % tensorflow::DataType_Name(tfDataType)
2080                     % nodeDef.name()
2081                     % CHECK_LOCATION().AsString()));
2082     }
2083
2084     const std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2085     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2086     const TensorInfo& prevLayerTensorInfo = prevLayerOutputSlot.GetTensorInfo();
2087     unsigned int prevLayerDimensions = prevLayerTensorInfo.GetNumDimensions();
2088
2089     std::vector<int32_t> shapeTensorData;
2090     shapeTensorData.reserve(prevLayerDimensions);
2091
2092     for (unsigned int i=0; i<prevLayerDimensions; ++i)
2093     {
2094         shapeTensorData.push_back(static_cast<int32_t>(prevLayerTensorInfo.GetShape()[i]));
2095     }
2096
2097     TensorInfo shapeTensorInfo(1, &prevLayerDimensions, DataType::Signed32);
2098
2099     return std::make_unique<ParsedConstTfOperation<int32_t>>(this,
2100                                                              nodeDef,
2101                                                              &shapeTensorData[0],
2102                                                              shapeTensorInfo);
2103 }
2104
2105 ParsedTfOperationPtr TfParser::ParseReshape(const tensorflow::NodeDef& nodeDef,
2106     const tensorflow::GraphDef& graphDef)
2107 {
2108     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2109     ParsedTfOperation* inputNode = inputs[0].m_IndexedValue;
2110
2111     if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
2112     {
2113         throw ParseException(
2114             boost::str(
2115                 boost::format(
2116                     "ArmNN only supports Reshape layers with constant shapes. "
2117                     "Input %1% Node %2% %3%")
2118                     % inputs[1].m_IndexedValue->GetNode().name()
2119                     % nodeDef.name()
2120                     % CHECK_LOCATION().AsString()));
2121     }
2122     ParsedConstTfOperation<int32_t>* shapeNode =
2123         boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2124
2125     armnn::IOutputSlot& prevLayerOutputSlot = inputNode->ResolveArmnnOutputSlot(inputs[0].m_Index);
2126     TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
2127
2128     std::vector<int32_t> shapeTensorData;
2129     ConstTensor shapeTensor = shapeNode->GetConstTensor(shapeTensorData);
2130     const TensorInfo outputTensorInfo = PrepareReshape(inputTensorInfo, shapeTensorData);
2131
2132     TensorShape targetShape = outputTensorInfo.GetShape();
2133     ReshapeDescriptor reshapeDesc;
2134     reshapeDesc.m_TargetShape = targetShape;
2135
2136     IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
2137     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2138     layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2139
2140     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2141 }
2142
2143 ParsedTfOperationPtr TfParser::ParseResizeBilinear(const tensorflow::NodeDef& nodeDef,
2144     const tensorflow::GraphDef& graphDef)
2145 {
2146     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2147
2148     if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
2149     {
2150         throw ParseException(
2151             boost::str(
2152                 boost::format(
2153                     "ArmNN only supports ResizeBilinear layers with constant sizes. "
2154                     "Input %1%. Node %2% %3%")
2155                     % inputs[1].m_IndexedValue->GetNode().name()
2156                     % nodeDef.name()
2157                     % CHECK_LOCATION().AsString()));
2158     }
2159     ParsedConstTfOperation<int32_t>* sizeNode =
2160         boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2161
2162     // Checks the align_corners attribute is not set.
2163     if (ReadOptionalNodeBoolAttribute(nodeDef, "align_corners", false))
2164     {
2165         throw ParseException(
2166             boost::str(
2167                 boost::format(
2168                     "ArmNN only supports ResizeBilinear layers with align_corners set to false. "
2169                     "Node %1% %2%")
2170                     % nodeDef.name()
2171                     % CHECK_LOCATION().AsString()));
2172     }
2173
2174     // Data for the parsed tensor args (size) must be stored locally.
2175     std::vector<int32_t> sizeTensorData;
2176     ConstTensor sizeTensor = sizeNode->GetConstTensor(sizeTensorData);
2177
2178     // The descriptor only has target height and width attributes, which we get from the size tensor.
2179     ResizeBilinearDescriptor desc;
2180     desc.m_TargetHeight = static_cast<uint32_t> (sizeTensorData[0]);
2181     desc.m_TargetWidth = static_cast<uint32_t> (sizeTensorData[1]);
2182     desc.m_DataLayout = armnn::DataLayout::NHWC;
2183
2184     IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc, nodeDef.name().c_str());
2185
2186     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2187     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2188     // The input shape is always in BHWC format, this will be swizzled below; for now,
2189     // get the batch and channels to make up the ArmNN output shape with the target size.
2190     unsigned int outBatch = inputTensorInfo.GetShape()[0];
2191     unsigned int outChannels = inputTensorInfo.GetShape()[3];
2192     unsigned int outHeight = desc.m_TargetHeight;
2193     unsigned int outWidth = desc.m_TargetWidth;
2194     TensorShape outShape({outBatch, outHeight, outWidth, outChannels });
2195     // The output DataType is always Float32, regardless of the input DataType.
2196     const TensorInfo outputTensorInfo(outShape, armnn::DataType::Float32);
2197     layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2198
2199     inputSlot.Connect(layer->GetInputSlot(0));
2200
2201     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2202 }
2203
2204 TensorInfo OutputShapeOfSqueeze(const tensorflow::NodeDef& nodeDef, TensorInfo inputTensorInfo)
2205 {
2206     BOOST_ASSERT(nodeDef.op() == "Squeeze");
2207     tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "T");
2208
2209     DataType type;
2210     if (tfDataType == tensorflow::DT_FLOAT)
2211     {
2212         type = DataType::Float32;
2213     }
2214     else if (tfDataType == tensorflow::DT_INT32)
2215     {
2216         type = DataType::Signed32;
2217     }
2218     else
2219     {
2220         throw ParseException(
2221             boost::str(
2222                 boost::format("Unsupported DataType %1% for Squeeze operation %2% %3%")
2223                 % tensorflow::DataType_Name(tfDataType)
2224                 % nodeDef.name()
2225                 % CHECK_LOCATION().AsString()));
2226     }
2227
2228
2229     if (inputTensorInfo.GetNumDimensions() > 4)
2230     {
2231         throw ParseException(
2232             boost::str(
2233                 boost::format(
2234                     "Unsupported number of dimensions: %1% for input shape for Squeeze %2% %3%")
2235                     % inputTensorInfo.GetNumDimensions()
2236                     % nodeDef.name()
2237                     % CHECK_LOCATION().AsString()));
2238     }
2239
2240     std::vector<uint32_t> squeezeDims = ReadOptionalNodeUint32ListAttribute(nodeDef, "squeeze_dims");
2241     static const uint32_t dimensionSequence[] = { 0, 1, 2, 3 };
2242
2243     if (squeezeDims.empty())
2244     {
2245         squeezeDims.assign(dimensionSequence,
2246                            dimensionSequence+inputTensorInfo.GetNumDimensions());
2247     }
2248
2249     std::vector<uint32_t> outputDims;
2250     for(unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); i++)
2251     {
2252         bool skipSqueeze = (std::find(squeezeDims.begin(), squeezeDims.end(), i) == squeezeDims.end());
2253         auto currentDimension = inputTensorInfo.GetShape()[i];
2254         if (skipSqueeze || currentDimension != 1)
2255         {
2256             outputDims.push_back(currentDimension);
2257         }
2258     }
2259
2260     if (outputDims.size() > 4)
2261     {
2262         throw ParseException(
2263             boost::str(
2264                 boost::format(
2265                     "Unsupported number of dimensions: %1% for output shape for Squeeze %2% %3%")
2266                     % outputDims.size()
2267                     % nodeDef.name()
2268                     % CHECK_LOCATION().AsString()));
2269     }
2270
2271     TensorShape outShape = TensorShape(static_cast<unsigned int>(outputDims.size()),
2272                                        outputDims.data());
2273
2274     TensorInfo outTensorInfo = inputTensorInfo;
2275     outTensorInfo.SetShape(outShape);
2276     outTensorInfo.SetDataType(type);
2277
2278     return outTensorInfo;
2279 }
2280
2281 ParsedTfOperationPtr TfParser::ParseSqueeze(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2282 {
2283     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2284
2285     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2286     TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
2287
2288     TensorInfo outputInfo;
2289     outputInfo = OutputShapeOfSqueeze(nodeDef, inputTensorInfo);
2290
2291     ReshapeDescriptor reshapeDesc;
2292     reshapeDesc.m_TargetShape = outputInfo.GetShape();
2293     IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
2294     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2295     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2296
2297     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2298 }
2299
2300 ParsedTfOperationPtr TfParser::ParseLrn(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2301 {
2302     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2303
2304     NormalizationDescriptor normalizationDescriptor;
2305     normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
2306     normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
2307     normalizationDescriptor.m_Alpha = ReadMandatoryNodeFloatAttribute(nodeDef, "alpha");
2308     normalizationDescriptor.m_Beta = ReadMandatoryNodeFloatAttribute(nodeDef, "beta");
2309     normalizationDescriptor.m_K = ReadMandatoryNodeFloatAttribute(nodeDef, "bias");
2310     normalizationDescriptor.m_NormSize = ReadMandatoryNodeUint32Attribute(nodeDef, "depth_radius");
2311     normalizationDescriptor.m_DataLayout = armnn::DataLayout::NHWC;
2312
2313     // The window size must be an odd value. For a window size of (2 * n + 1), TensorFlow defines depth_radius = n.
2314     normalizationDescriptor.m_NormSize = normalizationDescriptor.m_NormSize * 2 + 1;
2315
2316     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2317     IConnectableLayer* layer = m_Network->AddNormalizationLayer(normalizationDescriptor,
2318         nodeDef.name().c_str());
2319     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2320     layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
2321
2322     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2323 }
2324
2325 /// An ParsedTfOperation for a MatMul node.
2326 /// Creation of the armnn FullyConnected layer is deferred until it is actually needed, because
2327 /// MatMul nodes are often used for the first part of a biased FullyConnected (MatMul followed
2328 /// by Add) and in these cases armnn doesn't need a separate layer for the MatMul.
2329 ///
2330 class ParsedMatMulTfOperation : public DeferredSingleLayerParsedTfOperation
2331 {
2332 public:
2333     ParsedMatMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
2334         : DeferredSingleLayerParsedTfOperation(parser, node)
2335     {
2336     }
2337
2338     void CreateLayerDeferred() override
2339     {
2340         BOOST_ASSERT(m_Layer == nullptr);
2341         m_Layer = m_Parser->AddFullyConnectedLayer(m_Node, nullptr, m_Node.name().c_str());
2342     }
2343 };
2344
2345 ParsedTfOperationPtr TfParser::ParseMatMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2346 {
2347     // Defers the creation of the layer (see ParsedMatMulTfOperation).
2348     return std::make_unique<ParsedMatMulTfOperation>(this, nodeDef);
2349 }
2350
2351 ParsedTfOperationPtr TfParser::ParseMean(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2352 {
2353     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2354     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2355     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2356
2357     if (inputs.size() != 2)
2358     {
2359         throw ParseException(
2360                 boost::str(boost::format("Mean expects two inputs!. Got %1% for Node %2% %3%")
2361                            % inputs.size()
2362                            % nodeDef.name()
2363                            % CHECK_LOCATION().AsString()));
2364     }
2365
2366     bool keepDims = ReadMandatoryNodeBoolAttribute(nodeDef, "keep_dims");
2367
2368     ParsedConstTfOperation<int32_t>* axisNode =
2369              boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2370
2371     const TensorInfo& axisTensorInfo = axisNode->GetTensorInfo();
2372
2373     ConstTensor axisTensor(axisTensorInfo, axisNode->GetStorage());
2374     const int* axisData = static_cast<const int*>(axisTensor.GetMemoryArea());
2375
2376     TensorInfo outputTensorInfo;
2377     MeanDescriptor meanDescriptor;
2378     meanDescriptor.m_KeepDims = keepDims;
2379
2380     // Negative axis values are supported so that the process requires
2381     // to convert them into the corresponding positive ones.
2382     // Duplicate values are also removed.
2383     std::vector<int> rawAxisVector(axisData, axisData + axisTensorInfo.GetNumElements());
2384     std::set<unsigned int> positiveAxisSet;
2385     int rank = static_cast<int>(inputTensorInfo.GetNumDimensions());
2386
2387     std::transform(rawAxisVector.begin(), rawAxisVector.end(),
2388                    std::inserter(positiveAxisSet, positiveAxisSet.begin()),
2389                    [rank](int i) -> unsigned int { return static_cast<unsigned int>((i + rank) % rank); });
2390
2391     CalculateReducedOutputTensoInfo(inputTensorInfo, axisTensorInfo, positiveAxisSet, keepDims, outputTensorInfo);
2392
2393     if (inputTensorInfo.GetNumDimensions() > positiveAxisSet.size())
2394     {
2395         meanDescriptor.m_Axis.assign(positiveAxisSet.begin(), positiveAxisSet.end());
2396     }
2397
2398     IConnectableLayer* layer = m_Network->AddMeanLayer(meanDescriptor, nodeDef.name().c_str());
2399     layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2400     inputSlot.Connect(layer->GetInputSlot(0));
2401
2402     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2403 }
2404
2405 /// An ParsedTfOperation for a Mul node.
2406 /// Creation of the armnn Mul layer is deferred until it is actually needed, because Mul nodes
2407 /// are also used for the first part of a leaky relu activation function (Mul followed by Maximum)
2408 /// and in these cases armnn doesn't need a separate layer for the Mul.
2409 ///
2410 class ParsedMulTfOperation : public DeferredSingleLayerParsedTfOperation
2411 {
2412 public:
2413     ParsedMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
2414         : DeferredSingleLayerParsedTfOperation(parser, node)
2415     {
2416     }
2417
2418     void CreateLayerDeferred() override
2419     {
2420         BOOST_ASSERT(m_Layer == nullptr);
2421         m_Layer = m_Parser->AddMultiplicationLayer(m_Node);
2422     }
2423 };
2424
2425 ParsedTfOperationPtr TfParser::ParseMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2426 {
2427     boost::ignore_unused(graphDef);
2428
2429     return std::make_unique<ParsedMulTfOperation>(this, nodeDef);
2430 }
2431
2432 ParsedTfOperationPtr TfParser::ParsePlaceholder(const tensorflow::NodeDef& nodeDef,
2433     const tensorflow::GraphDef& graphDef)
2434 {
2435     boost::ignore_unused(graphDef);
2436
2437     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 0);
2438
2439     const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkInputsBindingInfo.size());
2440
2441     auto it = m_InputShapes.find(nodeDef.name());
2442     if (it == m_InputShapes.end())
2443     {
2444         throw ParseException(
2445             boost::str(
2446                 boost::format(
2447                     "Missing input shape for Placeholder '%1%' %2%")
2448                     % nodeDef.name()
2449                     % CHECK_LOCATION().AsString()));
2450     }
2451     TensorInfo tensorInfo(it->second, DataType::Float32);
2452
2453     IConnectableLayer* const layer = m_Network->AddInputLayer(layerId, nodeDef.name().c_str());
2454
2455     layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2456
2457     TrackInputBinding(layer, layerId, tensorInfo);
2458
2459     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2460 }
2461
2462 ParsedTfOperationPtr TfParser::ParseRealDiv(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2463 {
2464     boost::ignore_unused(graphDef);
2465     return AddRealDivLayer(nodeDef);
2466 }
2467
2468 ParsedTfOperationPtr TfParser::ParseRelu(const tensorflow::NodeDef& nodeDef,
2469     const tensorflow::GraphDef& graphDef)
2470 {
2471     boost::ignore_unused(graphDef);
2472
2473     ActivationDescriptor activationDesc;
2474     activationDesc.m_Function = ActivationFunction::ReLu;
2475     return AddActivationLayer(nodeDef, activationDesc);
2476 }
2477
2478 ParsedTfOperationPtr TfParser::ParseRelu6(const tensorflow::NodeDef& nodeDef,
2479     const tensorflow::GraphDef& graphDef)
2480 {
2481     boost::ignore_unused(graphDef);
2482
2483     ActivationDescriptor activationDesc;
2484     activationDesc.m_Function = ActivationFunction::BoundedReLu;
2485     activationDesc.m_A = 6.0f;
2486     activationDesc.m_B = 0.0f;
2487
2488     return AddActivationLayer(nodeDef, activationDesc);
2489 }
2490
2491 ParsedTfOperationPtr TfParser::ParseSigmoid(const tensorflow::NodeDef& nodeDef,
2492     const tensorflow::GraphDef& graphDef)
2493 {
2494     boost::ignore_unused(graphDef);
2495
2496     ActivationDescriptor activationDesc;
2497     activationDesc.m_Function = ActivationFunction::Sigmoid;
2498
2499     return AddActivationLayer(nodeDef, activationDesc);
2500 }
2501
2502 ParsedTfOperationPtr TfParser::ParseRsqrt(const tensorflow::NodeDef &nodeDef,
2503     const tensorflow::GraphDef &graphDef)
2504 {
2505     boost::ignore_unused(graphDef);
2506
2507     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2508
2509     IConnectableLayer* const layer = m_Network->AddRsqrtLayer(nodeDef.name().c_str());
2510
2511     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2512     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2513     layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
2514
2515     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2516 }
2517
2518 ParsedTfOperationPtr TfParser::ParseSoftmax(const tensorflow::NodeDef& nodeDef,
2519     const tensorflow::GraphDef& graphDef)
2520 {
2521     boost::ignore_unused(graphDef);
2522
2523     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2524
2525     SoftmaxDescriptor softmaxDescriptor;
2526     IConnectableLayer* const layer = m_Network->AddSoftmaxLayer(softmaxDescriptor, nodeDef.name().c_str());
2527
2528     IOutputSlot& prevLayerSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2529     prevLayerSlot.Connect(layer->GetInputSlot(0));
2530     layer->GetOutputSlot(0).SetTensorInfo(prevLayerSlot.GetTensorInfo());
2531
2532     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2533 }
2534
2535 ParsedTfOperationPtr TfParser::ParseSplit(const tensorflow::NodeDef& nodeDef,
2536     const tensorflow::GraphDef& graphDef)
2537 {
2538     boost::ignore_unused(graphDef);
2539
2540     std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
2541     unsigned int numInputs = static_cast<unsigned int>(nodes.size());
2542     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
2543
2544     // The last input is the axis for split operation.
2545     if (!HasParsedConstTensor<int32_t>(inputs[numInputs - 1].m_IndexedValue->GetNode().name()))
2546     {
2547         throw ParseException(
2548             boost::str(
2549                 boost::format(
2550                     "ArmNN only supports split with constant axis. "
2551                     "Input %1%. Node %2% %3%")
2552                 % inputs[numInputs - 1].m_IndexedValue->GetNode().name()
2553                 % nodeDef.name()
2554                 % CHECK_LOCATION().AsString()));
2555     }
2556     ParsedConstTfOperation<int32_t>* shapeNode =
2557         boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[numInputs - 1].m_IndexedValue);
2558
2559     // Get the axis tensor data
2560     std::vector<int32_t> axisTensorData;
2561     shapeNode->GetConstTensor(axisTensorData);
2562
2563     // This splitDim indicates the data format: 3 is the NHWC, 1 is the NCHW.
2564     const unsigned int splitDim = static_cast<unsigned int>(axisTensorData[0]);
2565
2566     // Armnn supports split along the channel dimension for data formats NHWC and NCHW.
2567     if (splitDim == 0 || splitDim == 2)
2568     {
2569         throw ParseException(
2570             boost::str(
2571                 boost::format(
2572                     "Dimension %1% for split is not supported by Armnn. "
2573                     "Node %2% %3%")
2574                 % splitDim
2575                 % nodeDef.name()
2576                 % CHECK_LOCATION().AsString()));
2577     }
2578
2579     // As Armnn only supports splitter outputs of the same shape, therefore num_splits will be limited to an integer.
2580     uint32_t num_split = ReadMandatoryNodeUint32Attribute(nodeDef, "num_or_size_splits");
2581
2582     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2583     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2584
2585     if (inputTensorInfo.GetNumDimensions() != MaxNumOfTensorDimensions)
2586     {
2587         throw armnn::ParseException(
2588             boost::str(
2589                 boost::format(
2590                     "The number of dimensions: %1% for input tensors of the "
2591                     "splitter op should be %2% %3%")
2592                 % inputTensorInfo.GetNumDimensions()
2593                 % MaxNumOfTensorDimensions
2594                 % CHECK_LOCATION().AsString()));
2595     }
2596     auto inputDimSize = inputTensorInfo.GetNumDimensions();
2597
2598     std::vector<unsigned int> splitterDimSizes(inputDimSize);
2599
2600     // Add current input shape to splitterDimSizes
2601     for (unsigned int i = 0; i < inputDimSize; ++i)
2602     {
2603         splitterDimSizes[i] = inputTensorInfo.GetShape()[i];
2604     }
2605
2606     if (splitterDimSizes[splitDim] % num_split != 0)
2607     {
2608         throw ParseException("Number of splits must evenly divide the dimension");
2609     }
2610     splitterDimSizes[splitDim] /= num_split;
2611
2612     SplitterDescriptor splitDesc(num_split);
2613     for (unsigned int g = 0; g < num_split; ++g)
2614     {
2615         // Set the size of the views.
2616         for (unsigned int dimIdx = 0; dimIdx < splitterDimSizes.size(); ++dimIdx)
2617         {
2618             splitDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
2619         }
2620         splitDesc.SetViewOriginCoord(g, splitDim, splitterDimSizes[splitDim] * g);
2621     }
2622
2623     IConnectableLayer *layer = m_Network->AddSplitterLayer(splitDesc, nodeDef.name().c_str());
2624
2625     inputSlot.Connect(layer->GetInputSlot(0));
2626
2627     TensorShape outShape = TensorShape(static_cast<unsigned int>(splitterDimSizes.size()),
2628                                        splitterDimSizes.data());
2629
2630     for (unsigned int i = 0; i < layer->GetNumOutputSlots(); ++i)
2631     {
2632         layer->GetOutputSlot(i).SetTensorInfo(armnn::TensorInfo(outShape, inputTensorInfo.GetDataType()));
2633     }
2634
2635     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2636 }
2637
2638 ParsedTfOperationPtr TfParser::ParseSoftplus(const tensorflow::NodeDef& nodeDef,
2639     const tensorflow::GraphDef& graphDef)
2640 {
2641     boost::ignore_unused(graphDef);
2642
2643     ActivationDescriptor activationDesc;
2644     activationDesc.m_Function = ActivationFunction::SoftReLu;
2645
2646     return AddActivationLayer(nodeDef, activationDesc);
2647 }
2648
2649 ParsedTfOperationPtr TfParser::ParseTanh(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2650 {
2651     boost::ignore_unused(graphDef);
2652
2653     ActivationDescriptor activationDesc;
2654     activationDesc.m_Function = ActivationFunction::TanH;
2655     activationDesc.m_A = 1.0f;
2656     activationDesc.m_B = 1.0f;
2657
2658     return AddActivationLayer(nodeDef, activationDesc);
2659 }
2660
2661 ParsedTfOperationPtr TfParser::AddActivationLayer(const tensorflow::NodeDef& nodeDef,
2662     ActivationDescriptor& activationDesc)
2663 {
2664     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2665
2666     IConnectableLayer* const layer = m_Network->AddActivationLayer(activationDesc, nodeDef.name().c_str());
2667
2668     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2669     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2670     layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
2671     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2672 }
2673
2674 ParsedTfOperationPtr TfParser::ParseMaxPool(const tensorflow::NodeDef& nodeDef,
2675     const tensorflow::GraphDef& graphDef)
2676 {
2677     return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Max);
2678 }
2679
2680 ParsedTfOperationPtr TfParser::ParseAvgPool(const tensorflow::NodeDef& nodeDef,
2681     const tensorflow::GraphDef& graphDef)
2682 {
2683     return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Average);
2684 }
2685
2686 ParsedTfOperationPtr TfParser::ParsePooling2d(const tensorflow::NodeDef& nodeDef,
2687     const tensorflow::GraphDef& graphDef, PoolingAlgorithm pooltype)
2688 {
2689     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2690     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2691     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2692
2693     if (inputs.size() != 1)
2694     {
2695         throw ParseException(
2696             boost::str(
2697                 boost::format(
2698                     "2D Pooling expects one input!. Got %1% for Node %2% %3%")
2699                     % inputs.size()
2700                     % nodeDef.name()
2701                     % CHECK_LOCATION().AsString()));
2702     }
2703
2704     std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
2705     std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
2706     std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
2707     std::vector<uint32_t> ksize = ReadMandatoryNodeUint32ListAttribute(nodeDef, "ksize"); // size of pool windows
2708
2709     Pooling2dDescriptor pooling2dDescriptor;
2710     pooling2dDescriptor.m_PoolType            = pooltype;
2711     pooling2dDescriptor.m_PaddingMethod       = PaddingMethod::Exclude;
2712     pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Floor;
2713
2714     CHECK_DATA_FORMAT(nodeDef, dataFormat, "Pooling2D");
2715     DataLayout dataLayout = dataFormat == "NHWC" ? DataLayout::NHWC : DataLayout::NCHW;
2716     pooling2dDescriptor.m_DataLayout = dataLayout;
2717     DataLayoutIndexed dataLayoutIndexed(dataLayout);
2718
2719     pooling2dDescriptor.m_StrideX    = strides[dataLayoutIndexed.GetWidthIndex()];
2720     pooling2dDescriptor.m_StrideY    = strides[dataLayoutIndexed.GetHeightIndex()];
2721     pooling2dDescriptor.m_PoolWidth  = ksize[dataLayoutIndexed.GetWidthIndex()];
2722     pooling2dDescriptor.m_PoolHeight = ksize[dataLayoutIndexed.GetHeightIndex()];
2723
2724     uint32_t inputHeight = inputTensorInfo.GetShape()[dataLayoutIndexed.GetHeightIndex()];
2725     uint32_t inputWidth  = inputTensorInfo.GetShape()[dataLayoutIndexed.GetWidthIndex()];
2726
2727     bool padding = false;
2728     TensorInfo outputInfo;
2729     unsigned int outputHeight = 0;
2730     unsigned int outputWidth  = 0;
2731
2732     CHECK_PADDING_TYPE(nodeDef, paddingString);
2733
2734     if (paddingString == "SAME")
2735     {
2736         padding = true;
2737
2738         outputHeight = static_cast<uint32_t>(ceil(static_cast<float>(inputHeight) /
2739                                                   static_cast<float>(pooling2dDescriptor.m_StrideY)));
2740         outputWidth  = static_cast<uint32_t>(ceil(static_cast<float>(inputWidth) /
2741                                                   static_cast<float>(pooling2dDescriptor.m_StrideX)));
2742     }
2743     else if (paddingString == "VALID")
2744     {
2745         padding = false;
2746
2747         outputHeight = static_cast<uint32_t>(ceil(
2748                                               static_cast<float>(inputHeight - pooling2dDescriptor.m_PoolHeight + 1) /
2749                                               static_cast<float>(pooling2dDescriptor.m_StrideY)));
2750         outputWidth  = static_cast<uint32_t>(ceil(
2751                                               static_cast<float>(inputWidth - pooling2dDescriptor.m_PoolWidth + 1) /
2752                                               static_cast<float>(pooling2dDescriptor.m_StrideX)));
2753     }
2754
2755     switch (dataLayout)
2756     {
2757         case DataLayout::NHWC:
2758             outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
2759                                       outputHeight,
2760                                       outputWidth,
2761                                       inputTensorInfo.GetShape()[3] },
2762                                     DataType::Float32);
2763             break;
2764         case DataLayout::NCHW:
2765             outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
2766                                       inputTensorInfo.GetShape()[1],
2767                                       outputHeight,
2768                                       outputWidth },
2769                                     DataType::Float32);
2770             break;
2771     }
2772
2773     CalcPadding(inputWidth, pooling2dDescriptor.m_PoolWidth, pooling2dDescriptor.m_StrideX,
2774                 pooling2dDescriptor.m_PadLeft, pooling2dDescriptor.m_PadRight, padding);
2775     CalcPadding(inputHeight, pooling2dDescriptor.m_PoolHeight, pooling2dDescriptor.m_StrideY,
2776                 pooling2dDescriptor.m_PadTop, pooling2dDescriptor.m_PadBottom, padding);
2777
2778
2779     IConnectableLayer* layer = m_Network->AddPooling2dLayer(pooling2dDescriptor, nodeDef.name().c_str());
2780     if (layer == nullptr)
2781     {
2782         throw ParseException(
2783             boost::str(
2784                 boost::format(
2785                     "Failed to add pooling2d layer for %1% %2%")
2786                     % nodeDef.name()
2787                     % CHECK_LOCATION().AsString()));
2788     }
2789
2790     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2791
2792     inputSlot.Connect(layer->GetInputSlot(0));
2793
2794     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2795 }
2796
2797 ParsedTfOperationPtr TfParser::AddAdditionLayer(const tensorflow::NodeDef& nodeDef, bool isBiasAdd)
2798 {
2799     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2800
2801     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2802     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2803
2804     const TensorInfo& input0Info = input0Slot->GetTensorInfo();
2805     const TensorInfo& input1Info = input1Slot->GetTensorInfo();
2806
2807     if (isBiasAdd)
2808     {
2809         // BiasAdd takes bias as a 1D tensor. We need to add a reshape layer to create a 4D tensor
2810         // with the same data in the correct dimension for broadcast in addition.
2811         if(input1Info.GetNumDimensions() != 1)
2812         {
2813             throw ParseException(
2814                 boost::str(
2815                     boost::format(
2816                         "Unsupported bias for BiasAdd. It should be a 1D vector. "
2817                         "Got %1% dimensions for input %2%. Node %3% %4%")
2818                         % input1Info.GetNumDimensions()
2819                         % inputs[1].m_IndexedValue->GetNode().name()
2820                         % nodeDef.name()
2821                         % CHECK_LOCATION().AsString()));
2822         }
2823
2824         const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
2825
2826         CHECK_DATA_FORMAT(nodeDef, dataFormat, "BiasAdd");
2827         input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, dataFormat == "NHWC", *m_Network, nodeDef);
2828     }
2829     else
2830     {
2831         if (input0Info.GetNumDimensions() == 1)
2832         {
2833             const bool isNHWC = true;
2834             input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
2835         }
2836
2837         if (input1Info.GetNumDimensions() == 1)
2838         {
2839             const bool isNHWC = true;
2840             input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
2841         }
2842     }
2843
2844     IConnectableLayer* const layer = m_Network->AddAdditionLayer(nodeDef.name().c_str());
2845
2846     input0Slot->Connect(layer->GetInputSlot(0));
2847     input1Slot->Connect(layer->GetInputSlot(1));
2848
2849     if (input0Info.GetNumDimensions() == 1 && isBiasAdd == false)
2850     {
2851         layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
2852     }
2853     else
2854     {
2855         layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
2856     }
2857
2858     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2859 }
2860
2861 ParsedTfOperationPtr TfParser::AddRealDivLayer(const tensorflow::NodeDef& nodeDef)
2862 {
2863     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2864
2865     IConnectableLayer* const layer = m_Network->AddDivisionLayer(nodeDef.name().c_str());
2866     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2867     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2868
2869     auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
2870     auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
2871
2872
2873     if (input0NumDims < input1NumDims)
2874     {
2875         const bool isNHWC = true;
2876         input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
2877     }
2878     if (input1NumDims < input0NumDims)
2879     {
2880         const bool isNHWC = true;
2881         input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
2882     }
2883
2884     input0Slot->Connect(layer->GetInputSlot(0));
2885     input1Slot->Connect(layer->GetInputSlot(1));
2886
2887     if (input0NumDims < input1NumDims)
2888     {
2889         layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
2890     }
2891     else
2892     {
2893         layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
2894
2895     }
2896     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2897 }
2898
2899 ParsedTfOperationPtr TfParser::AddMaximumLayer(const tensorflow::NodeDef& nodeDef)
2900 {
2901     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2902
2903     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2904     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2905
2906     auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
2907     auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
2908
2909     if (input0NumDims < input1NumDims)
2910     {
2911         const bool isNHWC = true;
2912         input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
2913     }
2914     if (input1NumDims < input0NumDims)
2915     {
2916         const bool isNHWC = true;
2917         input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
2918     }
2919
2920     IConnectableLayer* const layer = m_Network->AddMaximumLayer(nodeDef.name().c_str());
2921
2922     input0Slot->Connect(layer->GetInputSlot(0));
2923     input1Slot->Connect(layer->GetInputSlot(1));
2924
2925     TensorInfo outputInfo = input0Slot->GetTensorInfo();
2926     std::vector<unsigned int> outputShape;
2927
2928     const TensorShape& input0Shape = input0Slot->GetTensorInfo().GetShape();
2929     const TensorShape& input1Shape = input1Slot->GetTensorInfo().GetShape();
2930
2931     for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++)
2932     {
2933         outputShape.push_back(std::max(input0Shape[i], input1Shape[i]));
2934     }
2935
2936     outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data()));
2937     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2938
2939     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2940 }
2941
2942 IConnectableLayer* TfParser::AddMultiplicationLayer(const tensorflow::NodeDef& nodeDef)
2943 {
2944     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2945
2946     IConnectableLayer* const layer = m_Network->AddMultiplicationLayer(nodeDef.name().c_str());
2947     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2948     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2949
2950     auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
2951     auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
2952
2953     if (input0NumDims < input1NumDims)
2954     {
2955         const bool isNHWC = true;
2956         input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
2957     }
2958     if (input1NumDims < input0NumDims)
2959     {
2960         const bool isNHWC = true;
2961         input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
2962     }
2963
2964     input0Slot->Connect(layer->GetInputSlot(0));
2965     input1Slot->Connect(layer->GetInputSlot(1));
2966
2967     if (input0NumDims < input1NumDims)
2968     {
2969         layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
2970     }
2971     else
2972     {
2973         layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
2974     }
2975     return layer;
2976 }
2977
2978 IConnectableLayer* TfParser::AddFullyConnectedLayer(const tensorflow::NodeDef& matMulNodeDef,
2979     const tensorflow::NodeDef* addNodeDef, const char* armnnLayerName)
2980 {
2981     // Finds bias const (if applicable).
2982     ParsedConstTfOperation<float>* biasNode = nullptr;
2983     if (addNodeDef != nullptr)
2984     {
2985         std::vector<OutputOfParsedTfOperation> addInputs = GetInputParsedTfOperationsChecked(*addNodeDef, 2);
2986         // Finds our inputs.
2987         if (HasParsedConstTensor<float>(addInputs[0].m_IndexedValue->GetNode().name()))
2988         {
2989             biasNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(addInputs[0].m_IndexedValue);
2990         }
2991         else if (HasParsedConstTensor<float>(addInputs[1].m_IndexedValue->GetNode().name()))
2992         {
2993             biasNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(addInputs[1].m_IndexedValue);
2994         }
2995         else
2996         {
2997             throw ParseException(
2998                 boost::str(
2999                     boost::format(
3000                         "ArmNN only supports fully connected layers with constant bias. "
3001                         "Inputs %1% and %2%. AddNode %3%. MatMulNode %4% %5%")
3002                         % addInputs[0].m_IndexedValue->GetNode().name()
3003                         % addInputs[1].m_IndexedValue->GetNode().name()
3004                         % addNodeDef->name()
3005                         % matMulNodeDef.name()
3006                         % CHECK_LOCATION().AsString()));
3007         }
3008     }
3009
3010     // Finds matmul inputs.
3011     ParsedConstTfOperation<float>* weightNode = nullptr;
3012     ParsedTfOperation* inputNode  = nullptr;
3013     unsigned int inputIdx = 0;
3014     std::vector<OutputOfParsedTfOperation> mulInputs = GetInputParsedTfOperationsChecked(matMulNodeDef, 2);
3015     if (HasParsedConstTensor<float>(mulInputs[0].m_IndexedValue->GetNode().name()))
3016     {
3017         weightNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(mulInputs[0].m_IndexedValue);
3018         inputNode = mulInputs[1].m_IndexedValue;
3019         inputIdx = mulInputs[1].m_Index;
3020     }
3021     else if (HasParsedConstTensor<float>(mulInputs[1].m_IndexedValue->GetNode().name()))
3022     {
3023         weightNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(mulInputs[1].m_IndexedValue);
3024         inputNode = mulInputs[0].m_IndexedValue;
3025         inputIdx = mulInputs[0].m_Index;
3026     }
3027     else
3028     {
3029         throw ParseException(
3030             boost::str(
3031                 boost::format(
3032                     "ArmNN only supports fully connected layers with constant weights. "
3033                     "Inputs %1% and %2%. MatMulNode %3% %4%")
3034                     % mulInputs[0].m_IndexedValue->GetNode().name()
3035                     % mulInputs[1].m_IndexedValue->GetNode().name()
3036                     % matMulNodeDef.name()
3037                     % CHECK_LOCATION().AsString()));
3038     }
3039
3040     std::vector<float> weightTensorData;
3041     // Handles weight.
3042     ConstTensor weights = weightNode->GetConstTensor(weightTensorData);
3043
3044     FullyConnectedDescriptor desc;
3045     desc.m_BiasEnabled = addNodeDef != nullptr;
3046
3047     IConnectableLayer* layer = nullptr;
3048     // Makes the layer.
3049     if (addNodeDef != nullptr)
3050     {
3051         std::vector<float> biasTensorData;
3052         ConstTensor biases = biasNode->GetConstTensor(biasTensorData);
3053
3054         if (weights.GetShape()[1] != biases.GetShape()[0])
3055         {
3056             throw ParseException(
3057                 boost::str(
3058                     boost::format(
3059                         "Shape of matmul weights and bias do not match. "
3060                         "AddNode %1%. MatMulNode %2% %3%")
3061                         % addNodeDef->name()
3062                         % matMulNodeDef.name()
3063                         % CHECK_LOCATION().AsString()));
3064         }
3065
3066         layer = m_Network->AddFullyConnectedLayer(desc, weights, biases, armnnLayerName);
3067     }
3068     else
3069     {
3070         layer = m_Network->AddFullyConnectedLayer(desc, weights, armnnLayerName);
3071     }
3072
3073     BOOST_ASSERT(layer != nullptr);
3074
3075     inputNode->ResolveArmnnOutputSlot(inputIdx).Connect(layer->GetInputSlot(0));
3076     unsigned int batches = inputNode->ResolveArmnnOutputSlot(inputIdx).GetTensorInfo().GetShape()[0];
3077
3078     // Handles output.
3079     TensorInfo outputInfo({ batches, weights.GetShape()[1] }, DataType::Float32);
3080     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
3081     return layer;
3082 }
3083
3084 void TfParser::LoadNodeDef(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
3085 {
3086     // Gets the type of the node (assume float).
3087     tensorflow::DataType type = tensorflow::DT_FLOAT;
3088     if (nodeDef.attr().count("T") != 0)
3089     {
3090         auto attr = nodeDef.attr().at("T");
3091         type      = attr.type();
3092     }
3093     else if (nodeDef.attr().count("dtype") != 0)
3094     {
3095         auto attr = nodeDef.attr().at("dtype");
3096         type      = attr.type();
3097     }
3098
3099     if (type != tensorflow::DT_FLOAT && nodeDef.op() != "Const")
3100     {
3101         throw ParseException(
3102             boost::str(
3103                 boost::format(
3104                     "Currently only FLOAT is supported for tensorflow nodes (apart from Const). "
3105                     "Got %1% for Node %2% %3%")
3106                     % tensorflow::DataType_Name(type)
3107                     % nodeDef.name()
3108                     % CHECK_LOCATION().AsString()));
3109     }
3110
3111     const std::string& operation = nodeDef.op();
3112     auto itControlInput = std::find(m_ControlInputs.begin(), m_ControlInputs.end(), operation);
3113     if (itControlInput != m_ControlInputs.end())
3114     {
3115         // We currently allow Control Input from TensorFlow graph but we ignore them from ArmNN graph.
3116         return;
3117     }
3118     auto it = ms_OperationNameToParsingFunctions.find(operation);
3119     if (it != ms_OperationNameToParsingFunctions.end())
3120     {
3121         auto func = it->second;
3122         ParsedTfOperationPtr parsedTfOperation = (this->*func)(nodeDef, graphDef);
3123         ParsedTfOperation* parsedTfOperationRaw = parsedTfOperation.get();
3124
3125         // Stores the parsed operation so that dependent layers can connect to it.
3126         auto it = m_ParsedTfOperations.find(nodeDef.name());
3127         if (it != m_ParsedTfOperations.end())
3128         {
3129             throw ParseException(boost::str(boost::format("Name %1% used by more than one node") % nodeDef.name()));
3130         }
3131         m_ParsedTfOperations[nodeDef.name()] = std::move(parsedTfOperation);
3132
3133         // If this node was requested as an output from the network, then adds an ArmNN output layer.
3134         if (std::find(m_RequestedOutputs.begin(), m_RequestedOutputs.end(), nodeDef.name()) !=
3135             m_RequestedOutputs.end())
3136         {
3137             auto outId = ParseOutputId(nodeDef.name());
3138             const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkOutputsBindingInfo.size());
3139             IOutputSlot& prevSlot = parsedTfOperationRaw->ResolveArmnnOutputSlot(outId.m_Index);
3140
3141             TensorInfo tensorInfo = prevSlot.GetTensorInfo();
3142
3143             IConnectableLayer* outputLayer = m_Network->AddOutputLayer(layerId, nodeDef.name().c_str());
3144
3145             prevSlot.Connect(outputLayer->GetInputSlot(0));
3146
3147             TrackOutputBinding(outputLayer, layerId, tensorInfo);
3148         }
3149     }
3150     else
3151     {
3152         throw ParseException(
3153             boost::str(
3154                 boost::format(
3155                     "Unsupported operation %1% in tensorflow::GraphDef %2%")
3156                     % operation
3157                     % CHECK_LOCATION().AsString()));
3158     }
3159 }
3160
3161 void TfParser::LoadGraphDef(const tensorflow::GraphDef& graphDef)
3162 {
3163     // Adds all nodes to our map.
3164     m_NodesByName.clear();
3165     m_NetworkInputsBindingInfo.clear();
3166     m_NetworkOutputsBindingInfo.clear();
3167
3168     for (int i = 0; i < graphDef.node_size(); ++i)
3169     {
3170         const tensorflow::NodeDef& node = graphDef.node(i);
3171         m_NodesByName[node.name()]      = &node;
3172     }
3173
3174     // Finds the output nodes the user requested.
3175     std::vector<const tensorflow::NodeDef*> targetNodes;
3176     for (const std::string& requestedOutputName : m_RequestedOutputs)
3177     {
3178         auto nodeIt = m_NodesByName.find(requestedOutputName);
3179         if (nodeIt == m_NodesByName.end())
3180         {
3181             throw ParseException(
3182                 boost::str(
3183                     boost::format(
3184                         "Couldn't find requested output node '%1%' in graph %2%")
3185                         % requestedOutputName
3186                         % CHECK_LOCATION().AsString()));
3187         }
3188         targetNodes.push_back(nodeIt->second);
3189     }
3190
3191     // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
3192     std::vector<const tensorflow::NodeDef*> sortedNodes;
3193     if (!armnnUtils::GraphTopologicalSort<const tensorflow::NodeDef*>(
3194         targetNodes,
3195         [this](const tensorflow::NodeDef* node)
3196         {
3197             auto outputs = GetTfInputNodes(*node);
3198             std::vector<const tensorflow::NodeDef*> nodesOnly;
3199             for (const auto & o : outputs) {
3200                 nodesOnly.push_back(o.m_IndexedValue);
3201             }
3202             return nodesOnly;
3203         },
3204         sortedNodes))
3205     {
3206         throw ParseException(
3207             boost::str(
3208                 boost::format(
3209                     "Cycle detected in graph %1%")
3210                     % CHECK_LOCATION().AsString()));
3211     }
3212
3213     // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
3214     for (const auto& it : sortedNodes)
3215     {
3216         const tensorflow::NodeDef& currentNode = *it;
3217         LoadNodeDef(currentNode, graphDef);
3218     }
3219 }
3220
3221 INetworkPtr TfParser::CreateNetworkFromTextFile(const char* graphFile,
3222     const std::map<std::string, TensorShape>& inputShapes,
3223     const std::vector<std::string>& requestedOutputs)
3224 {
3225     FILE* fd = fopen(graphFile, "r");
3226
3227     if (fd == nullptr)
3228     {
3229         throw FileNotFoundException(
3230             boost::str(
3231                 boost::format(
3232                     "Graph file %1% failed to open %2%")
3233                     % graphFile
3234                     % CHECK_LOCATION().AsString()));
3235     }
3236
3237     // Parses the file into a message.
3238     tensorflow::GraphDef graphDef;
3239     auto                 input   = new google::protobuf::io::FileInputStream(fileno(fd));
3240     bool                 success = google::protobuf::TextFormat::Parse(input, &graphDef);
3241     delete input;
3242     fclose(fd);
3243
3244     if (!success)
3245     {
3246         throw ParseException(
3247             boost::str(
3248                 boost::format(
3249                     "Failed to parse graph file %1%")
3250                     % CHECK_LOCATION().AsString()));
3251     }
3252
3253     return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
3254 }
3255
3256 INetworkPtr TfParser::CreateNetworkFromString(const char* protoText,
3257     const std::map<std::string, TensorShape>& inputShapes,
3258     const std::vector<std::string>& requestedOutputs)
3259 {
3260     // Parses the string into a message.
3261     tensorflow::GraphDef graphDef;
3262     bool success = google::protobuf::TextFormat::ParseFromString(protoText, &graphDef);
3263
3264     if (!success)
3265     {
3266         throw ParseException(
3267             boost::str(
3268                 boost::format(
3269                     "Failed to parse graph file %1%")
3270                     % CHECK_LOCATION().AsString()));
3271     }
3272
3273     return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
3274 }
3275
3276 INetworkPtr TfParser::CreateNetworkFromBinaryFile(const char* graphFile,
3277     const std::map<std::string, TensorShape>& inputShapes,
3278     const std::vector<std::string>& requestedOutputs)
3279 {
3280     FILE* fd = fopen(graphFile, "rb");
3281
3282     if (fd == nullptr)
3283     {
3284         throw FileNotFoundException(
3285             boost::str(
3286                 boost::format(
3287                     "Graph file %1% failed to open %2%")
3288                     % graphFile
3289                     % CHECK_LOCATION().AsString()));
3290     }
3291
3292     // Parses the file into a message.
3293     tensorflow::GraphDef graphDef;
3294
3295     google::protobuf::io::FileInputStream  inStream(fileno(fd));
3296     google::protobuf::io::CodedInputStream codedStream(&inStream);
3297     codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
3298     bool success = graphDef.ParseFromCodedStream(&codedStream);
3299     fclose(fd);
3300
3301     if (!success)
3302     {
3303         throw ParseException(
3304             boost::str(
3305                 boost::format(
3306                     "Failed to parse protobuf file %1% %2%")
3307                     % graphFile
3308                     % CHECK_LOCATION().AsString()));
3309     }
3310
3311     return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
3312 }
3313
3314 INetworkPtr TfParser::CreateNetworkFromGraphDef(const tensorflow::GraphDef& graphDef,
3315     const std::map<std::string, TensorShape>& inputShapes,
3316     const std::vector<std::string>& requestedOutputs)
3317 {
3318     m_Network = INetwork::Create();
3319
3320     m_InputShapes = inputShapes;
3321     if (requestedOutputs.size() == 0)
3322     {
3323         throw ParseException(
3324             boost::str(
3325                 boost::format(
3326                     "requestedOutputs must have at least one entry %1%")
3327                     % CHECK_LOCATION().AsString()));
3328     }
3329     m_RequestedOutputs = requestedOutputs;
3330
3331     try
3332     {
3333         LoadGraphDef(graphDef);
3334     }
3335     catch (const ParseException& e)
3336     {
3337         Cleanup();
3338         throw e;
3339     }
3340
3341     Cleanup();
3342
3343     return std::move(m_Network);
3344 }
3345
3346 void TfParser::Cleanup()
3347 {
3348     // Cleanup, in case we reuse this parser.
3349     m_InputShapes.clear();
3350     m_RequestedOutputs.clear();
3351     m_NodesByName.clear();
3352     m_ParsedTfOperations.clear();
3353 }
3354
3355 BindingPointInfo TfParser::GetNetworkInputBindingInfo(const std::string& name) const
3356 {
3357     return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
3358 }
3359
3360 BindingPointInfo TfParser::GetNetworkOutputBindingInfo(const std::string& name) const
3361 {
3362     return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
3363 }
3364
3365 std::pair<LayerBindingId, TensorInfo> TfParser::GetBindingInfo(const std::string& layerName,
3366     const char* bindingPointDesc,
3367     const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
3368 {
3369     auto it = nameToBindingInfo.find(layerName);
3370     if (it == nameToBindingInfo.end())
3371     {
3372         throw InvalidArgumentException(
3373             boost::str(
3374                 boost::format(
3375                     "Unknown %1% '%2%' %3%")
3376                     % bindingPointDesc
3377                     % layerName
3378                     % CHECK_LOCATION().AsString()));
3379     }
3380     return it->second;
3381 }
3382
3383 void TfParser::TrackInputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
3384 {
3385     return TrackBindingPoint(layer, id, tensorInfo, "input", m_NetworkInputsBindingInfo);
3386 }
3387
3388 void TfParser::TrackOutputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
3389 {
3390     return TrackBindingPoint(layer, id, tensorInfo, "output", m_NetworkOutputsBindingInfo);
3391 }
3392
3393 void TfParser::TrackBindingPoint(IConnectableLayer* layer,
3394     LayerBindingId id,
3395     const TensorInfo& tensorInfo,
3396     const char* bindingPointDesc,
3397     std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
3398 {
3399     const std::string layerName = layer->GetName();
3400     auto it = nameToBindingInfo.find(layerName);
3401     if (it == nameToBindingInfo.end())
3402     {
3403         nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
3404     }
3405     else
3406     {
3407         throw ParseException(
3408             boost::str(
3409                 boost::format(
3410                     "Id %1% used by more than one %2% layer %3%")
3411                     % id
3412                     % bindingPointDesc
3413                     % CHECK_LOCATION().AsString()));
3414     }
3415 }
3416
3417 } // namespace armnnTfParser