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