blob: 52ba92cad59ae7671f36888f475c28115770d839 [file] [log] [blame]
surmeh01bceff2f2018-03-29 16:29:27 +01001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
David Beckecb56cd2018-09-05 12:52:57 +01003// SPDX-License-Identifier: MIT
surmeh01bceff2f2018-03-29 16:29:27 +01004//
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>
Sadik Armagan479045b2018-10-01 11:51:37 +010014#include <ParserHelper.hpp>
surmeh01bceff2f2018-03-29 16:29:27 +010015#include <Permute.hpp>
telsoa01c577f2c2018-08-31 09:22:23 +010016#include <VerificationHelpers.hpp>
surmeh01bceff2f2018-03-29 16:29:27 +010017
18#include <google/protobuf/io/zero_copy_stream_impl.h>
19#include <google/protobuf/text_format.h>
20
21#include "tensorflow/core/framework/graph.pb.h"
22#include "tensorflow/core/framework/node_def.pb.h"
23#include "tensorflow/core/framework/types.pb.h"
24#include "tensorflow/core/framework/tensor.pb.h"
25#include "tensorflow/core/framework/tensor_shape.pb.h"
26
27#include <boost/assert.hpp>
28#include <boost/format.hpp>
29#include <boost/core/ignore_unused.hpp>
30#include <boost/log/trivial.hpp>
31#include <boost/numeric/conversion/cast.hpp>
32#include <boost/polymorphic_cast.hpp>
33
34#include <memory>
35#include <sstream>
36#include <numeric>
37#include <functional>
38
39using namespace armnn;
40
41namespace armnnTfParser
42{
43namespace
44{
45
46const PermutationVector NHWCToArmNN = { 0, 2, 3, 1 };
47const PermutationVector ArmNNToNHWC = { 0, 3, 1, 2 };
48
49IConnectableLayer* AddSwizzleLayer(INetwork& network, IOutputSlot& input, const PermutationVector& mapping,
50 const std::string& name)
51{
telsoa01c577f2c2018-08-31 09:22:23 +010052 // Adds swizzle layer.
surmeh01bceff2f2018-03-29 16:29:27 +010053 IConnectableLayer* const layer = network.AddPermuteLayer(mapping, name.c_str());
54
telsoa01c577f2c2018-08-31 09:22:23 +010055 // Connects intput to swizzle layer.
surmeh01bceff2f2018-03-29 16:29:27 +010056 input.Connect(layer->GetInputSlot(0));
57
telsoa01c577f2c2018-08-31 09:22:23 +010058 // Sets up swizzled output.
surmeh01bceff2f2018-03-29 16:29:27 +010059 const TensorInfo outInfo = armnnUtils::Permuted(input.GetTensorInfo(), mapping);
60 layer->GetOutputSlot(0).SetTensorInfo(outInfo);
61
62 return layer;
63}
64
65IConnectableLayer* SwizzleInDeswizzleOut(INetwork& network, IOutputSlot& input, IConnectableLayer& layer,
66 const std::string& name)
67{
telsoa01c577f2c2018-08-31 09:22:23 +010068 // Adds swizzle layer.
surmeh01bceff2f2018-03-29 16:29:27 +010069 IConnectableLayer* const swizzleLayer = AddSwizzleLayer(network, input, NHWCToArmNN, "swizzle_for-" + name);
70
telsoa01c577f2c2018-08-31 09:22:23 +010071 // Connects swizzledInput to layer.
surmeh01bceff2f2018-03-29 16:29:27 +010072 swizzleLayer->GetOutputSlot(0).Connect(layer.GetInputSlot(0));
73
telsoa01c577f2c2018-08-31 09:22:23 +010074 // Adds deswizzle layer.
surmeh01bceff2f2018-03-29 16:29:27 +010075 IConnectableLayer* const deswizzleLayer = AddSwizzleLayer(network, layer.GetOutputSlot(0), ArmNNToNHWC,
76 "deswizzle_for-" + name);
77
78 return deswizzleLayer;
79}
80
81template <typename Callable>
82void ReadMandatoryNodeAttributeImpl(const tensorflow::NodeDef& nodeDef,
83 const std::string& attribName,
84 tensorflow::AttrValue::ValueCase expectedValueCase,
85 Callable callable)
86{
87 auto iter = nodeDef.attr().find(attribName);
88 if (iter != nodeDef.attr().end())
89 {
90 const auto& attrValue = iter->second;
91 if (attrValue.value_case() == expectedValueCase)
92 {
93 callable(attrValue);
94 }
95 else
96 {
telsoa01c577f2c2018-08-31 09:22:23 +010097 throw ParseException(
98 boost::str(
99 boost::format(
100 "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, "
101 "but found %4% instead %5%")
102 % attribName
103 % nodeDef.name()
104 % static_cast<int>(expectedValueCase)
105 % static_cast<int>(attrValue.value_case())
106 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100107 }
108 }
109 else
110 {
telsoa01c577f2c2018-08-31 09:22:23 +0100111 throw ParseException(
112 boost::str(
113 boost::format(
114 "Could not find required attribute %1% in node %2% %3%")
115 % attribName
116 % nodeDef.name()
117 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100118 }
119}
120
121template <typename Callable>
122void ReadOptionalNodeAttributeImpl(const tensorflow::NodeDef& nodeDef,
123 const std::string& attribName,
124 tensorflow::AttrValue::ValueCase expectedValueCase,
125 Callable callable)
126{
127 auto iter = nodeDef.attr().find(attribName);
128 if (iter != nodeDef.attr().end())
129 {
130 const auto& attrValue = iter->second;
131 if (attrValue.value_case() == expectedValueCase)
132 {
133 callable(attrValue);
134 }
135 else
136 {
telsoa01c577f2c2018-08-31 09:22:23 +0100137 throw ParseException(
138 boost::str(
139 boost::format(
140 "Attribute %1% of node %2% expected to have %3% as tensorflow::AttrValue::ValueCase, "
141 "but found %4% instead %5%")
142 % attribName
143 % nodeDef.name()
144 % static_cast<int>(expectedValueCase)
145 % static_cast<int>(attrValue.value_case())
146 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100147 }
148 }
149}
150
151float ReadMandatoryNodeFloatAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
152{
153 float attribValue = 0.0f;
154 ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kF,
155 [&attribValue](const tensorflow::AttrValue& attrValue)
156 {
157 attribValue = attrValue.f();
158 });
159 return attribValue;
160}
161
162uint32_t ReadMandatoryNodeUint32Attribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
163{
164 uint32_t attribValue = 0u;
165 ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kI,
166 [&attribValue](const tensorflow::AttrValue& attrValue)
167 {
168 attribValue = static_cast<uint32_t>(attrValue.i());
169 });
170 return attribValue;
171}
172
173std::string ReadMandatoryNodeStringAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
174{
175 std::string attribValue = "";
176 ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kS,
177 [&attribValue](const tensorflow::AttrValue& attrValue)
178 {
179 attribValue = attrValue.s();
180 });
181 return attribValue;
182}
183
184std::vector<uint32_t> ReadMandatoryNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef,
185 const std::string& name)
186{
187 std::vector<uint32_t> attriList;
188 ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList,
189 [&attriList](const tensorflow::AttrValue& attrValue)
190 {
191 for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum)
192 {
193 attriList.push_back(static_cast<uint32_t>(attrValue.list().i(attriNum)));
194 }
195 });
196
197 return attriList;
198}
199
200std::vector<uint32_t> ReadOptionalNodeUint32ListAttribute(const tensorflow::NodeDef& nodeDef,
201 const std::string& name)
202{
203 std::vector<uint32_t> attriList;
204 ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kList,
205 [&attriList](const tensorflow::AttrValue& attrValue)
206 {
207 for (int attriNum = 0; attriNum < attrValue.list().i_size(); ++attriNum)
208 {
209 attriList.push_back(static_cast<uint32_t>(attrValue.list().i(attriNum)));
210 }
211 });
212
213 return attriList;
214}
215
216bool ReadOptionalNodeBoolAttribute(const tensorflow::NodeDef& nodeDef,
217 const std::string& name,
218 bool defaultValue = false)
219{
220 bool attribValue = defaultValue;
221 ReadOptionalNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kB,
222 [&attribValue](const tensorflow::AttrValue& attrValue)
223 {
224 attribValue = attrValue.b();
225 });
226 return attribValue;
227}
228
229tensorflow::DataType ReadMandatoryNodeTypeAttribute(const tensorflow::NodeDef& nodeDef, const std::string& name)
230{
231 tensorflow::DataType attribValue = tensorflow::DT_INVALID;
232 ReadMandatoryNodeAttributeImpl(nodeDef, name, tensorflow::AttrValue::kType,
233 [&attribValue](const tensorflow::AttrValue& attrValue)
234 {
235 attribValue = attrValue.type();
236 });
237 return attribValue;
238}
239
240TensorInfo PrepareReshape(const TensorInfo& input, const std::vector<int32_t>& targetDims)
241{
242 std::vector<unsigned int> outDims(targetDims.begin(), targetDims.end());
243 const auto stretchDim = std::find(targetDims.begin(), targetDims.end(), -1);
244
245 if (stretchDim != targetDims.end())
246 {
247 if (std::find(std::next(stretchDim), targetDims.end(), -1) != targetDims.end())
248 {
telsoa01c577f2c2018-08-31 09:22:23 +0100249 throw ParseException(
250 boost::str(
251 boost::format(
252 "At most one component of shape can be -1 %1%")
253 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100254 }
255
telsoa01c577f2c2018-08-31 09:22:23 +0100256 auto targetNumElements =
257 boost::numeric_cast<unsigned int>(
258 std::accumulate(targetDims.begin(), targetDims.end(), -1, std::multiplies<int32_t>()));
surmeh01bceff2f2018-03-29 16:29:27 +0100259 auto stretchIndex = static_cast<size_t>(std::distance(targetDims.begin(), stretchDim));
260 outDims[stretchIndex] = input.GetNumElements() / targetNumElements;
261 }
262
263 TensorInfo reshapeInfo = input;
264 reshapeInfo.SetShape(TensorShape{ static_cast<unsigned int>(outDims.size()), outDims.data() });
265
266 return reshapeInfo;
267}
268
telsoa01c577f2c2018-08-31 09:22:23 +0100269// We need the input0Slot to guide the reshape for input1Slot.
saoste01bbd40612018-08-28 15:41:51 +0100270IOutputSlot* AddBroadcastReshapeLayer(IOutputSlot* input0Slot, IOutputSlot* input1Slot, bool isNHWC,
271 INetwork& m_Network, const tensorflow::NodeDef& nodeDef)
surmeh01bceff2f2018-03-29 16:29:27 +0100272{
273 const TensorInfo& input1Info = input1Slot->GetTensorInfo();
274 const TensorInfo inputTensorInfo = input0Slot->GetTensorInfo();
275 const unsigned int matchDim = inputTensorInfo.GetNumDimensions() - (isNHWC ? 1 : 3);
276 std::array<unsigned int, MaxNumOfTensorDimensions> reshapedDimensions;
277 std::fill_n(reshapedDimensions.begin(), inputTensorInfo.GetNumDimensions(), 1);
278 reshapedDimensions[matchDim] = input1Info.GetShape()[0];
279
280 armnn::TensorInfo reshapedInfo = input1Info;
281 reshapedInfo.SetShape(TensorShape{ inputTensorInfo.GetNumDimensions(), reshapedDimensions.data() });
282
283 const std::string reshapeLayerName = "reshape_for-" + nodeDef.name();
284 ReshapeDescriptor reshapeDesc;
285 reshapeDesc.m_TargetShape = reshapedInfo.GetShape();
286 IConnectableLayer* const reshapeLayer = m_Network.AddReshapeLayer(reshapeDesc, reshapeLayerName.c_str());
287
288 input1Slot->Connect(reshapeLayer->GetInputSlot(0));
289 reshapeLayer->GetOutputSlot(0).SetTensorInfo(reshapedInfo);
290
291 input1Slot = &reshapeLayer->GetOutputSlot(0);
292
293 return input1Slot;
294}
295
296OutputId ParseOutputId(const std::string & name)
297{
298 unsigned int outputNum = 0;
299 size_t colonPos = name.find_last_of(":");
300 if (colonPos != std::string::npos)
301 {
302 int n = std::stoi(name.substr(colonPos+1));
303 if (n<0 || n>100)
304 {
telsoa01c577f2c2018-08-31 09:22:23 +0100305 throw ParseException(
306 boost::str(
307 boost::format(
308 "Output tensor id is out of range for %1% %2%")
309 % name
310 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100311 }
312 outputNum = static_cast<unsigned int>(n);
313 }
314 return OutputId(name.substr(0,colonPos),outputNum);
315}
316
telsoa01c577f2c2018-08-31 09:22:23 +0100317#define CHECK_DATA_FORMAT(NODE_DEF, FORMAT, NODE_TYPE) \
318 if( FORMAT != "NHWC" && FORMAT != "NCHW" ) \
319 { \
320 throw ParseException( \
321 boost::str( \
322 boost::format( \
323 "Unsupported data format %1% passed for %2% node %3%. " \
324 "Only NHWC and NCHW supported %4%") \
325 % FORMAT \
326 % NODE_TYPE \
327 % NODE_DEF.name() \
328 % CHECK_LOCATION().AsString())); \
329 }
330
331#define CHECK_PADDING_TYPE(NODE_DEF, PADDING) \
332 if(PADDING != "SAME" && PADDING != "VALID" ) \
333 { \
334 throw ParseException( \
335 boost::str( \
336 boost::format( \
337 "Only 'SAME' and 'VALID' padding supported. Got %1% for %2% %3%") \
338 % PADDING \
339 % NODE_DEF.name() \
340 % CHECK_LOCATION().AsString())); \
341 } \
342
surmeh01bceff2f2018-03-29 16:29:27 +0100343} // namespace
344
345const std::map<std::string, TfParser::OperationParsingFunction> TfParser::ms_OperationNameToParsingFunctions = {
346 { "Const", &TfParser::ParseConst },
347 { "Add", &TfParser::ParseAdd },
348 { "BiasAdd", &TfParser::ParseBiasAdd },
349 { "Identity", &TfParser::ParseIdentity },
350 { "Conv2D", &TfParser::ParseConv2D },
351 { "DepthwiseConv2dNative", &TfParser::ParseDepthwiseConv2D },
352 { "FusedBatchNorm", &TfParser::ParseFusedBatchNorm },
353 { "ConcatV2", &TfParser::ParseConcat },
354 { "LRN", &TfParser::ParseLrn },
355 { "MatMul", &TfParser::ParseMatMul },
356 { "Mul", &TfParser::ParseMul },
357 { "Placeholder", &TfParser::ParsePlaceholder },
saoste01bbd40612018-08-28 15:41:51 +0100358 { "RealDiv", &TfParser::ParseRealDiv },
surmeh01bceff2f2018-03-29 16:29:27 +0100359 { "Relu", &TfParser::ParseRelu },
360 { "Relu6", &TfParser::ParseRelu6 },
361 { "Reshape", &TfParser::ParseReshape },
362 { "ResizeBilinear", &TfParser::ParseResizeBilinear },
363 { "Shape", &TfParser::ParseShape },
364 { "Squeeze", &TfParser::ParseSqueeze },
365 { "Sigmoid", &TfParser::ParseSigmoid },
366 { "Softmax", &TfParser::ParseSoftmax },
367 { "Softplus", &TfParser::ParseSoftplus },
368 { "Tanh", &TfParser::ParseTanh },
369 { "MaxPool", &TfParser::ParseMaxPool },
370 { "AvgPool", &TfParser::ParseAvgPool },
telsoa01c577f2c2018-08-31 09:22:23 +0100371 { "Maximum", &TfParser::ParseMaximum },
surmeh01bceff2f2018-03-29 16:29:27 +0100372};
373
374ITfParser* ITfParser::CreateRaw()
375{
376 return new TfParser();
377}
378
379ITfParserPtr ITfParser::Create()
380{
381 return ITfParserPtr(CreateRaw(), &ITfParser::Destroy);
382}
383
384void ITfParser::Destroy(ITfParser* parser)
385{
386 delete parser;
387}
388
389inline void CalculateSamePadding(uint32_t inputSize, uint32_t stride,
390 uint32_t filterSize, bool samePadding,
391 uint32_t* paddingFront, uint32_t* paddingBack) {
392 *paddingFront = 0;
393 *paddingBack = 0;
394
395 if (samePadding) {
396 uint32_t outputSize = (inputSize + stride - 1) / stride;
397 uint32_t temp = (outputSize - 1) * stride + filterSize;
398 if (temp > inputSize) {
399 *paddingFront = (temp - inputSize) / 2;
400 *paddingBack = (temp - inputSize) - *paddingFront;
401 }
402 }
403}
404
405void CalcPadding(uint32_t input, uint32_t kernel, uint32_t stride, uint32_t& outPadHead, uint32_t& outPadTail,
406 bool samePadding)
407{
408 CalculateSamePadding(input, stride, kernel, samePadding, &outPadHead, &outPadTail);
409}
410
411/// An Abstract base class which represents a single tensorflow operation (node)
412/// that has been (potentially partially) converted to Armnn.
413/// It may not yet have been fully converted into actual Armnn layers.
414class ParsedTfOperation
415{
416public:
417 ParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
418 : m_Parser(parser)
419 , m_Node(node)
420 {
421 }
422
423 virtual ~ParsedTfOperation() {};
424
425 const tensorflow::NodeDef& GetNode() const { return m_Node; }
426
427 /// Gets the ArmNN IOutputSlot corresponding to the given output index of the Tensorflow operation.
428 /// This may result in the creation of Armnn layers if this was deferred (e.g. see ParsedConstTfOperation).
429 virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) = 0;
430
431 /// If this operation is an Identity then this will follow return the 'parent' operation (recursively).
432 virtual ParsedTfOperation* ResolveIdentityOperations()
433 {
434 return this;
435 }
436
437protected:
438 TfParser* m_Parser;
439 const tensorflow::NodeDef& m_Node;
440};
441
442/// An ParsedTfOperation where the Armnn equivalent is a single layer,
443/// with output slots that correspond directly to the Tf node outputs.
444class SingleLayerParsedTfOperation : public ParsedTfOperation
445{
446public:
447 SingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node, IConnectableLayer* layer)
448 : ParsedTfOperation(parser, node)
449 , m_Layer(layer)
450 {
451 }
452
453 IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
454 {
455 BOOST_ASSERT(m_Layer);
telsoa01c577f2c2018-08-31 09:22:23 +0100456 // Assumes one-to-one mapping between Tf and armnn output slots.
surmeh01bceff2f2018-03-29 16:29:27 +0100457 unsigned int armnnOutputSlotIdx = tfOutputIndex;
458 if (armnnOutputSlotIdx >= m_Layer->GetNumOutputSlots())
459 {
460 throw ParseException(
telsoa01c577f2c2018-08-31 09:22:23 +0100461 boost::str(
462 boost::format(
463 "The requested output slot #%1% "
464 "for %2% does not exist %3%")
465 % armnnOutputSlotIdx
466 % m_Layer->GetName()
467 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100468 }
469 return m_Layer->GetOutputSlot(armnnOutputSlotIdx);
470 }
471
472protected:
473 IConnectableLayer* m_Layer;
474};
475
telsoa01c577f2c2018-08-31 09:22:23 +0100476/// A SingleLayerParsedTfOperation for deferred layer creation.
surmeh01bceff2f2018-03-29 16:29:27 +0100477class DeferredSingleLayerParsedTfOperation : public SingleLayerParsedTfOperation
478{
479public:
480 DeferredSingleLayerParsedTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
481 : SingleLayerParsedTfOperation(parser, node, nullptr)
482 {
483 }
484
485 IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
486 {
487 if (!m_Layer)
488 {
489 CreateLayerDeferred();
490 }
491 return SingleLayerParsedTfOperation::ResolveArmnnOutputSlot(tfOutputIndex);
492 }
493
494private:
495 virtual void CreateLayerDeferred() = 0;
496};
497
498
499TfParser::TfParser()
500 : m_Network(nullptr, nullptr)
501{
502}
503
504
505const tensorflow::NodeDef* TfParser::ResolveIdentityNode(const tensorflow::NodeDef* nodeDef)
506{
507 if (nodeDef->op() != "Identity")
508 {
509 return nodeDef;
510 }
511
512 if (nodeDef->input_size() != 1)
513 {
telsoa01c577f2c2018-08-31 09:22:23 +0100514 throw ParseException(
515 boost::str(
516 boost::format(
517 "Identity node should have a single input! %1% has %2% inputs %3%")
518 % nodeDef->name()
519 % nodeDef->input_size()
520 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100521 }
522
523 auto it = m_NodesByName.find(nodeDef->input(0));
524 if (it != m_NodesByName.end())
525 {
526 const tensorflow::NodeDef* inputNode = it->second;
527 return ResolveIdentityNode(inputNode);
528 }
529 else
530 {
telsoa01c577f2c2018-08-31 09:22:23 +0100531 throw ParseException(
532 boost::str(
533 boost::format(
534 "Cannot find what the Identity node %1% is linked to! %2%")
535 % nodeDef->name()
536 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100537 }
538}
539
540std::vector<OutputOfConstNodeDef>
541TfParser::GetTfInputNodes(const tensorflow::NodeDef& nodeDef) const
542{
543 std::vector<OutputOfConstNodeDef> ret;
544
surmeh013537c2c2018-05-18 16:31:43 +0100545 if (nodeDef.op() == "Const")
546 {
547 // For some reason const node can have "Control Inputs". We ignore them for now.
548 return ret;
549 }
550
surmeh01bceff2f2018-03-29 16:29:27 +0100551 ret.reserve(boost::numeric_cast<size_t>(nodeDef.input_size()));
552 for (int j = 0; j < nodeDef.input_size(); ++j)
553 {
554 OutputId outputId = ParseOutputId(nodeDef.input(j));
surmeh013537c2c2018-05-18 16:31:43 +0100555
556 if (nodeDef.input(j)[0] == '^') // I couldn't find a better test for control inputs.
557 {
558 throw ParseException(
telsoa01c577f2c2018-08-31 09:22:23 +0100559 boost::str(
560 boost::format(
561 "Node '%1%' has Control Input '%2%' for input #%3% which is unsupported. %4%")
562 % nodeDef.name()
563 % nodeDef.input(j)
564 % j
565 % CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100566 }
567
surmeh01bceff2f2018-03-29 16:29:27 +0100568 auto inputIt = m_NodesByName.find(outputId.m_IndexedValue);
569 if (inputIt == m_NodesByName.end())
570 {
571 throw ParseException(
telsoa01c577f2c2018-08-31 09:22:23 +0100572 boost::str(
573 boost::format(
574 "Can't find node '%1%', which is listed as an input of '%2%' %3%")
575 % nodeDef.input(j)
576 % nodeDef.name()
577 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100578 }
579 ret.push_back(OutputOfConstNodeDef(inputIt->second,outputId.m_Index));
580 }
581
582 return ret;
583}
584
585std::vector<OutputOfParsedTfOperation>
586TfParser::GetInputParsedTfOperationsChecked(const tensorflow::NodeDef& nodeDef,
587 std::size_t expectedNumInputs)
588{
telsoa01c577f2c2018-08-31 09:22:23 +0100589 // Fetches the tensorflow nodes connected as inputs and validate the size.
surmeh01bceff2f2018-03-29 16:29:27 +0100590 std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
591 const std::size_t numInputs = nodes.size();
592 if (numInputs != expectedNumInputs)
593 {
telsoa01c577f2c2018-08-31 09:22:23 +0100594 throw ParseException(
595 boost::str(
596 boost::format(
597 "Unexpected number of inputs for node %1%. Expected %2%, found %3% %4%")
598 % nodeDef.name()
599 % expectedNumInputs
600 % numInputs
601 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100602 }
telsoa01c577f2c2018-08-31 09:22:23 +0100603 // Fetches the corresponding ParsedTfOperation operations
surmeh01bceff2f2018-03-29 16:29:27 +0100604 std::vector<OutputOfParsedTfOperation> result;
605 for (auto&& node : nodes)
606 {
607 auto it = m_ParsedTfOperations.find(node.m_IndexedValue->name());
608 if (it == m_ParsedTfOperations.end())
609 {
telsoa01c577f2c2018-08-31 09:22:23 +0100610 throw ParseException(
611 boost::str(
612 boost::format(
613 "Node with name '%1%' has not been parsed %2%")
614 % node.m_IndexedValue->name()
615 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100616 }
617 ParsedTfOperation* parsedOp = it->second.get();
618 // Transparently 'skip' any Identity operations. This simplifies the logic inside the ParseXXX() functions.
619 parsedOp = parsedOp->ResolveIdentityOperations();
620 result.push_back(OutputOfParsedTfOperation(parsedOp,node.m_Index));
621 }
622 return result;
623}
624
625ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
626{
627 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
628
telsoa01c577f2c2018-08-31 09:22:23 +0100629 // If one of the inputs is a MatMul and the other is a const, then we handle both nodes
630 // together as FullyConnected.
surmeh01bceff2f2018-03-29 16:29:27 +0100631 if (inputs[0].m_IndexedValue->GetNode().op() == "MatMul" &&
632 HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
633 {
634 IConnectableLayer* layer =
635 AddFullyConnectedLayer(inputs[0].m_IndexedValue->GetNode(),
636 &nodeDef,nodeDef.name().c_str());
637 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
638 }
639 else if (HasParsedConstTensor<float>(inputs[0].m_IndexedValue->GetNode().name()) &&
640 inputs[1].m_IndexedValue->GetNode().op() == "MatMul")
641 {
642 IConnectableLayer* layer =
643 AddFullyConnectedLayer(inputs[1].m_IndexedValue->GetNode(),
644 &nodeDef,nodeDef.name().c_str());
645 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
646 }
647 else
648 {
telsoa01c577f2c2018-08-31 09:22:23 +0100649 // Otherwise it's just a regular addition.
surmeh01bceff2f2018-03-29 16:29:27 +0100650 return AddAdditionLayer(nodeDef);
651 }
652}
653
654ParsedTfOperationPtr TfParser::ParseBiasAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
655{
656 return AddAdditionLayer(nodeDef, true);
657}
658
659/// An ParsedTfOperation which forwards to another (used for Identity nodes).
660class ParsedIdentityTfOperation : public ParsedTfOperation
661{
662public:
663 ParsedIdentityTfOperation(TfParser* parser, const tensorflow::NodeDef& node, ParsedTfOperation* representative)
664 : ParsedTfOperation(parser, node)
665 , m_Representative(representative)
666 {
667 }
668
669 virtual IOutputSlot& ResolveArmnnOutputSlot(unsigned int tfOutputIndex) override
670 {
671 BOOST_ASSERT(m_Representative);
672 return m_Representative->ResolveArmnnOutputSlot(tfOutputIndex);
673 }
674
675 virtual ParsedTfOperation* ResolveIdentityOperations() override
676 {
677 return m_Representative->ResolveIdentityOperations();
678 }
679
680private:
681 ParsedTfOperation* m_Representative;
682};
683
684ParsedTfOperationPtr TfParser::ParseIdentity(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
685{
686 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
687 // Any requests for the output slots of this node should be forwarded to the node connected as input.
688 return std::make_unique<ParsedIdentityTfOperation>(this, nodeDef, inputs[0].m_IndexedValue);
689}
690
691/// An ParsedTfOperation for a Const node.
692/// Creation of the armnn ConstLayer is deferred until it is actually needed, because Const nodes are mostly used
693/// for weight inputs to MatMul/Conv2D nodes and in these cases armnn doesn't need a ConstLayer.
694template <typename T>
695class ParsedConstTfOperation : public DeferredSingleLayerParsedTfOperation
696{
697public:
698 ParsedConstTfOperation(TfParser* parser, const tensorflow::NodeDef& node,
699 const T* tensorData, const TensorInfo& tensorInfo)
700 : DeferredSingleLayerParsedTfOperation(parser, node),
701 m_Storage(tensorData, tensorData + tensorInfo.GetNumElements()),
702 m_TensorInfo(tensorInfo)
703 {
704 BOOST_ASSERT(tensorInfo.GetDataType() == GetDataType<T>());
705 }
706
707 void CreateLayerDeferred() override
708 {
709 BOOST_ASSERT(m_Layer == nullptr);
710 m_Layer = m_Parser->m_Network->AddConstantLayer(ConstTensor(m_TensorInfo, m_Storage), m_Node.name().c_str());
711 m_Layer->GetOutputSlot(0).SetTensorInfo(m_TensorInfo);
712 }
713
714 ConstTensor GetConstTensor(bool swizzleForConvolutionWeights, std::vector<T>& outputTensorData) const
715 {
716 // Mappings from TensorFlow filter tensors to the ArmNN filter tensors.
telsoa01c577f2c2018-08-31 09:22:23 +0100717 // Tensorflow weights are [H, W, In, Out].
718 // ArmNN weights are [Out, In, H, W].
surmeh01bceff2f2018-03-29 16:29:27 +0100719 static const PermutationVector HWIOToOIHW = {2, 3, 1, 0};
720
721 const TensorInfo outInfo = swizzleForConvolutionWeights
722 ? armnnUtils::Permuted(m_TensorInfo, HWIOToOIHW)
723 : m_TensorInfo;
724
725 outputTensorData.resize(m_TensorInfo.GetNumElements());
726
telsoa01c577f2c2018-08-31 09:22:23 +0100727 // Copies or swizzles from the permanent storage into the storage the caller provided.
surmeh01bceff2f2018-03-29 16:29:27 +0100728 if (swizzleForConvolutionWeights)
729 {
730 armnnUtils::Permute(outInfo.GetShape(), HWIOToOIHW, m_Storage.data(), outputTensorData.data());
731 }
732 else
733 {
734 memcpy(outputTensorData.data(), m_Storage.data(), m_TensorInfo.GetNumBytes());
735 }
telsoa01c577f2c2018-08-31 09:22:23 +0100736 // Updates the result to point to the user provided storage.
surmeh01bceff2f2018-03-29 16:29:27 +0100737 ConstTensor constTensor(outInfo, outputTensorData);
738 return constTensor;
739 }
740
741private:
742 ///< Manages the lifetime of the tensor data.
743 std::vector<T> m_Storage;
744 ///< Describes the layout of the tensor and points to the data in m_Storage.
745 TensorInfo m_TensorInfo;
746};
747
telsoa01c577f2c2018-08-31 09:22:23 +0100748DataType ConvertTfTensorDataType(const tensorflow::DataType tfDataType,
749 const tensorflow::NodeDef& nodeDef)
surmeh01bceff2f2018-03-29 16:29:27 +0100750{
751 switch (tfDataType)
752 {
753 case tensorflow::DT_FLOAT:
754 return DataType::Float32;
755 break;
756 case tensorflow::DT_INT32:
757 return DataType::Signed32;
758 break;
759 default:
telsoa01c577f2c2018-08-31 09:22:23 +0100760 throw ParseException(
761 boost::str(
762 boost::format(
763 "Unknown DataType %1% for node %2% %3%")
764 % tensorflow::DataType_Name(tfDataType)
765 % nodeDef.name()
766 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100767 }
768}
769
770struct ParseTfTensorValueList
771{
772 template<typename DataType>
773 static void Parse(
774 const tensorflow::TensorProto& tfTensor,
775 unsigned int dstElements,
776 std::vector<int8_t>& outputData);
777
778 template <typename DataType>
779 static void ReadData(const void* srcData, unsigned int numSrcElements,
780 std::vector<int8_t>& dstData, unsigned int numDstElements)
781 {
telsoa01c577f2c2018-08-31 09:22:23 +0100782 // If there are no entries in the list, perform no action.
surmeh01bceff2f2018-03-29 16:29:27 +0100783 if (numSrcElements == 0)
784 {
785 return;
786 }
787
telsoa01c577f2c2018-08-31 09:22:23 +0100788 // If no size was provided, use the length of the value list.
surmeh01bceff2f2018-03-29 16:29:27 +0100789 if (numDstElements == 0)
790 {
791 numDstElements = numSrcElements;
792 }
793
telsoa01c577f2c2018-08-31 09:22:23 +0100794 // Allocates memory.
surmeh01bceff2f2018-03-29 16:29:27 +0100795 dstData.resize(std::max(numSrcElements, numDstElements) * sizeof(DataType));
796
797 const DataType* srcTensor = reinterpret_cast<const DataType*>(srcData);
798 DataType* dstTensor = reinterpret_cast<DataType*>(dstData.data());
799
telsoa01c577f2c2018-08-31 09:22:23 +0100800 // Copies the value list entries into the destination.
surmeh01bceff2f2018-03-29 16:29:27 +0100801 std::copy(srcTensor, srcTensor + numSrcElements, dstTensor);
802
803 if (numDstElements > numSrcElements)
804 {
telsoa01c577f2c2018-08-31 09:22:23 +0100805 // Uses the last element in the list to fill the remaining entries.
surmeh01bceff2f2018-03-29 16:29:27 +0100806 std::fill(dstTensor + numSrcElements, dstTensor + numDstElements, srcTensor[numSrcElements - 1]);
807 }
808 }
809
810};
811
812template <>
813void ParseTfTensorValueList::Parse<float>(const tensorflow::TensorProto& tfTensor,
814 unsigned int dstElements, std::vector<int8_t>& outputData)
815{
816 ReadData<float>(tfTensor.float_val().data(), static_cast<unsigned int>(tfTensor.float_val_size()),
817 outputData, dstElements);
818}
819
820template <>
821void ParseTfTensorValueList::Parse<int32_t>(const tensorflow::TensorProto& tfTensor,
822 unsigned int dstElements, std::vector<int8_t>& outputData)
823{
824 ReadData<int32_t>(tfTensor.int_val().data(), static_cast<unsigned int>(tfTensor.int_val_size()),
825 outputData, dstElements);
826}
827
828template <template<typename> class OperatorType, typename T = int8_t>
829struct MakeTfOperation
830{
831 template<typename DataType, class... Args>
832 inline static std::unique_ptr<OperatorType<DataType>> Parse(TfParser* parser, const tensorflow::NodeDef& node,
833 Args&&... args)
834 {
835 return std::make_unique<OperatorType<DataType>>(parser, node, std::forward<Args>(args)...);
836 }
837};
838
839template <>
840struct MakeTfOperation<ParsedConstTfOperation>
841{
842 template<typename DataType, class... Args>
843 inline static std::unique_ptr<ParsedConstTfOperation<DataType>> Parse(TfParser* parser,
844 const tensorflow::NodeDef& node, const std::vector<int8_t>& tensorData, const TensorInfo& tensorInfo)
845 {
846 return std::make_unique<ParsedConstTfOperation<DataType>>(parser, node,
847 reinterpret_cast<const DataType*>(tensorData.data()), tensorInfo);
848 }
849};
850
851template <class FuncType>
852struct InvokeParseFunction
853{
854 template<class ResType, class... Args>
855 inline static ResType Result(DataType dataType, Args&&... args)
856 {
857 if (dataType == DataType::Float32)
858 {
859 return FuncType::template Parse<float>(std::forward<Args>(args)...);
860 }
861 else if (dataType == DataType::Signed32)
862 {
863 return FuncType::template Parse<int32_t>(std::forward<Args>(args)...);
864 }
865
866 return ResType();
867 }
868
869 template<class... Args>
870 inline static void Result(DataType dataType, Args&&... args)
871 {
872 if (dataType == DataType::Float32)
873 {
874 FuncType::template Parse<float>(std::forward<Args>(args)...);
875 }
876 else if (dataType == DataType::Signed32)
877 {
878 FuncType::template Parse<int32_t>(std::forward<Args>(args)...);
879 }
880 }
881};
882
883ParsedTfOperationPtr TfParser::ParseConst(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
884{
885 BOOST_ASSERT(nodeDef.op() == "Const");
886
887 if (nodeDef.attr().count("value") == 0)
888 {
telsoa01c577f2c2018-08-31 09:22:23 +0100889 throw ParseException(
890 boost::str(
891 boost::format(
892 "Value not found for Const node - %1% %2%")
893 % nodeDef.name()
894 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100895 }
896
897 const tensorflow::TensorProto& tfTensor = nodeDef.attr().at("value").tensor();
898 const tensorflow::TensorShapeProto& tfTensorShape = tfTensor.tensor_shape();
899 const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "dtype");
900
901 const auto GetDimensionSize = [](auto& d) { return d.size(); };
902
903 std::vector<unsigned int> dimensionSizes;
904 std::transform(tfTensorShape.dim().begin(), tfTensorShape.dim().end(),
905 std::back_inserter(dimensionSizes), GetDimensionSize);
906
telsoa01c577f2c2018-08-31 09:22:23 +0100907 // Calculates number of elements.
908 const DataType dataType = ConvertTfTensorDataType(tfDataType, nodeDef);
surmeh01bceff2f2018-03-29 16:29:27 +0100909 unsigned int numElements = 0U;
910
911 if (!dimensionSizes.empty())
912 {
913 numElements = std::accumulate(dimensionSizes.begin(), dimensionSizes.end(),
914 1U, std::multiplies<unsigned int>());
915 }
916
917 std::vector<int8_t> tensorData;
918
telsoa01c577f2c2018-08-31 09:22:23 +0100919 // Get tensor data from the list of values attribute.
surmeh01bceff2f2018-03-29 16:29:27 +0100920 if (tfTensor.tensor_content().empty())
921 {
922 InvokeParseFunction<ParseTfTensorValueList>::Result<void>(dataType, tfTensor, numElements, tensorData);
923
924 // If the tensor shape is not defined, but there is a value list, then interpret the data as a 1D
telsoa01c577f2c2018-08-31 09:22:23 +0100925 // tensor of the provided number of elements.
surmeh01bceff2f2018-03-29 16:29:27 +0100926 if (numElements == 0)
927 {
telsoa01c577f2c2018-08-31 09:22:23 +0100928 const unsigned int tfNumElements =
929 static_cast<unsigned int>(tensorData.size()) / GetDataTypeSize(dataType);
surmeh01bceff2f2018-03-29 16:29:27 +0100930 dimensionSizes.push_back(tfNumElements);
931 }
932 }
telsoa01c577f2c2018-08-31 09:22:23 +0100933 // Gets tensor data from tensor content attribute.
surmeh01bceff2f2018-03-29 16:29:27 +0100934 else
935 {
936 tensorData.assign(tfTensor.tensor_content().begin(), tfTensor.tensor_content().end());
937
telsoa01c577f2c2018-08-31 09:22:23 +0100938 // Checks if a tensor shape is defined for the tensor content.
surmeh01bceff2f2018-03-29 16:29:27 +0100939 if (numElements == 0)
940 {
telsoa01c577f2c2018-08-31 09:22:23 +0100941 throw ParseException(
942 boost::str(
943 boost::format(
944 "No tensor shape found for Const node - %1% %2%")
945 % nodeDef.name()
946 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100947 }
948 }
949
telsoa01c577f2c2018-08-31 09:22:23 +0100950 // Const node requires at least a list of values or a content attribute.
surmeh01bceff2f2018-03-29 16:29:27 +0100951 if (tensorData.empty())
952 {
telsoa01c577f2c2018-08-31 09:22:23 +0100953 throw ParseException(
954 boost::str(
955 boost::format(
956 "No tensor data found for Const node - %1% %2%")
957 % nodeDef.name()
958 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100959 }
960
telsoa01c577f2c2018-08-31 09:22:23 +0100961 const TensorInfo tensorInfo(static_cast<unsigned int>(dimensionSizes.size()),
962 dimensionSizes.data(),
963 dataType);
surmeh01bceff2f2018-03-29 16:29:27 +0100964
965 // If we have a list of values, then the length of the list must be
telsoa01c577f2c2018-08-31 09:22:23 +0100966 // less than or equal to the number of elements implied by the shape argument.
surmeh01bceff2f2018-03-29 16:29:27 +0100967 if (tensorData.size() > tensorInfo.GetNumBytes())
968 {
telsoa01c577f2c2018-08-31 09:22:23 +0100969 throw ParseException(
970 boost::str(
971 boost::format(
972 "Number of elements (%1%) should be less than or equal "
973 "to the number of elements implied by the shape argument (%2%) for Const node - %3% %4%")
974 % (tensorData.size() / GetDataTypeSize(dataType))
975 % tensorInfo.GetNumElements()
976 % nodeDef.name()
977 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +0100978 }
979
980 return InvokeParseFunction<MakeTfOperation<ParsedConstTfOperation>>::Result<ParsedTfOperationPtr>(
981 dataType, this, nodeDef, tensorData, tensorInfo);
982}
983
984template<typename Type>
985bool TfParser::HasParsedConstTensor(const std::string & nodeName) const
986{
987 auto it = m_ParsedTfOperations.find(nodeName);
988 if (it == m_ParsedTfOperations.end() ||
989 dynamic_cast<ParsedConstTfOperation<Type>*>(it->second.get()) == nullptr)
990 {
991 return false;
992 }
993 else
994 {
995 return true;
996 }
997}
998
999ParsedTfOperationPtr TfParser::ParseConv2D(const tensorflow::NodeDef& nodeDef,
1000 const tensorflow::GraphDef& graphDef)
1001{
1002 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1003 IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1004 TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1005
1006 if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1007 {
telsoa01c577f2c2018-08-31 09:22:23 +01001008 throw ParseException(
1009 boost::str(
1010 boost::format(
1011 "ArmNN only supports Convolution layers with constant weights for %1%, input %2% %3%")
1012 % nodeDef.name()
1013 % inputs[1].m_IndexedValue->GetNode().name()
1014 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001015 }
1016 ParsedConstTfOperation<float>* weightNode =
1017 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1018
1019 std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1020 std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1021 std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1022
telsoa01c577f2c2018-08-31 09:22:23 +01001023 // Read the dilations, if present - only [1,1,1,1] (the default) is supported.
surmeh01bceff2f2018-03-29 16:29:27 +01001024 std::vector<uint32_t> dilations = ReadOptionalNodeUint32ListAttribute(nodeDef, "dilations");
1025 if (!dilations.empty())
1026 {
1027 for (auto dilation : dilations)
1028 {
1029 if (dilation != 1u)
1030 {
telsoa01c577f2c2018-08-31 09:22:23 +01001031 throw ParseException(
1032 boost::str(
1033 boost::format(
1034 "ArmNN only supports Convolution layers with dilations [1,1,1,1] for %1% %2%")
1035 % nodeDef.name()
1036 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001037 }
1038 }
1039 }
1040
1041 Convolution2dDescriptor desc;
1042 desc.m_BiasEnabled = false;
1043
telsoa01c577f2c2018-08-31 09:22:23 +01001044 CHECK_DATA_FORMAT(nodeDef, dataFormat, "Conv2D");
1045
surmeh01bceff2f2018-03-29 16:29:27 +01001046 if (dataFormat == "NHWC")
1047 {
1048 desc.m_StrideX = strides[2];
1049 desc.m_StrideY = strides[1];
telsoa01c577f2c2018-08-31 09:22:23 +01001050 // Swizzles input to supported memory layout.
surmeh01bceff2f2018-03-29 16:29:27 +01001051 inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
1052 }
1053 else if (dataFormat == "NCHW")
1054 {
1055 desc.m_StrideX = strides[3];
1056 desc.m_StrideY = strides[2];
1057 }
surmeh01bceff2f2018-03-29 16:29:27 +01001058
1059 uint32_t inputHeight = inputTensorInfo.GetShape()[2];
1060 uint32_t inputWidth = inputTensorInfo.GetShape()[3];
1061
1062 std::vector<float> outputTensorData;
1063
1064 ConstTensor weightTensor = weightNode->GetConstTensor(true, outputTensorData);
1065
1066 uint32_t weightHeight = weightTensor.GetShape()[2];
1067 uint32_t weightWidth = weightTensor.GetShape()[3];
1068
1069 bool padding = false;
1070 TensorInfo outputInfo;
telsoa01c577f2c2018-08-31 09:22:23 +01001071
1072 CHECK_PADDING_TYPE(nodeDef, paddingString);
1073
surmeh01bceff2f2018-03-29 16:29:27 +01001074 if (paddingString == "SAME")
1075 {
1076 padding = true;
1077 outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1078 weightTensor.GetShape()[0],
1079 static_cast<uint32_t>(ceil(
1080 static_cast<float>(inputHeight) /
1081 static_cast<float>(desc.m_StrideY))),
1082 static_cast<uint32_t>(ceil(
1083 static_cast<float>(inputWidth) /
1084 static_cast<float>(desc.m_StrideX)))
1085 }, DataType::Float32);
1086 }
1087 else if (paddingString == "VALID")
1088 {
1089 padding = false;
1090 outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1091 weightTensor.GetShape()[0],
1092 static_cast<uint32_t>(ceil(
1093 static_cast<float>(inputHeight - weightHeight + 1) /
1094 static_cast<float>(desc.m_StrideY))),
1095 static_cast<uint32_t>(ceil(
1096 static_cast<float>(inputWidth - weightWidth + 1) /
1097 static_cast<float>(desc.m_StrideX)))
1098 }, DataType::Float32);
1099 }
surmeh01bceff2f2018-03-29 16:29:27 +01001100
1101 CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding);
1102 CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding);
1103
1104 IConnectableLayer* layer = m_Network->AddConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str());
1105 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1106
1107 if (dataFormat == "NHWC")
1108 {
1109 layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1110 }
1111 else
1112 {
1113 inputSlot.Connect(layer->GetInputSlot(0));
1114 }
1115
1116 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1117}
1118
1119ParsedTfOperationPtr TfParser::ParseDepthwiseConv2D(const tensorflow::NodeDef& nodeDef,
telsoa01c577f2c2018-08-31 09:22:23 +01001120 const tensorflow::GraphDef& graphDef)
surmeh01bceff2f2018-03-29 16:29:27 +01001121{
1122 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1123 IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1124 TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1125
1126 if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1127 {
telsoa01c577f2c2018-08-31 09:22:23 +01001128 throw ParseException(
1129 boost::str(
1130 boost::format(
1131 "ArmNN only supports Depthwise Convolution layer with constant weights. "
1132 "Non const input found %1% for node %2% %3%")
1133 % inputs[1].m_IndexedValue->GetNode().name()
1134 % nodeDef.name()
1135 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001136 }
1137 ParsedConstTfOperation<float>* weightNode =
1138 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1139
1140
1141 std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1142 std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1143 std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1144
1145 DepthwiseConvolution2dDescriptor desc;
1146 desc.m_BiasEnabled = false;
1147
telsoa01c577f2c2018-08-31 09:22:23 +01001148 CHECK_DATA_FORMAT(nodeDef, dataFormat, "DepthwiseConv2dNative");
1149
surmeh01bceff2f2018-03-29 16:29:27 +01001150 if (dataFormat == "NHWC")
1151 {
1152 desc.m_StrideX = strides[2];
1153 desc.m_StrideY = strides[1];
telsoa01c577f2c2018-08-31 09:22:23 +01001154 // Swizzles input to supported memory layout.
surmeh01bceff2f2018-03-29 16:29:27 +01001155 inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
1156 }
1157 else if (dataFormat == "NCHW")
1158 {
1159 desc.m_StrideX = strides[3];
1160 desc.m_StrideY = strides[2];
1161 }
surmeh01bceff2f2018-03-29 16:29:27 +01001162
1163 uint32_t inputHeight = inputTensorInfo.GetShape()[2];
1164 uint32_t inputWidth = inputTensorInfo.GetShape()[3];
1165
1166 std::vector<float> outputTensorData;
1167
1168 ConstTensor weightTensor = weightNode->GetConstTensor(true, outputTensorData);
1169
1170 uint32_t weightHeight = weightTensor.GetShape()[2];
1171 uint32_t weightWidth = weightTensor.GetShape()[3];
1172
1173 bool padding = false;
1174 TensorInfo outputInfo;
telsoa01c577f2c2018-08-31 09:22:23 +01001175
1176 CHECK_PADDING_TYPE(nodeDef, paddingString);
1177
surmeh01bceff2f2018-03-29 16:29:27 +01001178 if (paddingString == "SAME")
1179 {
1180 padding = true;
1181 outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1182 weightTensor.GetShape()[0] * weightTensor.GetShape()[1],
1183 static_cast<uint32_t>(ceil(
1184 static_cast<float>(inputHeight) /
1185 static_cast<float>(desc.m_StrideY))),
1186 static_cast<uint32_t>(ceil(
1187 static_cast<float>(inputWidth) /
1188 static_cast<float>(desc.m_StrideX)))
1189 }, DataType::Float32);
1190 }
1191 else if (paddingString == "VALID")
1192 {
1193 padding = false;
1194 outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
1195 weightTensor.GetShape()[0] * weightTensor.GetShape()[1],
1196 static_cast<uint32_t>(ceil(
1197 static_cast<float>(inputHeight - weightHeight + 1) /
1198 static_cast<float>(desc.m_StrideY))),
1199 static_cast<uint32_t>(ceil(
1200 static_cast<float>(inputWidth - weightWidth + 1) /
1201 static_cast<float>(desc.m_StrideX)))
1202 }, DataType::Float32);
1203 }
surmeh01bceff2f2018-03-29 16:29:27 +01001204
1205 CalcPadding(inputHeight, weightHeight, desc.m_StrideY, desc.m_PadTop, desc.m_PadBottom, padding);
1206 CalcPadding(inputWidth, weightWidth, desc.m_StrideX, desc.m_PadLeft, desc.m_PadRight, padding);
1207
1208 IConnectableLayer* layer = m_Network->AddDepthwiseConvolution2dLayer(desc, weightTensor, nodeDef.name().c_str());
1209 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1210
1211 if (dataFormat == "NHWC")
1212 {
1213 layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1214 }
1215 else
1216 {
1217 inputSlot.Connect(layer->GetInputSlot(0));
1218 }
1219
1220 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1221}
1222
1223ParsedTfOperationPtr TfParser::ParseFusedBatchNorm(const tensorflow::NodeDef& nodeDef,
1224 const tensorflow::GraphDef& graphDef)
1225{
1226 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 5);
1227
1228 if (!HasParsedConstTensor<float>(inputs[1].m_IndexedValue->GetNode().name()))
1229 {
telsoa01c577f2c2018-08-31 09:22:23 +01001230 throw ParseException(
1231 boost::str(
1232 boost::format(
1233 "ArmNN only supports FusedBatchNormalization layers with constant scale. "
1234 "Input %1%. Node %2% %3%")
1235 % inputs[1].m_IndexedValue->GetNode().name()
1236 % nodeDef.name()
1237 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001238 }
1239 ParsedConstTfOperation<float>* scaleNode =
1240 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[1].m_IndexedValue);
1241
1242 if (!HasParsedConstTensor<float>(inputs[2].m_IndexedValue->GetNode().name()))
1243 {
telsoa01c577f2c2018-08-31 09:22:23 +01001244 throw ParseException(
1245 boost::str(
1246 boost::format(
1247 "ArmNN only supports FusedBatchNormalization layers with constant offset. "
1248 "Input %1%. Node %2% %3%")
1249 % inputs[2].m_IndexedValue->GetNode().name()
1250 % nodeDef.name()
1251 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001252 }
1253 ParsedConstTfOperation<float>* offsetNode =
1254 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[2].m_IndexedValue);
1255
1256 if (!HasParsedConstTensor<float>(inputs[3].m_IndexedValue->GetNode().name()))
1257 {
telsoa01c577f2c2018-08-31 09:22:23 +01001258 throw ParseException(
1259 boost::str(
1260 boost::format(
1261 "ArmNN only supports FusedBatchNormalization layers with constant mean. "
1262 "Input %1%. Node %2% %3%")
1263 % inputs[3].m_IndexedValue->GetNode().name()
1264 % nodeDef.name()
1265 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001266 }
1267 ParsedConstTfOperation<float>* meanNode =
1268 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[3].m_IndexedValue);
1269
1270 if (!HasParsedConstTensor<float>(inputs[4].m_IndexedValue->GetNode().name()))
1271 {
telsoa01c577f2c2018-08-31 09:22:23 +01001272 throw ParseException(
1273 boost::str(
1274 boost::format(
1275 "ArmNN only supports FusedBatchNormalization layers with constant variance. "
1276 "Input %1%. Node %2% %3%")
1277 % inputs[4].m_IndexedValue->GetNode().name()
1278 % nodeDef.name()
1279 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001280 }
1281 ParsedConstTfOperation<float>* varianceNode =
1282 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(inputs[4].m_IndexedValue);
1283
telsoa01c577f2c2018-08-31 09:22:23 +01001284 // The descriptor only has the epsilon attribute.
surmeh01bceff2f2018-03-29 16:29:27 +01001285 BatchNormalizationDescriptor desc;
1286 desc.m_Eps = ReadMandatoryNodeFloatAttribute(nodeDef, "epsilon");
1287
telsoa01c577f2c2018-08-31 09:22:23 +01001288 // Data for the parsed tensor args (scale, offset, mean, variance) must be stored
1289 // locally until the layer is added.
surmeh01bceff2f2018-03-29 16:29:27 +01001290 std::vector<float> scaleTensorData;
1291 ConstTensor scaleTensor = scaleNode->GetConstTensor(false, scaleTensorData);
1292
1293 std::vector<float> offsetTensorData;
1294 ConstTensor offsetTensor = offsetNode->GetConstTensor(false, offsetTensorData);
1295
1296 std::vector<float> meanTensorData;
1297 ConstTensor meanTensor = meanNode->GetConstTensor(false, meanTensorData);
1298
1299 std::vector<float> varianceTensorData;
1300 ConstTensor varianceTensor = varianceNode->GetConstTensor(false, varianceTensorData);
1301
1302 IConnectableLayer* layer = m_Network->AddBatchNormalizationLayer(desc,
1303 meanTensor,
1304 varianceTensor,
1305 offsetTensor,
1306 scaleTensor,
1307 nodeDef.name().c_str());
1308
1309 IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1310
1311 const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1312
1313 if (dataFormat == "NHWC")
1314 {
1315 const TensorInfo outputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
1316 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1317 layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1318 }
1319 else
1320 {
1321 layer->GetOutputSlot(0).SetTensorInfo(inputSlot.GetTensorInfo());
1322 inputSlot.Connect(layer->GetInputSlot(0));
1323 }
1324
1325 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1326}
1327
telsoa01c577f2c2018-08-31 09:22:23 +01001328bool TfParser::IsSupportedLeakyReluPattern(const tensorflow::NodeDef& mulNodeDef,
1329 size_t alphaLayerIndex,
1330 const OutputOfParsedTfOperation& otherOp,
1331 armnn::IOutputSlot** outputOfLeakyRelu,
1332 armnn::ActivationDescriptor & desc)
1333{
1334 const tensorflow::NodeDef& otherNodeDef = otherOp.m_IndexedValue->GetNode();
1335
1336 // Verifying all these assumptions hold:
1337 //
1338 // 1, the mulNodeDef is an elementwise multiplication node "Mul"
1339 // 2, the alphaLayerIndex selects a constant node from the inputs of the "Mul" node
1340 // 3, the inputLayerIndex selects a layer which has the same name as otherNodeDef
1341 //
1342
1343 if (mulNodeDef.op() == "Mul")
1344 {
1345 size_t otherLayerIndex = (alphaLayerIndex == 0 ? 1 : 0);
1346 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(mulNodeDef, 2);
1347
1348 BOOST_ASSERT(inputs.size() == 2);
1349 BOOST_ASSERT((otherLayerIndex == 0 || alphaLayerIndex == 0));
1350 BOOST_ASSERT((otherLayerIndex == 1 || alphaLayerIndex == 1));
1351 BOOST_ASSERT(((otherLayerIndex + alphaLayerIndex) == 1));
1352
1353 if (inputs[otherLayerIndex].m_IndexedValue->GetNode().name() == otherNodeDef.name())
1354 {
1355 if (HasParsedConstTensor<float>(inputs[alphaLayerIndex].m_IndexedValue->GetNode().name()))
1356 {
1357 ParsedConstTfOperation<float>* alpha =
1358 boost::polymorphic_downcast<ParsedConstTfOperation<float> *>(
1359 inputs[alphaLayerIndex].m_IndexedValue);
1360
1361 std::vector<float> const_data;
1362 ConstTensor const_tensor = alpha->GetConstTensor(false, const_data);
1363
1364 if (const_data.size() == 1)
1365 {
1366 desc.m_Function = ActivationFunction::LeakyReLu;
1367 desc.m_A = const_data[0];
1368
1369 *outputOfLeakyRelu = &(otherOp.m_IndexedValue->ResolveArmnnOutputSlot(otherOp.m_Index));
1370 return true;
1371 }
1372 }
1373 }
1374 }
1375 return false;
1376}
1377
1378// For max nodes, we only support those as part of a leaky relu, i.e.,
1379// as part for a max(mul(a, x), x) expression. We thus need to
1380// identify one input as a multiplication with a scalar constant,
1381// extract the constant and the two inputs, verify that the two other
1382// inputs are the same node, and then create a leaky relu node.
1383
1384ParsedTfOperationPtr TfParser::ParseMaximum(const tensorflow::NodeDef& nodeDef,
1385 const tensorflow::GraphDef& graphDef)
1386{
1387 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1388 auto inputNode0 = inputs[0].m_IndexedValue->GetNode();
1389 auto inputNode1 = inputs[1].m_IndexedValue->GetNode();
1390 IOutputSlot* outputOfLeakyRelu = nullptr;
1391
1392 ActivationDescriptor desc;
1393
1394 // There are four possible scenarios we need to support (respectively below):
1395 // 1, max(mul(a, x), x)
1396 // 2, max(mul(x, a), x)
1397 // 3, max(x, mul(a, x))
1398 // 4, max(x, mul(x, a))
1399
1400 if (IsSupportedLeakyReluPattern(inputNode0, 0, inputs[1], &outputOfLeakyRelu, desc) ||
1401 IsSupportedLeakyReluPattern(inputNode0, 1, inputs[1], &outputOfLeakyRelu, desc) ||
1402 IsSupportedLeakyReluPattern(inputNode1, 0, inputs[0], &outputOfLeakyRelu, desc) ||
1403 IsSupportedLeakyReluPattern(inputNode1, 1, inputs[0], &outputOfLeakyRelu, desc))
1404 {
1405 BOOST_ASSERT(outputOfLeakyRelu != nullptr);
1406
1407 IConnectableLayer* const layer = m_Network->AddActivationLayer(desc, nodeDef.name().c_str());
1408 outputOfLeakyRelu->Connect(layer->GetInputSlot(0));
1409 layer->GetOutputSlot(0).SetTensorInfo(outputOfLeakyRelu->GetTensorInfo());
1410 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1411 }
1412 else
1413 {
1414 throw ParseException(
1415 boost::str(
1416 boost::format(
1417 "ArmNN currenly offers limited support for Maximum node when it can be fused to "
1418 "form a LeakyRelu activation as leakyrelu=max(mul(alpha, X), X). "
1419 "Node: %1% %2%")
1420 % nodeDef.name()
1421 % CHECK_LOCATION().AsString()));
1422 }
1423}
1424
surmeh01bceff2f2018-03-29 16:29:27 +01001425ParsedTfOperationPtr TfParser::ParseConcat(const tensorflow::NodeDef& nodeDef,
1426 const tensorflow::GraphDef& graphDef)
1427{
1428 std::vector<OutputOfConstNodeDef> nodes = GetTfInputNodes(nodeDef);
telsoa01c577f2c2018-08-31 09:22:23 +01001429 // In tensorflow, we have the last input of the Concat layer as the axis for concatenation.
surmeh01bceff2f2018-03-29 16:29:27 +01001430 unsigned int numInputs = static_cast<unsigned int>(nodes.size());
1431 unsigned int numConcatView = numInputs - 1;
1432
1433 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numConcatView), MaxNumOfTensorDimensions);
1434 std::vector<unsigned int>mergeDimSizes(MaxNumOfTensorDimensions, 0u);
1435
1436 unsigned int mergeDim = 0;
1437 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numInputs);
1438
telsoa01c577f2c2018-08-31 09:22:23 +01001439 // The last input is the axis for concatenation.
surmeh01bceff2f2018-03-29 16:29:27 +01001440 if (!HasParsedConstTensor<int32_t>(inputs[numInputs - 1].m_IndexedValue->GetNode().name()))
1441 {
telsoa01c577f2c2018-08-31 09:22:23 +01001442 throw ParseException(
1443 boost::str(
1444 boost::format(
1445 "ArmNN only supports Concat with constant axis. "
1446 "Input %1%. Node %2% %3%")
1447 % inputs[numInputs - 1].m_IndexedValue->GetNode().name()
1448 % nodeDef.name()
1449 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001450 }
1451 ParsedConstTfOperation<int32_t>* shapeNode =
1452 boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[numInputs - 1].m_IndexedValue);
1453
1454 std::vector<int32_t> axisTensorData;
1455 ConstTensor axisTensor = shapeNode->GetConstTensor(false, axisTensorData);
1456
telsoa01c577f2c2018-08-31 09:22:23 +01001457 // This concatDim indicates the data format: 3 is the NHWC, 1 is the NCHW.
surmeh01bceff2f2018-03-29 16:29:27 +01001458 const unsigned int concatDimInput = static_cast<unsigned int>(axisTensorData[0]);
1459
telsoa01c577f2c2018-08-31 09:22:23 +01001460 // Armnn supports concatenation along the channel dimension for data formats NHWC and NCHW.
surmeh01bceff2f2018-03-29 16:29:27 +01001461 if (concatDimInput == 0 || concatDimInput == 2)
1462 {
telsoa01c577f2c2018-08-31 09:22:23 +01001463 throw ParseException(
1464 boost::str(
1465 boost::format(
1466 "Dimension %1% for concatenation is not supported by Armnn. "
1467 "Node %2% %3%")
1468 % concatDimInput
1469 % nodeDef.name()
1470 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001471 }
1472
telsoa01c577f2c2018-08-31 09:22:23 +01001473 // This is the only concatDim we support in armnn.
surmeh01bceff2f2018-03-29 16:29:27 +01001474 const unsigned int concatDim = 1;
1475 for (unsigned int viewIndex = 0; viewIndex < numConcatView; ++viewIndex)
1476 {
telsoa01c577f2c2018-08-31 09:22:23 +01001477 // Need to double check whether it should be
surmeh01bceff2f2018-03-29 16:29:27 +01001478 IOutputSlot& inputSlot =
1479 inputs[viewIndex].m_IndexedValue->ResolveArmnnOutputSlot(inputs[viewIndex].m_Index);
1480 TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1481
Sadik Armagan479045b2018-10-01 11:51:37 +01001482 // process the input tensor info
1483 armnnUtils::ProcessConcatInputTensorInfo(inputTensorInfo, concatDescriptor,
1484 concatDimInput, viewIndex, mergeDimSizes, mergeDim);
surmeh01bceff2f2018-03-29 16:29:27 +01001485 }
1486
1487 mergeDimSizes[concatDim] = mergeDim;
1488 armnn::IConnectableLayer *layer = m_Network->AddMergerLayer(concatDescriptor, nodeDef.name().c_str());
1489
1490 layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(MaxNumOfTensorDimensions, mergeDimSizes.data(),
1491 DataType::Float32));
1492
1493 for (unsigned int v = 0; v < numConcatView; ++v)
1494 {
1495 IOutputSlot& inputSlot = inputs[v].m_IndexedValue->ResolveArmnnOutputSlot(inputs[v].m_Index);
1496 if (concatDimInput == 3)
1497 {
1498 IConnectableLayer* const swizzleLayer = AddSwizzleLayer(*m_Network, inputSlot, NHWCToArmNN,
1499 "swizzle_for-" + nodeDef.name());
1500 swizzleLayer->GetOutputSlot(0).Connect(layer->GetInputSlot(v));
1501 }
1502 else
1503 {
1504 inputSlot.Connect(layer->GetInputSlot(v));
1505 }
1506 }
1507
1508 if (concatDimInput == 3)
1509 {
1510 IConnectableLayer* const deswizzleLayer = AddSwizzleLayer(*m_Network, layer->GetOutputSlot(0), ArmNNToNHWC,
1511 "deswizzle_for-" + nodeDef.name());
1512 layer = deswizzleLayer;
1513 }
1514
1515 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1516}
1517
1518ParsedTfOperationPtr TfParser::ParseShape(const tensorflow::NodeDef& nodeDef,
1519 const tensorflow::GraphDef& graphDef)
1520{
telsoa01c577f2c2018-08-31 09:22:23 +01001521 // Note: the Shape layer is handled in a special way, because:
1522 // 1. ARMNN doesn't support int32 tensors which it outputs.
1523 // 2. ARMNN works with statically shaped tensors which are known at parse time.
surmeh01bceff2f2018-03-29 16:29:27 +01001524 // 3. because of 1. and 2. we treat the output of Shape as a temporary const int32
telsoa01c577f2c2018-08-31 09:22:23 +01001525 // tensor which may be used as an input to other ops, most likely a Reshape.
surmeh01bceff2f2018-03-29 16:29:27 +01001526
1527 const tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "out_type");
1528 if (tfDataType != tensorflow::DT_INT32)
1529 {
telsoa01c577f2c2018-08-31 09:22:23 +01001530 throw ParseException(
1531 boost::str(
1532 boost::format(
1533 "Armnn only supports DT_INT32 as out_type. Got %1% for Node %2% %3%")
1534 % tensorflow::DataType_Name(tfDataType)
1535 % nodeDef.name()
1536 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001537 }
1538
1539 const std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1540 IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1541 const TensorInfo& prevLayerTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1542 unsigned int prevLayerDimensions = prevLayerTensorInfo.GetNumDimensions();
1543
1544 std::vector<int32_t> shapeTensorData;
1545 shapeTensorData.reserve(prevLayerDimensions);
1546
1547 for (unsigned int i=0; i<prevLayerDimensions; ++i)
1548 {
1549 shapeTensorData.push_back(static_cast<int32_t>(prevLayerTensorInfo.GetShape()[i]));
1550 }
1551
1552 TensorInfo shapeTensorInfo(1, &prevLayerDimensions, DataType::Signed32);
1553
1554 return std::make_unique<ParsedConstTfOperation<int32_t>>(this,
1555 nodeDef,
1556 &shapeTensorData[0],
1557 shapeTensorInfo);
1558}
1559
1560ParsedTfOperationPtr TfParser::ParseReshape(const tensorflow::NodeDef& nodeDef,
1561 const tensorflow::GraphDef& graphDef)
1562{
1563 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1564 ParsedTfOperation* inputNode = inputs[0].m_IndexedValue;
1565
1566 if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
1567 {
telsoa01c577f2c2018-08-31 09:22:23 +01001568 throw ParseException(
1569 boost::str(
1570 boost::format(
1571 "ArmNN only supports Reshape layers with constant shapes. "
1572 "Input %1% Node %2% %3%")
1573 % inputs[1].m_IndexedValue->GetNode().name()
1574 % nodeDef.name()
1575 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001576 }
1577 ParsedConstTfOperation<int32_t>* shapeNode =
1578 boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
1579
1580 armnn::IOutputSlot& prevLayerOutputSlot = inputNode->ResolveArmnnOutputSlot(inputs[0].m_Index);
1581 TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1582
1583 std::vector<int32_t> shapeTensorData;
1584 ConstTensor shapeTensor = shapeNode->GetConstTensor(false, shapeTensorData);
1585 const TensorInfo outputTensorInfo = PrepareReshape(inputTensorInfo, shapeTensorData);
1586
1587 TensorShape targetShape = outputTensorInfo.GetShape();
1588 ReshapeDescriptor reshapeDesc;
1589 reshapeDesc.m_TargetShape = targetShape;
1590
1591 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
1592 prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1593 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1594
1595 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1596}
1597
1598ParsedTfOperationPtr TfParser::ParseResizeBilinear(const tensorflow::NodeDef& nodeDef,
1599 const tensorflow::GraphDef& graphDef)
1600{
1601 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
1602
1603 if (!HasParsedConstTensor<int32_t>(inputs[1].m_IndexedValue->GetNode().name()))
1604 {
telsoa01c577f2c2018-08-31 09:22:23 +01001605 throw ParseException(
1606 boost::str(
1607 boost::format(
1608 "ArmNN only supports ResizeBilinear layers with constant sizes. "
1609 "Input %1%. Node %2% %3%")
1610 % inputs[1].m_IndexedValue->GetNode().name()
1611 % nodeDef.name()
1612 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001613 }
1614 ParsedConstTfOperation<int32_t>* sizeNode =
1615 boost::polymorphic_downcast<ParsedConstTfOperation<int32_t>*>(inputs[1].m_IndexedValue);
1616
telsoa01c577f2c2018-08-31 09:22:23 +01001617 // Checks the align_corners attribute is not set.
surmeh01bceff2f2018-03-29 16:29:27 +01001618 if (ReadOptionalNodeBoolAttribute(nodeDef, "align_corners", false))
1619 {
telsoa01c577f2c2018-08-31 09:22:23 +01001620 throw ParseException(
1621 boost::str(
1622 boost::format(
1623 "ArmNN only supports ResizeBilinear layers with align_corners set to false. "
1624 "Node %1% %2%")
1625 % nodeDef.name()
1626 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001627 }
1628
telsoa01c577f2c2018-08-31 09:22:23 +01001629 // Data for the parsed tensor args (size) must be stored locally.
surmeh01bceff2f2018-03-29 16:29:27 +01001630 std::vector<int32_t> sizeTensorData;
1631 ConstTensor sizeTensor = sizeNode->GetConstTensor(false, sizeTensorData);
1632
telsoa01c577f2c2018-08-31 09:22:23 +01001633 // The descriptor only has target height and width attributes, which we get from the size tensor.
surmeh01bceff2f2018-03-29 16:29:27 +01001634 ResizeBilinearDescriptor desc;
1635 desc.m_TargetHeight = static_cast<uint32_t> (sizeTensorData[0]);
1636 desc.m_TargetWidth = static_cast<uint32_t> (sizeTensorData[1]);
1637
1638 IConnectableLayer* layer = m_Network->AddResizeBilinearLayer(desc, nodeDef.name().c_str());
1639
1640 IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1641 TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001642 // The input shape is always in BHWC format, this will be swizzled below; for now,
1643 // get the batch and channels to make up the ArmNN output shape with the target size.
surmeh01bceff2f2018-03-29 16:29:27 +01001644 unsigned int outBatch = inputTensorInfo.GetShape()[0];
1645 unsigned int outChannels = inputTensorInfo.GetShape()[3];
1646 unsigned int outHeight = desc.m_TargetHeight;
1647 unsigned int outWidth = desc.m_TargetWidth;
1648 TensorShape outShape({outBatch, outChannels, outHeight, outWidth});
telsoa01c577f2c2018-08-31 09:22:23 +01001649 // The output DataType is always Float32, regardless of the input DataType.
surmeh01bceff2f2018-03-29 16:29:27 +01001650 const TensorInfo outputTensorInfo(outShape, armnn::DataType::Float32);
1651 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
1652
telsoa01c577f2c2018-08-31 09:22:23 +01001653 // TensorFlow ResizeBilinear input is always in BHWC format, so add swizzle and deswizzle layers.
surmeh01bceff2f2018-03-29 16:29:27 +01001654 layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
1655
1656 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1657}
1658
1659TensorInfo OutputShapeOfSqueeze(const tensorflow::NodeDef& nodeDef, TensorInfo inputTensorInfo)
1660{
1661 BOOST_ASSERT(nodeDef.op() == "Squeeze");
1662 tensorflow::DataType tfDataType = ReadMandatoryNodeTypeAttribute(nodeDef, "T");
1663
1664 DataType type;
1665 if (tfDataType == tensorflow::DT_FLOAT)
1666 {
1667 type = DataType::Float32;
1668 }
1669 else if (tfDataType == tensorflow::DT_INT32)
1670 {
1671 type = DataType::Signed32;
1672 }
1673 else
1674 {
telsoa01c577f2c2018-08-31 09:22:23 +01001675 throw ParseException(
1676 boost::str(
1677 boost::format("Unsupported DataType %1% for Squeeze operation %2% %3%")
1678 % tensorflow::DataType_Name(tfDataType)
1679 % nodeDef.name()
1680 % CHECK_LOCATION().AsString()));
1681 }
1682
1683
1684 if (inputTensorInfo.GetNumDimensions() > 4)
1685 {
1686 throw ParseException(
1687 boost::str(
1688 boost::format(
1689 "Unsupported number of dimensions: %1% for input shape for Squeeze %2% %3%")
1690 % inputTensorInfo.GetNumDimensions()
1691 % nodeDef.name()
1692 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001693 }
1694
1695 std::vector<uint32_t> squeezeDims = ReadOptionalNodeUint32ListAttribute(nodeDef, "squeeze_dims");
telsoa01c577f2c2018-08-31 09:22:23 +01001696 static const uint32_t dimensionSequence[] = { 0, 1, 2, 3 };
1697
surmeh01bceff2f2018-03-29 16:29:27 +01001698 if (squeezeDims.empty())
1699 {
telsoa01c577f2c2018-08-31 09:22:23 +01001700 squeezeDims.assign(dimensionSequence,
1701 dimensionSequence+inputTensorInfo.GetNumDimensions());
surmeh01bceff2f2018-03-29 16:29:27 +01001702 }
1703
1704 std::vector<uint32_t> outputDims;
1705 for(unsigned int i = 0; i < inputTensorInfo.GetNumDimensions(); i++)
1706 {
telsoa01c577f2c2018-08-31 09:22:23 +01001707 bool skipSqueeze = (std::find(squeezeDims.begin(), squeezeDims.end(), i) == squeezeDims.end());
1708 auto currentDimension = inputTensorInfo.GetShape()[i];
1709 if (skipSqueeze || currentDimension != 1)
surmeh01bceff2f2018-03-29 16:29:27 +01001710 {
telsoa01c577f2c2018-08-31 09:22:23 +01001711 outputDims.push_back(currentDimension);
surmeh01bceff2f2018-03-29 16:29:27 +01001712 }
1713 }
1714
1715 if (outputDims.size() > 4)
1716 {
telsoa01c577f2c2018-08-31 09:22:23 +01001717 throw ParseException(
1718 boost::str(
1719 boost::format(
1720 "Unsupported number of dimensions: %1% for output shape for Squeeze %2% %3%")
1721 % outputDims.size()
1722 % nodeDef.name()
1723 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001724 }
1725
telsoa01c577f2c2018-08-31 09:22:23 +01001726 TensorShape outShape = TensorShape(static_cast<unsigned int>(outputDims.size()),
1727 outputDims.data());
1728
1729 TensorInfo outTensorInfo = inputTensorInfo;
1730 outTensorInfo.SetShape(outShape);
1731 outTensorInfo.SetDataType(type);
surmeh01bceff2f2018-03-29 16:29:27 +01001732
1733 return outTensorInfo;
1734}
1735
1736ParsedTfOperationPtr TfParser::ParseSqueeze(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1737{
1738 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1739
1740 IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1741 TensorInfo inputTensorInfo = prevLayerOutputSlot.GetTensorInfo();
1742
1743 TensorInfo outputInfo;
1744 outputInfo = OutputShapeOfSqueeze(nodeDef, inputTensorInfo);
1745
1746 ReshapeDescriptor reshapeDesc;
1747 reshapeDesc.m_TargetShape = outputInfo.GetShape();
1748 IConnectableLayer* layer = m_Network->AddReshapeLayer(reshapeDesc, nodeDef.name().c_str());
1749 prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1750 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1751
1752 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1753}
1754
1755ParsedTfOperationPtr TfParser::ParseLrn(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1756{
1757 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1758
1759 NormalizationDescriptor normalizationDescriptor;
1760 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1761 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1762 normalizationDescriptor.m_Alpha = ReadMandatoryNodeFloatAttribute(nodeDef, "alpha");
1763 normalizationDescriptor.m_Beta = ReadMandatoryNodeFloatAttribute(nodeDef, "beta");
1764 normalizationDescriptor.m_K = ReadMandatoryNodeFloatAttribute(nodeDef, "bias");
1765 normalizationDescriptor.m_NormSize = ReadMandatoryNodeUint32Attribute(nodeDef, "depth_radius");
1766
1767 // The window size must be an odd value. For a window size of (2 * n + 1), TensorFlow defines depth_radius = n.
1768 normalizationDescriptor.m_NormSize = normalizationDescriptor.m_NormSize * 2 + 1;
1769
1770 IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1771
1772 IConnectableLayer* layer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1773 nodeDef.name().c_str());
1774
1775 const TensorInfo permutedInfo = armnnUtils::Permuted(prevLayerOutputSlot.GetTensorInfo(), NHWCToArmNN);
1776 layer->GetOutputSlot(0).SetTensorInfo(permutedInfo);
1777
1778 layer = SwizzleInDeswizzleOut(*m_Network, prevLayerOutputSlot, *layer, nodeDef.name());
1779
1780 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1781}
1782
1783/// An ParsedTfOperation for a MatMul node.
telsoa01c577f2c2018-08-31 09:22:23 +01001784/// Creation of the armnn FullyConnected layer is deferred until it is actually needed, because
1785/// MatMul nodes are often used for the first part of a biased FullyConnected (MatMul followed
1786/// by Add) and in these cases armnn doesn't need a separate layer for the MatMul.
1787///
surmeh01bceff2f2018-03-29 16:29:27 +01001788class ParsedMatMulTfOperation : public DeferredSingleLayerParsedTfOperation
1789{
1790public:
1791 ParsedMatMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
1792 : DeferredSingleLayerParsedTfOperation(parser, node)
1793 {
1794 }
1795
1796 void CreateLayerDeferred() override
1797 {
1798 BOOST_ASSERT(m_Layer == nullptr);
1799 m_Layer = m_Parser->AddFullyConnectedLayer(m_Node, nullptr, m_Node.name().c_str());
1800 }
1801};
1802
1803ParsedTfOperationPtr TfParser::ParseMatMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1804{
telsoa01c577f2c2018-08-31 09:22:23 +01001805 // Defers the creation of the layer (see ParsedMatMulTfOperation).
surmeh01bceff2f2018-03-29 16:29:27 +01001806 return std::make_unique<ParsedMatMulTfOperation>(this, nodeDef);
1807}
1808
telsoa01c577f2c2018-08-31 09:22:23 +01001809/// An ParsedTfOperation for a Mul node.
1810/// Creation of the armnn Mul layer is deferred until it is actually needed, because Mul nodes
1811/// are also used for the first part of a leaky relu activation function (Mul followed by Maximum)
1812/// and in these cases armnn doesn't need a separate layer for the Mul.
1813///
1814class ParsedMulTfOperation : public DeferredSingleLayerParsedTfOperation
1815{
1816public:
1817 ParsedMulTfOperation(TfParser* parser, const tensorflow::NodeDef& node)
1818 : DeferredSingleLayerParsedTfOperation(parser, node)
1819 {
1820 }
1821
1822 void CreateLayerDeferred() override
1823 {
1824 BOOST_ASSERT(m_Layer == nullptr);
1825 m_Layer = m_Parser->AddMultiplicationLayer(m_Node);
1826 }
1827};
1828
surmeh01bceff2f2018-03-29 16:29:27 +01001829ParsedTfOperationPtr TfParser::ParseMul(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1830{
1831 boost::ignore_unused(graphDef);
1832
telsoa01c577f2c2018-08-31 09:22:23 +01001833 return std::make_unique<ParsedMulTfOperation>(this, nodeDef);
surmeh01bceff2f2018-03-29 16:29:27 +01001834}
1835
1836ParsedTfOperationPtr TfParser::ParsePlaceholder(const tensorflow::NodeDef& nodeDef,
1837 const tensorflow::GraphDef& graphDef)
1838{
1839 boost::ignore_unused(graphDef);
1840
1841 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 0);
1842
1843 const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkInputsBindingInfo.size());
1844
1845 auto it = m_InputShapes.find(nodeDef.name());
1846 if (it == m_InputShapes.end())
1847 {
telsoa01c577f2c2018-08-31 09:22:23 +01001848 throw ParseException(
1849 boost::str(
1850 boost::format(
1851 "Missing input shape for Placeholder '%1%' %2%")
1852 % nodeDef.name()
1853 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001854 }
1855 TensorInfo tensorInfo(it->second, DataType::Float32);
1856
1857 IConnectableLayer* const layer = m_Network->AddInputLayer(layerId, nodeDef.name().c_str());
1858
1859 layer->GetOutputSlot(0).SetTensorInfo(tensorInfo);
1860
1861 TrackInputBinding(layer, layerId, tensorInfo);
1862
1863 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1864}
1865
saoste01bbd40612018-08-28 15:41:51 +01001866ParsedTfOperationPtr TfParser::ParseRealDiv(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1867{
1868 boost::ignore_unused(graphDef);
1869 return AddRealDivLayer(nodeDef);
1870}
1871
surmeh01bceff2f2018-03-29 16:29:27 +01001872ParsedTfOperationPtr TfParser::ParseRelu(const tensorflow::NodeDef& nodeDef,
1873 const tensorflow::GraphDef& graphDef)
1874{
1875 boost::ignore_unused(graphDef);
1876
1877 ActivationDescriptor activationDesc;
1878 activationDesc.m_Function = ActivationFunction::ReLu;
1879 return AddActivationLayer(nodeDef, activationDesc);
1880}
1881
1882ParsedTfOperationPtr TfParser::ParseRelu6(const tensorflow::NodeDef& nodeDef,
1883 const tensorflow::GraphDef& graphDef)
1884{
1885 boost::ignore_unused(graphDef);
1886
1887 ActivationDescriptor activationDesc;
1888 activationDesc.m_Function = ActivationFunction::BoundedReLu;
1889 activationDesc.m_A = 6.0f;
1890 activationDesc.m_B = 0.0f;
1891
1892 return AddActivationLayer(nodeDef, activationDesc);
1893}
1894
1895ParsedTfOperationPtr TfParser::ParseSigmoid(const tensorflow::NodeDef& nodeDef,
1896 const tensorflow::GraphDef& graphDef)
1897{
1898 boost::ignore_unused(graphDef);
1899
1900 ActivationDescriptor activationDesc;
1901 activationDesc.m_Function = ActivationFunction::Sigmoid;
1902
1903 return AddActivationLayer(nodeDef, activationDesc);
1904}
1905
1906ParsedTfOperationPtr TfParser::ParseSoftmax(const tensorflow::NodeDef& nodeDef,
1907 const tensorflow::GraphDef& graphDef)
1908{
1909 boost::ignore_unused(graphDef);
1910
1911 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1912
1913 SoftmaxDescriptor softmaxDescriptor;
1914 IConnectableLayer* const layer = m_Network->AddSoftmaxLayer(softmaxDescriptor, nodeDef.name().c_str());
1915
1916 IOutputSlot& prevLayerSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1917 prevLayerSlot.Connect(layer->GetInputSlot(0));
1918 layer->GetOutputSlot(0).SetTensorInfo(prevLayerSlot.GetTensorInfo());
1919
1920 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1921}
1922
1923ParsedTfOperationPtr TfParser::ParseSoftplus(const tensorflow::NodeDef& nodeDef,
1924 const tensorflow::GraphDef& graphDef)
1925{
1926 boost::ignore_unused(graphDef);
1927
1928 ActivationDescriptor activationDesc;
1929 activationDesc.m_Function = ActivationFunction::SoftReLu;
1930
1931 return AddActivationLayer(nodeDef, activationDesc);
1932}
1933
1934ParsedTfOperationPtr TfParser::ParseTanh(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
1935{
1936 boost::ignore_unused(graphDef);
1937
1938 ActivationDescriptor activationDesc;
1939 activationDesc.m_Function = ActivationFunction::TanH;
1940 activationDesc.m_A = 1.0f;
1941 activationDesc.m_B = 1.0f;
1942
1943 return AddActivationLayer(nodeDef, activationDesc);
1944}
1945
1946ParsedTfOperationPtr TfParser::AddActivationLayer(const tensorflow::NodeDef& nodeDef,
1947 ActivationDescriptor& activationDesc)
1948{
1949 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1950
1951 IConnectableLayer* const layer = m_Network->AddActivationLayer(activationDesc, nodeDef.name().c_str());
1952
1953 IOutputSlot& prevLayerOutputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1954 prevLayerOutputSlot.Connect(layer->GetInputSlot(0));
1955 layer->GetOutputSlot(0).SetTensorInfo(prevLayerOutputSlot.GetTensorInfo());
1956 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
1957}
1958
1959ParsedTfOperationPtr TfParser::ParseMaxPool(const tensorflow::NodeDef& nodeDef,
1960 const tensorflow::GraphDef& graphDef)
1961{
1962 return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Max);
1963}
1964
1965ParsedTfOperationPtr TfParser::ParseAvgPool(const tensorflow::NodeDef& nodeDef,
1966 const tensorflow::GraphDef& graphDef)
1967{
1968 return ParsePooling2d(nodeDef, graphDef, PoolingAlgorithm::Average);
1969}
1970
1971ParsedTfOperationPtr TfParser::ParsePooling2d(const tensorflow::NodeDef& nodeDef,
1972 const tensorflow::GraphDef& graphDef, PoolingAlgorithm pooltype)
1973{
1974 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 1);
1975 IOutputSlot& inputSlot = inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
1976 TensorInfo inputTensorInfo = inputSlot.GetTensorInfo();
1977
1978 if (inputs.size() != 1)
1979 {
telsoa01c577f2c2018-08-31 09:22:23 +01001980 throw ParseException(
1981 boost::str(
1982 boost::format(
1983 "2D Pooling expects one input!. Got %1% for Node %2% %3%")
1984 % inputs.size()
1985 % nodeDef.name()
1986 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01001987 }
1988
1989 std::string paddingString = ReadMandatoryNodeStringAttribute(nodeDef, "padding");
1990 std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
1991 std::vector<uint32_t> strides = ReadMandatoryNodeUint32ListAttribute(nodeDef, "strides");
1992 std::vector<uint32_t> ksize = ReadMandatoryNodeUint32ListAttribute(nodeDef, "ksize"); // size of pool windows
1993
1994 Pooling2dDescriptor pooling2dDescriptor;
1995 pooling2dDescriptor.m_PoolType = pooltype;
1996 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::Exclude;
1997 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Floor;
1998
telsoa01c577f2c2018-08-31 09:22:23 +01001999 CHECK_DATA_FORMAT(nodeDef, dataFormat, "Pooling2D");
2000
surmeh01bceff2f2018-03-29 16:29:27 +01002001 if (dataFormat == "NHWC")
2002 {
2003 pooling2dDescriptor.m_StrideX = strides[2];
2004 pooling2dDescriptor.m_StrideY = strides[1];
2005 pooling2dDescriptor.m_PoolWidth = ksize[2];
2006 pooling2dDescriptor.m_PoolHeight = ksize[1];
telsoa01c577f2c2018-08-31 09:22:23 +01002007 // Swizzles input to supported memory layout.
surmeh01bceff2f2018-03-29 16:29:27 +01002008 inputTensorInfo = armnnUtils::Permuted(inputSlot.GetTensorInfo(), NHWCToArmNN);
2009 }
2010 else if (dataFormat == "NCHW")
2011 {
2012 pooling2dDescriptor.m_StrideX = strides[3];
2013 pooling2dDescriptor.m_StrideY = strides[2];
2014 pooling2dDescriptor.m_PoolWidth = ksize[3];
2015 pooling2dDescriptor.m_PoolHeight = ksize[2];
2016 }
surmeh01bceff2f2018-03-29 16:29:27 +01002017
2018 uint32_t inputHeight = inputTensorInfo.GetShape()[2];
2019 uint32_t inputWidth = inputTensorInfo.GetShape()[3];
2020
2021 bool padding = false;
2022 TensorInfo outputInfo;
telsoa01c577f2c2018-08-31 09:22:23 +01002023
2024 CHECK_PADDING_TYPE(nodeDef, paddingString);
2025
surmeh01bceff2f2018-03-29 16:29:27 +01002026 if (paddingString == "SAME")
2027 {
2028 padding = true;
2029 outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
2030 inputTensorInfo.GetShape()[1],
2031 static_cast<uint32_t>(ceil(
2032 static_cast<float>(inputHeight) /
2033 static_cast<float>(pooling2dDescriptor.m_StrideY))),
2034 static_cast<uint32_t>(ceil(
2035 static_cast<float>(inputWidth) /
2036 static_cast<float>(pooling2dDescriptor.m_StrideX)))
2037 }, DataType::Float32);
2038 }
2039 else if (paddingString == "VALID")
2040 {
2041 padding = false;
2042 outputInfo = TensorInfo({ inputTensorInfo.GetShape()[0],
2043 inputTensorInfo.GetShape()[1],
2044 static_cast<uint32_t>(ceil(
2045 static_cast<float>(inputHeight - pooling2dDescriptor.m_PoolHeight + 1) /
2046 static_cast<float>(pooling2dDescriptor.m_StrideY))),
2047 static_cast<uint32_t>(ceil(
2048 static_cast<float>(inputWidth - pooling2dDescriptor.m_PoolWidth + 1) /
2049 static_cast<float>(pooling2dDescriptor.m_StrideX)))
2050 }, DataType::Float32);
2051 }
surmeh01bceff2f2018-03-29 16:29:27 +01002052
2053 CalcPadding(inputWidth, pooling2dDescriptor.m_PoolWidth, pooling2dDescriptor.m_StrideX,
2054 pooling2dDescriptor.m_PadLeft, pooling2dDescriptor.m_PadRight, padding);
2055 CalcPadding(inputHeight, pooling2dDescriptor.m_PoolHeight, pooling2dDescriptor.m_StrideY,
2056 pooling2dDescriptor.m_PadTop, pooling2dDescriptor.m_PadBottom, padding);
2057
2058
2059 IConnectableLayer* layer = m_Network->AddPooling2dLayer(pooling2dDescriptor, nodeDef.name().c_str());
2060 if (layer == nullptr)
2061 {
telsoa01c577f2c2018-08-31 09:22:23 +01002062 throw ParseException(
2063 boost::str(
2064 boost::format(
2065 "Failed to add pooling2d layer for %1% %2%")
2066 % nodeDef.name()
2067 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002068 }
2069
2070 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2071
2072 if (dataFormat == "NHWC")
2073 {
2074 layer = SwizzleInDeswizzleOut(*m_Network, inputSlot, *layer, nodeDef.name());
2075 }
2076 else
2077 {
2078 inputSlot.Connect(layer->GetInputSlot(0));
2079 }
2080
2081 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2082}
2083
2084ParsedTfOperationPtr TfParser::AddAdditionLayer(const tensorflow::NodeDef& nodeDef, bool isBiasAdd)
2085{
2086 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2087
2088 IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2089 IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2090
2091 const TensorInfo& input0Info = input0Slot->GetTensorInfo();
2092 const TensorInfo& input1Info = input1Slot->GetTensorInfo();
2093
2094 if (isBiasAdd)
2095 {
2096 // BiasAdd takes bias as a 1D tensor. We need to add a reshape layer to create a 4D tensor
2097 // with the same data in the correct dimension for broadcast in addition.
2098 if(input1Info.GetNumDimensions() != 1)
2099 {
telsoa01c577f2c2018-08-31 09:22:23 +01002100 throw ParseException(
2101 boost::str(
2102 boost::format(
2103 "Unsupported bias for BiasAdd. It should be a 1D vector. "
2104 "Got %1% dimensions for input %2%. Node %3% %4%")
2105 % input1Info.GetNumDimensions()
2106 % inputs[1].m_IndexedValue->GetNode().name()
2107 % nodeDef.name()
2108 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002109 }
2110
2111 const std::string dataFormat = ReadMandatoryNodeStringAttribute(nodeDef, "data_format");
surmeh01bceff2f2018-03-29 16:29:27 +01002112
telsoa01c577f2c2018-08-31 09:22:23 +01002113 CHECK_DATA_FORMAT(nodeDef, dataFormat, "BiasAdd");
saoste01bbd40612018-08-28 15:41:51 +01002114 input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, dataFormat == "NHWC", *m_Network, nodeDef);
surmeh01bceff2f2018-03-29 16:29:27 +01002115 }
2116 else
2117 {
2118 if (input0Info.GetNumDimensions() == 1)
2119 {
2120 const bool isNHWC = true;
saoste01bbd40612018-08-28 15:41:51 +01002121 input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
surmeh01bceff2f2018-03-29 16:29:27 +01002122 }
2123
2124 if (input1Info.GetNumDimensions() == 1)
2125 {
2126 const bool isNHWC = true;
saoste01bbd40612018-08-28 15:41:51 +01002127 input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
surmeh01bceff2f2018-03-29 16:29:27 +01002128 }
2129 }
2130
2131 IConnectableLayer* const layer = m_Network->AddAdditionLayer(nodeDef.name().c_str());
2132
2133 input0Slot->Connect(layer->GetInputSlot(0));
2134 input1Slot->Connect(layer->GetInputSlot(1));
2135
2136 if (input0Info.GetNumDimensions() == 1 && isBiasAdd == false)
2137 {
2138 layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
2139 }
2140 else
2141 {
2142 layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
2143 }
2144
2145 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2146}
2147
saoste01bbd40612018-08-28 15:41:51 +01002148ParsedTfOperationPtr TfParser::AddRealDivLayer(const tensorflow::NodeDef& nodeDef)
2149{
2150 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2151
2152 IConnectableLayer* const layer = m_Network->AddDivisionLayer(nodeDef.name().c_str());
2153 IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2154 IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2155
2156 auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
2157 auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
2158
2159
2160 if (input0NumDims < input1NumDims)
2161 {
2162 const bool isNHWC = true;
2163 input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
2164 }
2165 if (input1NumDims < input0NumDims)
2166 {
2167 const bool isNHWC = true;
2168 input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
2169 }
2170
2171 input0Slot->Connect(layer->GetInputSlot(0));
2172 input1Slot->Connect(layer->GetInputSlot(1));
2173
2174 if (input0NumDims < input1NumDims)
2175 {
2176 layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
2177 }
2178 else
2179 {
2180 layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
2181
2182 }
2183 return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, layer);
2184}
2185
telsoa01c577f2c2018-08-31 09:22:23 +01002186IConnectableLayer* TfParser::AddMultiplicationLayer(const tensorflow::NodeDef& nodeDef)
2187{
2188 std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
2189
2190 IConnectableLayer* const layer = m_Network->AddMultiplicationLayer(nodeDef.name().c_str());
2191 IOutputSlot* input0Slot = &inputs[0].m_IndexedValue->ResolveArmnnOutputSlot(inputs[0].m_Index);
2192 IOutputSlot* input1Slot = &inputs[1].m_IndexedValue->ResolveArmnnOutputSlot(inputs[1].m_Index);
2193
2194 auto const input0NumDims = input0Slot->GetTensorInfo().GetNumDimensions();
2195 auto const input1NumDims = input1Slot->GetTensorInfo().GetNumDimensions();
2196
2197 if (input0NumDims < input1NumDims)
2198 {
2199 const bool isNHWC = true;
saoste01bbd40612018-08-28 15:41:51 +01002200 input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, isNHWC, *m_Network, nodeDef);
telsoa01c577f2c2018-08-31 09:22:23 +01002201 }
2202 if (input1NumDims < input0NumDims)
2203 {
2204 const bool isNHWC = true;
saoste01bbd40612018-08-28 15:41:51 +01002205 input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, isNHWC, *m_Network, nodeDef);
telsoa01c577f2c2018-08-31 09:22:23 +01002206 }
2207
2208 input0Slot->Connect(layer->GetInputSlot(0));
2209 input1Slot->Connect(layer->GetInputSlot(1));
2210
2211 if (input0NumDims < input1NumDims)
2212 {
2213 layer->GetOutputSlot(0).SetTensorInfo(input1Slot->GetTensorInfo());
2214 }
2215 else
2216 {
2217 layer->GetOutputSlot(0).SetTensorInfo(input0Slot->GetTensorInfo());
2218 }
2219 return layer;
2220}
2221
surmeh01bceff2f2018-03-29 16:29:27 +01002222IConnectableLayer* TfParser::AddFullyConnectedLayer(const tensorflow::NodeDef& matMulNodeDef,
2223 const tensorflow::NodeDef* addNodeDef, const char* armnnLayerName)
2224{
telsoa01c577f2c2018-08-31 09:22:23 +01002225 // Finds bias const (if applicable).
surmeh01bceff2f2018-03-29 16:29:27 +01002226 ParsedConstTfOperation<float>* biasNode = nullptr;
2227 if (addNodeDef != nullptr)
2228 {
2229 std::vector<OutputOfParsedTfOperation> addInputs = GetInputParsedTfOperationsChecked(*addNodeDef, 2);
telsoa01c577f2c2018-08-31 09:22:23 +01002230 // Finds our inputs.
surmeh01bceff2f2018-03-29 16:29:27 +01002231 if (HasParsedConstTensor<float>(addInputs[0].m_IndexedValue->GetNode().name()))
2232 {
2233 biasNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(addInputs[0].m_IndexedValue);
2234 }
2235 else if (HasParsedConstTensor<float>(addInputs[1].m_IndexedValue->GetNode().name()))
2236 {
2237 biasNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(addInputs[1].m_IndexedValue);
2238 }
2239 else
2240 {
telsoa01c577f2c2018-08-31 09:22:23 +01002241 throw ParseException(
2242 boost::str(
2243 boost::format(
2244 "ArmNN only supports fully connected layers with constant bias. "
2245 "Inputs %1% and %2%. AddNode %3%. MatMulNode %4% %5%")
2246 % addInputs[0].m_IndexedValue->GetNode().name()
2247 % addInputs[1].m_IndexedValue->GetNode().name()
2248 % addNodeDef->name()
2249 % matMulNodeDef.name()
2250 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002251 }
2252 }
2253
telsoa01c577f2c2018-08-31 09:22:23 +01002254 // Finds matmul inputs.
surmeh01bceff2f2018-03-29 16:29:27 +01002255 ParsedConstTfOperation<float>* weightNode = nullptr;
2256 ParsedTfOperation* inputNode = nullptr;
2257 unsigned int inputIdx = 0;
2258 std::vector<OutputOfParsedTfOperation> mulInputs = GetInputParsedTfOperationsChecked(matMulNodeDef, 2);
2259 if (HasParsedConstTensor<float>(mulInputs[0].m_IndexedValue->GetNode().name()))
2260 {
2261 weightNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(mulInputs[0].m_IndexedValue);
2262 inputNode = mulInputs[1].m_IndexedValue;
2263 inputIdx = mulInputs[1].m_Index;
2264 }
2265 else if (HasParsedConstTensor<float>(mulInputs[1].m_IndexedValue->GetNode().name()))
2266 {
2267 weightNode = boost::polymorphic_downcast<ParsedConstTfOperation<float>*>(mulInputs[1].m_IndexedValue);
2268 inputNode = mulInputs[0].m_IndexedValue;
2269 inputIdx = mulInputs[0].m_Index;
2270 }
2271 else
2272 {
telsoa01c577f2c2018-08-31 09:22:23 +01002273 throw ParseException(
2274 boost::str(
2275 boost::format(
2276 "ArmNN only supports fully connected layers with constant weights. "
2277 "Inputs %1% and %2%. MatMulNode %3% %4%")
2278 % mulInputs[0].m_IndexedValue->GetNode().name()
2279 % mulInputs[1].m_IndexedValue->GetNode().name()
2280 % matMulNodeDef.name()
2281 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002282 }
2283
2284 std::vector<float> weightTensorData;
telsoa01c577f2c2018-08-31 09:22:23 +01002285 // Handles weight.
surmeh01bceff2f2018-03-29 16:29:27 +01002286 ConstTensor weights = weightNode->GetConstTensor(false, weightTensorData);
2287
2288 FullyConnectedDescriptor desc;
2289 desc.m_BiasEnabled = addNodeDef != nullptr;
2290
2291 IConnectableLayer* layer = nullptr;
telsoa01c577f2c2018-08-31 09:22:23 +01002292 // Makes the layer.
surmeh01bceff2f2018-03-29 16:29:27 +01002293 if (addNodeDef != nullptr)
2294 {
2295 std::vector<float> biasTensorData;
2296 ConstTensor biases = biasNode->GetConstTensor(false, biasTensorData);
2297
2298 if (weights.GetShape()[1] != biases.GetShape()[0])
2299 {
telsoa01c577f2c2018-08-31 09:22:23 +01002300 throw ParseException(
2301 boost::str(
2302 boost::format(
2303 "Shape of matmul weights and bias do not match. "
2304 "AddNode %1%. MatMulNode %2% %3%")
2305 % addNodeDef->name()
2306 % matMulNodeDef.name()
2307 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002308 }
2309
2310 layer = m_Network->AddFullyConnectedLayer(desc, weights, biases, armnnLayerName);
2311 }
2312 else
2313 {
2314 layer = m_Network->AddFullyConnectedLayer(desc, weights, armnnLayerName);
2315 }
2316
2317 BOOST_ASSERT(layer != nullptr);
2318
2319 inputNode->ResolveArmnnOutputSlot(inputIdx).Connect(layer->GetInputSlot(0));
2320 unsigned int batches = inputNode->ResolveArmnnOutputSlot(inputIdx).GetTensorInfo().GetShape()[0];
2321
telsoa01c577f2c2018-08-31 09:22:23 +01002322 // Handles output.
surmeh01bceff2f2018-03-29 16:29:27 +01002323 TensorInfo outputInfo({ batches, weights.GetShape()[1] }, DataType::Float32);
2324 layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
2325 return layer;
2326}
2327
2328void TfParser::LoadNodeDef(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
2329{
telsoa01c577f2c2018-08-31 09:22:23 +01002330 // Gets the type of the node (assume float).
surmeh01bceff2f2018-03-29 16:29:27 +01002331 tensorflow::DataType type = tensorflow::DT_FLOAT;
2332 if (nodeDef.attr().count("T") != 0)
2333 {
2334 auto attr = nodeDef.attr().at("T");
2335 type = attr.type();
2336 }
2337 else if (nodeDef.attr().count("dtype") != 0)
2338 {
2339 auto attr = nodeDef.attr().at("dtype");
2340 type = attr.type();
2341 }
2342
2343 if (type != tensorflow::DT_FLOAT && nodeDef.op() != "Const")
2344 {
telsoa01c577f2c2018-08-31 09:22:23 +01002345 throw ParseException(
2346 boost::str(
2347 boost::format(
2348 "Currently only FLOAT is supported for tensorflow nodes (apart from Const). "
2349 "Got %1% for Node %2% %3%")
2350 % tensorflow::DataType_Name(type)
2351 % nodeDef.name()
2352 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002353 }
2354
2355 const std::string& operation = nodeDef.op();
2356 auto it = ms_OperationNameToParsingFunctions.find(operation);
2357 if (it != ms_OperationNameToParsingFunctions.end())
2358 {
2359 auto func = it->second;
2360 ParsedTfOperationPtr parsedTfOperation = (this->*func)(nodeDef, graphDef);
2361 ParsedTfOperation* parsedTfOperationRaw = parsedTfOperation.get();
2362
telsoa01c577f2c2018-08-31 09:22:23 +01002363 // Stores the parsed operation so that dependent layers can connect to it.
surmeh01bceff2f2018-03-29 16:29:27 +01002364 auto it = m_ParsedTfOperations.find(nodeDef.name());
2365 if (it != m_ParsedTfOperations.end())
2366 {
2367 throw ParseException(boost::str(boost::format("Name %1% used by more than one node") % nodeDef.name()));
2368 }
2369 m_ParsedTfOperations[nodeDef.name()] = std::move(parsedTfOperation);
2370
telsoa01c577f2c2018-08-31 09:22:23 +01002371 // If this node was requested as an output from the network, then adds an ArmNN output layer.
surmeh01bceff2f2018-03-29 16:29:27 +01002372 if (std::find(m_RequestedOutputs.begin(), m_RequestedOutputs.end(), nodeDef.name()) !=
2373 m_RequestedOutputs.end())
2374 {
2375 auto outId = ParseOutputId(nodeDef.name());
2376 const LayerBindingId layerId = boost::numeric_cast<LayerBindingId>(m_NetworkOutputsBindingInfo.size());
2377 IOutputSlot& prevSlot = parsedTfOperationRaw->ResolveArmnnOutputSlot(outId.m_Index);
2378
2379 TensorInfo tensorInfo = prevSlot.GetTensorInfo();
2380
2381 IConnectableLayer* outputLayer = m_Network->AddOutputLayer(layerId, nodeDef.name().c_str());
2382
2383 prevSlot.Connect(outputLayer->GetInputSlot(0));
2384
2385 TrackOutputBinding(outputLayer, layerId, tensorInfo);
2386 }
2387 }
2388 else
2389 {
telsoa01c577f2c2018-08-31 09:22:23 +01002390 throw ParseException(
2391 boost::str(
2392 boost::format(
2393 "Unsupported operation %1% in tensorflow::GraphDef %2%")
2394 % operation
2395 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002396 }
2397}
2398
2399void TfParser::LoadGraphDef(const tensorflow::GraphDef& graphDef)
2400{
telsoa01c577f2c2018-08-31 09:22:23 +01002401 // Adds all nodes to our map.
surmeh01bceff2f2018-03-29 16:29:27 +01002402 m_NodesByName.clear();
2403 m_NetworkInputsBindingInfo.clear();
2404 m_NetworkOutputsBindingInfo.clear();
2405
2406 for (int i = 0; i < graphDef.node_size(); ++i)
2407 {
2408 const tensorflow::NodeDef& node = graphDef.node(i);
2409 m_NodesByName[node.name()] = &node;
2410 }
2411
telsoa01c577f2c2018-08-31 09:22:23 +01002412 // Finds the output nodes the user requested.
surmeh01bceff2f2018-03-29 16:29:27 +01002413 std::vector<const tensorflow::NodeDef*> targetNodes;
2414 for (const std::string& requestedOutputName : m_RequestedOutputs)
2415 {
2416 auto nodeIt = m_NodesByName.find(requestedOutputName);
2417 if (nodeIt == m_NodesByName.end())
2418 {
telsoa01c577f2c2018-08-31 09:22:23 +01002419 throw ParseException(
2420 boost::str(
2421 boost::format(
2422 "Couldn't find requested output node '%1%' in graph %2%")
2423 % requestedOutputName
2424 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002425 }
2426 targetNodes.push_back(nodeIt->second);
2427 }
2428
telsoa01c577f2c2018-08-31 09:22:23 +01002429 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
surmeh01bceff2f2018-03-29 16:29:27 +01002430 std::vector<const tensorflow::NodeDef*> sortedNodes;
2431 if (!armnnUtils::GraphTopologicalSort<const tensorflow::NodeDef*>(
2432 targetNodes,
2433 [this](const tensorflow::NodeDef* node)
2434 {
2435 auto outputs = GetTfInputNodes(*node);
2436 std::vector<const tensorflow::NodeDef*> nodesOnly;
2437 for (const auto & o : outputs) {
2438 nodesOnly.push_back(o.m_IndexedValue);
2439 }
2440 return nodesOnly;
2441 },
2442 sortedNodes))
2443 {
telsoa01c577f2c2018-08-31 09:22:23 +01002444 throw ParseException(
2445 boost::str(
2446 boost::format(
2447 "Cycle detected in graph %1%")
2448 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002449 }
2450
telsoa01c577f2c2018-08-31 09:22:23 +01002451 // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
surmeh01bceff2f2018-03-29 16:29:27 +01002452 for (const auto& it : sortedNodes)
2453 {
2454 const tensorflow::NodeDef& currentNode = *it;
2455 LoadNodeDef(currentNode, graphDef);
2456 }
2457}
2458
2459INetworkPtr TfParser::CreateNetworkFromTextFile(const char* graphFile,
2460 const std::map<std::string, TensorShape>& inputShapes,
2461 const std::vector<std::string>& requestedOutputs)
2462{
2463 FILE* fd = fopen(graphFile, "r");
2464
2465 if (fd == nullptr)
2466 {
telsoa01c577f2c2018-08-31 09:22:23 +01002467 throw FileNotFoundException(
2468 boost::str(
2469 boost::format(
2470 "Graph file %1% failed to open %2%")
2471 % graphFile
2472 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002473 }
2474
telsoa01c577f2c2018-08-31 09:22:23 +01002475 // Parses the file into a message.
surmeh01bceff2f2018-03-29 16:29:27 +01002476 tensorflow::GraphDef graphDef;
2477 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
2478 bool success = google::protobuf::TextFormat::Parse(input, &graphDef);
2479 delete input;
2480 fclose(fd);
2481
2482 if (!success)
2483 {
telsoa01c577f2c2018-08-31 09:22:23 +01002484 throw ParseException(
2485 boost::str(
2486 boost::format(
2487 "Failed to parse graph file %1%")
2488 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002489 }
2490
2491 return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
2492}
2493
2494INetworkPtr TfParser::CreateNetworkFromString(const char* protoText,
2495 const std::map<std::string, TensorShape>& inputShapes,
2496 const std::vector<std::string>& requestedOutputs)
2497{
telsoa01c577f2c2018-08-31 09:22:23 +01002498 // Parses the string into a message.
surmeh01bceff2f2018-03-29 16:29:27 +01002499 tensorflow::GraphDef graphDef;
2500 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &graphDef);
2501
2502 if (!success)
2503 {
telsoa01c577f2c2018-08-31 09:22:23 +01002504 throw ParseException(
2505 boost::str(
2506 boost::format(
2507 "Failed to parse graph file %1%")
2508 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002509 }
2510
2511 return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
2512}
2513
2514INetworkPtr TfParser::CreateNetworkFromBinaryFile(const char* graphFile,
2515 const std::map<std::string, TensorShape>& inputShapes,
2516 const std::vector<std::string>& requestedOutputs)
2517{
2518 FILE* fd = fopen(graphFile, "rb");
2519
2520 if (fd == nullptr)
2521 {
telsoa01c577f2c2018-08-31 09:22:23 +01002522 throw FileNotFoundException(
2523 boost::str(
2524 boost::format(
2525 "Graph file %1% failed to open %2%")
2526 % graphFile
2527 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002528 }
2529
telsoa01c577f2c2018-08-31 09:22:23 +01002530 // Parses the file into a message.
surmeh01bceff2f2018-03-29 16:29:27 +01002531 tensorflow::GraphDef graphDef;
2532
2533 google::protobuf::io::FileInputStream inStream(fileno(fd));
2534 google::protobuf::io::CodedInputStream codedStream(&inStream);
2535 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
2536 bool success = graphDef.ParseFromCodedStream(&codedStream);
2537 fclose(fd);
2538
2539 if (!success)
2540 {
telsoa01c577f2c2018-08-31 09:22:23 +01002541 throw ParseException(
2542 boost::str(
2543 boost::format(
2544 "Failed to parse protobuf file %1% %2%")
2545 % graphFile
2546 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002547 }
2548
2549 return CreateNetworkFromGraphDef(graphDef, inputShapes, requestedOutputs);
2550}
2551
2552INetworkPtr TfParser::CreateNetworkFromGraphDef(const tensorflow::GraphDef& graphDef,
2553 const std::map<std::string, TensorShape>& inputShapes,
2554 const std::vector<std::string>& requestedOutputs)
2555{
2556 m_Network = INetwork::Create();
2557
2558 m_InputShapes = inputShapes;
2559 if (requestedOutputs.size() == 0)
2560 {
telsoa01c577f2c2018-08-31 09:22:23 +01002561 throw ParseException(
2562 boost::str(
2563 boost::format(
2564 "requestedOutputs must have at least one entry %1%")
2565 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002566 }
2567 m_RequestedOutputs = requestedOutputs;
2568
2569 try
2570 {
2571 LoadGraphDef(graphDef);
2572 }
2573 catch (const ParseException& e)
2574 {
2575 Cleanup();
2576 throw e;
2577 }
2578
2579 Cleanup();
2580
2581 return std::move(m_Network);
2582}
2583
2584void TfParser::Cleanup()
2585{
telsoa01c577f2c2018-08-31 09:22:23 +01002586 // Cleanup, in case we reuse this parser.
surmeh01bceff2f2018-03-29 16:29:27 +01002587 m_InputShapes.clear();
2588 m_RequestedOutputs.clear();
2589 m_NodesByName.clear();
2590 m_ParsedTfOperations.clear();
2591}
2592
2593BindingPointInfo TfParser::GetNetworkInputBindingInfo(const std::string& name) const
2594{
2595 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
2596}
2597
2598BindingPointInfo TfParser::GetNetworkOutputBindingInfo(const std::string& name) const
2599{
2600 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
2601}
2602
2603std::pair<LayerBindingId, TensorInfo> TfParser::GetBindingInfo(const std::string& layerName,
2604 const char* bindingPointDesc,
2605 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
2606{
2607 auto it = nameToBindingInfo.find(layerName);
2608 if (it == nameToBindingInfo.end())
2609 {
telsoa01c577f2c2018-08-31 09:22:23 +01002610 throw InvalidArgumentException(
2611 boost::str(
2612 boost::format(
2613 "Unknown %1% '%2%' %3%")
2614 % bindingPointDesc
2615 % layerName
2616 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002617 }
2618 return it->second;
2619}
2620
2621void TfParser::TrackInputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
2622{
2623 return TrackBindingPoint(layer, id, tensorInfo, "input", m_NetworkInputsBindingInfo);
2624}
2625
2626void TfParser::TrackOutputBinding(IConnectableLayer* layer, LayerBindingId id, const TensorInfo& tensorInfo)
2627{
2628 return TrackBindingPoint(layer, id, tensorInfo, "output", m_NetworkOutputsBindingInfo);
2629}
2630
2631void TfParser::TrackBindingPoint(IConnectableLayer* layer,
2632 LayerBindingId id,
2633 const TensorInfo& tensorInfo,
2634 const char* bindingPointDesc,
2635 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
2636{
2637 const std::string layerName = layer->GetName();
2638 auto it = nameToBindingInfo.find(layerName);
2639 if (it == nameToBindingInfo.end())
2640 {
2641 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
2642 }
2643 else
2644 {
telsoa01c577f2c2018-08-31 09:22:23 +01002645 throw ParseException(
2646 boost::str(
2647 boost::format(
2648 "Id %1% used by more than one %2% layer %3%")
2649 % id
2650 % bindingPointDesc
2651 % CHECK_LOCATION().AsString()));
surmeh01bceff2f2018-03-29 16:29:27 +01002652 }
2653}
2654
2655} // namespace armnnTfParser