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