ArmNN
 20.08
TfParser.cpp
Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd and Contributors. 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  GatherDescriptor descriptor;
1857  descriptor.m_Axis = ReadMandatoryNodeInt32Attribute(nodeDef, "axis");
1858 
1859  // Infer shape of output tensor
1860  unsigned int paramsDim = params.GetTensorInfo().GetNumDimensions();
1861  unsigned int indicesDim = indices.GetTensorInfo().GetNumDimensions();
1862  unsigned int outputDim = paramsDim - 1 + indicesDim;
1863 
1864  std::vector<unsigned int> dimSizes;
1865 
1866  for (unsigned int i = 0; i < indicesDim; ++i)
1867  {
1868  dimSizes.push_back(indices.GetTensorInfo().GetShape()[i]);
1869  }
1870  for (unsigned int i = 1; i < paramsDim; ++i)
1871  {
1872  dimSizes.push_back(params.GetTensorInfo().GetShape()[i]);
1873  }
1874 
1875  const TensorShape& inferredShape = TensorShape(outputDim, dimSizes.data());
1876 
1877  const TensorInfo inferredOutputInfo(inferredShape, params.GetTensorInfo().GetDataType());
1878 
1879  IConnectableLayer* const layer = m_Network->AddGatherLayer(descriptor, nodeDef.name().c_str());
1880  layer->GetOutputSlot(0).SetTensorInfo(inferredOutputInfo);
1881 
1882  params.Connect(layer->GetInputSlot(0));
1883  indices.Connect(layer->GetInputSlot(1));
1884 
1885  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1886 }
1887 
1888 ParsedTfOperationPtr TfParser::ParseGreater(const tensorflow::NodeDef& nodeDef,
1889  const tensorflow::GraphDef& graphDef)
1890 {
1891  IgnoreUnused(graphDef);
1892  std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> inputLayers = ProcessElementwiseInputSlots(nodeDef, "Greater");
1893  IOutputSlot* input0Slot = inputLayers.first;
1894  IOutputSlot* input1Slot = inputLayers.second;
1895 
1896  ComparisonDescriptor descriptor(ComparisonOperation::Greater);
1897  IConnectableLayer* const layer = m_Network->AddComparisonLayer(descriptor, nodeDef.name().c_str());
1898 
1899  return ProcessComparisonLayer(input0Slot, input1Slot, layer, nodeDef);
1900 }
1901 
1902 ParsedTfOperationPtr TfParser::ParseEqual(const tensorflow::NodeDef& nodeDef,
1903  const tensorflow::GraphDef& graphDef)
1904 {
1905  IgnoreUnused(graphDef);
1906  std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> inputLayers = ProcessElementwiseInputSlots(nodeDef, "Equal");
1907  IOutputSlot* input0Slot = inputLayers.first;
1908  IOutputSlot* input1Slot = inputLayers.second;
1909 
1910  ComparisonDescriptor descriptor(ComparisonOperation::Equal);
1911  IConnectableLayer* const layer = m_Network->AddComparisonLayer(descriptor, nodeDef.name().c_str());
1912 
1913  return ProcessComparisonLayer(input0Slot, input1Slot, layer, nodeDef);
1914 }
1915 
1916 ParsedTfOperationPtr TfParser::ParseMinimum(const tensorflow::NodeDef& nodeDef,
1917  const tensorflow::GraphDef& graphDef)
1918 {
1919  IgnoreUnused(graphDef);
1920  std::pair<armnn::IOutputSlot*, armnn::IOutputSlot*> inputLayers = ProcessElementwiseInputSlots(nodeDef, "Minimum");
1921  IOutputSlot* input0Slot = inputLayers.first;
1922  IOutputSlot* input1Slot = inputLayers.second;
1923 
1924  IConnectableLayer* const layer = m_Network->AddMinimumLayer(nodeDef.name().c_str());
1925 
1926  return ProcessElementwiseLayer(input0Slot, input1Slot, layer, nodeDef);
1927 }
1928 
1929 ParsedTfOperationPtr TfParser::ParseSub(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1930 {
1931  IgnoreUnused(graphDef);
1932  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1933 
1934  IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1935  IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
1936 
1937  const TensorInfo& input0Info = input0Slot->GetTensorInfo();
1938  const TensorInfo& input1Info = input1Slot->GetTensorInfo();
1939 
1940  if (input0Info.GetNumDimensions() == 1)
1941  {
1942  const bool isNHWC = true;
1943  input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
1944  }
1945 
1946  if (input1Info.GetNumDimensions() == 1)
1947  {
1948  const bool isNHWC = true;
1949  input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
1950  }
1951 
1952  IConnectableLayer* const layer = m_Network->AddSubtractionLayer(nodeDef.name().c_str());
1953 
1954  input0Slot->Connect(layer->GetInputSlot(0));
1955  input1Slot->Connect(layer->GetInputSlot(1));
1956 
1957  if (input0Info.GetNumDimensions() == 1)
1958  {
1959  layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
1960  }
1961  else
1962  {
1963  layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
1964  }
1965 
1966  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1967 }
1968 
1969 ParsedTfOperationPtr TfParser::ParseStack(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1970 {
1971  IgnoreUnused(graphDef);
1972  std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
1973 
1974  unsigned int numInputs = static_cast<unsigned int>(nodes.size());
1975  if (numInputs < 1)
1976  {
1977  throw ParseException(
1978  boost::str(
1979  boost::format(
1980  "Pack/Stack expects at least one input. Got %1% for Node %2% %3%")
1981  % numInputs
1982  % nodeDef.name()
1983  % CHECK_LOCATION().AsString()));
1984  }
1985 
1986  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
1987  // Use the tensor shape of the first input as the "correct" input shape in the descriptor
1988  IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1989  const TensorInfo& inputTensorInfo = input0Slot->GetTensorInfo();
1990  auto numDimensions = inputTensorInfo.GetShape().GetNumDimensions();
1991 
1992  // validate axis
1993  int32_t axis = ReadMandatoryNodeInt32Attribute(nodeDef, "axis");
1994  const int sNumDimensions = (static_cast<int>(numDimensions) + 1);
1995  if (!(axis < sNumDimensions && axis >= -sNumDimensions))
1996  {
1997  throw ParseException(
1998  boost::str(
1999  boost::format(
2000  "Axis index is not in range. Got %1% for Node %2% %3%")
2001  % axis
2002  % nodeDef.name()
2003  % CHECK_LOCATION().AsString()));
2004  }
2005 
2006  if (axis < 0)
2007  {
2008  axis = static_cast<int32_t>(numDimensions) + axis + 1;
2009  }
2010 
2011  StackDescriptor stackDescriptor;
2012  stackDescriptor.m_Axis = static_cast<uint32_t>(axis);
2013  stackDescriptor.m_NumInputs = static_cast<uint32_t>(numInputs);
2014  stackDescriptor.m_InputShape = inputTensorInfo.GetShape();
2015 
2016  const unsigned int supportedNumDims = 4;
2017  for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
2018  {
2019  IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
2020  TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2021 
2022  // Double check dimensions of the tensors
2023  if (inputTensorInfo.GetNumDimensions() >= supportedNumDims)
2024  {
2025  throw armnn::ParseException(
2026  boost::str(
2027  boost::format(
2028  "The number of dimensions: %1% for input tensors of the "
2029  "Pack/Stack op. Number of dimensions should be less than %2% %3%")
2030  % inputTensorInfo.GetNumDimensions()
2031  % supportedNumDims
2032  % CHECK_LOCATION().AsString()));
2033  }
2034  }
2035 
2036  std::vector<unsigned int> outputDimensions;
2037  for (unsigned int i = 0; i < stackDescriptor.m_InputShape.GetNumDimensions(); ++i)
2038  {
2039  outputDimensions.push_back(stackDescriptor.m_InputShape[i]);
2040  }
2041  outputDimensions.insert(outputDimensions.begin() + axis, numInputs);
2042 
2043  // add Stack Layer
2044  IConnectableLayer* const layer = m_Network->AddStackLayer(stackDescriptor, nodeDef.name().c_str());
2045 
2046  for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
2047  {
2048  IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
2049  inputSlot.Connect(layer->GetInputSlot(viewIndex));
2050  }
2051 
2052  layer->GetOutputSlot(0).SetTensorInfo(
2053  armnn::TensorInfo(static_cast<uint32_t>(outputDimensions.size()),
2054  outputDimensions.data(),
2055  inputTensorInfo.GetDataType()));
2056 
2057  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2058 }
2059 
2060 ParsedTfOperationPtr TfParser::ParseTranspose(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2061 {
2062  IgnoreUnused(graphDef);
2063 
2064  auto inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2065  const auto inputCount = inputs.size();
2066 
2067  if (inputCount != 2)
2068  {
2069  throw ParseException(
2070  boost::str(
2071  boost::format(
2072  "The number of given input is %1%. It should be two for Transpose op."
2073  "Node %2% %3%")
2074  % inputCount
2075  % nodeDef.name()
2076  % CHECK_LOCATION().AsString()));
2077  }
2078 
2079  auto* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2080 
2081  const auto constInput = inputs[GetConstInputIndex(inputs)];
2082  auto* permuteVectorInput =
2083  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(constInput.m_IndexedValue);
2084  const auto& permuteVectorInfo = permuteVectorInput->GetTensorInfo();
2085 
2086  std::vector<int32_t> permuteVectorData;
2087  permuteVectorInput->GetConstTensor(permuteVectorData);
2088 
2089  std::vector<unsigned int> armnnPermuteVectorData(permuteVectorData.begin(), permuteVectorData.end());
2090 
2091  const auto permutationVector = PermutationVector(armnnPermuteVectorData.data(), permuteVectorInfo.GetNumElements());
2092  const auto desc = TransposeDescriptor(permutationVector);
2093 
2094  auto* layer = m_Network->AddTransposeLayer(desc, nodeDef.name().c_str());
2095  ARMNN_ASSERT(layer);
2096 
2097  input0Slot->Connect(layer->GetInputSlot(0));
2098 
2099  const auto& input0Info = input0Slot->GetTensorInfo();
2100  armnn::TensorInfo outputInfo {input0Info};
2101  outputInfo.SetShape(armnnUtils::TransposeTensorShape(input0Info.GetShape(), desc.m_DimMappings));
2102  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2103 
2104  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2105 }
2106 
2107 unsigned int CheckPaddingTensor(const ConstTensor& paddingTensor,
2108  const TensorInfo& inputTensorInfo,
2109  const std::string& nodeName)
2110 {
2111  unsigned int rank = paddingTensor.GetShape()[0];
2112  unsigned int expectedRank = inputTensorInfo.GetNumDimensions();
2113  if (rank != expectedRank)
2114  {
2115  throw ParseException(
2116  boost::str(
2117  boost::format(
2118  "Expected the padding tensor to be of rank %1 not %2 on Node %3 %4.")
2119  % expectedRank
2120  % rank
2121  % nodeName
2122  % CHECK_LOCATION().AsString()));
2123  }
2124  unsigned int second = paddingTensor.GetShape()[1];
2125  if (second != 2)
2126  {
2127  throw ParseException(
2128  boost::str(
2129  boost::format(
2130  "Expected the padding tensor to be of dimensions [%1, 2] not [%1, %2] on Node %3 %4.")
2131  % rank
2132  % second
2133  % nodeName
2134  % CHECK_LOCATION().AsString()));
2135  }
2136  return rank;
2137 }
2138 
2140  const std::vector<std::pair<unsigned int, unsigned int>>& padList)
2141 {
2142  unsigned int numDims = inputTensorInfo.GetNumDimensions();
2143  std::vector<unsigned int> outDims;
2144  for (unsigned int i = 0; i < numDims; ++i)
2145  {
2146  unsigned int dimSize = inputTensorInfo.GetShape()[i];
2147  const std::pair<unsigned int, unsigned int>& dimPadding = padList[i];
2148  dimSize += dimPadding.first;
2149  dimSize += dimPadding.second;
2150  outDims.push_back(dimSize);
2151  }
2152  TensorInfo paddedTensorInfo = inputTensorInfo;
2153  unsigned int outDimsSize = static_cast<unsigned int>(outDims.size());
2154  paddedTensorInfo.SetShape(TensorShape{ outDimsSize, outDims.data() });
2155  return paddedTensorInfo;
2156 }
2157 
2158 ParsedTfOperationPtr TfParser::ParsePad(const tensorflow::NodeDef& nodeDef,
2159  const tensorflow::GraphDef& graphDef)
2160 {
2161  IgnoreUnused(graphDef);
2162  // input consists of:
2163  // input[0] the tensor which will be padded
2164  // input[1] the tensor holding the padding values
2165  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2166  IOutputSlot& previousLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2167  TensorInfo inputTensorInfo = previousLayerOutputSlot.GetTensorInfo();
2168  if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue))
2169  {
2170  throw ParseException(
2171  boost::str(
2172  boost::format(
2173  "ArmNN only supports Pad with constant padding. "
2174  "Input %1%. Node %2% %3%")
2175  % inputs[1].m_IndexedValue->GetNode().name()
2176  % nodeDef.name()
2177  % CHECK_LOCATION().AsString()));
2178 
2179  }
2180  ParsedConstTfOperation<int32_t>* paddingTensorOp =
2181  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2182 
2183  std::vector<int32_t> paddingTensorData;
2184  ConstTensor paddingTensor = paddingTensorOp->GetConstTensor(paddingTensorData);
2185  // paddings is an integer tensor with shape [n, 2], where n is the rank of tensor
2186  // and should match the rank of the input tensor that is being padded.
2187  // For each dimension D of input, paddings[D, 0] indicates how many values to add
2188  // before the contents of tensor in that dimension, and paddings[D, 1] indicates how
2189  // many values to add after the contents of tensor in that dimension
2190  // This needs to be translated into a padList for ACL
2191  std::vector<std::pair<unsigned int, unsigned int>> padList;
2192  unsigned int rank = CheckPaddingTensor(paddingTensor, inputTensorInfo, nodeDef.name());
2193  for (unsigned int i = 0; i < rank; ++i)
2194  {
2195  std::pair<unsigned int, unsigned int> paddingForDim;
2196  for (unsigned int j = 0; j < 2; j++)
2197  {
2198  unsigned int index = (i * 2) + j;
2199  int paddingAmount = paddingTensorData[index];
2200  // make sure we can cast to an unsigned value
2201  if (paddingAmount < 0)
2202  {
2203  throw ParseException(
2204  boost::str(
2205  boost::format(
2206  "Negative amount %1 specified at [%2, %3] of padding tensor on Node %4 %5.")
2207  % paddingAmount
2208  % i
2209  % j
2210  % nodeDef.name()
2211  % CHECK_LOCATION().AsString()));
2212  }
2213  if (j == 0)
2214  {
2215  paddingForDim.first = static_cast<unsigned int>(paddingAmount);
2216  }
2217  else
2218  {
2219  paddingForDim.second = static_cast<unsigned int>(paddingAmount);
2220  }
2221  }
2222  padList.push_back(paddingForDim);
2223  }
2224  PadDescriptor padDescriptor(padList);
2225  IConnectableLayer* layer = m_Network->AddPadLayer(padDescriptor, nodeDef.name().c_str());
2226  previousLayerOutputSlot.Connect(layer->GetInputSlot(0));
2227  // Use the padding to calculate the new output tensor shape
2228  TensorInfo outputTensorInfo = CalculatePaddedOutputTensorInfo(inputTensorInfo, padList);
2229  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2230  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2231 }
2232 
2233 ParsedTfOperationPtr TfParser::ParseConcat(const tensorflow::NodeDef& nodeDef,
2234  const tensorflow::GraphDef& graphDef)
2235 {
2236  IgnoreUnused(graphDef);
2237  std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
2238 
2239  // In tensorflow, we have the last input of the Concat layer as the axis for concatenation.
2240  unsigned int numInputs = static_cast<unsigned int>(nodes.size());
2241 
2242  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
2243 
2244  // Constant tensor index
2245  unsigned int index = GetConstInputIndex(inputs);
2246  // Get the axis tensor data
2247  ParsedConstTfOperation<int32_t>* shapeNode =
2248  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(inputs[index].m_IndexedValue);
2249 
2250  std::vector<int32_t> axisTensorData;
2251  shapeNode->GetConstTensor(axisTensorData);
2252 
2253  // This concatDim indicates the data format: 3 is the NHWC, 1 is the NCHW.
2254  const unsigned int concatDim = static_cast<unsigned int>(axisTensorData[0]);
2255 
2256  // Armnn supports concatenation along the channel dimension for data formats NHWC and NCHW.
2257  if (concatDim == 0 || concatDim == 2)
2258  {
2259  throw ParseException(
2260  boost::str(
2261  boost::format(
2262  "Dimension %1% for concatenation is not supported by Armnn. "
2263  "Node %2% %3%")
2264  % concatDim
2265  % nodeDef.name()
2266  % CHECK_LOCATION().AsString()));
2267  }
2268 
2269  const unsigned int supportedNumDims = 4;
2270  unsigned int numConcatViews = numInputs - 1;
2271  OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numConcatViews), supportedNumDims);
2272  concatDescriptor.SetConcatAxis(concatDim);
2273  TensorShape mergeDims(supportedNumDims);
2274  unsigned int mergeDim = 0;
2275  for (unsigned int viewIndex = 0; viewIndex < numConcatViews; ++viewIndex)
2276  {
2277  // Need to double check whether it should be
2278  IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
2279  TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2280 
2281  // Double check dimensions of the tensors
2282  if (inputTensorInfo.GetNumDimensions() != supportedNumDims)
2283  {
2284  throw armnn::ParseException(
2285  boost::str(
2286  boost::format(
2287  "The number of dimensions: %1% for input tensors of the "
2288  "concatenation op should be %2% %3%")
2289  % inputTensorInfo.GetNumDimensions()
2290  % supportedNumDims
2291  % CHECK_LOCATION().AsString()));
2292  }
2293 
2294  // Copy the input tensor shape to mergeDimSizes and initialize the view origin coordinates for the current input
2295  mergeDims = inputTensorInfo.GetShape();
2296  unsigned int* viewOrigin = const_cast<unsigned int*>(concatDescriptor.GetViewOrigin(viewIndex));
2297  std::fill(viewOrigin, viewOrigin + supportedNumDims, 0);
2298 
2299  // Update the view origin coordinates and the merge dimension value
2300  concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
2301  mergeDim += mergeDims[concatDim];
2302  }
2303 
2304  // Update the output shape
2305  mergeDims[concatDim] = mergeDim;
2306  armnn::IConnectableLayer *layer = m_Network->AddConcatLayer(concatDescriptor, nodeDef.name().c_str());
2307 
2308  layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(mergeDims, DataType::Float32));
2309 
2310  for (unsigned int viewIndex = 0; viewIndex < numConcatViews; ++viewIndex)
2311  {
2312  IOutputSlot& inputSlot = inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
2313  inputSlot.Connect(layer->GetInputSlot(viewIndex));
2314  }
2315 
2316  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2317 }
2318 
2319 ParsedTfOperationPtr TfParser::ParseShape(const tensorflow::NodeDef& nodeDef,
2320  const tensorflow::GraphDef& graphDef)
2321 {
2322  IgnoreUnused(graphDef);
2323  // Note: the Shape layer is handled in a special way, because:
2324  // 1. ARMNN doesn't support int32 tensors which it outputs.
2325  // 2. ARMNN works with statically shaped tensors which are known at parse time.
2326  // 3. because of 1. and 2. we treat the output of Shape as a temporary const int32
2327  // tensor which may be used as an input to other ops, most likely a Reshape.
2328 
2329  const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "out_type");
2330  if (tfDataType != tensorflow::DT_INT32)
2331  {
2332  throw ParseException(
2333  boost::str(
2334  boost::format(
2335  "Armnn only supports DT_INT32 as out_type. Got %1% for Node %2% %3%")
2336  % tensorflow::DataType_Name(tfDataType)
2337  % nodeDef.name()
2338  % CHECK_LOCATION().AsString()));
2339  }
2340 
2341  const std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2342  IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2343  const TensorInfo& prevLayerTensorInfo = prevLayerOutputSlot.GetTensorInfo();
2344  unsigned int prevLayerDimensions = prevLayerTensorInfo.GetNumDimensions();
2345 
2346  std::vector<int32_t> shapeTensorData;
2347  shapeTensorData.reserve(prevLayerDimensions);
2348 
2349  for (unsigned int i=0; i<prevLayerDimensions; ++i)
2350  {
2351  shapeTensorData.push_back(static_cast<int32_t>(prevLayerTensorInfo.GetShape()[i]));
2352  }
2353 
2354  TensorInfo shapeTensorInfo(1, &prevLayerDimensions, DataType::Signed32);
2355 
2356  return std::make_unique<ParsedConstTfOperation<int32_t>>(this,
2357  nodeDef,
2358  &shapeTensorData[0],
2359  shapeTensorInfo);
2360 }
2361 
2362 ParsedTfOperationPtr TfParser::ParseReshape(const tensorflow::NodeDef& nodeDef,
2363  const tensorflow::GraphDef& graphDef)
2364 {
2365  IgnoreUnused(graphDef);
2366  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2367  ParsedTfOperation* inputNode = inputs[0].m_IndexedValue;
2368 
2369  if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
2370  {
2371  throw ParseException(
2372  boost::str(
2373  boost::format(
2374  "ArmNN only supports Reshape layers with constant shapes. "
2375  "Input %1% Node %2% %3%")
2376  % inputs[1].m_IndexedValue->GetNode().name()
2377  % nodeDef.name()
2378  % CHECK_LOCATION().AsString()));
2379  }
2380  ParsedConstTfOperation<int32_t>* shapeNode =
2381  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2382 
2383  armnn::IOutputSlot& prevLayerOutputSlot = inputNode->ResolveArmnnOutputSlot(inputs[0].m_Index);
2384  TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
2385 
2386  std::vector<int32_t> shapeTensorData;
2387  ConstTensor shapeTensor = shapeNode->GetConstTensor(shapeTensorData);
2388  const TensorInfo outputTensorInfo = PrepareReshape(inputTensorInfo, shapeTensorData);
2389 
2390  TensorShape targetShape = outputTensorInfo.GetShape();
2391  ReshapeDescriptor reshapeDesc;
2392  reshapeDesc.m_TargetShape = targetShape;
2393 
2394  IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
2395  prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2396  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2397 
2398  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2399 }
2400 
2401 ParsedTfOperationPtr TfParser::ParseResizeBilinear(const tensorflow::NodeDef& nodeDef,
2402  const tensorflow::GraphDef& graphDef)
2403 {
2404  IgnoreUnused(graphDef);
2405  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2406 
2407  if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
2408  {
2409  throw ParseException(
2410  boost::str(
2411  boost::format(
2412  "ArmNN only supports ResizeBilinear layers with constant sizes. "
2413  "Input %1%. Node %2% %3%")
2414  % inputs[1].m_IndexedValue->GetNode().name()
2415  % nodeDef.name()
2416  % CHECK_LOCATION().AsString()));
2417  }
2418  ParsedConstTfOperation<int32_t>* sizeNode =
2419  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2420 
2421  // Checks the align_corners attribute is not set.
2422  if (ReadOptionalNodeBoolAttribute(nodeDef, "align_corners", false))
2423  {
2424  throw ParseException(
2425  boost::str(
2426  boost::format(
2427  "ArmNN only supports ResizeBilinear layers with align_corners set to false. "
2428  "Node %1% %2%")
2429  % nodeDef.name()
2430  % CHECK_LOCATION().AsString()));
2431  }
2432 
2433  // Data for the parsed tensor args (size) must be stored locally.
2434  std::vector<int32_t> sizeTensorData;
2435  ConstTensor sizeTensor = sizeNode->GetConstTensor(sizeTensorData);
2436 
2437  // The descriptor only has target height and width attributes, which we get from the size tensor.
2438  ResizeDescriptor desc;
2440  desc.m_TargetHeight = static_cast<uint32_t> (sizeTensorData[0]);
2441  desc.m_TargetWidth = static_cast<uint32_t> (sizeTensorData[1]);
2443 
2444  IConnectableLayer* layer = m_Network->AddResizeLayer(desc, nodeDef.name().c_str());
2445 
2446  IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2447  TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2448  // The input shape is always in BHWC format, this will be swizzled below; for now,
2449  // get the batch and channels to make up the ArmNN output shape with the target size.
2450  unsigned int outBatch = inputTensorInfo.GetShape()[0];
2451  unsigned int outChannels = inputTensorInfo.GetShape()[3];
2452  unsigned int outHeight = desc.m_TargetHeight;
2453  unsigned int outWidth = desc.m_TargetWidth;
2454  TensorShape outShape({outBatch, outHeight, outWidth, outChannels });
2455  // The output DataType is always Float32, regardless of the input DataType.
2456  const TensorInfo outputTensorInfo(outShape, armnn::DataType::Float32);
2457  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2458 
2459  inputSlot.Connect(layer->GetInputSlot(0));
2460 
2461  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2462 }
2463 
2464 TensorInfo OutputShapeOfSqueeze(const tensorflow::NodeDef& nodeDef, TensorInfo inputTensorInfo)
2465 {
2466  ARMNN_ASSERT(nodeDef.op() == "Squeeze");
2467  tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "T");
2468 
2469  DataType type;
2470  if (tfDataType == tensorflow::DT_FLOAT)
2471  {
2472  type = DataType::Float32;
2473  }
2474  else if (tfDataType == tensorflow::DT_INT32)
2475  {
2476  type = DataType::Signed32;
2477  }
2478  else
2479  {
2480  throw ParseException(
2481  boost::str(
2482  boost::format("Unsupported DataType %1% for Squeeze operation %2% %3%")
2483  % tensorflow::DataType_Name(tfDataType)
2484  % nodeDef.name()
2485  % CHECK_LOCATION().AsString()));
2486  }
2487 
2488 
2489  if (inputTensorInfo.GetNumDimensions() > 4)
2490  {
2491  throw ParseException(
2492  boost::str(
2493  boost::format(
2494  "Unsupported number of dimensions: %1% for input shape for Squeeze %2% %3%")
2495  % inputTensorInfo.GetNumDimensions()
2496  % nodeDef.name()
2497  % CHECK_LOCATION().AsString()));
2498  }
2499 
2500  std::vector<uint32_t> squeezeDims = ReadOptionalNodeUint32ListAttribute(nodeDef, "squeeze_dims");
2501  static const uint32_t dimensionSequence[] = { 0, 1, 2, 3 };
2502 
2503  if (squeezeDims.empty())
2504  {
2505  squeezeDims.assign(dimensionSequence,
2506  dimensionSequence+inputTensorInfo.GetNumDimensions());
2507  }
2508 
2509  std::vector<uint32_t> outputDims;
2510  for(unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); i++)
2511  {
2512  bool skipSqueeze = (std::find(squeezeDims.begin(), squeezeDims.end(), i) == squeezeDims.end());
2513  auto currentDimension = inputTensorInfo.GetShape()[i];
2514  if (skipSqueeze || currentDimension != 1)
2515  {
2516  outputDims.push_back(currentDimension);
2517  }
2518  }
2519 
2520  if (outputDims.size() > 4)
2521  {
2522  throw ParseException(
2523  boost::str(
2524  boost::format(
2525  "Unsupported number of dimensions: %1% for output shape for Squeeze %2% %3%")
2526  % outputDims.size()
2527  % nodeDef.name()
2528  % CHECK_LOCATION().AsString()));
2529  }
2530 
2531  TensorShape outShape = TensorShape(static_cast<unsigned int>(outputDims.size()),
2532  outputDims.data());
2533 
2534  TensorInfo outTensorInfo = inputTensorInfo;
2535  outTensorInfo.SetShape(outShape);
2536  outTensorInfo.SetDataType(type);
2537 
2538  return outTensorInfo;
2539 }
2540 
2541 ParsedTfOperationPtr TfParser::ParseSqueeze(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2542 {
2543  IgnoreUnused(graphDef);
2544  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2545 
2546  IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2547  TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
2548 
2549  TensorInfo outputInfo;
2550  outputInfo = OutputShapeOfSqueeze(nodeDef, inputTensorInfo);
2551 
2552  ReshapeDescriptor reshapeDesc;
2553  reshapeDesc.m_TargetShape = outputInfo.GetShape();
2554  IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
2555  prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2556  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2557 
2558  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2559 }
2560 
2561 ParsedTfOperationPtr TfParser::ParseLrn(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2562 {
2563  IgnoreUnused(graphDef);
2564  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2565 
2566  NormalizationDescriptor normalizationDescriptor;
2567  normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
2568  normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
2569  normalizationDescriptor.m_Alpha = ReadMandatoryNodeFloatAttribute(nodeDef, "alpha");
2570  normalizationDescriptor.m_Beta = ReadMandatoryNodeFloatAttribute(nodeDef, "beta");
2571  normalizationDescriptor.m_K = ReadMandatoryNodeFloatAttribute(nodeDef, "bias");
2572  normalizationDescriptor.m_NormSize = ReadMandatoryNodeUint32Attribute(nodeDef, "depth_radius");
2573  normalizationDescriptor.m_DataLayout = armnn::DataLayout::NHWC;
2574 
2575  // The window size must be an odd value. For a window size of (2 * n + 1), TensorFlow defines depth_radius = n.
2576  normalizationDescriptor.m_NormSize = normalizationDescriptor.m_NormSize * 2 + 1;
2577 
2578  IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2579  IConnectableLayer* layer = m_Network->AddNormalizationLayer(normalizationDescriptor,
2580  nodeDef.name().c_str());
2581  prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2582  layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
2583 
2584  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2585 }
2586 
2587 /// An ParsedTfOperation for a MatMul node.
2588 /// Creation of the armnn FullyConnected layer is deferred until it is actually needed, because
2589 /// MatMul nodes are often used for the first part of a biased FullyConnected (MatMul followed
2590 /// by Add) and in these cases armnn doesn't need a separate layer for the MatMul.
2591 ///
2592 class ParsedMatMulTfOperation : public DeferredSingleLayerParsedTfOperation
2593 {
2594 public:
2595  ParsedMatMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
2596  : DeferredSingleLayerParsedTfOperation(parser, node)
2597  {
2598  }
2599 
2600  void CreateLayerDeferred() override
2601  {
2602  ARMNN_ASSERT(m_Layer == nullptr);
2603  m_Layer = m_Parser->AddFullyConnectedLayer(m_Node, nullptr, m_Node.name().c_str());
2604  }
2605 };
2606 
2607 ParsedTfOperationPtr TfParser::ParseMatMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2608 {
2609  IgnoreUnused(graphDef);
2610 
2611  // Defers the creation of the layer (see ParsedMatMulTfOperation).
2612  return std::make_unique<ParsedMatMulTfOperation>(this, nodeDef);
2613 }
2614 
2615 ParsedTfOperationPtr TfParser::ParseMean(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2616 {
2617  IgnoreUnused(graphDef);
2618  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2619  IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2620  TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2621 
2622  if (inputs.size() != 2)
2623  {
2624  throw ParseException(
2625  boost::str(boost::format("Mean expects two inputs!. Got %1% for Node %2% %3%")
2626  % inputs.size()
2627  % nodeDef.name()
2628  % CHECK_LOCATION().AsString()));
2629  }
2630 
2631  bool keepDims = ReadMandatoryNodeBoolAttribute(nodeDef, "keep_dims");
2632 
2633  ParsedConstTfOperation<int32_t>* axisNode =
2634  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
2635 
2636  const TensorInfo& axisTensorInfo = axisNode->GetTensorInfo();
2637 
2638  ConstTensor axisTensor(axisTensorInfo, axisNode->GetStorage());
2639  const int* axisData = static_cast<const int*>(axisTensor.GetMemoryArea());
2640 
2641  TensorInfo outputTensorInfo;
2642  MeanDescriptor meanDescriptor;
2643  meanDescriptor.m_KeepDims = keepDims;
2644 
2645  // Negative axis values are supported so that the process requires
2646  // to convert them into the corresponding positive ones.
2647  // Duplicate values are also removed.
2648  std::vector<int> rawAxisVector(axisData, axisData + axisTensorInfo.GetNumElements());
2649  std::set<unsigned int> positiveAxisSet;
2650  int rank = static_cast<int>(inputTensorInfo.GetNumDimensions());
2651 
2652  std::transform(rawAxisVector.begin(), rawAxisVector.end(),
2653  std::inserter(positiveAxisSet, positiveAxisSet.begin()),
2654  [rank](int i) -> unsigned int { return static_cast<unsigned int>((i + rank) % rank); });
2655 
2656  CalculateReducedOutputTensoInfo(inputTensorInfo, positiveAxisSet, keepDims, outputTensorInfo);
2657 
2658  if (inputTensorInfo.GetNumDimensions() > positiveAxisSet.size())
2659  {
2660  meanDescriptor.m_Axis.assign(positiveAxisSet.begin(), positiveAxisSet.end());
2661  }
2662 
2663  IConnectableLayer* layer = m_Network->AddMeanLayer(meanDescriptor, nodeDef.name().c_str());
2664  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2665  inputSlot.Connect(layer->GetInputSlot(0));
2666 
2667  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2668 }
2669 
2670 /// An ParsedTfOperation for a Mul node.
2671 /// Creation of the armnn Mul layer is deferred until it is actually needed, because Mul nodes
2672 /// are also used for the first part of a leaky relu activation function (Mul followed by Maximum)
2673 /// and in these cases armnn doesn't need a separate layer for the Mul.
2674 ///
2675 class ParsedMulTfOperation : public DeferredSingleLayerParsedTfOperation
2676 {
2677 public:
2678  ParsedMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
2679  : DeferredSingleLayerParsedTfOperation(parser, node)
2680  {
2681  }
2682 
2683  void CreateLayerDeferred() override
2684  {
2685  ARMNN_ASSERT(m_Layer == nullptr);
2686  m_Layer = m_Parser->AddMultiplicationLayer(m_Node);
2687  }
2688 };
2689 
2690 ParsedTfOperationPtr TfParser::ParseMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2691 {
2692  IgnoreUnused(graphDef);
2693 
2694  return std::make_unique<ParsedMulTfOperation>(this, nodeDef);
2695 }
2696 
2697 ParsedTfOperationPtr TfParser::ParsePlaceholder(const tensorflow::NodeDef& nodeDef,
2698  const tensorflow::GraphDef& graphDef)
2699 {
2700  IgnoreUnused(graphDef);
2701 
2702  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 0);
2703 
2704  const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkInputsBindingInfo.size());
2705 
2706  auto it = m_InputShapes.find(nodeDef.name());
2707  if (it == m_InputShapes.end())
2708  {
2709  throw ParseException(
2710  boost::str(
2711  boost::format(
2712  "Missing input shape for Placeholder '%1%' %2%")
2713  % nodeDef.name()
2714  % CHECK_LOCATION().AsString()));
2715  }
2716  TensorInfo tensorInfo(it->second, DataType::Float32);
2717 
2718  IConnectableLayer* const layer = m_Network->AddInputLayer(layerId, nodeDef.name().c_str());
2719 
2720  layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
2721 
2722  TrackInputBinding(layer, layerId, tensorInfo);
2723 
2724  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2725 }
2726 
2727 ParsedTfOperationPtr TfParser::ParseRealDiv(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2728 {
2729  IgnoreUnused(graphDef);
2730  return AddRealDivLayer(nodeDef);
2731 }
2732 
2733 ParsedTfOperationPtr TfParser::ParseRelu(const tensorflow::NodeDef& nodeDef,
2734  const tensorflow::GraphDef& graphDef)
2735 {
2736  IgnoreUnused(graphDef);
2737 
2738  ActivationDescriptor activationDesc;
2739  activationDesc.m_Function = ActivationFunction::ReLu;
2740  return AddActivationLayer(nodeDef, activationDesc);
2741 }
2742 
2743 ParsedTfOperationPtr TfParser::ParseRelu6(const tensorflow::NodeDef& nodeDef,
2744  const tensorflow::GraphDef& graphDef)
2745 {
2746  IgnoreUnused(graphDef);
2747 
2748  ActivationDescriptor activationDesc;
2749  activationDesc.m_Function = ActivationFunction::BoundedReLu;
2750  activationDesc.m_A = 6.0f;
2751  activationDesc.m_B = 0.0f;
2752 
2753  return AddActivationLayer(nodeDef, activationDesc);
2754 }
2755 
2756 ParsedTfOperationPtr TfParser::ParseSigmoid(const tensorflow::NodeDef& nodeDef,
2757  const tensorflow::GraphDef& graphDef)
2758 {
2759  IgnoreUnused(graphDef);
2760 
2761  ActivationDescriptor activationDesc;
2762  activationDesc.m_Function = ActivationFunction::Sigmoid;
2763 
2764  return AddActivationLayer(nodeDef, activationDesc);
2765 }
2766 
2767 ParsedTfOperationPtr TfParser::ParseRsqrt(const tensorflow::NodeDef &nodeDef,
2768  const tensorflow::GraphDef &graphDef)
2769 {
2770  IgnoreUnused(graphDef);
2771 
2772  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2773 
2774  ElementwiseUnaryDescriptor descriptor(UnaryOperation::Rsqrt);
2775  IConnectableLayer* const layer = m_Network->AddElementwiseUnaryLayer(descriptor, nodeDef.name().c_str());
2776 
2777  IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2778  prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2779  layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
2780 
2781  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2782 }
2783 
2784 ParsedTfOperationPtr TfParser::ParseSoftmax(const tensorflow::NodeDef& nodeDef,
2785  const tensorflow::GraphDef& graphDef)
2786 {
2787  IgnoreUnused(graphDef);
2788 
2789  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2790 
2791  SoftmaxDescriptor softmaxDescriptor;
2792  IConnectableLayer* const layer = m_Network->AddSoftmaxLayer(softmaxDescriptor, nodeDef.name().c_str());
2793 
2794  IOutputSlot& prevLayerSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2795  prevLayerSlot.Connect(layer->GetInputSlot(0));
2796  layer->GetOutputSlot(0).SetTensorInfo(prevLayerSlot.GetTensorInfo());
2797 
2798  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2799 }
2800 
2801 ParsedTfOperationPtr TfParser::ParseSplit(const tensorflow::NodeDef& nodeDef,
2802  const tensorflow::GraphDef& graphDef)
2803 {
2804  IgnoreUnused(graphDef);
2805 
2806  std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
2807  unsigned int numInputs = static_cast<unsigned int>(nodes.size());
2808  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
2809 
2810  // Constant tensor index
2811  unsigned int index = GetConstInputIndex(inputs);
2812  // Get the axis tensor data
2813  ParsedConstTfOperation<int32_t>* shapeNode =
2814  PolymorphicDowncast<ParsedConstTfOperation<int32_t>*>(inputs[index].m_IndexedValue);
2815 
2816  std::vector<int32_t> axisTensorData;
2817  shapeNode->GetConstTensor(axisTensorData);
2818 
2819  // This splitDim indicates the data format: 3 is the NHWC, 1 is the NCHW.
2820  const unsigned int splitDim = static_cast<unsigned int>(axisTensorData[0]);
2821 
2822  // Armnn supports split along the channel dimension for data formats NHWC and NCHW.
2823  if (splitDim == 0 || splitDim == 2)
2824  {
2825  throw armnn::ParseException(
2826  boost::str(
2827  boost::format(
2828  "Dimension %1% for split is not supported by Armnn. "
2829  "Node %2% %3%")
2830  % splitDim
2831  % nodeDef.name()
2832  % CHECK_LOCATION().AsString()));
2833  }
2834 
2835  // As Armnn only supports splitter outputs of the same shape, therefore num_split will be limited to an integer.
2836  uint32_t num_split = ReadMandatoryNodeUint32Attribute(nodeDef, "num_split");
2837 
2838  IOutputSlot& inputSlot = inputs[1 - index].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1 - index].m_Index);
2839  TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
2840 
2841  const unsigned int supportedNumDims = 4;
2842  auto inputDimSize = inputTensorInfo.GetNumDimensions();
2843 
2844  if (inputDimSize != supportedNumDims)
2845  {
2846  throw armnn::ParseException(
2847  boost::str(
2848  boost::format(
2849  "The number of dimensions: %1% for input tensors of the "
2850  "split op should be %2% %3%")
2851  % inputTensorInfo.GetNumDimensions()
2852  % supportedNumDims
2853  % CHECK_LOCATION().AsString()));
2854  }
2855 
2856  std::vector<unsigned int> splitterDimSizes(inputDimSize);
2857 
2858  // Add current input shape to splitterDimSizes
2859  for (unsigned int i = 0; i < inputDimSize; ++i)
2860  {
2861  splitterDimSizes[i] = inputTensorInfo.GetShape()[i];
2862  }
2863 
2864  if (splitterDimSizes[splitDim] % num_split != 0)
2865  {
2866  throw ParseException("Number of splits must evenly divide the dimension");
2867  }
2868  splitterDimSizes[splitDim] /= num_split;
2869 
2870  SplitterDescriptor splitDesc(num_split);
2871  for (unsigned int g = 0; g < num_split; ++g)
2872  {
2873  // Set the size of the views.
2874  for (unsigned int dimIdx = 0; dimIdx < splitterDimSizes.size(); ++dimIdx)
2875  {
2876  splitDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
2877  }
2878  splitDesc.SetViewOriginCoord(g, splitDim, splitterDimSizes[splitDim] * g);
2879  }
2880 
2881  IConnectableLayer *layer = m_Network->AddSplitterLayer(splitDesc, nodeDef.name().c_str());
2882 
2883  inputSlot.Connect(layer->GetInputSlot(0));
2884 
2885  TensorShape outShape = TensorShape(static_cast<unsigned int>(splitterDimSizes.size()),
2886  splitterDimSizes.data());
2887 
2888  for (unsigned int i = 0; i < layer->GetNumOutputSlots(); ++i)
2889  {
2890  layer->GetOutputSlot(i).SetTensorInfo(armnn::TensorInfo(outShape, inputTensorInfo.GetDataType()));
2891  }
2892 
2893  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2894 }
2895 
2896 ParsedTfOperationPtr TfParser::ParseSoftplus(const tensorflow::NodeDef& nodeDef,
2897  const tensorflow::GraphDef& graphDef)
2898 {
2899  IgnoreUnused(graphDef);
2900 
2901  ActivationDescriptor activationDesc;
2902  activationDesc.m_Function = ActivationFunction::SoftReLu;
2903 
2904  return AddActivationLayer(nodeDef, activationDesc);
2905 }
2906 
2907 ParsedTfOperationPtr TfParser::ParseStridedSlice(const tensorflow::NodeDef& nodeDef,
2908  const tensorflow::GraphDef& graphDef)
2909 {
2910  IgnoreUnused(graphDef);
2911 
2912  std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
2913  unsigned int numInputs = static_cast<unsigned int>(nodes.size());
2914  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
2915 
2916  ParsedConstTfOperation<int32_t>* beginNode =
2917  PolymorphicDowncast<ParsedConstTfOperation<int32_t> *>(inputs[1].m_IndexedValue);
2918  std::vector<int32_t> beginTensorData;
2919  beginNode->GetConstTensor(beginTensorData);
2920 
2921  ParsedConstTfOperation<int32_t>* endNode =
2922  PolymorphicDowncast<ParsedConstTfOperation<int32_t> *>(inputs[2].m_IndexedValue);
2923  std::vector<int32_t> endTensorData;
2924  endNode->GetConstTensor(endTensorData);
2925 
2926  ParsedConstTfOperation<int32_t>* stridesNode =
2927  PolymorphicDowncast<ParsedConstTfOperation<int32_t> *>(inputs[3].m_IndexedValue);
2928  std::vector<int32_t> stridesTensorData;
2929  stridesNode->GetConstTensor(stridesTensorData);
2930 
2932  desc.m_Begin = beginTensorData;
2933  desc.m_End = endTensorData;
2934  desc.m_Stride = stridesTensorData;
2935  desc.m_BeginMask = ReadMandatoryNodeInt32Attribute(nodeDef, "begin_mask");
2936  desc.m_EndMask = ReadMandatoryNodeInt32Attribute(nodeDef, "end_mask");
2937  desc.m_EllipsisMask = ReadMandatoryNodeInt32Attribute(nodeDef, "ellipsis_mask");
2938  desc.m_NewAxisMask = ReadMandatoryNodeInt32Attribute(nodeDef, "new_axis_mask");
2939  desc.m_ShrinkAxisMask = ReadMandatoryNodeInt32Attribute(nodeDef, "shrink_axis_mask");
2941  IConnectableLayer* const layer = m_Network->AddStridedSliceLayer(desc, nodeDef.name().c_str());
2942 
2943  IOutputSlot& prevLayerSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2944  TensorInfo inputTensorInfo = prevLayerSlot.GetTensorInfo();
2945 
2946  TensorInfo outputTensorInfo;
2947  CalculateStridedSliceOutputTensorInfo(inputTensorInfo, desc, outputTensorInfo);
2948 
2949  prevLayerSlot.Connect(layer->GetInputSlot(0));
2950  layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
2951 
2952  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2953 }
2954 
2955 ParsedTfOperationPtr TfParser::ParseTanh(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2956 {
2957  IgnoreUnused(graphDef);
2958 
2959  ActivationDescriptor activationDesc;
2960  activationDesc.m_Function = ActivationFunction::TanH;
2961  activationDesc.m_A = 1.0f;
2962  activationDesc.m_B = 1.0f;
2963 
2964  return AddActivationLayer(nodeDef, activationDesc);
2965 }
2966 
2967 ParsedTfOperationPtr TfParser::AddActivationLayer(const tensorflow::NodeDef& nodeDef,
2968  ActivationDescriptor& activationDesc)
2969 {
2970  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2971 
2972  IConnectableLayer* const layer = m_Network->AddActivationLayer(activationDesc, nodeDef.name().c_str());
2973 
2974  IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2975  prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
2976  layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
2977  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2978 }
2979 
2980 ParsedTfOperationPtr TfParser::ParseMaxPool(const tensorflow::NodeDef& nodeDef,
2981  const tensorflow::GraphDef& graphDef)
2982 {
2983  return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Max);
2984 }
2985 
2986 ParsedTfOperationPtr TfParser::ParseAvgPool(const tensorflow::NodeDef& nodeDef,
2987  const tensorflow::GraphDef& graphDef)
2988 {
2989  return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Average);
2990 }
2991 
2992 ParsedTfOperationPtr TfParser::ParsePooling2d(const tensorflow::NodeDef& nodeDef,
2993  const tensorflow::GraphDef& graphDef, PoolingAlgorithm pooltype)
2994 {
2995  IgnoreUnused(graphDef);
2996 
2997  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
2998  IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2999  TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
3000 
3001  if (inputs.size() != 1)
3002  {
3003  throw ParseException(
3004  boost::str(
3005  boost::format(
3006  "2D Pooling expects one input!. Got %1% for Node %2% %3%")
3007  % inputs.size()
3008  % nodeDef.name()
3009  % CHECK_LOCATION().AsString()));
3010  }
3011 
3012  std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
3013  std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
3014  std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
3015  std::vector<uint32_t> ksize = ReadMandatoryNodeUint32ListAttribute(nodeDef, "ksize"); // size of pool windows
3016 
3017  Pooling2dDescriptor pooling2dDescriptor;
3018  pooling2dDescriptor.m_PoolType = pooltype;
3019  pooling2dDescriptor.m_PaddingMethod = PaddingMethod::Exclude;
3020  pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Floor;
3021 
3022  CHECK_DATA_FORMAT(nodeDef, dataFormat, "Pooling2D");
3023  DataLayout dataLayout = dataFormat == "NHWC" ? DataLayout::NHWC : DataLayout::NCHW;
3024  pooling2dDescriptor.m_DataLayout = dataLayout;
3025  DataLayoutIndexed dataLayoutIndexed(dataLayout);
3026 
3027  pooling2dDescriptor.m_StrideX = strides[dataLayoutIndexed.GetWidthIndex()];
3028  pooling2dDescriptor.m_StrideY = strides[dataLayoutIndexed.GetHeightIndex()];
3029  pooling2dDescriptor.m_PoolWidth = ksize[dataLayoutIndexed.GetWidthIndex()];
3030  pooling2dDescriptor.m_PoolHeight = ksize[dataLayoutIndexed.GetHeightIndex()];
3031 
3032  uint32_t inputHeight = inputTensorInfo.GetShape()[dataLayoutIndexed.GetHeightIndex()];
3033  uint32_t inputWidth = inputTensorInfo.GetShape()[dataLayoutIndexed.GetWidthIndex()];
3034 
3035  bool padding = false;
3036  TensorInfo outputInfo;
3037  unsigned int outputHeight = 0;
3038  unsigned int outputWidth = 0;
3039 
3040  CHECK_PADDING_TYPE(nodeDef, paddingString);
3041 
3042  if (paddingString == "SAME")
3043  {
3044  padding = true;
3045 
3046  outputHeight = static_cast<uint32_t>(ceil(static_cast<float>(inputHeight) /
3047  static_cast<float>(pooling2dDescriptor.m_StrideY)));
3048  outputWidth = static_cast<uint32_t>(ceil(static_cast<float>(inputWidth) /
3049  static_cast<float>(pooling2dDescriptor.m_StrideX)));
3050  }
3051  else if (paddingString == "VALID")
3052  {
3053  padding = false;
3054 
3055  outputHeight = static_cast<uint32_t>(ceil(
3056  static_cast<float>(inputHeight - pooling2dDescriptor.m_PoolHeight + 1) /
3057  static_cast<float>(pooling2dDescriptor.m_StrideY)));
3058  outputWidth = static_cast<uint32_t>(ceil(
3059  static_cast<float>(inputWidth - pooling2dDescriptor.m_PoolWidth + 1) /
3060  static_cast<float>(pooling2dDescriptor.m_StrideX)));
3061  }
3062 
3063  switch (dataLayout)
3064  {
3065  case DataLayout::NHWC:
3066  outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
3067  outputHeight,
3068  outputWidth,
3069  inputTensorInfo.GetShape()[3] },
3070  DataType::Float32);
3071  break;
3072  case DataLayout::NCHW:
3073  outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
3074  inputTensorInfo.GetShape()[1],
3075  outputHeight,
3076  outputWidth },
3077  DataType::Float32);
3078  break;
3079  }
3080 
3081  CalcPadding(inputWidth, pooling2dDescriptor.m_PoolWidth, pooling2dDescriptor.m_StrideX,
3082  pooling2dDescriptor.m_PadLeft, pooling2dDescriptor.m_PadRight, padding);
3083  CalcPadding(inputHeight, pooling2dDescriptor.m_PoolHeight, pooling2dDescriptor.m_StrideY,
3084  pooling2dDescriptor.m_PadTop, pooling2dDescriptor.m_PadBottom, padding);
3085 
3086 
3087  IConnectableLayer* layer = m_Network->AddPooling2dLayer(pooling2dDescriptor, nodeDef.name().c_str());
3088  if (layer == nullptr)
3089  {
3090  throw ParseException(
3091  boost::str(
3092  boost::format(
3093  "Failed to add pooling2d layer for %1% %2%")
3094  % nodeDef.name()
3095  % CHECK_LOCATION().AsString()));
3096  }
3097 
3098  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
3099 
3100  inputSlot.Connect(layer->GetInputSlot(0));
3101 
3102  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
3103 }
3104 
3105 ParsedTfOperationPtr TfParser::AddAdditionLayer(const tensorflow::NodeDef& nodeDef, bool isBiasAdd)
3106 {
3107  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
3108 
3109  IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
3110  IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
3111 
3112  const TensorInfo& input0Info = input0Slot->GetTensorInfo();
3113  const TensorInfo& input1Info = input1Slot->GetTensorInfo();
3114 
3115  if (isBiasAdd)
3116  {
3117  // BiasAdd takes bias as a 1D tensor. We need to add a reshape layer to create a 4D tensor
3118  // with the same data in the correct dimension for broadcast in addition.
3119  if(input1Info.GetNumDimensions() != 1)
3120  {
3121  throw ParseException(
3122  boost::str(
3123  boost::format(
3124  "Unsupported bias for BiasAdd. It should be a 1D vector. "
3125  "Got %1% dimensions for input %2%. Node %3% %4%")
3126  % input1Info.GetNumDimensions()
3127  % inputs[1].m_IndexedValue->GetNode().name()
3128  % nodeDef.name()
3129  % CHECK_LOCATION().AsString()));
3130  }
3131 
3132  const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
3133 
3134  CHECK_DATA_FORMAT(nodeDef, dataFormat, "BiasAdd");
3135  input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, dataFormat == "NHWC", *m_Network, nodeDef);
3136  }
3137  else
3138  {
3139  if (input0Info.GetNumDimensions() == 1)
3140  {
3141  const bool isNHWC = true;
3142  input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
3143  }
3144 
3145  if (input1Info.GetNumDimensions() == 1)
3146  {
3147  const bool isNHWC = true;
3148  input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
3149  }
3150  }
3151 
3152  IConnectableLayer* const layer = m_Network->AddAdditionLayer(nodeDef.name().c_str());
3153 
3154  input0Slot->Connect(layer->GetInputSlot(0));
3155  input1Slot->Connect(layer->GetInputSlot(1));
3156 
3157  if (input0Info.GetNumDimensions() == input1Info.GetNumDimensions())
3158  {
3159  const TensorShape& input0Shape = input0Info.GetShape();
3160  const TensorShape& input1Shape = input1Info.GetShape();
3161 
3162  std::vector<unsigned int> outputShape;
3163  outputShape.reserve(input0Shape.GetNumDimensions());
3164  TensorInfo outputInfo(input0Info);
3165 
3166  for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++)
3167  {
3168  outputShape.push_back(std::max(input0Shape[i], input1Shape[i]));
3169  }
3170 
3171  outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data()));
3172 
3173  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
3174  }
3175  else if (input0Info.GetNumDimensions() == 1 && isBiasAdd == false)
3176  {
3177  layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
3178  }
3179  else
3180  {
3181  layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
3182  }
3183 
3184  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
3185 }
3186 
3187 ParsedTfOperationPtr TfParser::AddRealDivLayer(const tensorflow::NodeDef& nodeDef)
3188 {
3189  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
3190 
3191  IConnectableLayer* const layer = m_Network->AddDivisionLayer(nodeDef.name().c_str());
3192  IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
3193  IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
3194 
3195  auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
3196  auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
3197 
3198 
3199  if (input0NumDims < input1NumDims)
3200  {
3201  const bool isNHWC = true;
3202  input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
3203  }
3204  if (input1NumDims < input0NumDims)
3205  {
3206  const bool isNHWC = true;
3207  input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
3208  }
3209 
3210  input0Slot->Connect(layer->GetInputSlot(0));
3211  input1Slot->Connect(layer->GetInputSlot(1));
3212 
3213  if (input0NumDims < input1NumDims)
3214  {
3215  layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
3216  }
3217  else
3218  {
3219  layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
3220 
3221  }
3222  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
3223 }
3224 
3225 ParsedTfOperationPtr TfParser::AddMaximumLayer(const tensorflow::NodeDef& nodeDef)
3226 {
3227  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
3228 
3229  IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
3230  IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
3231 
3232  auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
3233  auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
3234 
3235  if (input0NumDims < input1NumDims)
3236  {
3237  const bool isNHWC = true;
3238  input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
3239  }
3240  if (input1NumDims < input0NumDims)
3241  {
3242  const bool isNHWC = true;
3243  input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
3244  }
3245 
3246  IConnectableLayer* const layer = m_Network->AddMaximumLayer(nodeDef.name().c_str());
3247 
3248  input0Slot->Connect(layer->GetInputSlot(0));
3249  input1Slot->Connect(layer->GetInputSlot(1));
3250 
3251  TensorInfo outputInfo = input0Slot->GetTensorInfo();
3252  std::vector<unsigned int> outputShape;
3253 
3254  const TensorShape& input0Shape = input0Slot->GetTensorInfo().GetShape();
3255  const TensorShape& input1Shape = input1Slot->GetTensorInfo().GetShape();
3256 
3257  for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++)
3258  {
3259  outputShape.push_back(std::max(input0Shape[i], input1Shape[i]));
3260  }
3261 
3262  outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data()));
3263  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
3264 
3265  return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
3266 }
3267 
3268 IConnectableLayer* TfParser::AddMultiplicationLayer(const tensorflow::NodeDef& nodeDef)
3269 {
3270  std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
3271 
3272  IConnectableLayer* const layer = m_Network->AddMultiplicationLayer(nodeDef.name().c_str());
3273  IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
3274  IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
3275 
3276  auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
3277  auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
3278 
3279  if (input0NumDims < input1NumDims)
3280  {
3281  const bool isNHWC = true;
3282  input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
3283  }
3284  if (input1NumDims < input0NumDims)
3285  {
3286  const bool isNHWC = true;
3287  input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
3288  }
3289 
3290  input0Slot->Connect(layer->GetInputSlot(0));
3291  input1Slot->Connect(layer->GetInputSlot(1));
3292 
3293  if (input0NumDims < input1NumDims)
3294  {
3295  layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
3296  }
3297  else
3298  {
3299  layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
3300  }
3301  return layer;
3302 }
3303 
3304 IConnectableLayer* TfParser::AddFullyConnectedLayer(const tensorflow::NodeDef& matMulNodeDef,
3305  const tensorflow::NodeDef* addNodeDef, const char* armnnLayerName)
3306 {
3307  // Finds bias const (if applicable).
3308  ParsedConstTfOperation<float>* biasNode = nullptr;
3309  if (addNodeDef != nullptr)
3310  {
3311  std::vector<OutputOfParsedTfOperation> addInputs = GetInputParsedTfOperationsChecked(*addNodeDef, 2);
3312  // Finds our inputs.
3313  if (HasParsedConstTensor<float>(addInputs[0].m_IndexedValue->GetNode().name()))
3314  {
3315  biasNode = PolymorphicDowncast<ParsedConstTfOperation<float>*>(addInputs[0].m_IndexedValue);
3316  }
3317  else if (HasParsedConstTensor<float>(addInputs[1].m_IndexedValue->GetNode().name()))
3318  {
3319  biasNode = PolymorphicDowncast<ParsedConstTfOperation<float>*>(addInputs[1].m_IndexedValue);
3320  }
3321  else
3322  {
3323  throw ParseException(
3324  boost::str(
3325  boost::format(
3326  "ArmNN only supports fully connected layers with constant bias. "
3327  "Inputs %1% and %2%. AddNode %3%. MatMulNode %4% %5%")
3328  % addInputs[0].m_IndexedValue->GetNode().name()
3329  % addInputs[1].m_IndexedValue->GetNode().name()
3330  % addNodeDef->name()
3331  % matMulNodeDef.name()
3332  % CHECK_LOCATION().AsString()));
3333  }
3334  }
3335 
3336  // Finds matmul inputs.
3337  ParsedConstTfOperation<float>* weightNode = nullptr;
3338  ParsedTfOperation* inputNode = nullptr;
3339  unsigned int inputIdx = 0;
3340  std::vector<OutputOfParsedTfOperation> mulInputs = GetInputParsedTfOperationsChecked(matMulNodeDef, 2);
3341  if (HasParsedConstTensor<float>(mulInputs[0].m_IndexedValue->GetNode().name()))
3342  {
3343  weightNode = PolymorphicDowncast<ParsedConstTfOperation<float>*>(mulInputs[0].m_IndexedValue);
3344  inputNode = mulInputs[1].m_IndexedValue;
3345  inputIdx = mulInputs[1].m_Index;
3346  }
3347  else if (HasParsedConstTensor<float>(mulInputs[1].m_IndexedValue->GetNode().name()))
3348  {
3349  weightNode = PolymorphicDowncast<ParsedConstTfOperation<float>*>(mulInputs[1].m_IndexedValue);
3350  inputNode = mulInputs[0].m_IndexedValue;
3351  inputIdx = mulInputs[0].m_Index;
3352  }
3353  else
3354  {
3355  throw ParseException(
3356  boost::str(
3357  boost::format(
3358  "ArmNN only supports fully connected layers with constant weights. "
3359  "Inputs %1% and %2%. MatMulNode %3% %4%")
3360  % mulInputs[0].m_IndexedValue->GetNode().name()
3361  % mulInputs[1].m_IndexedValue->GetNode().name()
3362  % matMulNodeDef.name()
3363  % CHECK_LOCATION().AsString()));
3364  }
3365 
3366  std::vector<float> weightTensorData;
3367  // Handles weight.
3368  ConstTensor weights = weightNode->GetConstTensor(weightTensorData);
3369 
3371  desc.m_BiasEnabled = addNodeDef != nullptr;
3372 
3373  IConnectableLayer* layer = nullptr;
3374  Optional<ConstTensor> optionalBiases;
3375  std::vector<float> biasTensorData;
3376  // Makes the layer.
3377  if (addNodeDef != nullptr)
3378  {
3379  ConstTensor biases = biasNode->GetConstTensor(biasTensorData);
3380 
3381  if (weights.GetShape()[1] != biases.GetShape()[0])
3382  {
3383  throw ParseException(
3384  boost::str(
3385  boost::format(
3386  "Shape of matmul weights and bias do not match. "
3387  "AddNode %1%. MatMulNode %2% %3%")
3388  % addNodeDef->name()
3389  % matMulNodeDef.name()
3390  % CHECK_LOCATION().AsString()));
3391  }
3392 
3393  optionalBiases = Optional<ConstTensor>(biases);
3394  }
3395  layer = m_Network->AddFullyConnectedLayer(desc, weights, optionalBiases, armnnLayerName);
3396 
3397  ARMNN_ASSERT(layer != nullptr);
3398 
3399  inputNode->ResolveArmnnOutputSlot(inputIdx).Connect(layer->GetInputSlot(0));
3400  unsigned int batches = inputNode->ResolveArmnnOutputSlot(inputIdx).GetTensorInfo().GetShape()[0];
3401 
3402  // Handles output.
3403  TensorInfo outputInfo({ batches, weights.GetShape()[1] }, DataType::Float32);
3404  layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
3405  return layer;
3406 }
3407 
3408 void TfParser::LoadNodeDef(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
3409 {
3410  // Gets the type of the node (assume float).
3411  tensorflow::DataType type = tensorflow::DT_FLOAT;
3412  if (nodeDef.attr().count("T") != 0)
3413  {
3414  auto attr = nodeDef.attr().at("T");
3415  type = attr.type();
3416  }
3417  else if (nodeDef.attr().count("dtype") != 0)
3418  {
3419  auto attr = nodeDef.attr().at("dtype");
3420  type = attr.type();
3421  }
3422 
3423  if ((type != tensorflow::DT_FLOAT && type != tensorflow::DT_INT32) && nodeDef.op() != "Const")
3424  {
3425  throw ParseException(
3426  boost::str(
3427  boost::format(
3428  "Currently only FLOAT and INT32 are supported for tensorflow nodes (apart from Const). "
3429  "Got %1% for Node %2% %3%")
3430  % tensorflow::DataType_Name(type)
3431  % nodeDef.name()
3432  % CHECK_LOCATION().AsString()));
3433  }
3434 
3435  const std::string& operation = nodeDef.op();
3436  auto itControlInput = std::find(m_ControlInputs.begin(), m_ControlInputs.end(), operation);
3437  if (itControlInput != m_ControlInputs.end())
3438  {
3439  // We currently allow Control Input from TensorFlow graph but we ignore them from ArmNN graph.
3440  return;
3441  }
3442  auto it = ms_OperationNameToParsingFunctions.find(operation);
3443  if (it != ms_OperationNameToParsingFunctions.end())
3444  {
3445  auto func = it->second;
3446  ParsedTfOperationPtr parsedTfOperation = (this->*func)(nodeDef, graphDef);
3447  ParsedTfOperation* parsedTfOperationRaw = parsedTfOperation.get();
3448 
3449  // Stores the parsed operation so that dependent layers can connect to it.
3450  auto it = m_ParsedTfOperations.find(nodeDef.name());
3451  if (it != m_ParsedTfOperations.end())
3452  {
3453  throw ParseException(boost::str(boost::format("Name %1% used by more than one node") % nodeDef.name()));
3454  }
3455  m_ParsedTfOperations[nodeDef.name()] = std::move(parsedTfOperation);
3456 
3457  // If this node was requested as an output from the network, then adds an ArmNN output layer.
3458  if (std::find(m_RequestedOutputs.begin(), m_RequestedOutputs.end(), nodeDef.name()) !=
3459  m_RequestedOutputs.end())
3460  {
3461  auto outId = ParseOutputId(nodeDef.name());
3462  const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkOutputsBindingInfo.size());
3463  IOutputSlot& prevSlot = parsedTfOperationRaw->ResolveArmnnOutputSlot(outId.m_Index);
3464 
3465  TensorInfo tensorInfo = prevSlot.GetTensorInfo();
3466 
3467  IConnectableLayer* outputLayer = m_Network->AddOutputLayer(layerId, nodeDef.name().c_str());
3468 
3469  prevSlot.Connect(outputLayer->GetInputSlot(0));
3470 
3471  TrackOutputBinding(outputLayer, layerId, tensorInfo);
3472  }
3473  }
3474  else
3475  {
3476  throw ParseException(
3477  boost::str(
3478  boost::format(
3479  "Unsupported operation %1% in tensorflow::GraphDef %2%")
3480  % operation
3481  % CHECK_LOCATION().AsString()));
3482  }
3483 }
3484 
3485 void TfParser::LoadGraphDef(const tensorflow::GraphDef& graphDef)
3486 {
3487  // Adds all nodes to our map.
3488  m_NodesByName.clear();
3489  m_NetworkInputsBindingInfo.clear();
3490  m_NetworkOutputsBindingInfo.clear();
3491 
3492  for (int i = 0; i < graphDef.node_size(); ++i)
3493  {
3494  const tensorflow::NodeDef& node = graphDef.node(i);
3495  m_NodesByName[node.name()] = &node;
3496  }
3497 
3498  // Checks that the input nodes the user has requested exist.
3499  for (const auto& pair : m_InputShapes)
3500  {
3501  const std::string& requestedInputName = pair.first;
3502  auto nodeIt = m_NodesByName.find(requestedInputName);
3503  if (nodeIt == m_NodesByName.end())
3504  {
3505  throw ParseException(
3506  boost::str(
3507  boost::format(
3508  "Couldn't find requested input node '%1%' in graph %2%")
3509  % requestedInputName
3510  % CHECK_LOCATION().AsString()));
3511  }
3512  }
3513 
3514  // Finds the output nodes the user requested.
3515  std::vector<const tensorflow::NodeDef*> targetNodes;
3516  for (const std::string& requestedOutputName : m_RequestedOutputs)
3517  {
3518  auto nodeIt = m_NodesByName.find(requestedOutputName);
3519  if (nodeIt == m_NodesByName.end())
3520  {
3521  throw ParseException(
3522  boost::str(
3523  boost::format(
3524  "Couldn't find requested output node '%1%' in graph %2%")
3525  % requestedOutputName
3526  % CHECK_LOCATION().AsString()));
3527  }
3528  targetNodes.push_back(nodeIt->second);
3529  }
3530 
3531  // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
3532  std::vector<const tensorflow::NodeDef*> sortedNodes;
3533  if (!armnnUtils::GraphTopologicalSort<const tensorflow::NodeDef*>(
3534  targetNodes,
3535  [this](const tensorflow::NodeDef* node)
3536  {
3537  auto outputs = GetTfInputNodes(*node);
3538  std::vector<const tensorflow::NodeDef*> nodesOnly;
3539  for (const auto & o : outputs) {
3540  nodesOnly.push_back(o.m_IndexedValue);
3541  }
3542  return nodesOnly;
3543  },
3544  sortedNodes))
3545  {
3546  throw ParseException(
3547  boost::str(
3548  boost::format(
3549  "Cycle detected in graph %1%")
3550  % CHECK_LOCATION().AsString()));
3551  }
3552 
3553  // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
3554  for (const auto& it : sortedNodes)
3555  {
3556  const tensorflow::NodeDef& currentNode = *it;
3557  LoadNodeDef(currentNode, graphDef);
3558  }
3559 }
3560 
3562  const std::map<std::string, TensorShape>& inputShapes,
3563  const std::vector<std::string>& requestedOutputs)
3564 {
3565  FILE* fd = fopen(graphFile, "r");
3566 
3567  if (fd == nullptr)
3568  {
3569  throw FileNotFoundException(
3570  boost::str(
3571  boost::format(
3572  "Graph file %1% failed to open %2%")
3573  % graphFile
3574  % CHECK_LOCATION().AsString()));
3575  }
3576 
3577  // Parses the file into a message.
3578  tensorflow::GraphDef graphDef;
3579  auto input = new google::protobuf::io::FileInputStream(fileno(fd));
3580  bool success = google::protobuf::TextFormat::Parse(input, &graphDef);
3581  delete input;
3582  fclose(fd);
3583 
3584  if (!success)
3585  {
3586  throw ParseException(
3587  boost::str(
3588  boost::format(
3589  "Failed to parse graph file %1%")
3590  % CHECK_LOCATION().AsString()));
3591  }
3592 
3593  return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
3594 }
3595 
3597  const std::map<std::string, TensorShape>& inputShapes,
3598  const std::vector<std::string>& requestedOutputs)
3599 {
3600  // Parses the string into a message.
3601  tensorflow::GraphDef graphDef;
3602  bool success = google::protobuf::TextFormat::ParseFromString(protoText, &graphDef);
3603 
3604  if (!success)
3605  {
3606  throw ParseException(
3607  boost::str(
3608  boost::format(
3609  "Failed to parse graph file %1%")
3610  % CHECK_LOCATION().AsString()));
3611  }
3612 
3613  return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
3614 }
3615 
3617  const std::map<std::string, TensorShape>& inputShapes,
3618  const std::vector<std::string>& requestedOutputs)
3619 {
3620  FILE* fd = fopen(graphFile, "rb");
3621 
3622  if (fd == nullptr)
3623  {
3624  throw FileNotFoundException(
3625  boost::str(
3626  boost::format(
3627  "Graph file %1% failed to open %2%")
3628  % graphFile
3629  % CHECK_LOCATION().AsString()));
3630  }
3631 
3632  // Parses the file into a message.
3633  tensorflow::GraphDef graphDef;
3634 
3635  google::protobuf::io::FileInputStream inStream(fileno(fd));
3636  google::protobuf::io::CodedInputStream codedStream(&inStream);
3637  codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
3638  bool success = graphDef.ParseFromCodedStream(&codedStream);
3639  fclose(fd);
3640 
3641  if (!success)
3642  {
3643  throw ParseException(
3644  boost::str(
3645  boost::format(
3646  "Failed to parse protobuf file %1% %2%")
3647  % graphFile
3648  % CHECK_LOCATION().AsString()));
3649  }
3650 
3651  return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
3652 }
3653 
3654 INetworkPtr TfParser::CreateNetworkFromGraphDef(const tensorflow::GraphDef& graphDef,
3655  const std::map<std::string, TensorShape>& inputShapes,
3656  const std::vector<std::string>& requestedOutputs)
3657 {
3658  m_Network = INetwork::Create();
3659 
3660  m_InputShapes = inputShapes;
3661  if (requestedOutputs.size() == 0)
3662  {
3663  throw ParseException(
3664  boost::str(
3665  boost::format(
3666  "requestedOutputs must have at least one entry %1%")
3667  % CHECK_LOCATION().AsString()));
3668  }
3669  m_RequestedOutputs = requestedOutputs;
3670 
3671  try
3672  {
3673  LoadGraphDef(graphDef);
3674  }
3675  catch (const ParseException& e)
3676  {
3677  Cleanup();
3678  throw e;
3679  }
3680 
3681  Cleanup();
3682 
3683  return std::move(m_Network);
3684 }
3685 
3686 void TfParser::Cleanup()
3687 {
3688  // Cleanup, in case we reuse this parser.
3689  m_InputShapes.clear();
3690  m_RequestedOutputs.clear();
3691  m_NodesByName.clear();
3692  m_ParsedTfOperations.clear();
3693 }
3694 
3696 {
3697  return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
3698 }
3699 
3701 {
3702  return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
3703 }
3704 
3705 std::pair<LayerBindingId, TensorInfo> TfParser::GetBindingInfo(const std::string& layerName,
3706  const char* bindingPointDesc,
3707  const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
3708 {
3709  auto it = nameToBindingInfo.find(layerName);
3710  if (it == nameToBindingInfo.end())
3711  {
3713  boost::str(
3714  boost::format(
3715  "Unknown %1% '%2%' %3%")
3716  % bindingPointDesc
3717  % layerName
3718  % CHECK_LOCATION().AsString()));
3719  }
3720  return it->second;
3721 }
3722 
3723 void TfParser::TrackInputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
3724 {
3725  return TrackBindingPoint(layer, id, tensorInfo, "input", m_NetworkInputsBindingInfo);
3726 }
3727 
3728 void TfParser::TrackOutputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
3729 {
3730  return TrackBindingPoint(layer, id, tensorInfo, "output", m_NetworkOutputsBindingInfo);
3731 }
3732 
3733 void TfParser::TrackBindingPoint(IConnectableLayer* layer,
3734  LayerBindingId id,
3735  const TensorInfo& tensorInfo,
3736  const char* bindingPointDesc,
3737  std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
3738 {
3739  const std::string layerName = layer->GetName();
3740  auto it = nameToBindingInfo.find(layerName);
3741  if (it == nameToBindingInfo.end())
3742  {
3743  nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
3744  }
3745  else
3746  {
3747  throw ParseException(
3748  boost::str(
3749  boost::format(
3750  "Id %1% used by more than one %2% layer %3%")
3751  % id
3752  % bindingPointDesc
3753  % CHECK_LOCATION().AsString()));
3754  }
3755 }
3756 
3757 } // 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:187
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:268
unsigned int GetNumBytes() const
Definition: Tensor.cpp:419
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:2107
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:3700
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:310
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:194
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:189
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:131
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:194
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:298
uint32_t m_TargetWidth
Target width value.
A GatherDescriptor for the GatherLayer.
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:3596
#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:197
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:195
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.
int32_t m_Axis
The axis in params to gather indices from.
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
Function that returns the tensor rank.
Definition: Tensor.cpp:175
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:3561
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:314
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:307
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:3695
DataLayout m_DataLayout
The data layout to be used (NCHW, NHWC).
const armnn::PermutationVector ArmNNToNHWC
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:191
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:3616
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:192
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:2139
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:2464