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