Release 18.03
[platform/upstream/armnn.git] / src / armnnTfParser / TfParser.cpp
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // See LICENSE file in the project root for full license information.
4 //
5 #include "TfParser.hpp"
6
7 #include <armnn/INetwork.hpp>
8 #include <armnn/Utils.hpp>
9 #include <armnn/TypesUtils.hpp>
10 #include <armnn/Exceptions.hpp>
11 #include <armnn/Descriptors.hpp>
12
13 #include <GraphTopologicalSort.hpp>
14 #include <Permute.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 #include "tensorflow/core/framework/node_def.pb.h"
21 #include "tensorflow/core/framework/types.pb.h"
22 #include "tensorflow/core/framework/tensor.pb.h"
23 #include "tensorflow/core/framework/tensor_shape.pb.h"
24
25 #include <boost/assert.hpp>
26 #include <boost/format.hpp>
27 #include <boost/core/ignore_unused.hpp>
28 #include <boost/log/trivial.hpp>
29 #include <boost/numeric/conversion/cast.hpp>
30 #include <boost/polymorphic_cast.hpp>
31
32 #include <memory>
33 #include <sstream>
34 #include <numeric>
35 #include <functional>
36
37 using namespace armnn;
38
39 namespace armnnTfParser
40 {
41 namespace
42 {
43
44 const PermutationVector NHWCToArmNN = { 0, 2, 3, 1 };
45 const PermutationVector ArmNNToNHWC = { 0, 3, 1, 2 };
46
47 IConnectableLayer* AddSwizzleLayer(INetwork& network, IOutputSlot& input, const PermutationVector& mapping,
48     const std::string& name)
49 {
50     // Add swizzle layer
51     IConnectableLayer* const layer = network.AddPermuteLayer(mapping, name.c_str());
52
53     // Connect intput to swizzle layer
54     input.Connect(layer->GetInputSlot(0));
55
56     // Setup swizzled output
57     const TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mapping);
58     layer->GetOutputSlot(0).SetTensorInfo(outInfo);
59
60     return layer;
61 }
62
63 IConnectableLayer* SwizzleInDeswizzleOut(INetwork& network, IOutputSlot& input, IConnectableLayer& layer,
64     const std::string& name)
65 {
66     // Add swizzle layer
67     IConnectableLayer* const swizzleLayer = AddSwizzleLayer(network, input, NHWCToArmNN, "swizzle_for-" + name);
68
69     // Connect swizzledInput to layer
70     swizzleLayer->GetOutputSlot(0).Connect(layer.GetInputSlot(0));
71
72     // Add deswizzle layer
73     IConnectableLayer* const deswizzleLayer = AddSwizzleLayer(network, layer.GetOutputSlot(0), ArmNNToNHWC,
74         "deswizzle_for-" + name);
75
76     return deswizzleLayer;
77 }
78
79 template <typename Callable>
80 void ReadMandatoryNodeAttributeImpl(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(boost::str(boost::format(
96                 "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, "
97                 "but found %4% instead")
98                 % attribName
99                 % nodeDef.name()
100                 % static_cast<int>(expectedValueCase)
101                 % static_cast<int>(attrValue.value_case())));
102         }
103     }
104     else
105     {
106         throw ParseException(boost::str(boost::format("Could not find required attribute %1% in node %2%")
107             % attribName % nodeDef.name()));
108     }
109 }
110
111 template <typename Callable>
112 void ReadOptionalNodeAttributeImpl(const tensorflow::NodeDef& nodeDef,
113     const std::string& attribName,
114     tensorflow::AttrValue::ValueCase expectedValueCase,
115     Callable callable)
116 {
117     auto iter = nodeDef.attr().find(attribName);
118     if (iter != nodeDef.attr().end())
119     {
120         const auto& attrValue = iter->second;
121         if (attrValue.value_case() == expectedValueCase)
122         {
123             callable(attrValue);
124         }
125         else
126         {
127             throw ParseException(boost::str(boost::format(
128                 "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, "
129                 "but found %4% instead")
130                 % attribName
131                 % nodeDef.name()
132                 % static_cast<int>(expectedValueCase)
133                 % static_cast<int>(attrValue.value_case())));
134         }
135     }
136 }
137
138 float ReadMandatoryNodeFloatAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
139 {
140     float attribValue = 0.0f;
141     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kF,
142         [&attribValue](const tensorflow::AttrValue& attrValue)
143     {
144         attribValue = attrValue.f();
145     });
146     return attribValue;
147 }
148
149 uint32_t ReadMandatoryNodeUint32Attribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
150 {
151     uint32_t attribValue = 0u;
152     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kI,
153         [&attribValue](const tensorflow::AttrValue& attrValue)
154     {
155         attribValue = static_cast<uint32_t>(attrValue.i());
156     });
157     return attribValue;
158 }
159
160 std::string ReadMandatoryNodeStringAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
161 {
162     std::string attribValue = "";
163     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kS,
164         [&attribValue](const tensorflow::AttrValue& attrValue)
165     {
166         attribValue = attrValue.s();
167     });
168     return attribValue;
169 }
170
171 std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef,
172     const std::string& name)
173 {
174     std::vector<uint32_t> attriList;
175     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList,
176         [&attriList](const tensorflow::AttrValue& attrValue)
177     {
178         for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum)
179         {
180             attriList.push_back(static_cast<uint32_t>(attrValue.list().i(attriNum)));
181         }
182     });
183
184     return attriList;
185 }
186
187 std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef,
188     const std::string& name)
189 {
190     std::vector<uint32_t> attriList;
191     ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList,
192         [&attriList](const tensorflow::AttrValue& attrValue)
193     {
194         for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum)
195         {
196             attriList.push_back(static_cast<uint32_t>(attrValue.list().i(attriNum)));
197         }
198     });
199
200     return attriList;
201 }
202
203 bool ReadOptionalNodeBoolAttribute(const tensorflow::NodeDef& nodeDef,
204     const std::string& name,
205     bool defaultValue = false)
206 {
207     bool attribValue = defaultValue;
208     ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kB,
209         [&attribValue](const tensorflow::AttrValue& attrValue)
210     {
211         attribValue = attrValue.b();
212     });
213     return attribValue;
214 }
215
216 tensorflow::DataType ReadMandatoryNodeTypeAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
217 {
218     tensorflow::DataType attribValue = tensorflow::DT_INVALID;
219     ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kType,
220         [&attribValue](const tensorflow::AttrValue& attrValue)
221     {
222         attribValue = attrValue.type();
223     });
224     return attribValue;
225 }
226
227 TensorInfo PrepareReshape(const TensorInfo& input, const std::vector<int32_t>& targetDims)
228 {
229     std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
230     const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
231
232     if (stretchDim != targetDims.end())
233     {
234         if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
235         {
236             throw ParseException("At most one component of shape can be -1");
237         }
238
239         auto targetNumElements = boost::numeric_cast<unsigned int>(std::accumulate(targetDims.begin(), targetDims.end(),
240             -1, std::multiplies<int32_t>()));
241         auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
242         outDims[stretchIndex] = input.GetNumElements() / targetNumElements;
243     }
244
245     TensorInfo reshapeInfo = input;
246     reshapeInfo.SetShape(TensorShape{ static_cast<unsigned int>(outDims.size()), outDims.data() });
247
248     return reshapeInfo;
249 }
250
251 // We need the input0Slot to guide the reshape for input1Slot
252 IOutputSlot* BroadcastForAddandMul(IOutputSlot* input0Slot, IOutputSlot* input1Slot, bool isNHWC, INetwork& m_Network,
253                                    const tensorflow::NodeDef& nodeDef)
254 {
255     const TensorInfo& input1Info = input1Slot->GetTensorInfo();
256     const TensorInfo inputTensorInfo = input0Slot->GetTensorInfo();
257     const unsigned int matchDim = inputTensorInfo.GetNumDimensions() - (isNHWC ? 1 : 3);
258     std::array<unsigned int, MaxNumOfTensorDimensions> reshapedDimensions;
259     std::fill_n(reshapedDimensions.begin(), inputTensorInfo.GetNumDimensions(), 1);
260     reshapedDimensions[matchDim] = input1Info.GetShape()[0];
261
262     armnn::TensorInfo reshapedInfo = input1Info;
263     reshapedInfo.SetShape(TensorShape{ inputTensorInfo.GetNumDimensions(), reshapedDimensions.data() });
264
265     const std::string reshapeLayerName = "reshape_for-" + nodeDef.name();
266     ReshapeDescriptor reshapeDesc;
267     reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
268     IConnectableLayer* const reshapeLayer = m_Network.AddReshapeLayer(reshapeDesc, reshapeLayerName.c_str());
269
270     input1Slot->Connect(reshapeLayer->GetInputSlot(0));
271     reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
272
273     input1Slot = &reshapeLayer->GetOutputSlot(0);
274
275     return input1Slot;
276 }
277
278 OutputId ParseOutputId(const std::string & name)
279 {
280     unsigned int outputNum = 0;
281     size_t colonPos = name.find_last_of(":");
282     if (colonPos != std::string::npos)
283     {
284         int n = std::stoi(name.substr(colonPos+1));
285         if (n<0 || n>100)
286         {
287             throw ParseException("Output tensor id is out of range for "+name);
288         }
289         outputNum = static_cast<unsigned int>(n);
290     }
291     return OutputId(name.substr(0,colonPos),outputNum);
292 }
293
294 } // namespace
295
296 const std::map<std::string, TfParser::OperationParsingFunction> TfParser::ms_OperationNameToParsingFunctions = {
297     { "Const",                 &TfParser::ParseConst },
298     { "Add",                   &TfParser::ParseAdd },
299     { "BiasAdd",               &TfParser::ParseBiasAdd },
300     { "Identity",              &TfParser::ParseIdentity },
301     { "Conv2D",                &TfParser::ParseConv2D },
302     { "DepthwiseConv2dNative", &TfParser::ParseDepthwiseConv2D },
303     { "FusedBatchNorm",        &TfParser::ParseFusedBatchNorm },
304     { "ConcatV2",              &TfParser::ParseConcat },
305     { "LRN",                   &TfParser::ParseLrn },
306     { "MatMul",                &TfParser::ParseMatMul },
307     { "Mul",                   &TfParser::ParseMul },
308     { "Placeholder",           &TfParser::ParsePlaceholder },
309     { "Relu",                  &TfParser::ParseRelu },
310     { "Relu6",                 &TfParser::ParseRelu6 },
311     { "Reshape",               &TfParser::ParseReshape },
312     { "ResizeBilinear",        &TfParser::ParseResizeBilinear },
313     { "Shape",                 &TfParser::ParseShape },
314     { "Squeeze",               &TfParser::ParseSqueeze },
315     { "Sigmoid",               &TfParser::ParseSigmoid },
316     { "Softmax",               &TfParser::ParseSoftmax },
317     { "Softplus",              &TfParser::ParseSoftplus },
318     { "Tanh",                  &TfParser::ParseTanh },
319     { "MaxPool",               &TfParser::ParseMaxPool },
320     { "AvgPool",               &TfParser::ParseAvgPool },
321 };
322
323 ITfParser* ITfParser::CreateRaw()
324 {
325     return new TfParser();
326 }
327
328 ITfParserPtr ITfParser::Create()
329 {
330     return ITfParserPtr(CreateRaw(), &ITfParser::Destroy);
331 }
332
333 void ITfParser::Destroy(ITfParser* parser)
334 {
335     delete parser;
336 }
337
338 inline void CalculateSamePadding(uint32_t inputSize, uint32_t stride,
339                                  uint32_t filterSize, bool samePadding,
340                                  uint32_t* paddingFront, uint32_t* paddingBack) {
341     *paddingFront = 0;
342     *paddingBack = 0;
343
344     if (samePadding) {
345         uint32_t outputSize = (inputSize + stride - 1) / stride;
346         uint32_t temp = (outputSize - 1) * stride + filterSize;
347         if (temp > inputSize) {
348             *paddingFront = (temp - inputSize) / 2;
349             *paddingBack = (temp - inputSize) - *paddingFront;
350         }
351     }
352 }
353
354 void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
355                  bool samePadding)
356 {
357     CalculateSamePadding(input, stride, kernel, samePadding, &outPadHead, &outPadTail);
358 }
359
360 /// An Abstract base class which represents a single tensorflow operation (node)
361 /// that has been (potentially partially) converted to Armnn.
362 /// It may not yet have been fully converted into actual Armnn layers.
363 class ParsedTfOperation
364 {
365 public:
366     ParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
367     : m_Parser(parser)
368     , m_Node(node)
369     {
370     }
371
372     virtual ~ParsedTfOperation() {};
373
374     const tensorflow::NodeDef& GetNode() const { return m_Node; }
375
376     /// Gets the ArmNN IOutputSlot corresponding to the given output index of the Tensorflow operation.
377     /// This may result in the creation of Armnn layers if this was deferred (e.g. see ParsedConstTfOperation).
378     virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) = 0;
379
380     /// If this operation is an Identity then this will follow return the 'parent' operation (recursively).
381     virtual ParsedTfOperation* ResolveIdentityOperations()
382     {
383         return this;
384     }
385
386 protected:
387     TfParser* m_Parser;
388     const tensorflow::NodeDef& m_Node;
389 };
390
391 /// An ParsedTfOperation where the Armnn equivalent is a single layer,
392 /// with output slots that correspond directly to the Tf node outputs.
393 class SingleLayerParsedTfOperation : public ParsedTfOperation
394 {
395 public:
396     SingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node, IConnectableLayer* layer)
397     : ParsedTfOperation(parser, node)
398     , m_Layer(layer)
399     {
400     }
401
402     IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
403     {
404         BOOST_ASSERT(m_Layer);
405         // Assume one-to-one mapping between Tf and armnn output slots.
406         unsigned int armnnOutputSlotIdx = tfOutputIndex;
407         if (armnnOutputSlotIdx >= m_Layer->GetNumOutputSlots())
408         {
409             throw ParseException(
410                 boost::str(boost::format("The requested output slot #%1% "
411                     "for %2% does not exist") % armnnOutputSlotIdx % m_Layer->GetName()));
412         }
413         return m_Layer->GetOutputSlot(armnnOutputSlotIdx);
414     }
415
416 protected:
417     IConnectableLayer* m_Layer;
418 };
419
420 /// A SingleLayerParsedTfOperation for deferred layer creation
421 class DeferredSingleLayerParsedTfOperation : public SingleLayerParsedTfOperation
422 {
423 public:
424     DeferredSingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
425     : SingleLayerParsedTfOperation(parser, node, nullptr)
426     {
427     }
428
429     IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
430     {
431         if (!m_Layer)
432         {
433             CreateLayerDeferred();
434         }
435         return SingleLayerParsedTfOperation::ResolveArmnnOutputSlot(tfOutputIndex);
436     }
437
438 private:
439     virtual void CreateLayerDeferred() = 0;
440 };
441
442
443 TfParser::TfParser()
444     : m_Network(nullptr, nullptr)
445 {
446 }
447
448
449 const tensorflow::NodeDef* TfParser::ResolveIdentityNode(const tensorflow::NodeDef* nodeDef)
450 {
451     if (nodeDef->op() != "Identity")
452     {
453         return nodeDef;
454     }
455
456     if (nodeDef->input_size() != 1)
457     {
458         throw ParseException("Identity node does not have correct amount of inputs!");
459     }
460
461     auto it = m_NodesByName.find(nodeDef->input(0));
462     if (it != m_NodesByName.end())
463     {
464         const tensorflow::NodeDef* inputNode = it->second;
465         return ResolveIdentityNode(inputNode);
466     }
467     else
468     {
469         throw ParseException("Cannot find what the Identity node is linked to!");
470     }
471 }
472
473 std::vector<OutputOfConstNodeDef>
474 TfParser::GetTfInputNodes(const tensorflow::NodeDef& nodeDef) const
475 {
476     std::vector<OutputOfConstNodeDef> ret;
477
478     ret.reserve(boost::numeric_cast<size_t>(nodeDef.input_size()));
479     for (int j = 0; j < nodeDef.input_size(); ++j)
480     {
481         OutputId outputId = ParseOutputId(nodeDef.input(j));
482         auto inputIt = m_NodesByName.find(outputId.m_IndexedValue);
483         if (inputIt == m_NodesByName.end())
484         {
485             throw ParseException(
486                 "Can't find node '" + nodeDef.input(j) +
487                 "', which is listed as an input of '" + nodeDef.name() + "'");
488         }
489         ret.push_back(OutputOfConstNodeDef(inputIt->second,outputId.m_Index));
490     }
491
492     return ret;
493 }
494
495 std::vector<OutputOfParsedTfOperation>
496 TfParser::GetInputParsedTfOperationsChecked(const tensorflow::NodeDef& nodeDef,
497                                             std::size_t expectedNumInputs)
498 {
499     // Fetch the tensorflow nodes connected as inputs and validate the size.
500     std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
501     const std::size_t numInputs = nodes.size();
502     if (numInputs != expectedNumInputs)
503     {
504         throw ParseException(boost::str(boost::format("Unexpected number of inputs for node %1%. "
505             "Expected %2%, found %3%") % nodeDef.name() % expectedNumInputs % numInputs));
506     }
507     // Fetch the corresponding ParsedTfOperation operations
508     std::vector<OutputOfParsedTfOperation> result;
509     for (auto&& node : nodes)
510     {
511         auto it = m_ParsedTfOperations.find(node.m_IndexedValue->name());
512         if (it == m_ParsedTfOperations.end())
513         {
514             throw ParseException("Node with name '" + node.m_IndexedValue->name() + "' has not been parsed");
515         }
516         ParsedTfOperation* parsedOp = it->second.get();
517         // Transparently 'skip' any Identity operations. This simplifies the logic inside the ParseXXX() functions.
518         parsedOp = parsedOp->ResolveIdentityOperations();
519         result.push_back(OutputOfParsedTfOperation(parsedOp,node.m_Index));
520     }
521     return result;
522 }
523
524 ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
525 {
526     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
527
528     // If one of the inputs is a MatMul and the other is a const, then we handle both nodes together as FullyConnected
529     if (inputs[0].m_IndexedValue->GetNode().op() == "MatMul" &&
530         HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
531     {
532         IConnectableLayer* layer =
533             AddFullyConnectedLayer(inputs[0].m_IndexedValue->GetNode(),
534                                    &nodeDef,nodeDef.name().c_str());
535         return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
536     }
537     else if (HasParsedConstTensor<float>(inputs[0].m_IndexedValue->GetNode().name()) &&
538                                          inputs[1].m_IndexedValue->GetNode().op() == "MatMul")
539     {
540         IConnectableLayer* layer =
541             AddFullyConnectedLayer(inputs[1].m_IndexedValue->GetNode(),
542                                    &nodeDef,nodeDef.name().c_str());
543         return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
544     }
545     else
546     {
547         // Otherwise it's just a regular addition
548         return AddAdditionLayer(nodeDef);
549     }
550 }
551
552 ParsedTfOperationPtr TfParser::ParseBiasAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
553 {
554     return AddAdditionLayer(nodeDef, true);
555 }
556
557 /// An ParsedTfOperation which forwards to another (used for Identity nodes).
558 class ParsedIdentityTfOperation : public ParsedTfOperation
559 {
560 public:
561     ParsedIdentityTfOperation(TfParser* parser, const tensorflow::NodeDef& node, ParsedTfOperation* representative)
562         : ParsedTfOperation(parser, node)
563         , m_Representative(representative)
564     {
565     }
566
567     virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
568     {
569         BOOST_ASSERT(m_Representative);
570         return m_Representative->ResolveArmnnOutputSlot(tfOutputIndex);
571     }
572
573     virtual ParsedTfOperation* ResolveIdentityOperations() override
574     {
575         return m_Representative->ResolveIdentityOperations();
576     }
577
578 private:
579     ParsedTfOperation* m_Representative;
580 };
581
582 ParsedTfOperationPtr TfParser::ParseIdentity(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
583 {
584     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
585     // Any requests for the output slots of this node should be forwarded to the node connected as input.
586     return std::make_unique<ParsedIdentityTfOperation>(this, nodeDef, inputs[0].m_IndexedValue);
587 }
588
589 /// An ParsedTfOperation for a Const node.
590 /// Creation of the armnn ConstLayer is deferred until it is actually needed, because Const nodes are mostly used
591 /// for weight inputs to MatMul/Conv2D nodes and in these cases armnn doesn't need a ConstLayer.
592 template <typename T>
593 class ParsedConstTfOperation : public DeferredSingleLayerParsedTfOperation
594 {
595 public:
596     ParsedConstTfOperation(TfParser* parser, const tensorflow::NodeDef& node,
597         const T* tensorData, const TensorInfo& tensorInfo)
598         : DeferredSingleLayerParsedTfOperation(parser, node),
599         m_Storage(tensorData, tensorData + tensorInfo.GetNumElements()),
600         m_TensorInfo(tensorInfo)
601     {
602         BOOST_ASSERT(tensorInfo.GetDataType() == GetDataType<T>());
603     }
604
605     void CreateLayerDeferred() override
606     {
607         BOOST_ASSERT(m_Layer == nullptr);
608         m_Layer = m_Parser->m_Network->AddConstantLayer(ConstTensor(m_TensorInfo, m_Storage), m_Node.name().c_str());
609         m_Layer->GetOutputSlot(0).SetTensorInfo(m_TensorInfo);
610     }
611
612     ConstTensor GetConstTensor(bool swizzleForConvolutionWeights, std::vector<T>& outputTensorData) const
613     {
614         // Mappings from TensorFlow filter tensors to the ArmNN filter tensors.
615         // Tensorflow weights are [H, W, In, Out]
616         // ArmNN weights are [Out, In, H, W]
617         static const PermutationVector HWIOToOIHW = {2, 3, 1, 0};
618
619         const TensorInfo outInfo = swizzleForConvolutionWeights
620                                    ? armnnUtils::Permuted(m_TensorInfo, HWIOToOIHW)
621                                    : m_TensorInfo;
622
623         outputTensorData.resize(m_TensorInfo.GetNumElements());
624
625         // Copy or swizzle from the permanent storage into the storage the caller provided.
626         if (swizzleForConvolutionWeights)
627         {
628             armnnUtils::Permute(outInfo.GetShape(), HWIOToOIHW, m_Storage.data(), outputTensorData.data());
629         }
630         else
631         {
632             memcpy(outputTensorData.data(), m_Storage.data(), m_TensorInfo.GetNumBytes());
633         }
634         // Update the result to point to the user provided storage
635         ConstTensor constTensor(outInfo, outputTensorData);
636         return constTensor;
637     }
638
639 private:
640     ///< Manages the lifetime of the tensor data.
641     std::vector<T> m_Storage;
642     ///< Describes the layout of the tensor and points to the data in m_Storage.
643     TensorInfo m_TensorInfo;
644 };
645
646 DataType ConvertTfTensorDataType(const tensorflow::DataType tfDataType)
647 {
648     switch (tfDataType)
649     {
650     case tensorflow::DT_FLOAT:
651         return DataType::Float32;
652         break;
653     case tensorflow::DT_INT32:
654         return DataType::Signed32;
655         break;
656     default:
657         throw ParseException(boost::str(
658             boost::format("Unknown DataType %1% for node")
659             % tensorflow::DataType_Name(tfDataType)));
660     }
661 }
662
663 struct ParseTfTensorValueList
664 {
665     template<typename DataType>
666     static void Parse(
667         const tensorflow::TensorProto& tfTensor,
668         unsigned int dstElements,
669         std::vector<int8_t>& outputData);
670
671     template <typename DataType>
672     static void ReadData(const void* srcData, unsigned int numSrcElements,
673         std::vector<int8_t>& dstData, unsigned int numDstElements)
674     {
675         // If there are no entries in the list, perform no action
676         if (numSrcElements == 0)
677         {
678             return;
679         }
680
681         // If no size was provided, use the length of the value list
682         if (numDstElements == 0)
683         {
684             numDstElements = numSrcElements;
685         }
686
687         // Allocate memory
688         dstData.resize(std::max(numSrcElements, numDstElements) * sizeof(DataType));
689
690         const DataType* srcTensor = reinterpret_cast<const DataType*>(srcData);
691         DataType* dstTensor = reinterpret_cast<DataType*>(dstData.data());
692
693         // Copy the value list entries into the destination
694         std::copy(srcTensor, srcTensor + numSrcElements, dstTensor);
695
696         if (numDstElements > numSrcElements)
697         {
698             // Use the last element in the list to fill the remaining entries
699             std::fill(dstTensor + numSrcElements, dstTensor + numDstElements, srcTensor[numSrcElements - 1]);
700         }
701     }
702
703 };
704
705 template <>
706 void ParseTfTensorValueList::Parse<float>(const tensorflow::TensorProto& tfTensor,
707     unsigned int dstElements, std::vector<int8_t>& outputData)
708 {
709     ReadData<float>(tfTensor.float_val().data(), static_cast<unsigned int>(tfTensor.float_val_size()),
710         outputData, dstElements);
711 }
712
713 template <>
714 void ParseTfTensorValueList::Parse<int32_t>(const tensorflow::TensorProto& tfTensor,
715     unsigned int dstElements, std::vector<int8_t>& outputData)
716 {
717     ReadData<int32_t>(tfTensor.int_val().data(), static_cast<unsigned int>(tfTensor.int_val_size()),
718         outputData, dstElements);
719 }
720
721 template <template<typename> class OperatorType, typename T = int8_t>
722 struct MakeTfOperation
723 {
724     template<typename DataType, class... Args>
725     inline static std::unique_ptr<OperatorType<DataType>> Parse(TfParser* parser, const tensorflow::NodeDef& node,
726         Args&&... args)
727     {
728         return std::make_unique<OperatorType<DataType>>(parser, node, std::forward<Args>(args)...);
729     }
730 };
731
732 template <>
733 struct MakeTfOperation<ParsedConstTfOperation>
734 {
735     template<typename DataType, class... Args>
736     inline static std::unique_ptr<ParsedConstTfOperation<DataType>> Parse(TfParser* parser,
737         const tensorflow::NodeDef& node, const std::vector<int8_t>& tensorData, const TensorInfo& tensorInfo)
738     {
739         return std::make_unique<ParsedConstTfOperation<DataType>>(parser, node,
740             reinterpret_cast<const DataType*>(tensorData.data()), tensorInfo);
741     }
742 };
743
744 template <class FuncType>
745 struct InvokeParseFunction
746 {
747     template<class ResType, class... Args>
748     inline static ResType Result(DataType dataType, Args&&... args)
749     {
750         if (dataType == DataType::Float32)
751         {
752             return FuncType::template Parse<float>(std::forward<Args>(args)...);
753         }
754         else if (dataType == DataType::Signed32)
755         {
756             return FuncType::template Parse<int32_t>(std::forward<Args>(args)...);
757         }
758
759         return ResType();
760     }
761
762     template<class... Args>
763     inline static void Result(DataType dataType, Args&&... args)
764     {
765         if (dataType == DataType::Float32)
766         {
767             FuncType::template Parse<float>(std::forward<Args>(args)...);
768         }
769         else if (dataType == DataType::Signed32)
770         {
771             FuncType::template Parse<int32_t>(std::forward<Args>(args)...);
772         }
773     }
774 };
775
776 ParsedTfOperationPtr TfParser::ParseConst(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
777 {
778     BOOST_ASSERT(nodeDef.op() == "Const");
779
780     if (nodeDef.attr().count("value") == 0)
781     {
782         throw ParseException(boost::str(
783             boost::format("Value not found for Const node - %1%")
784             % nodeDef.name()));
785     }
786
787     const tensorflow::TensorProto& tfTensor = nodeDef.attr().at("value").tensor();
788     const tensorflow::TensorShapeProto& tfTensorShape = tfTensor.tensor_shape();
789     const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "dtype");
790
791     const auto GetDimensionSize = [](auto& d) { return d.size(); };
792
793     std::vector<unsigned int> dimensionSizes;
794     std::transform(tfTensorShape.dim().begin(), tfTensorShape.dim().end(),
795         std::back_inserter(dimensionSizes), GetDimensionSize);
796
797     // Calculate number of elements
798     const DataType dataType = ConvertTfTensorDataType(tfDataType);
799     unsigned int numElements = 0U;
800
801     if (!dimensionSizes.empty())
802     {
803         numElements = std::accumulate(dimensionSizes.begin(), dimensionSizes.end(),
804                                       1U, std::multiplies<unsigned int>());
805     }
806
807     std::vector<int8_t> tensorData;
808
809     // Get tensor data from the list of values attribute
810     if (tfTensor.tensor_content().empty())
811     {
812         InvokeParseFunction<ParseTfTensorValueList>::Result<void>(dataType, tfTensor, numElements, tensorData);
813
814         // If the tensor shape is not defined, but there is a value list, then interpret the data as a 1D
815         // tensor of the provided number of elements
816         if (numElements == 0)
817         {
818             const unsigned int tfNumElements = static_cast<unsigned int>(tensorData.size()) / GetDataTypeSize(dataType);
819             dimensionSizes.push_back(tfNumElements);
820         }
821     }
822     // Get tensor data from tensor content attribute
823     else
824     {
825         tensorData.assign(tfTensor.tensor_content().begin(), tfTensor.tensor_content().end());
826
827         // Check if a tensor shape is defined for the tensor content
828         if (numElements == 0)
829         {
830             throw ParseException(boost::str(
831                 boost::format("No tensor shape found for Const node - %1%")
832                 % nodeDef.name()));
833         }
834     }
835
836     // Const node requires at least a list of values or a content attribute
837     if (tensorData.empty())
838     {
839         throw ParseException(boost::str(
840             boost::format("No tensor data found for Const node - %1%")
841             % nodeDef.name()));
842     }
843
844     const TensorInfo tensorInfo(static_cast<unsigned int>(dimensionSizes.size()), dimensionSizes.data(), dataType);
845
846     // If we have a list of values, then the length of the list must be
847     // less than or equal to the number of elements implied by the shape argument
848     if (tensorData.size() > tensorInfo.GetNumBytes())
849     {
850         throw ParseException(boost::str(
851             boost::format("Number of elements (%1%) should be less than or equal \
852             to the number of elements implied by the shape argument (%2%) for Const node - %3%")
853             % (tensorData.size() / GetDataTypeSize(dataType))
854             % tensorInfo.GetNumElements()
855             % nodeDef.name()));
856     }
857
858     return InvokeParseFunction<MakeTfOperation<ParsedConstTfOperation>>::Result<ParsedTfOperationPtr>(
859         dataType, this, nodeDef, tensorData, tensorInfo);
860 }
861
862 template<typename Type>
863 bool TfParser::HasParsedConstTensor(const std::string & nodeName) const
864 {
865     auto it = m_ParsedTfOperations.find(nodeName);
866     if (it == m_ParsedTfOperations.end() ||
867         dynamic_cast<ParsedConstTfOperation<Type>*>(it->second.get()) == nullptr)
868     {
869         return false;
870     }
871     else
872     {
873         return true;
874     }
875 }
876
877 ParsedTfOperationPtr TfParser::ParseConv2D(const tensorflow::NodeDef& nodeDef,
878     const tensorflow::GraphDef& graphDef)
879 {
880     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
881     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
882     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
883
884     if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
885     {
886         throw ParseException("ArmNN only supports Convolution layers with constant weights");
887     }
888     ParsedConstTfOperation<float>* weightNode =
889         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
890
891     std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
892     std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
893     std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
894
895     // read the dilations, if present - only [1,1,1,1] (the default) is supported
896     std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(nodeDef, "dilations");
897     if (!dilations.empty())
898     {
899         for (auto dilation : dilations)
900         {
901             if (dilation != 1u)
902             {
903                 throw ParseException("ArmNN only supports Convolution layers with dilations [1,1,1,1]");
904             }
905         }
906     }
907
908     Convolution2dDescriptor desc;
909     desc.m_BiasEnabled = false;
910
911     if (dataFormat == "NHWC")
912     {
913         desc.m_StrideX = strides[2];
914         desc.m_StrideY = strides[1];
915         // Swizzle input to supported memory layout
916         inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
917     }
918     else if (dataFormat == "NCHW")
919     {
920         desc.m_StrideX = strides[3];
921         desc.m_StrideY = strides[2];
922     }
923     else
924     {
925         throw ParseException("Unsupported data format passed for Conv2D. Only NHWC and NCHW supported");
926     }
927
928     uint32_t inputHeight = inputTensorInfo.GetShape()[2];
929     uint32_t inputWidth = inputTensorInfo.GetShape()[3];
930
931     std::vector<float> outputTensorData;
932
933     ConstTensor weightTensor = weightNode->GetConstTensor(true, outputTensorData);
934
935     uint32_t weightHeight = weightTensor.GetShape()[2];
936     uint32_t weightWidth = weightTensor.GetShape()[3];
937
938     bool padding = false;
939     TensorInfo outputInfo;
940     if (paddingString == "SAME")
941     {
942         padding = true;
943         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
944                                   weightTensor.GetShape()[0],
945                                   static_cast<uint32_t>(ceil(
946                                       static_cast<float>(inputHeight) /
947                                       static_cast<float>(desc.m_StrideY))),
948                                   static_cast<uint32_t>(ceil(
949                                       static_cast<float>(inputWidth) /
950                                       static_cast<float>(desc.m_StrideX)))
951                                 }, DataType::Float32);
952     }
953     else if (paddingString == "VALID")
954     {
955         padding = false;
956         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
957                                   weightTensor.GetShape()[0],
958                                   static_cast<uint32_t>(ceil(
959                                       static_cast<float>(inputHeight - weightHeight + 1) /
960                                       static_cast<float>(desc.m_StrideY))),
961                                   static_cast<uint32_t>(ceil(
962                                       static_cast<float>(inputWidth - weightWidth + 1) /
963                                       static_cast<float>(desc.m_StrideX)))
964                                 }, DataType::Float32);
965     }
966     else
967     {
968         throw ParseException("Only 'SAME' and 'VALID' padding supported");
969     }
970
971     CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding);
972     CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding);
973
974     IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str());
975     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
976
977     if (dataFormat == "NHWC")
978     {
979         layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
980     }
981     else
982     {
983         inputSlot.Connect(layer->GetInputSlot(0));
984     }
985
986     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
987 }
988
989 ParsedTfOperationPtr TfParser::ParseDepthwiseConv2D(const tensorflow::NodeDef& nodeDef,
990                                                    const tensorflow::GraphDef& graphDef)
991 {
992     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
993     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
994     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
995
996     if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
997     {
998         throw ParseException("ArmNN only supports Depthwise Convolution layers with constant weights");
999     }
1000     ParsedConstTfOperation<float>* weightNode =
1001         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1002
1003
1004     std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1005     std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1006     std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1007
1008     DepthwiseConvolution2dDescriptor desc;
1009     desc.m_BiasEnabled = false;
1010
1011     if (dataFormat == "NHWC")
1012     {
1013         desc.m_StrideX = strides[2];
1014         desc.m_StrideY = strides[1];
1015         // Swizzle input to supported memory layout
1016         inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
1017     }
1018     else if (dataFormat == "NCHW")
1019     {
1020         desc.m_StrideX = strides[3];
1021         desc.m_StrideY = strides[2];
1022     }
1023     else
1024     {
1025         throw ParseException("Unsupported data format passed for DepthwiseConv2dNative. Only NHWC and NCHW supported");
1026     }
1027
1028     uint32_t inputHeight = inputTensorInfo.GetShape()[2];
1029     uint32_t inputWidth = inputTensorInfo.GetShape()[3];
1030
1031     std::vector<float> outputTensorData;
1032
1033     ConstTensor weightTensor = weightNode->GetConstTensor(true, outputTensorData);
1034
1035     uint32_t weightHeight = weightTensor.GetShape()[2];
1036     uint32_t weightWidth = weightTensor.GetShape()[3];
1037
1038     bool padding = false;
1039     TensorInfo outputInfo;
1040     if (paddingString == "SAME")
1041     {
1042         padding = true;
1043         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1044                                 weightTensor.GetShape()[0] * weightTensor.GetShape()[1],
1045                                 static_cast<uint32_t>(ceil(
1046                                     static_cast<float>(inputHeight) /
1047                                     static_cast<float>(desc.m_StrideY))),
1048                                 static_cast<uint32_t>(ceil(
1049                                     static_cast<float>(inputWidth) /
1050                                     static_cast<float>(desc.m_StrideX)))
1051                                 }, DataType::Float32);
1052     }
1053     else if (paddingString == "VALID")
1054     {
1055         padding = false;
1056         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1057                                 weightTensor.GetShape()[0] * weightTensor.GetShape()[1],
1058                                 static_cast<uint32_t>(ceil(
1059                                     static_cast<float>(inputHeight - weightHeight + 1) /
1060                                     static_cast<float>(desc.m_StrideY))),
1061                                 static_cast<uint32_t>(ceil(
1062                                     static_cast<float>(inputWidth - weightWidth + 1) /
1063                                     static_cast<float>(desc.m_StrideX)))
1064                                 }, DataType::Float32);
1065     }
1066     else
1067     {
1068         throw ParseException("Only 'SAME' and 'VALID' padding supported");
1069     }
1070
1071     CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding);
1072     CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding);
1073
1074     IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str());
1075     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1076
1077     if (dataFormat == "NHWC")
1078     {
1079         layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1080     }
1081     else
1082     {
1083         inputSlot.Connect(layer->GetInputSlot(0));
1084     }
1085
1086     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1087 }
1088
1089 ParsedTfOperationPtr TfParser::ParseFusedBatchNorm(const tensorflow::NodeDef& nodeDef,
1090                                                    const tensorflow::GraphDef& graphDef)
1091 {
1092     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 5);
1093
1094     if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1095     {
1096         throw ParseException("ArmNN only supports FusedBatchNormalization layers with constant scale");
1097     }
1098     ParsedConstTfOperation<float>* scaleNode =
1099         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1100
1101     if (!HasParsedConstTensor<float>(inputs[2].m_IndexedValue->GetNode().name()))
1102     {
1103         throw ParseException("ArmNN only supports FusedBatchNormalization layers with constant offset");
1104     }
1105     ParsedConstTfOperation<float>* offsetNode =
1106         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[2].m_IndexedValue);
1107
1108     if (!HasParsedConstTensor<float>(inputs[3].m_IndexedValue->GetNode().name()))
1109     {
1110         throw ParseException("ArmNN only supports FusedBatchNormalization layers with constant mean");
1111     }
1112     ParsedConstTfOperation<float>* meanNode =
1113         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[3].m_IndexedValue);
1114
1115     if (!HasParsedConstTensor<float>(inputs[4].m_IndexedValue->GetNode().name()))
1116     {
1117         throw ParseException("ArmNN only supports FusedBatchNormalization layers with constant variance");
1118     }
1119     ParsedConstTfOperation<float>* varianceNode =
1120         boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[4].m_IndexedValue);
1121
1122     // The descriptor only has the epsilon attribute
1123     BatchNormalizationDescriptor desc;
1124     desc.m_Eps = ReadMandatoryNodeFloatAttribute(nodeDef, "epsilon");
1125
1126     // data for the parsed tensor args (scale, offset, mean, variance) must be stored locally until the layer is added
1127     std::vector<float> scaleTensorData;
1128     ConstTensor scaleTensor = scaleNode->GetConstTensor(false, scaleTensorData);
1129
1130     std::vector<float> offsetTensorData;
1131     ConstTensor offsetTensor = offsetNode->GetConstTensor(false, offsetTensorData);
1132
1133     std::vector<float> meanTensorData;
1134     ConstTensor meanTensor = meanNode->GetConstTensor(false, meanTensorData);
1135
1136     std::vector<float> varianceTensorData;
1137     ConstTensor varianceTensor = varianceNode->GetConstTensor(false, varianceTensorData);
1138
1139     IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1140                                                                      meanTensor,
1141                                                                      varianceTensor,
1142                                                                      offsetTensor,
1143                                                                      scaleTensor,
1144                                                                      nodeDef.name().c_str());
1145
1146     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1147
1148     const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1149
1150     if (dataFormat == "NHWC")
1151     {
1152         const TensorInfo outputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
1153         layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1154         layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1155     }
1156     else
1157     {
1158         layer->GetOutputSlot(0).SetTensorInfo(inputSlot.GetTensorInfo());
1159         inputSlot.Connect(layer->GetInputSlot(0));
1160     }
1161
1162     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1163 }
1164
1165 ParsedTfOperationPtr TfParser::ParseConcat(const tensorflow::NodeDef& nodeDef,
1166                                            const tensorflow::GraphDef& graphDef)
1167 {
1168     std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
1169     // In tensorflow, we have the last input of the Concat layer as the axis for concatenation
1170     unsigned int numInputs = static_cast<unsigned int>(nodes.size());
1171     unsigned int numConcatView = numInputs - 1;
1172
1173     OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numConcatView), MaxNumOfTensorDimensions);
1174     std::vector<unsigned int>mergeDimSizes(MaxNumOfTensorDimensions, 0u);
1175
1176     unsigned int mergeDim = 0;
1177     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
1178
1179     // The last input is the axis for concatenation
1180     if (!HasParsedConstTensor<int32_t>(inputs[numInputs - 1].m_IndexedValue->GetNode().name()))
1181     {
1182         throw ParseException("ArmNN only supports Concat with constant axis");
1183     }
1184     ParsedConstTfOperation<int32_t>* shapeNode =
1185             boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[numInputs - 1].m_IndexedValue);
1186
1187     std::vector<int32_t> axisTensorData;
1188     ConstTensor axisTensor = shapeNode->GetConstTensor(false, axisTensorData);
1189
1190     // This concatDim indicates the data format: 3 is the NHWC, 1 is the NCHW
1191     const unsigned int concatDimInput = static_cast<unsigned int>(axisTensorData[0]);
1192
1193     // Armnn supports concatenation along the channel dimension for data format NHWC and NCHW
1194     if (concatDimInput == 0 || concatDimInput == 2)
1195     {
1196         throw ParseException("The dimension for concatenation is not supported by Armnn");
1197     }
1198
1199     // This is the only concatDim we support in Armnn
1200     const unsigned int concatDim = 1;
1201     for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1202     {
1203         // need to double check whether it should be
1204         IOutputSlot& inputSlot =
1205             inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
1206         TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1207
1208         if (inputTensorInfo.GetNumDimensions() != MaxNumOfTensorDimensions)
1209         {
1210             throw ParseException("The number of dimensions for input tensors of the concatenation op should be 4");
1211         }
1212
1213         if (concatDimInput == 3)
1214         {
1215             inputTensorInfo = armnnUtils::Permuted(inputTensorInfo, NHWCToArmNN);
1216         }
1217
1218         for (unsigned int dim = 0; dim < MaxNumOfTensorDimensions; ++dim)
1219         {
1220             mergeDimSizes[dim] = inputTensorInfo.GetShape()[dim];
1221         }
1222
1223         for (unsigned int j = 0; j < concatDim; ++j)
1224         {
1225             concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1226         }
1227
1228         concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1229         mergeDim += mergeDimSizes[concatDim];
1230
1231         for (unsigned int j = concatDim+1; j < MaxNumOfTensorDimensions; ++j)
1232         {
1233             concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1234         }
1235     }
1236
1237     mergeDimSizes[concatDim] = mergeDim;
1238     armnn::IConnectableLayer *layer = m_Network->AddMergerLayer(concatDescriptor, nodeDef.name().c_str());
1239
1240     layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(MaxNumOfTensorDimensions, mergeDimSizes.data(),
1241                                                             DataType::Float32));
1242
1243     for (unsigned int v = 0; v < numConcatView; ++v)
1244     {
1245         IOutputSlot& inputSlot = inputs[v].m_IndexedValue->ResolveArmnnOutputSlot(inputs[v].m_Index);
1246         if (concatDimInput == 3)
1247         {
1248             IConnectableLayer* const swizzleLayer = AddSwizzleLayer(*m_Network, inputSlot, NHWCToArmNN,
1249                                                                     "swizzle_for-" + nodeDef.name());
1250             swizzleLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(v));
1251         }
1252         else
1253         {
1254             inputSlot.Connect(layer->GetInputSlot(v));
1255         }
1256     }
1257
1258     if (concatDimInput == 3)
1259     {
1260         IConnectableLayer* const deswizzleLayer = AddSwizzleLayer(*m_Network, layer->GetOutputSlot(0), ArmNNToNHWC,
1261                                                                   "deswizzle_for-" + nodeDef.name());
1262         layer = deswizzleLayer;
1263     }
1264
1265     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1266 }
1267
1268 ParsedTfOperationPtr TfParser::ParseShape(const tensorflow::NodeDef& nodeDef,
1269     const tensorflow::GraphDef& graphDef)
1270 {
1271     // Note: The Shape layer is handled in a special way, because:
1272     //        1. ARMNN doesn't support int32 tensors which it outputs
1273     //        2. ARMNN works with statically shaped tensors which are known at parse time
1274     //        3. because of 1. and 2. we treat the output of Shape as a temporary const int32
1275     //           tensor which may be used as an input to other ops, most likely a Reshape
1276
1277     const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "out_type");
1278     if (tfDataType != tensorflow::DT_INT32)
1279     {
1280         throw ParseException("Armnn only supports DT_INT32 as out_type");
1281     }
1282
1283     const std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1284     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1285     const TensorInfo& prevLayerTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1286     unsigned int prevLayerDimensions = prevLayerTensorInfo.GetNumDimensions();
1287
1288     std::vector<int32_t> shapeTensorData;
1289     shapeTensorData.reserve(prevLayerDimensions);
1290
1291     for (unsigned int i=0; i<prevLayerDimensions; ++i)
1292     {
1293         shapeTensorData.push_back(static_cast<int32_t>(prevLayerTensorInfo.GetShape()[i]));
1294     }
1295
1296     TensorInfo shapeTensorInfo(1, &prevLayerDimensions, DataType::Signed32);
1297
1298     return std::make_unique<ParsedConstTfOperation<int32_t>>(this,
1299                                                              nodeDef,
1300                                                              &shapeTensorData[0],
1301                                                              shapeTensorInfo);
1302 }
1303
1304 ParsedTfOperationPtr TfParser::ParseReshape(const tensorflow::NodeDef& nodeDef,
1305     const tensorflow::GraphDef& graphDef)
1306 {
1307     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1308     ParsedTfOperation* inputNode = inputs[0].m_IndexedValue;
1309
1310     if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
1311     {
1312         throw ParseException("ArmNN only supports Reshape layers with constant shapes");
1313     }
1314     ParsedConstTfOperation<int32_t>* shapeNode =
1315         boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
1316
1317     armnn::IOutputSlot& prevLayerOutputSlot = inputNode->ResolveArmnnOutputSlot(inputs[0].m_Index);
1318     TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1319
1320     std::vector<int32_t> shapeTensorData;
1321     ConstTensor shapeTensor = shapeNode->GetConstTensor(false, shapeTensorData);
1322     const TensorInfo outputTensorInfo = PrepareReshape(inputTensorInfo, shapeTensorData);
1323
1324     TensorShape targetShape = outputTensorInfo.GetShape();
1325     ReshapeDescriptor reshapeDesc;
1326     reshapeDesc.m_TargetShape = targetShape;
1327
1328     IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
1329     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1330     layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1331
1332     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1333 }
1334
1335 ParsedTfOperationPtr TfParser::ParseResizeBilinear(const tensorflow::NodeDef& nodeDef,
1336     const tensorflow::GraphDef& graphDef)
1337 {
1338     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1339
1340     if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
1341     {
1342         throw ParseException("ArmNN only supports ResizeBilinear layers with constant sizes");
1343     }
1344     ParsedConstTfOperation<int32_t>* sizeNode =
1345         boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
1346
1347     // Check the align_corners attribute is not set
1348     if (ReadOptionalNodeBoolAttribute(nodeDef, "align_corners", false))
1349     {
1350         throw ParseException("ArmNN only supports ResizeBilinear layers with align_corners set to false");
1351     }
1352
1353     // data for the parsed tensor args (size) must be stored locally
1354     std::vector<int32_t> sizeTensorData;
1355     ConstTensor sizeTensor = sizeNode->GetConstTensor(false, sizeTensorData);
1356
1357     // The descriptor only has target height and width attributes, which we get from the size tensor
1358     ResizeBilinearDescriptor desc;
1359     desc.m_TargetHeight = static_cast<uint32_t> (sizeTensorData[0]);
1360     desc.m_TargetWidth = static_cast<uint32_t> (sizeTensorData[1]);
1361
1362     IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc, nodeDef.name().c_str());
1363
1364     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1365     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1366     // the input shape is always in BHWC format, this will be swizzled below; for now,
1367     // get the batch and channels to make up the ArmNN output shape with the target size
1368     unsigned int outBatch = inputTensorInfo.GetShape()[0];
1369     unsigned int outChannels = inputTensorInfo.GetShape()[3];
1370     unsigned int outHeight = desc.m_TargetHeight;
1371     unsigned int outWidth = desc.m_TargetWidth;
1372     TensorShape outShape({outBatch, outChannels, outHeight, outWidth});
1373     // The output DataType is always Float32, regardless of the input DataType
1374     const TensorInfo outputTensorInfo(outShape, armnn::DataType::Float32);
1375     layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1376
1377     // TensorFlow ResizeBilinear input is always in BHWC format, so add swizzle and deswizzle layers
1378     layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1379
1380     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1381 }
1382
1383 TensorInfo OutputShapeOfSqueeze(const tensorflow::NodeDef& nodeDef, TensorInfo inputTensorInfo)
1384 {
1385     BOOST_ASSERT(nodeDef.op() == "Squeeze");
1386     tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "T");
1387
1388     DataType type;
1389     if (tfDataType == tensorflow::DT_FLOAT)
1390     {
1391         type = DataType::Float32;
1392     }
1393     else if (tfDataType == tensorflow::DT_INT32)
1394     {
1395         type = DataType::Signed32;
1396     }
1397     else
1398     {
1399         throw ParseException(boost::str(
1400                 boost::format("Unsupported DataType %1% for Squeeze operation")
1401                 % tensorflow::DataType_Name(tfDataType)));
1402     }
1403
1404     std::vector<uint32_t> squeezeDims = ReadOptionalNodeUint32ListAttribute(nodeDef, "squeeze_dims");
1405     if (squeezeDims.empty())
1406     {
1407         for(unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); i++)
1408         {
1409             if (inputTensorInfo.GetShape()[i] == 1)
1410             {
1411                 squeezeDims.push_back(i);
1412             }
1413         }
1414     }
1415
1416     std::vector<uint32_t> outputDims;
1417     for(unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); i++)
1418     {
1419         bool includeDimension = (std::find(squeezeDims.begin(), squeezeDims.end(), i) == squeezeDims.end());
1420         if (includeDimension)
1421         {
1422             outputDims.push_back(inputTensorInfo.GetShape()[i]);
1423         }
1424     }
1425
1426     if (outputDims.size() > 4)
1427     {
1428         throw ParseException("Unsupported shape for Squeeze");
1429     }
1430
1431     TensorInfo outTensorInfo = TensorInfo(boost::numeric_cast<unsigned int>(outputDims.size()),
1432                                           outputDims.data(),
1433                                           type);
1434
1435     return outTensorInfo;
1436 }
1437
1438 ParsedTfOperationPtr TfParser::ParseSqueeze(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1439 {
1440     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1441
1442     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1443     TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1444
1445     TensorInfo outputInfo;
1446     outputInfo = OutputShapeOfSqueeze(nodeDef, inputTensorInfo);
1447
1448     ReshapeDescriptor reshapeDesc;
1449     reshapeDesc.m_TargetShape = outputInfo.GetShape();
1450     IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
1451     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1452     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1453
1454     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1455 }
1456
1457 ParsedTfOperationPtr TfParser::ParseLrn(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1458 {
1459     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1460
1461     NormalizationDescriptor normalizationDescriptor;
1462     normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1463     normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1464     normalizationDescriptor.m_Alpha = ReadMandatoryNodeFloatAttribute(nodeDef, "alpha");
1465     normalizationDescriptor.m_Beta = ReadMandatoryNodeFloatAttribute(nodeDef, "beta");
1466     normalizationDescriptor.m_K = ReadMandatoryNodeFloatAttribute(nodeDef, "bias");
1467     normalizationDescriptor.m_NormSize = ReadMandatoryNodeUint32Attribute(nodeDef, "depth_radius");
1468
1469     // The window size must be an odd value. For a window size of (2 * n + 1), TensorFlow defines depth_radius = n.
1470     normalizationDescriptor.m_NormSize = normalizationDescriptor.m_NormSize * 2 + 1;
1471
1472     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1473
1474     IConnectableLayer* layer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1475         nodeDef.name().c_str());
1476
1477     const TensorInfo permutedInfo = armnnUtils::Permuted(prevLayerOutputSlot.GetTensorInfo(), NHWCToArmNN);
1478     layer->GetOutputSlot(0).SetTensorInfo(permutedInfo);
1479
1480     layer = SwizzleInDeswizzleOut(*m_Network, prevLayerOutputSlot, *layer, nodeDef.name());
1481
1482     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1483 }
1484
1485 /// An ParsedTfOperation for a MatMul node.
1486 /// Creation of the armnn FullyConnected layer is deferred until it is actually needed, because MatMul nodes are
1487 /// often used for the first part of a biased FullyConnected (MatMul followed by Add) and in these cases armnn doesn't
1488 /// need a separate layer for the MatMul.
1489 class ParsedMatMulTfOperation : public DeferredSingleLayerParsedTfOperation
1490 {
1491 public:
1492     ParsedMatMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
1493         : DeferredSingleLayerParsedTfOperation(parser, node)
1494     {
1495     }
1496
1497     void CreateLayerDeferred() override
1498     {
1499         BOOST_ASSERT(m_Layer == nullptr);
1500         m_Layer = m_Parser->AddFullyConnectedLayer(m_Node, nullptr, m_Node.name().c_str());
1501     }
1502 };
1503
1504 ParsedTfOperationPtr TfParser::ParseMatMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1505 {
1506     // Defer the creation of the layer (see ParsedMatMulTfOperation).
1507     return std::make_unique<ParsedMatMulTfOperation>(this, nodeDef);
1508 }
1509
1510 ParsedTfOperationPtr TfParser::ParseMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1511 {
1512     boost::ignore_unused(graphDef);
1513
1514     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1515
1516     IConnectableLayer* const layer = m_Network->AddMultiplicationLayer(nodeDef.name().c_str());
1517     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1518     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
1519
1520     auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
1521     auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
1522
1523     if (input0NumDims < input1NumDims)
1524     {
1525         const bool isNHWC = true;
1526         input0Slot = BroadcastForAddandMul(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
1527     }
1528     if (input1NumDims < input0NumDims)
1529     {
1530         const bool isNHWC = true;
1531         input1Slot = BroadcastForAddandMul(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
1532     }
1533
1534     input0Slot->Connect(layer->GetInputSlot(0));
1535     input1Slot->Connect(layer->GetInputSlot(1));
1536
1537     if (input0NumDims < input1NumDims)
1538     {
1539         layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
1540     }
1541     else
1542     {
1543         layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
1544     }
1545     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1546 }
1547
1548 ParsedTfOperationPtr TfParser::ParsePlaceholder(const tensorflow::NodeDef& nodeDef,
1549     const tensorflow::GraphDef& graphDef)
1550 {
1551     boost::ignore_unused(graphDef);
1552
1553     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 0);
1554
1555     const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkInputsBindingInfo.size());
1556
1557     auto it = m_InputShapes.find(nodeDef.name());
1558     if (it == m_InputShapes.end())
1559     {
1560         throw ParseException("Missing input shape for Placeholder '" + nodeDef.name() + "'");
1561     }
1562     TensorInfo tensorInfo(it->second, DataType::Float32);
1563
1564     IConnectableLayer* const layer = m_Network->AddInputLayer(layerId, nodeDef.name().c_str());
1565
1566     layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1567
1568     TrackInputBinding(layer, layerId, tensorInfo);
1569
1570     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1571 }
1572
1573 ParsedTfOperationPtr TfParser::ParseRelu(const tensorflow::NodeDef& nodeDef,
1574     const tensorflow::GraphDef& graphDef)
1575 {
1576     boost::ignore_unused(graphDef);
1577
1578     ActivationDescriptor activationDesc;
1579     activationDesc.m_Function = ActivationFunction::ReLu;
1580     return AddActivationLayer(nodeDef, activationDesc);
1581 }
1582
1583 ParsedTfOperationPtr TfParser::ParseRelu6(const tensorflow::NodeDef& nodeDef,
1584     const tensorflow::GraphDef& graphDef)
1585 {
1586     boost::ignore_unused(graphDef);
1587
1588     ActivationDescriptor activationDesc;
1589     activationDesc.m_Function = ActivationFunction::BoundedReLu;
1590     activationDesc.m_A = 6.0f;
1591     activationDesc.m_B = 0.0f;
1592
1593     return AddActivationLayer(nodeDef, activationDesc);
1594 }
1595
1596 ParsedTfOperationPtr TfParser::ParseSigmoid(const tensorflow::NodeDef& nodeDef,
1597     const tensorflow::GraphDef& graphDef)
1598 {
1599     boost::ignore_unused(graphDef);
1600
1601     ActivationDescriptor activationDesc;
1602     activationDesc.m_Function = ActivationFunction::Sigmoid;
1603
1604     return AddActivationLayer(nodeDef, activationDesc);
1605 }
1606
1607 ParsedTfOperationPtr TfParser::ParseSoftmax(const tensorflow::NodeDef& nodeDef,
1608     const tensorflow::GraphDef& graphDef)
1609 {
1610     boost::ignore_unused(graphDef);
1611
1612     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1613
1614     SoftmaxDescriptor softmaxDescriptor;
1615     IConnectableLayer* const layer = m_Network->AddSoftmaxLayer(softmaxDescriptor, nodeDef.name().c_str());
1616
1617     IOutputSlot& prevLayerSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1618     prevLayerSlot.Connect(layer->GetInputSlot(0));
1619     layer->GetOutputSlot(0).SetTensorInfo(prevLayerSlot.GetTensorInfo());
1620
1621     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1622 }
1623
1624 ParsedTfOperationPtr TfParser::ParseSoftplus(const tensorflow::NodeDef& nodeDef,
1625     const tensorflow::GraphDef& graphDef)
1626 {
1627     boost::ignore_unused(graphDef);
1628
1629     ActivationDescriptor activationDesc;
1630     activationDesc.m_Function = ActivationFunction::SoftReLu;
1631
1632     return AddActivationLayer(nodeDef, activationDesc);
1633 }
1634
1635 ParsedTfOperationPtr TfParser::ParseTanh(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1636 {
1637     boost::ignore_unused(graphDef);
1638
1639     ActivationDescriptor activationDesc;
1640     activationDesc.m_Function = ActivationFunction::TanH;
1641     activationDesc.m_A = 1.0f;
1642     activationDesc.m_B = 1.0f;
1643
1644     return AddActivationLayer(nodeDef, activationDesc);
1645 }
1646
1647 ParsedTfOperationPtr TfParser::AddActivationLayer(const tensorflow::NodeDef& nodeDef,
1648     ActivationDescriptor& activationDesc)
1649 {
1650     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1651
1652     IConnectableLayer* const layer = m_Network->AddActivationLayer(activationDesc, nodeDef.name().c_str());
1653
1654     IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1655     prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1656     layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
1657     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1658 }
1659
1660 ParsedTfOperationPtr TfParser::ParseMaxPool(const tensorflow::NodeDef& nodeDef,
1661     const tensorflow::GraphDef& graphDef)
1662 {
1663     return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Max);
1664 }
1665
1666 ParsedTfOperationPtr TfParser::ParseAvgPool(const tensorflow::NodeDef& nodeDef,
1667     const tensorflow::GraphDef& graphDef)
1668 {
1669     return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Average);
1670 }
1671
1672 ParsedTfOperationPtr TfParser::ParsePooling2d(const tensorflow::NodeDef& nodeDef,
1673     const tensorflow::GraphDef& graphDef, PoolingAlgorithm pooltype)
1674 {
1675     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1676     IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1677     TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1678
1679     if (inputs.size() != 1)
1680     {
1681         throw ParseException("2D Pooling expects one input!");
1682     }
1683
1684     std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1685     std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1686     std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1687     std::vector<uint32_t> ksize = ReadMandatoryNodeUint32ListAttribute(nodeDef, "ksize"); // size of pool windows
1688
1689     Pooling2dDescriptor pooling2dDescriptor;
1690     pooling2dDescriptor.m_PoolType = pooltype;
1691     pooling2dDescriptor.m_PaddingMethod = PaddingMethod::Exclude;
1692     pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Floor;
1693
1694     if (dataFormat == "NHWC")
1695     {
1696         pooling2dDescriptor.m_StrideX    = strides[2];
1697         pooling2dDescriptor.m_StrideY    = strides[1];
1698         pooling2dDescriptor.m_PoolWidth  = ksize[2];
1699         pooling2dDescriptor.m_PoolHeight = ksize[1];
1700         // Swizzle input to supported memory layout
1701         inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
1702     }
1703     else if (dataFormat == "NCHW")
1704     {
1705         pooling2dDescriptor.m_StrideX    = strides[3];
1706         pooling2dDescriptor.m_StrideY    = strides[2];
1707         pooling2dDescriptor.m_PoolWidth  = ksize[3];
1708         pooling2dDescriptor.m_PoolHeight = ksize[2];
1709     }
1710     else
1711     {
1712         throw ParseException("Only NHWC or NCHW supported for Pooling2d");
1713     }
1714
1715     uint32_t inputHeight = inputTensorInfo.GetShape()[2];
1716     uint32_t inputWidth = inputTensorInfo.GetShape()[3];
1717
1718     bool padding = false;
1719     TensorInfo outputInfo;
1720     if (paddingString == "SAME")
1721     {
1722         padding = true;
1723         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1724                                   inputTensorInfo.GetShape()[1],
1725                                   static_cast<uint32_t>(ceil(
1726                                       static_cast<float>(inputHeight) /
1727                                       static_cast<float>(pooling2dDescriptor.m_StrideY))),
1728                                   static_cast<uint32_t>(ceil(
1729                                       static_cast<float>(inputWidth) /
1730                                       static_cast<float>(pooling2dDescriptor.m_StrideX)))
1731                                 }, DataType::Float32);
1732     }
1733     else if (paddingString == "VALID")
1734     {
1735         padding = false;
1736         outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1737                                   inputTensorInfo.GetShape()[1],
1738                                   static_cast<uint32_t>(ceil(
1739                                       static_cast<float>(inputHeight - pooling2dDescriptor.m_PoolHeight + 1) /
1740                                       static_cast<float>(pooling2dDescriptor.m_StrideY))),
1741                                   static_cast<uint32_t>(ceil(
1742                                       static_cast<float>(inputWidth - pooling2dDescriptor.m_PoolWidth + 1) /
1743                                       static_cast<float>(pooling2dDescriptor.m_StrideX)))
1744                                 }, DataType::Float32);
1745     }
1746     else
1747     {
1748         throw ParseException("Only 'SAME' and 'VALID' padding supported");
1749     }
1750
1751     CalcPadding(inputWidth, pooling2dDescriptor.m_PoolWidth, pooling2dDescriptor.m_StrideX,
1752                     pooling2dDescriptor.m_PadLeft, pooling2dDescriptor.m_PadRight, padding);
1753     CalcPadding(inputHeight, pooling2dDescriptor.m_PoolHeight, pooling2dDescriptor.m_StrideY,
1754                     pooling2dDescriptor.m_PadTop, pooling2dDescriptor.m_PadBottom, padding);
1755
1756
1757     IConnectableLayer* layer = m_Network->AddPooling2dLayer(pooling2dDescriptor, nodeDef.name().c_str());
1758     if (layer == nullptr)
1759     {
1760         throw ParseException("Failed to add pooling2d layer");
1761     }
1762
1763     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1764
1765     if (dataFormat == "NHWC")
1766     {
1767         layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1768     }
1769     else
1770     {
1771         inputSlot.Connect(layer->GetInputSlot(0));
1772     }
1773
1774     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1775 }
1776
1777 ParsedTfOperationPtr TfParser::AddAdditionLayer(const tensorflow::NodeDef& nodeDef, bool isBiasAdd)
1778 {
1779     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1780
1781     IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1782     IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
1783
1784     const TensorInfo& input0Info = input0Slot->GetTensorInfo();
1785     const TensorInfo& input1Info = input1Slot->GetTensorInfo();
1786
1787     if (isBiasAdd)
1788     {
1789         // BiasAdd takes bias as a 1D tensor. We need to add a reshape layer to create a 4D tensor
1790         // with the same data in the correct dimension for broadcast in addition.
1791         if(input1Info.GetNumDimensions() != 1)
1792         {
1793             throw ParseException("Unsupported bias for BiasAdd. It should be a 1D vector.");
1794         }
1795
1796         const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1797         const bool isNHWC = (dataFormat == "NHWC");
1798         const bool isNCHW = (dataFormat == "NCHW");
1799
1800         if (!isNHWC && ! isNCHW)
1801         {
1802             throw ParseException("Only NHWC or NCHW supported for BiasAdd");
1803         }
1804
1805         input1Slot = BroadcastForAddandMul(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
1806     }
1807     else
1808     {
1809         if (input0Info.GetNumDimensions() == 1)
1810         {
1811             const bool isNHWC = true;
1812             input0Slot = BroadcastForAddandMul(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
1813         }
1814
1815         if (input1Info.GetNumDimensions() == 1)
1816         {
1817             const bool isNHWC = true;
1818             input1Slot = BroadcastForAddandMul(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
1819         }
1820     }
1821
1822     IConnectableLayer* const layer = m_Network->AddAdditionLayer(nodeDef.name().c_str());
1823
1824     input0Slot->Connect(layer->GetInputSlot(0));
1825     input1Slot->Connect(layer->GetInputSlot(1));
1826
1827     if (input0Info.GetNumDimensions() == 1 && isBiasAdd == false)
1828     {
1829         layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
1830     }
1831     else
1832     {
1833         layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
1834     }
1835
1836     return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1837 }
1838
1839 IConnectableLayer* TfParser::AddFullyConnectedLayer(const tensorflow::NodeDef& matMulNodeDef,
1840     const tensorflow::NodeDef* addNodeDef, const char* armnnLayerName)
1841 {
1842     // find bias const (if applicable)
1843     ParsedConstTfOperation<float>* biasNode = nullptr;
1844     if (addNodeDef != nullptr)
1845     {
1846         std::vector<OutputOfParsedTfOperation> addInputs = GetInputParsedTfOperationsChecked(*addNodeDef, 2);
1847         // find our inputs
1848         if (HasParsedConstTensor<float>(addInputs[0].m_IndexedValue->GetNode().name()))
1849         {
1850             biasNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(addInputs[0].m_IndexedValue);
1851         }
1852         else if (HasParsedConstTensor<float>(addInputs[1].m_IndexedValue->GetNode().name()))
1853         {
1854             biasNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(addInputs[1].m_IndexedValue);
1855         }
1856         else
1857         {
1858             throw ParseException("ArmNN only supports fully connected layers with constant bias");
1859         }
1860     }
1861
1862     // find matmul inputs
1863     ParsedConstTfOperation<float>* weightNode = nullptr;
1864     ParsedTfOperation* inputNode  = nullptr;
1865     unsigned int inputIdx = 0;
1866     std::vector<OutputOfParsedTfOperation> mulInputs = GetInputParsedTfOperationsChecked(matMulNodeDef, 2);
1867     if (HasParsedConstTensor<float>(mulInputs[0].m_IndexedValue->GetNode().name()))
1868     {
1869         weightNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(mulInputs[0].m_IndexedValue);
1870         inputNode = mulInputs[1].m_IndexedValue;
1871         inputIdx = mulInputs[1].m_Index;
1872     }
1873     else if (HasParsedConstTensor<float>(mulInputs[1].m_IndexedValue->GetNode().name()))
1874     {
1875         weightNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(mulInputs[1].m_IndexedValue);
1876         inputNode = mulInputs[0].m_IndexedValue;
1877         inputIdx = mulInputs[0].m_Index;
1878     }
1879     else
1880     {
1881         throw ParseException("ArmNN only supports fully connected layers with constant weights");
1882     }
1883
1884     std::vector<float> weightTensorData;
1885     // handle weight
1886     ConstTensor weights = weightNode->GetConstTensor(false, weightTensorData);
1887
1888     FullyConnectedDescriptor desc;
1889     desc.m_BiasEnabled = addNodeDef != nullptr;
1890
1891     IConnectableLayer* layer = nullptr;
1892     // make the layer
1893     if (addNodeDef != nullptr)
1894     {
1895         std::vector<float> biasTensorData;
1896         ConstTensor biases = biasNode->GetConstTensor(false, biasTensorData);
1897
1898         if (weights.GetShape()[1] != biases.GetShape()[0])
1899         {
1900             throw ParseException("shape of matmul and bias do not match");
1901         }
1902
1903         layer = m_Network->AddFullyConnectedLayer(desc, weights, biases, armnnLayerName);
1904     }
1905     else
1906     {
1907         layer = m_Network->AddFullyConnectedLayer(desc, weights, armnnLayerName);
1908     }
1909
1910     BOOST_ASSERT(layer != nullptr);
1911
1912     inputNode->ResolveArmnnOutputSlot(inputIdx).Connect(layer->GetInputSlot(0));
1913     unsigned int batches = inputNode->ResolveArmnnOutputSlot(inputIdx).GetTensorInfo().GetShape()[0];
1914
1915     // handle output
1916     TensorInfo outputInfo({ batches, weights.GetShape()[1] }, DataType::Float32);
1917     layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1918     return layer;
1919 }
1920
1921 void TfParser::LoadNodeDef(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1922 {
1923     // get the type of the node (assume float)
1924     tensorflow::DataType type = tensorflow::DT_FLOAT;
1925     if (nodeDef.attr().count("T") != 0)
1926     {
1927         auto attr = nodeDef.attr().at("T");
1928         type      = attr.type();
1929     }
1930     else if (nodeDef.attr().count("dtype") != 0)
1931     {
1932         auto attr = nodeDef.attr().at("dtype");
1933         type      = attr.type();
1934     }
1935
1936     if (type != tensorflow::DT_FLOAT && nodeDef.op() != "Const")
1937     {
1938         throw ParseException("Currently only FLOAT is supported for tensorflow nodes (apart from Const)");
1939     }
1940
1941     const std::string& operation = nodeDef.op();
1942     auto it = ms_OperationNameToParsingFunctions.find(operation);
1943     if (it != ms_OperationNameToParsingFunctions.end())
1944     {
1945         auto func = it->second;
1946         ParsedTfOperationPtr parsedTfOperation = (this->*func)(nodeDef, graphDef);
1947         ParsedTfOperation* parsedTfOperationRaw = parsedTfOperation.get();
1948
1949         // Store the parsed operation so that dependent layers can connect to it
1950         auto it = m_ParsedTfOperations.find(nodeDef.name());
1951         if (it != m_ParsedTfOperations.end())
1952         {
1953             throw ParseException(boost::str(boost::format("Name %1% used by more than one node") % nodeDef.name()));
1954         }
1955         m_ParsedTfOperations[nodeDef.name()] = std::move(parsedTfOperation);
1956
1957         // If this node was requested as an output from the network then add an ArmNN output layer
1958         if (std::find(m_RequestedOutputs.begin(), m_RequestedOutputs.end(), nodeDef.name()) !=
1959             m_RequestedOutputs.end())
1960         {
1961             auto outId = ParseOutputId(nodeDef.name());
1962             const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkOutputsBindingInfo.size());
1963             IOutputSlot& prevSlot = parsedTfOperationRaw->ResolveArmnnOutputSlot(outId.m_Index);
1964
1965             TensorInfo tensorInfo = prevSlot.GetTensorInfo();
1966
1967             IConnectableLayer* outputLayer = m_Network->AddOutputLayer(layerId, nodeDef.name().c_str());
1968
1969             prevSlot.Connect(outputLayer->GetInputSlot(0));
1970
1971             TrackOutputBinding(outputLayer, layerId, tensorInfo);
1972         }
1973     }
1974     else
1975     {
1976         throw ParseException(boost::str(
1977             boost::format("Unsupported operation %1% in tensorflow::GraphDef") % operation));
1978     }
1979 }
1980
1981 void TfParser::LoadGraphDef(const tensorflow::GraphDef& graphDef)
1982 {
1983     // add all nodes to our map
1984     m_NodesByName.clear();
1985     m_NetworkInputsBindingInfo.clear();
1986     m_NetworkOutputsBindingInfo.clear();
1987
1988     for (int i = 0; i < graphDef.node_size(); ++i)
1989     {
1990         const tensorflow::NodeDef& node = graphDef.node(i);
1991         m_NodesByName[node.name()]      = &node;
1992     }
1993
1994     // Find the output nodes the user requested
1995     std::vector<const tensorflow::NodeDef*> targetNodes;
1996     for (const std::string& requestedOutputName : m_RequestedOutputs)
1997     {
1998         auto nodeIt = m_NodesByName.find(requestedOutputName);
1999         if (nodeIt == m_NodesByName.end())
2000         {
2001             throw ParseException("Couldn't find requested output node '" + requestedOutputName + "' in graph");
2002         }
2003         targetNodes.push_back(nodeIt->second);
2004     }
2005
2006     // Sort them into a linear ordering such that all inputs of a node are before the node itself
2007     std::vector<const tensorflow::NodeDef*> sortedNodes;
2008     if (!armnnUtils::GraphTopologicalSort<const tensorflow::NodeDef*>(
2009         targetNodes,
2010         [this](const tensorflow::NodeDef* node)
2011         {
2012             auto outputs = GetTfInputNodes(*node);
2013             std::vector<const tensorflow::NodeDef*> nodesOnly;
2014             for (const auto & o : outputs) {
2015                 nodesOnly.push_back(o.m_IndexedValue);
2016             }
2017             return nodesOnly;
2018         },
2019         sortedNodes))
2020     {
2021         throw ParseException("Cycle detected in graph");
2022     }
2023
2024     // Parse each node in order, knowing that all inputs of a node will be processed before the node itself
2025     for (const auto& it : sortedNodes)
2026     {
2027         const tensorflow::NodeDef& currentNode = *it;
2028         LoadNodeDef(currentNode, graphDef);
2029     }
2030 }
2031
2032 INetworkPtr TfParser::CreateNetworkFromTextFile(const char* graphFile,
2033     const std::map<std::string, TensorShape>& inputShapes,
2034     const std::vector<std::string>& requestedOutputs)
2035 {
2036     FILE* fd = fopen(graphFile, "r");
2037
2038     if (fd == nullptr)
2039     {
2040         std::stringstream error;
2041         error << "Graph file " << graphFile << " failed to open";
2042         throw FileNotFoundException(error.str());
2043     }
2044
2045     // Parse the file into a message
2046     tensorflow::GraphDef graphDef;
2047     auto                 input   = new google::protobuf::io::FileInputStream(fileno(fd));
2048     bool                 success = google::protobuf::TextFormat::Parse(input, &graphDef);
2049     delete input;
2050     fclose(fd);
2051
2052     if (!success)
2053     {
2054         std::stringstream error;
2055         error << "Failed to parse graph file";
2056         throw ParseException(error.str());
2057     }
2058
2059     return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
2060 }
2061
2062 INetworkPtr TfParser::CreateNetworkFromString(const char* protoText,
2063     const std::map<std::string, TensorShape>& inputShapes,
2064     const std::vector<std::string>& requestedOutputs)
2065 {
2066     // Parse the string into a message
2067     tensorflow::GraphDef graphDef;
2068     bool success = google::protobuf::TextFormat::ParseFromString(protoText, &graphDef);
2069
2070     if (!success)
2071     {
2072         std::stringstream error;
2073         error << "Failed to parse graph file";
2074         throw ParseException(error.str());
2075     }
2076
2077     return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
2078 }
2079
2080 INetworkPtr TfParser::CreateNetworkFromBinaryFile(const char* graphFile,
2081     const std::map<std::string, TensorShape>& inputShapes,
2082     const std::vector<std::string>& requestedOutputs)
2083 {
2084     FILE* fd = fopen(graphFile, "rb");
2085
2086     if (fd == nullptr)
2087     {
2088         std::stringstream error;
2089         error << "Graph file " << graphFile << " failed to open";
2090         throw FileNotFoundException(error.str());
2091     }
2092
2093     // Parse the file into a message
2094     tensorflow::GraphDef graphDef;
2095
2096     google::protobuf::io::FileInputStream  inStream(fileno(fd));
2097     google::protobuf::io::CodedInputStream codedStream(&inStream);
2098     codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
2099     bool success = graphDef.ParseFromCodedStream(&codedStream);
2100     fclose(fd);
2101
2102     if (!success)
2103     {
2104         std::stringstream error;
2105         error << "Failed to parse protobuf file" << graphFile;
2106         throw ParseException(error.str());
2107     }
2108
2109     return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
2110 }
2111
2112 INetworkPtr TfParser::CreateNetworkFromGraphDef(const tensorflow::GraphDef& graphDef,
2113     const std::map<std::string, TensorShape>& inputShapes,
2114     const std::vector<std::string>& requestedOutputs)
2115 {
2116     m_Network = INetwork::Create();
2117
2118     m_InputShapes = inputShapes;
2119     if (requestedOutputs.size() == 0)
2120     {
2121         throw ParseException("requestedOutputs must have at least one entry");
2122     }
2123     m_RequestedOutputs = requestedOutputs;
2124
2125     try
2126     {
2127         LoadGraphDef(graphDef);
2128     }
2129     catch (const ParseException& e)
2130     {
2131         Cleanup();
2132         throw e;
2133     }
2134
2135     Cleanup();
2136
2137     return std::move(m_Network);
2138 }
2139
2140 void TfParser::Cleanup()
2141 {
2142     // cleanup, in case we reuse this parser
2143     m_InputShapes.clear();
2144     m_RequestedOutputs.clear();
2145     m_NodesByName.clear();
2146     m_ParsedTfOperations.clear();
2147 }
2148
2149 BindingPointInfo TfParser::GetNetworkInputBindingInfo(const std::string& name) const
2150 {
2151     return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
2152 }
2153
2154 BindingPointInfo TfParser::GetNetworkOutputBindingInfo(const std::string& name) const
2155 {
2156     return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
2157 }
2158
2159 std::pair<LayerBindingId, TensorInfo> TfParser::GetBindingInfo(const std::string& layerName,
2160     const char* bindingPointDesc,
2161     const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
2162 {
2163     auto it = nameToBindingInfo.find(layerName);
2164     if (it == nameToBindingInfo.end())
2165     {
2166         throw InvalidArgumentException(boost::str(boost::format("Unknown %1% '%2%'") % bindingPointDesc % layerName));
2167     }
2168     return it->second;
2169 }
2170
2171 void TfParser::TrackInputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
2172 {
2173     return TrackBindingPoint(layer, id, tensorInfo, "input", m_NetworkInputsBindingInfo);
2174 }
2175
2176 void TfParser::TrackOutputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
2177 {
2178     return TrackBindingPoint(layer, id, tensorInfo, "output", m_NetworkOutputsBindingInfo);
2179 }
2180
2181 void TfParser::TrackBindingPoint(IConnectableLayer* layer,
2182     LayerBindingId id,
2183     const TensorInfo& tensorInfo,
2184     const char* bindingPointDesc,
2185     std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
2186 {
2187     const std::string layerName = layer->GetName();
2188     auto it = nameToBindingInfo.find(layerName);
2189     if (it == nameToBindingInfo.end())
2190     {
2191         nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
2192     }
2193     else
2194     {
2195         throw ParseException(boost::str(
2196             boost::format("Id %1% used by more than one %2% layer") % id % bindingPointDesc));
2197     }
2198 }
2199
2200 } // namespace armnnTfParser