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