From 4840dfb7543d66652dc11c5ff39c8f5c1e2f9370 Mon Sep 17 00:00:00 2001 From: Ryan OShea Date: Tue, 25 Aug 2020 12:35:58 +0100 Subject: Updating Doxygen Documentation for 20.08 release Signed-off-by: Ryan OShea Change-Id: I605409f8720de5353feceb161b39f8a5f0598180 --- 20.08/_caffe_parser_8cpp_source.xhtml | 253 ++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 20.08/_caffe_parser_8cpp_source.xhtml (limited to '20.08/_caffe_parser_8cpp_source.xhtml') diff --git a/20.08/_caffe_parser_8cpp_source.xhtml b/20.08/_caffe_parser_8cpp_source.xhtml new file mode 100644 index 0000000000..0d826b4942 --- /dev/null +++ b/20.08/_caffe_parser_8cpp_source.xhtml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + +ArmNN: src/armnnCaffeParser/CaffeParser.cpp Source File + + + + + + + + + + + + + + + + +
+
+ + + + ArmNN + + + +
+
+  20.08 +
+
+
+ + + + + + + +
+
+ +
+
+
+ +
+ +
+
+ + +
+ +
+ +
+
+
CaffeParser.cpp
+
+
+Go to the documentation of this file.
1 //
2 // Copyright © 2017 Arm Ltd. All rights reserved.
3 // SPDX-License-Identifier: MIT
4 //
5 #include "CaffeParser.hpp"
7 
8 #include "armnn/Descriptors.hpp"
9 #include "armnn/INetwork.hpp"
10 #include "armnn/Utils.hpp"
11 #include "armnn/Exceptions.hpp"
12 
13 #include "GraphTopologicalSort.hpp"
14 #include "VerificationHelpers.hpp"
15 
16 #include <armnn/utility/Assert.hpp>
17 
18 #include <boost/numeric/conversion/cast.hpp>
19 #include <boost/format.hpp>
20 
21 // Caffe
22 #include "caffe/proto/caffe.pb.h"
23 
24 // ProtoBuf
25 #include <google/protobuf/io/coded_stream.h>
26 #include <google/protobuf/io/zero_copy_stream.h>
27 #include <google/protobuf/io/zero_copy_stream_impl.h>
28 #include <google/protobuf/text_format.h>
29 #include <google/protobuf/stubs/common.h>
30 #include <google/protobuf/stubs/once.h>
31 #include <google/protobuf/io/coded_stream.h>
32 #include <google/protobuf/descriptor.h>
33 #include <google/protobuf/generated_message_reflection.h>
34 #include <google/protobuf/reflection_ops.h>
35 #include <google/protobuf/wire_format.h>
36 
37 #include <cmath>
38 #include <sstream>
39 #include <queue>
40 #include <fcntl.h>
41 
42 /// Caffe networks are loaded from protobuf files (binary or text) using the protobuf library and the generated
43 /// code from caffe.pb.h. This gives us a caffe::NetParameter which is an in-memory version of the file.
44 /// This contains a flat list of Caffe 'layers' (e.g. convolution, pooling etc.).
45 /// Each layer has inputs (called "bottoms") and outputs (called "tops"). Data flows from bottom to top.
46 /// The bottoms of a layer refer to the tops of other layers, not their names.
47 /// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't
48 /// need any other changes).
49 ///
50 /// Some layers (e.g. Relu) can be configured so that their top and bottom are both the same. This is called an
51 /// "in-place" layer and is a Caffe runtime feature used to reduce memory usage by modifying tensors in-place.
52 /// This isn't relevant to the parser and so we preprocess these layers to convert them to regular layers, to result
53 /// in a consistent graph structure.
54 
55 namespace armnnCaffeParser
56 {
57 
58 using namespace armnn;
59 using namespace caffe;
60 using namespace std;
61 using namespace google::protobuf::io;
62 
63 namespace
64 {
65 
66 const float* GetArrayPtrFromBlob(const LayerParameter& layerParam, unsigned int blobIndex)
67 {
68  auto nBlobs = layerParam.blobs_size();
69  if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
70  {
71  throw ParseException(
72  boost::str(
73  boost::format(
74  "Expected data blob at index %1% in layer %2% not found. nBlobs=%2%. %4%") %
75  blobIndex %
76  layerParam.name() %
77  nBlobs %
78  CHECK_LOCATION().AsString()));
79  }
80 
81  const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
82 
83  const float* arrayPtr = blob.data().data();
84  return arrayPtr;
85 }
86 
87 void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
88 {
89  auto nBlobs = layerParam.blobs_size();
90  if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
91  {
92  throw ParseException(
93  boost::str(
94  boost::format(
95  "Expected data blob at index %1% in layer %2% not found. %3%") %
96  blobIndex %
97  layerParam.name() %
98  CHECK_LOCATION().AsString()));
99  }
100 
101  const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
102 
103  size_t blobSize = boost::numeric_cast<size_t>(blob.data_size());
104  if (blobSize != outData.size())
105  {
106  throw ParseException(
107  boost::str(
108  boost::format(
109  "Data blob at index %1% in layer %2% has an unexpected size. "
110  "Expected %3% elements but got %4% elements. %5%") %
111  blobIndex %
112  layerParam.name() %
113  outData.size() %
114  blobSize %
115  CHECK_LOCATION().AsString()));
116  }
117 
118  int outSizeInt = boost::numeric_cast<int>(outData.size());
119  for (int i = 0; i < outSizeInt; ++i)
120  {
121  outData[static_cast<size_t>(i)] = blob.data(i);
122  }
123 }
124 
125 template <typename T>
126 size_t SizeOfVectorData(const vector<T>& vec)
127 {
128  return vec.size() * sizeof(T);
129 }
130 
131 void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
132  unsigned int numInputs,
133  unsigned int numOutputs)
134 {
135  int numInputsActual = layerParameter.bottom_size();
136  if (numInputs != boost::numeric_cast<unsigned int>(numInputsActual))
137  {
138  throw ParseException(
139  boost::str(
140  boost::format("Invalid number of inputs requested %1% for layer %2% "
141  "while only %3% present. %4%") %
142  numInputs %
143  layerParameter.name() %
144  numInputsActual %
145  CHECK_LOCATION().AsString()));
146  }
147 
148  int numOutputsActual = layerParameter.top_size();
149  if (numOutputs != boost::numeric_cast<unsigned int>(numOutputsActual))
150  {
151  throw ParseException(
152  boost::str(
153  boost::format("Invalid number of outputs requested %1% for layer %2% "
154  "while only %3% present. %4%") %
155  numOutputs %
156  layerParameter.name() %
157  numOutputsActual %
158  CHECK_LOCATION().AsString()));
159  }
160 }
161 
162 template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
163 ValueType GetOptionalWithFallback(const ParamType& param,
164  ExtractOptional extractOptional,
165  ExtractFallback extractFallback,
166  ValueType defaultValue)
167 {
168  auto optValue = extractOptional(param, defaultValue);
169  if (optValue.first)
170  {
171  return optValue.second;
172  }
173  auto fallbackValue = extractFallback(param, defaultValue);
174  return fallbackValue.second;
175 }
176 
177 #define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
178  PARAM_TYPE, \
179  OPTIONAL_VALUE, \
180  FALLBACK_VECTOR, \
181  VALUE_TYPE, \
182  DEFAULT_VALUE) \
183  GetOptionalWithFallback( \
184  PARAM, \
185  [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
186  { \
187  if (param.has_##OPTIONAL_VALUE ()) \
188  { \
189  return std::make_pair(true, param.OPTIONAL_VALUE ()); \
190  } \
191  else \
192  { \
193  return std::make_pair(false, defaultValue); \
194  } \
195  }, \
196  [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
197  { \
198  if (param.FALLBACK_VECTOR##_size() > 0) \
199  { \
200  return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
201  } \
202  else \
203  { \
204  return std::make_pair(false, defaultValue); \
205  } \
206  }, \
207  DEFAULT_VALUE)
208 
209 #define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
210  PARAM_TYPE, \
211  OPTIONAL_VALUE, \
212  FALLBACK_VALUE, \
213  VALUE_TYPE, \
214  DEFAULT_VALUE) \
215  GetOptionalWithFallback( \
216  PARAM, \
217  [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
218  { \
219  if (param.has_##OPTIONAL_VALUE ()) \
220  { \
221  return std::make_pair(true, param.OPTIONAL_VALUE ()); \
222  } \
223  else \
224  { \
225  return std::make_pair(false, defaultValue); \
226  } \
227  }, \
228  [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
229  { \
230  if (param.has_##FALLBACK_VALUE ()) \
231  { \
232  return std::make_pair(true, param.FALLBACK_VALUE ()); \
233  } \
234  else \
235  { \
236  return std::make_pair(false, defaultValue); \
237  } \
238  }, \
239  DEFAULT_VALUE)
240 
241 } // namespace <anonymous>
242 
243 const std::map<std::string, CaffeParserBase::OperationParsingFunction>
245  { "Input", &CaffeParserBase::ParseInputLayer },
246  { "Convolution", &CaffeParserBase::ParseConvLayer },
247  { "Pooling", &CaffeParserBase::ParsePoolingLayer },
248  { "ReLU", &CaffeParserBase::ParseReluLayer },
249  { "LRN", &CaffeParserBase::ParseLRNLayer },
250  { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
251  { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
252  { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
253  { "Concat", &CaffeParserBase::ParseConcatLayer },
254  { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
255  { "Scale", &CaffeParserBase::ParseScaleLayer },
256  { "Split", &CaffeParserBase::ParseSplitLayer },
257  { "Dropout", &CaffeParserBase::ParseDropoutLayer},
258 };
259 
261 {
262  return new RecordByRecordCaffeParser();
263 }
264 
266 {
267  return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
268 }
269 
271 {
272  delete parser;
273 }
274 
276  : m_Network(nullptr, nullptr)
277 {
278 
279 }
280 
282 : CaffeParserBase()
283 {
284 
285 }
286 
288 {
289  return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
290 }
291 
293 {
294  return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
295 }
296 
297 std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
298  const char* bindingPointDesc,
299  const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
300 {
301  auto it = nameToBindingInfo.find(layerName);
302  if (it == nameToBindingInfo.end())
303  {
305  boost::str(
306  boost::format(
307  "Unknown binding %1% for layer '%2%'. %3%") %
308  bindingPointDesc %
309  layerName %
310  CHECK_LOCATION().AsString()));
311  }
312  return it->second;
313 }
314 
315 TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
316 {
317  std::vector<unsigned int> shape;
318  for (int j = 0; j < blobShape.dim_size(); ++j)
319  {
320  shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
321  }
322 
323  return TensorInfo(boost::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
324 }
325 
326 BlobShape TensorDescToBlobShape(const TensorInfo& desc)
327 {
328  BlobShape ret;
329  for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
330  {
331  ret.add_dim(i);
332  ret.set_dim(boost::numeric_cast<int>(i), desc.GetShape()[i]);
333  }
334 
335  return ret;
336 }
337 
338 // Note: can move to CaffeParser when/if we optimise the text/string format
339 // to load on a layer by layer basis
340 vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
341 {
342  std::vector<const caffe::LayerParameter*> ret;
343  ret.reserve(boost::numeric_cast<size_t>(layerParam.bottom_size()));
344  for (int j = 0; j < layerParam.bottom_size(); ++j)
345  {
346  std::string inputName = layerParam.bottom(j);
347  auto inputIt = m_CaffeLayersByTopName.find(inputName);
348  if (inputIt == m_CaffeLayersByTopName.end())
349  {
350  throw ParseException(
351  boost::str(
352  boost::format(
353  "Can't find Caffe layer with top called '%1%', "
354  "which is listed as an input of '%2%'. %3%") %
355  inputName %
356  layerParam.name() %
357  CHECK_LOCATION().AsString()));
358  }
359  ret.push_back(inputIt->second);
360  }
361 
362  return ret;
363 }
364 
365 void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
366 {
367  ARMNN_ASSERT(layerParam.type() == "Input");
368  ValidateNumInputsOutputs(layerParam, 0, 1);
369 
370  const InputParameter& param = layerParam.input_param();
371 
374  armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
375 
376  // Decides the tensor info for this input. This can be specified in the Caffe network but can also
377  // be overriden by user input (m_inputShapes).
378  armnn::TensorInfo inputTensorInfo;
379 
380  const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
381  &param.shape(0) : nullptr;
382  if (originalShape)
383  {
384  inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
385  }
386 
387  auto overrideIt = m_InputShapes.find(layerParam.name());
388  if (overrideIt != m_InputShapes.end())
389  {
390  const TensorShape& overrideShape = overrideIt->second;
391  if (originalShape &&
392  ( originalShape->dim(1) != overrideShape[1]
393  || originalShape->dim(2) != overrideShape[2]
394  || originalShape->dim(3) != overrideShape[3]))
395  {
396  throw ParseException(
397  boost::str(
398  boost::format(
399  "Parsed input shape for '%1%' is incompatible with the override provided. %2%") %
400  layerParam.name() %
401  CHECK_LOCATION().AsString()));
402  }
403  inputTensorInfo.SetShape(overrideShape);
404  }
405  else if (!originalShape)
406  {
407  throw ParseException(
408  boost::str(
409  boost::format(
410  "No input descriptor given for '%1%' and no input shape found in caffe model. %2%") %
411  layerParam.name() %
412  CHECK_LOCATION().AsString()));
413  }
414 
415  TrackInputBinding(inputLayer, inputId, inputTensorInfo);
416  inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
417  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
418 }
419 
420 void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
421  const armnn::Convolution2dDescriptor& desc,
422  unsigned int kernelW,
423  unsigned int kernelH)
424 {
425  ARMNN_ASSERT(layerParam.type() == "Convolution");
426  ValidateNumInputsOutputs(layerParam, 1, 1);
427 
428  ConvolutionParameter convParam = layerParam.convolution_param();
429  BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
430  const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
431 
432  // asusme these were already verified by the caller ParseConvLayer() function
433  ARMNN_ASSERT(numGroups < inputShape.dim(1));
434  ARMNN_ASSERT(numGroups > 1);
435 
436  // Handle grouping
437  armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
438 
439  vector<string> convLayerNames(numGroups);
440  vector<armnn::IConnectableLayer*> convLayers(numGroups);
441  convLayerNames[0] = layerParam.name();
442 
443  // This convolution is to be applied to chunks of the input data so add a splitter layer
444 
445  // Redirect the convolution input to the splitter
446  unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
447  static_cast<unsigned int>(inputShape.dim(1)),
448  static_cast<unsigned int>(inputShape.dim(2)),
449  static_cast<unsigned int>(inputShape.dim(3))};
450 
451  // Split dimension 1 of the splitter output shape and conv input shapes
452  // according to the number of groups
453 
454  splitterDimSizes[1] /= numGroups;
455  inputShape.set_dim(1, splitterDimSizes[1]);
456 
457  // This is used to describe how the input is to be split
458  ViewsDescriptor splitterDesc(numGroups);
459 
460  // Create an output node for each group, giving each a unique name
461  for (unsigned int g = 0; g < numGroups; ++g)
462  {
463  // Work out the names of the splitter layers child convolutions
464  stringstream ss;
465  ss << layerParam.name() << "_" << g;
466  convLayerNames[g] = ss.str();
467 
468  splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
469 
470  // Set the size of the views.
471  for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
472  {
473  splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
474  }
475  }
476 
477  const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
478  armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
479 
480  inputConnection.Connect(splitterLayer->GetInputSlot(0));
481  for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
482  {
483  splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
484  }
485 
486  unsigned int numFilters = convParam.num_output();
487 
488  // Populates convolution output tensor descriptor dimensions.
489  BlobShape outputShape;
490  outputShape.add_dim(0);
491  outputShape.set_dim(0, inputShape.dim(0));
492  outputShape.add_dim(1);
493  // Ensures that dimension 1 of the convolution output is split according to the number of groups.
494  outputShape.set_dim(1, numFilters / numGroups);
495  outputShape.add_dim(2);
496  outputShape.set_dim(
497  2, (static_cast<int>(
498  static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
499  static_cast<float>(desc.m_StrideY)) + 1));
500  outputShape.add_dim(3);
501  outputShape.set_dim(
502  3, (static_cast<int>(
503  static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
504  static_cast<float>(desc.m_StrideX)) + 1));
505 
506  // Load the weight data for ALL groups
507  vector<float> weightData(boost::numeric_cast<size_t>(numGroups *
508  inputShape.dim(1) * // number of input channels
509  outputShape.dim(1) * // number of output channels
510  kernelH *
511  kernelW));
512  GetDataFromBlob(layerParam, weightData, 0);
513 
514  const unsigned int weightDimSizes[4] = {
515  static_cast<unsigned int>(outputShape.dim(1)),
516  static_cast<unsigned int>(inputShape.dim(1)),
517  kernelH,
518  kernelW};
519 
520  TensorInfo biasInfo;
521  vector<float> biasData;
522 
523  if (desc.m_BiasEnabled)
524  {
525  biasData.resize(boost::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
526  GetDataFromBlob(layerParam, biasData, 1);
527 
528  const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
529  biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
530  }
531 
532  const unsigned int numWeightsPerGroup = boost::numeric_cast<unsigned int>(weightData.size()) / numGroups;
533  const unsigned int numBiasesPerGroup = boost::numeric_cast<unsigned int>(biasData.size()) / numGroups;
534 
535  for (unsigned int g = 0; g < numGroups; ++g)
536  {
537  // Sets the slot index, group 0 should be connected to the 0th output of the splitter
538  // group 1 should be connected to the 1st output of the splitter.
539 
540  // Pulls out the weights for this group from that loaded from the model file earlier.
541  ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
542  weightData.data() + numWeightsPerGroup * g);
543 
544  IConnectableLayer* convLayer = nullptr;
545  Optional<ConstTensor> optionalBiases;
546  if (desc.m_BiasEnabled)
547  {
548  // Pulls out the biases for this group from that loaded from the model file earlier.
549  ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
550  optionalBiases = Optional<ConstTensor>(biases);
551  }
552  convLayer = m_Network->AddConvolution2dLayer(desc,
553  weights,
554  optionalBiases,
555  convLayerNames[g].c_str());
556  convLayers[g] = convLayer;
557 
558  // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
559  // otherwise it's the regular input to this layer.
560  armnn::IOutputSlot& splitterInputConnection =
561  splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
562  splitterInputConnection.Connect(convLayer->GetInputSlot(0));
563  convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
564  }
565 
566  // If the convolution was performed in chunks, add a layer to concatenate the results
567 
568  // The merge input shape matches that of the convolution output
569  unsigned int concatDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
570  static_cast<unsigned int>(outputShape.dim(1)),
571  static_cast<unsigned int>(outputShape.dim(2)),
572  static_cast<unsigned int>(outputShape.dim(3))};
573 
574  // This is used to describe how the input is to be concatenated
575  OriginsDescriptor concatDesc(numGroups);
576 
577  // Now create an input node for each group, using the name from
578  // the output of the corresponding convolution
579  for (unsigned int g = 0; g < numGroups; ++g)
580  {
581  concatDesc.SetViewOriginCoord(g, 1, concatDimSizes[1] * g);
582  }
583 
584  // Make sure the output from the concat is the correct size to hold the data for all groups
585  concatDimSizes[1] *= numGroups;
586  outputShape.set_dim(1, concatDimSizes[1]);
587 
588  // Finally add the concat layer
589  IConnectableLayer* concatLayer = m_Network->AddConcatLayer(concatDesc, layerParam.name().c_str());
590 
591  if (!concatLayer)
592  {
593  throw ParseException(
594  boost::str(
595  boost::format(
596  "Failed to create final concat layer for Split+Convolution+Concat. "
597  "Layer=%1% #groups=%2% #filters=%3% %4%") %
598  layerParam.name() %
599  numGroups %
600  numFilters %
601  CHECK_LOCATION().AsString()));
602  }
603 
604  for (unsigned int g = 0; g < numGroups; ++g)
605  {
606  convLayers[g]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(g));
607  }
608  concatLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, concatDimSizes, DataType::Float32));
609  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatLayer->GetOutputSlot(0));
610 }
611 
612 void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
613  const armnn::Convolution2dDescriptor& convDesc,
614  unsigned int kernelW,
615  unsigned int kernelH)
616 {
617  ARMNN_ASSERT(layerParam.type() == "Convolution");
618  ValidateNumInputsOutputs(layerParam, 1, 1);
619 
620  ConvolutionParameter convParam = layerParam.convolution_param();
621  BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
622 
624  desc.m_PadLeft = convDesc.m_PadLeft;
625  desc.m_PadRight = convDesc.m_PadRight;
626  desc.m_PadTop = convDesc.m_PadTop;
627  desc.m_PadBottom = convDesc.m_PadBottom;
628  desc.m_StrideX = convDesc.m_StrideX;
629  desc.m_StrideY = convDesc.m_StrideY;
630  desc.m_BiasEnabled = convDesc.m_BiasEnabled;
631 
632  unsigned int numFilters = convParam.num_output();
633 
634  BlobShape outputShape;
635  outputShape.add_dim(0);
636  outputShape.set_dim(0, inputShape.dim(0));
637  outputShape.add_dim(1);
638  outputShape.set_dim(1, numFilters);
639  outputShape.add_dim(2);
640  outputShape.set_dim(
641  2, (static_cast<int>(
642  static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
643  static_cast<float>(desc.m_StrideY)) + 1));
644  outputShape.add_dim(3);
645  outputShape.set_dim(
646  3, (static_cast<int>(
647  static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
648  static_cast<float>(desc.m_StrideX)) + 1));
649 
650  // Load the weight data
651  size_t allWeightsSize = boost::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
652  vector<float> weightData(allWeightsSize);
653 
654  GetDataFromBlob(layerParam, weightData, 0);
655 
656  // depth multiplier will be 1 for the depthwise convolution
657  const unsigned int weightDimSizes[4] = {
658  static_cast<unsigned int>(1), // depth multiplier
659  static_cast<unsigned int>(inputShape.dim(1)), // #channels
660  kernelH,
661  kernelW};
662 
663  armnn::IConnectableLayer* returnLayer = nullptr;
664  ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
665  Optional<ConstTensor> optionalBiases;
666  vector<float> biasData;
667  if (desc.m_BiasEnabled)
668  {
669  TensorInfo biasInfo;
670 
671  biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
672  GetDataFromBlob(layerParam, biasData, 1);
673 
674  const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
675  biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
676 
677  ConstTensor biases(biasInfo, biasData.data());
678  optionalBiases = Optional<ConstTensor>(biases);
679  }
680  returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc,
681  weights,
682  optionalBiases,
683  layerParam.name().c_str());
684 
685  if (!returnLayer)
686  {
687  throw ParseException(
688  boost::str(
689  boost::format(
690  "Failed to create depthwise convolution layer. "
691  "Layer=%1% #filters=%2% %3%") %
692  layerParam.name() %
693  numFilters %
694  CHECK_LOCATION().AsString()));
695  }
696  armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
697  inputConnection.Connect(returnLayer->GetInputSlot(0));
698  returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
699  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
700 }
701 
702 void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
703 {
704  // Ignored Caffe Parameters
705  // * Dilation Size
706  // * Weight Filler
707  // * Bias Filler
708  // * Engine
709  // * Force nd_im2col
710  // * Axis
711 
712  // Not Available ArmNN Interface Parameters
713  // * Rounding policy;
714 
715  ARMNN_ASSERT(layerParam.type() == "Convolution");
716  ValidateNumInputsOutputs(layerParam, 1, 1);
717 
718  ConvolutionParameter convParam = layerParam.convolution_param();
719  BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
720  const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
721  unsigned int numFilters = convParam.num_output();
722 
723  const auto notFound = std::numeric_limits<unsigned int>::max();
724 
725  unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
726  kernel_h, kernel_size, unsigned int, notFound);
727  unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
728  kernel_w, kernel_size, unsigned int, notFound);
729 
730  unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
731  stride_h, stride, unsigned int, 1u);
732  unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
733  stride_w, stride, unsigned int, 1u);
734 
735  unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
736  pad_h, pad, unsigned int, 0u);
737  unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
738  pad_w, pad, unsigned int, 0u);
739 
740  Convolution2dDescriptor convolution2dDescriptor;
741  convolution2dDescriptor.m_PadLeft = padW;
742  convolution2dDescriptor.m_PadRight = padW;
743  convolution2dDescriptor.m_PadTop = padH;
744  convolution2dDescriptor.m_PadBottom = padH;
745  convolution2dDescriptor.m_StrideX = strideW;
746  convolution2dDescriptor.m_StrideY = strideH;
747  convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
748 
749  if (numGroups > numFilters)
750  {
751  throw ParseException(
752  boost::str(
753  boost::format(
754  "Error parsing Convolution: %1%. "
755  "The 'group'=%2% parameter cannot be larger than the "
756  "number of filters supplied ='%3%'. %4%") %
757  layerParam.name() %
758  numGroups %
759  numFilters %
760  CHECK_LOCATION().AsString()));
761  }
762 
763  if (inputShape.dim_size() != 4)
764  {
765  throw ParseException(
766  boost::str(
767  boost::format(
768  "Convolution input shape is expected to have 4 dimensions. "
769  "%1%'s input has only %2%. %3%") %
770  layerParam.name() %
771  inputShape.dim_size() %
772  CHECK_LOCATION().AsString()));
773  }
774 
775  if (numGroups > 1)
776  {
777  if (numGroups > inputShape.dim(1))
778  {
779  throw ParseException(
780  boost::str(
781  boost::format(
782  "Error parsing Convolution: %1%. "
783  "The 'group'=%2% parameter cannot be larger than the "
784  "channel of the input shape=%3% (in NCHW format). %4%") %
785  layerParam.name() %
786  numGroups %
787  inputShape.dim(1) %
788  CHECK_LOCATION().AsString()));
789  }
790  else if (numGroups == inputShape.dim(1))
791  {
792  // we use a depthwise convolution here, because the number of groups equals to the
793  // input channels
794  AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
795  return;
796  }
797  else
798  {
799  // we split the input by channels into channels/groups separate convolutions
800  // and concatenate the results afterwards
801  AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
802  return;
803  }
804  }
805 
806  // NOTE: at this point we only need to handle #group=1 case, all other cases should be
807  // handled by the AddConvLayer* helpers
808 
809  // Populate convolution output tensor descriptor dimensions
810  BlobShape outputShape;
811  outputShape.add_dim(0);
812  outputShape.set_dim(0, inputShape.dim(0));
813  outputShape.add_dim(1);
814  outputShape.set_dim(1, numFilters);
815  outputShape.add_dim(2);
816  outputShape.set_dim(
817  2, (static_cast<int>(
818  static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
819  static_cast<float>(strideH)) + 1));
820  outputShape.add_dim(3);
821  outputShape.set_dim(
822  3, (static_cast<int>(
823  static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
824  static_cast<float>(strideW)) + 1));
825 
826  // Load the weight data for ALL groups
827  vector<float> weightData(boost::numeric_cast<size_t>(inputShape.dim(1) *
828  outputShape.dim(1) *
829  kernelH *
830  kernelW));
831  GetDataFromBlob(layerParam, weightData, 0);
832 
833  const unsigned int weightDimSizes[4] = {
834  static_cast<unsigned int>(outputShape.dim(1)), // output channels
835  static_cast<unsigned int>(inputShape.dim(1)), // input channels
836  kernelH,
837  kernelW};
838 
839  armnn::IConnectableLayer* returnLayer = nullptr;
840 
841  // Pull out the weights for this group from that loaded from the model file earlier
842  ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
843  Optional<ConstTensor> optionalBiases;
844  vector<float> biasData;
845  if (convolution2dDescriptor.m_BiasEnabled)
846  {
847  TensorInfo biasInfo;
848 
849  biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
850  GetDataFromBlob(layerParam, biasData, 1);
851 
852  const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
853  biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
854 
855  // Pull out the biases for this group from that loaded from the model file earlier
856  ConstTensor biases(biasInfo, biasData.data());
857  optionalBiases = Optional<ConstTensor>(biases);
858  }
859  returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
860  weights,
861  optionalBiases,
862  layerParam.name().c_str());
863 
864  armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
865  inputConnection.Connect(returnLayer->GetInputSlot(0));
866  returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
867 
868  if (!returnLayer)
869  {
870  throw ParseException(
871  boost::str(
872  boost::format(
873  "Failed to create Convolution layer. "
874  "Layer=%1% #groups=%2% #filters=%3% %4%") %
875  layerParam.name() %
876  numGroups %
877  numFilters %
878  CHECK_LOCATION().AsString()));
879  }
880 
881  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
882 }
883 
884 void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
885 {
886  // Ignored Caffe Parameters
887  // Stochastic Pooling
888  // Engine
889 
890  ValidateNumInputsOutputs(layerParam, 1, 1);
891  PoolingParameter param = layerParam.pooling_param();
892  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
893 
894  const auto notFound = std::numeric_limits<unsigned int>::max();
895 
896  unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
897  kernel_h, kernel_size, unsigned int, notFound);
898  unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
899  kernel_w, kernel_size, unsigned int, notFound);
900 
901  if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
902  {
903  kernel_h = inputInfo.GetShape()[2];
904  kernel_w = inputInfo.GetShape()[3];
905  }
906 
907  unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
908  stride_h, stride, unsigned int, notFound);
909  unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
910  stride_h, stride, unsigned int, notFound);
911 
912  if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
913  {
914  stride_h = 1;
915  stride_w = 1;
916  }
917 
918  unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
919  pad_h, pad, unsigned int, 0u);
920  unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
921  pad_w, pad, unsigned int, 0u);
922 
923  // Populate Weight and Bias Filter Descriptor
924  Pooling2dDescriptor pooling2dDescriptor;
925  if (param.has_pool())
926  {
927  PoolingParameter_PoolMethod p = param.pool();
928  switch (p)
929  {
930  case PoolingParameter_PoolMethod_MAX:
931  {
932  pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
933  break;
934  }
935  case PoolingParameter_PoolMethod_AVE:
936  {
937  pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
938  break;
939  }
940  case PoolingParameter_PoolMethod_STOCHASTIC:
941  {
942  throw ParseException(
943  boost::str(
944  boost::format(
945  "Pooling Layer: Stochastic Pooling Not Supported. Layer=%1% %2%") %
946  layerParam.name() %
947  CHECK_LOCATION().AsString()));
948  }
949  default:
950  {
951  throw ParseException(
952  boost::str(
953  boost::format(
954  "Pooling Layer: unknown pooling method: %1% for layer: %2% %3%") %
955  p %
956  layerParam.name() %
957  CHECK_LOCATION().AsString()));
958  }
959  }
960  }
961  else
962  {
963  throw ParseException(
964  boost::str(
965  boost::format(
966  "No Pooling Method Defined for %1% %2%") %
967  layerParam.name() %
968  CHECK_LOCATION().AsString()));
969  }
970 
971  pooling2dDescriptor.m_PadLeft = pad_w;
972  pooling2dDescriptor.m_PadRight = pad_w;
973  pooling2dDescriptor.m_PadTop = pad_h;
974  pooling2dDescriptor.m_PadBottom = pad_h;
975  pooling2dDescriptor.m_StrideX = stride_w;
976  pooling2dDescriptor.m_StrideY = stride_h;
977  pooling2dDescriptor.m_PoolWidth = kernel_w;
978  pooling2dDescriptor.m_PoolHeight = kernel_h;
979 
980  pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
981  pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
982 
983  armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
984  layerParam.name().c_str());
985 
986  TensorInfo outputInfo(
987  { inputInfo.GetShape()[0],
988  inputInfo.GetShape()[1],
989  static_cast<unsigned int>(ceil(
990  static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
991  boost::numeric_cast<float>(stride_h))) + 1,
992  static_cast<unsigned int>(ceil(
993  static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
994  boost::numeric_cast<float>(stride_w))) + 1 },
995  DataType::Float32);
996 
997  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
998  poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
999  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
1000 }
1001 
1002 void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
1003 {
1004  ValidateNumInputsOutputs(layerParam, 1, 1);
1005 
1006  const string& name = layerParam.name();
1007  const ReLUParameter& param = layerParam.relu_param();
1008 
1009  ActivationDescriptor activationDescriptor;
1010  const float negativeSlope = param.negative_slope();
1011  if (negativeSlope == 0.0f)
1012  {
1013  activationDescriptor.m_Function = ActivationFunction::ReLu;
1014  }
1015  else
1016  {
1017  activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
1018  activationDescriptor.m_A = negativeSlope;
1019  }
1020 
1021  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1022  IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
1023  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
1024  activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1025  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
1026 }
1027 
1028 void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
1029 {
1030  ValidateNumInputsOutputs(layerParam, 1, 1);
1031 
1032  LRNParameter param = layerParam.lrn_param();
1033 
1034  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1035 
1036  // Ignored BATCH NORMALIZATION Caffe Parameters.
1037  // Ignored MVN Caffe Parameters.
1038  // Ignored LRN Caffe Parameters.
1039  // Engine
1040 
1041  NormalizationDescriptor normalizationDescriptor;
1042  if (param.has_norm_region())
1043  {
1044  LRNParameter_NormRegion n = param.norm_region();
1045  switch (n)
1046  {
1047  case LRNParameter_NormRegion_ACROSS_CHANNELS:
1048  {
1049  normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1050  break;
1051  }
1052  case LRNParameter_NormRegion_WITHIN_CHANNEL:
1053  {
1054  normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1055  break;
1056  }
1057  default:
1058  {
1059  throw ParseException(
1060  boost::str(
1061  boost::format(
1062  "Unknown region %1% for LRN layer %2% %3%") %
1063  n %
1064  layerParam.name() %
1065  CHECK_LOCATION().AsString()));
1066  }
1067  }
1068  }
1069  else
1070  {
1071  // Caffe defaults to normalization across channels.
1072  normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1073  }
1074 
1075  normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1076  if (param.has_local_size())
1077  {
1078  normalizationDescriptor.m_NormSize = param.local_size();
1079  }
1080  else
1081  {
1082  throw ParseException(
1083  boost::str(
1084  boost::format(
1085  "local_size not defined for LRN layer %1% %2%") %
1086  layerParam.name() %
1087  CHECK_LOCATION().AsString()));
1088  }
1089 
1090  if (param.has_alpha())
1091  {
1092  normalizationDescriptor.m_Alpha = param.alpha();
1093  normalizationDescriptor.m_Alpha /= boost::numeric_cast<float>(param.local_size());
1094  }
1095  else
1096  {
1097  throw ParseException(
1098  boost::str(
1099  boost::format(
1100  "Alpha not defined for LRN layer %1% %2%") %
1101  layerParam.name() %
1102  CHECK_LOCATION().AsString()));
1103  }
1104  if (param.has_beta())
1105  {
1106  normalizationDescriptor.m_Beta = param.beta();
1107  }
1108  else
1109  {
1110  throw ParseException(
1111  boost::str(
1112  boost::format(
1113  "Beta not defined for LRN layer %1% %2%") %
1114  layerParam.name() %
1115  CHECK_LOCATION().AsString()));
1116  }
1117 
1118  if (param.has_k())
1119  {
1120  normalizationDescriptor.m_K = param.k();
1121  }
1122  else
1123  {
1124  normalizationDescriptor.m_K = 1;
1125  }
1126 
1127  IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1128  layerParam.name().c_str());
1129  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1130  normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1131 
1132  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1133 }
1134 
1135 void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
1136 {
1137  InnerProductParameter param = layerParam.inner_product_param();
1138 
1139  ValidateNumInputsOutputs(layerParam, 1, 1);
1140 
1141  unsigned int outputSize = param.num_output();
1142 
1143  // Ignored Caffe Parameters:
1144  // Weight Filler
1145  // Bias Filler
1146  // Engine
1147  // Axis
1148 
1149  FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1150 
1151  if (param.has_transpose())
1152  {
1153  // If true, assumes transposed weights.
1154  tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1155  }
1156  else
1157  {
1158  // Caffe defaults to transposed.
1159  tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1160  }
1161 
1162  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1163 
1164  TensorInfo weightInfo;
1165  TensorInfo biasInfo;
1166 
1167  // Allows implicit flattening of extra dimensions.
1168  unsigned int inputSize = inputInfo.GetShape()[1];
1169  for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1170  {
1171  inputSize *= inputInfo.GetShape()[i];
1172  }
1173 
1174  const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
1175  const unsigned int swTD[2] = { outputSize, inputSize };
1176  ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
1177 
1178  tensorFullyConnectedDescriptor.m_BiasEnabled = true;
1179  // Todo: check whether bias enabled.
1180  armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1181  if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1182  {
1183  // BIAS VALUE
1184  const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
1185 
1186  const unsigned int sbTD[1] = { outputSize };
1187 
1188  ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
1189 
1190  fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1191  weights,
1192  Optional<ConstTensor>(biases),
1193  layerParam.name().c_str());
1194  }
1195  else
1196  {
1197  fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1198  weights,
1199  EmptyOptional(),
1200  layerParam.name().c_str());
1201  }
1202 
1203  TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1204  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1205  fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1206  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1207 }
1208 
1209 void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
1210 {
1211  ValidateNumInputsOutputs(layerParam, 1, 1);
1212 
1213  SoftmaxParameter param = layerParam.softmax_param();
1214 
1215  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1216 
1217  // Ignored Caffe Parameters:
1218  // axis
1219  // Engine
1220 
1221  armnn::SoftmaxDescriptor softmaxDescriptor;
1222  softmaxDescriptor.m_Axis = 1;
1223  armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1224  softmaxDescriptor,
1225  layerParam.name().c_str());
1226  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1227  softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1228  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1229 }
1230 
1231 void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
1232 {
1233  ValidateNumInputsOutputs(layerParam, 2, 1);
1234 
1235  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1236 
1237  // Ignored Caffe Parameters:
1238  // coeff
1239 
1240  EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
1241 
1242  if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1243  {
1244  operation = layerParam.eltwise_param().operation();
1245  }
1246 
1247  armnn::IConnectableLayer* newLayer = nullptr;
1248  switch (operation)
1249  {
1250  case EltwiseParameter_EltwiseOp_SUM:
1251  {
1252  newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1253  break;
1254  }
1255  case EltwiseParameter_EltwiseOp_PROD:
1256  {
1257  newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1258  break;
1259  }
1260  default:
1261  {
1262  throw ParseException(
1263  boost::str(
1264  boost::format(
1265  "Unsupported operation %1% in Eltwise layer %2% %3%") %
1266  operation %
1267  layerParam.name() %
1268  CHECK_LOCATION().AsString()));
1269  }
1270  }
1271 
1272  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1273  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1274  newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1275  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1276 }
1277 
1278 void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
1279 {
1280  unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
1281  // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
1282  unsigned int concatDim = 1;
1283  unsigned int numOfDims = 4;
1284 
1285  // we only consider 4-D tensor here
1286  OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
1287  std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1288 
1289  unsigned int mergeDim = 0;
1290  for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1291  {
1292  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
1293  layerParam.bottom(boost::numeric_cast<int>(viewIndex))).GetTensorInfo();
1294  // Checks whether the dimensions of the input tensors are actually 4.
1295  if (inputInfo.GetNumDimensions()!=4)
1296  {
1297  throw ParseException(
1298  boost::str(
1299  boost::format(
1300  "The number of dimensions for input tensors of "
1301  "the concatenation op should be 4. Inputs of %1% has "
1302  "%2% dimensions. %3%") %
1303  layerParam.name() %
1304  inputInfo.GetNumDimensions() %
1305  CHECK_LOCATION().AsString()));
1306  }
1307 
1308  mergeDimSizes[0] = inputInfo.GetShape()[0];
1309  mergeDimSizes[1] = inputInfo.GetShape()[1];
1310  mergeDimSizes[2] = inputInfo.GetShape()[2];
1311  mergeDimSizes[3] = inputInfo.GetShape()[3];
1312 
1313  for (unsigned int j = 0; j < concatDim; ++j)
1314  {
1315  concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1316  }
1317 
1318  concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1319  mergeDim += mergeDimSizes[concatDim];
1320 
1321  for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1322  {
1323  concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1324  }
1325  }
1326  mergeDimSizes[concatDim] = mergeDim;
1327 
1328  armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
1329  for (unsigned int i = 0; i < numInputs; ++i)
1330  {
1331  armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(boost::numeric_cast<int>(i)));
1332  outputSlot.Connect(concatlayer->GetInputSlot(i));
1333  }
1334 
1335  concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1336  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1337 }
1338 
1339 void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
1340 {
1341  ValidateNumInputsOutputs(layerParam, 1, 1);
1342 
1343  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1344 
1345  string name = layerParam.name();
1346 
1347  BatchNormParameter param = layerParam.batch_norm_param();
1348  // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1349  // when the network is in the testing phase).
1350  if (param.has_use_global_stats())
1351  {
1352  if (!param.use_global_stats())
1353  {
1354  throw ParseException(
1355  boost::str(
1356  boost::format(
1357  "Error parsing Batch Norm layer '%1%': "
1358  "Parameter 'use_global_stats' is set to false, which is "
1359  "unsupported (value used for training). %2%") %
1360  name %
1361  CHECK_LOCATION().AsString()));
1362  }
1363  }
1364 
1366  desc.m_Eps = param.eps();
1367 
1368  unsigned int channels = inputInfo.GetShape()[1];
1369  unsigned int shape[] = {channels};
1370 
1371  vector<float> meanData(channels);
1372  GetDataFromBlob(layerParam, meanData, 0);
1373 
1374  vector<float> varianceData(channels);
1375  GetDataFromBlob(layerParam, varianceData, 1);
1376 
1377  // Reads moving average factor and applies scaling (if required).
1378  const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(2));
1379  const float movingAverageFactor = blob.data(boost::numeric_cast<int>(0));
1380  if(movingAverageFactor != 0.0f)
1381  {
1382  const float scaleFactor = 1.0f / movingAverageFactor;
1383  auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1384 
1385  std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1386  std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1387  }
1388 
1389  // Identifies scale operation.
1390  vector<float> betaData(channels, 0.0f);
1391  vector<float> gammaData(channels, 1.0f);
1392 
1393  ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1394  ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1395  ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1396  ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1397 
1398  armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1399  mean, variance, beta, gamma, name.c_str());
1400  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1401  batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1402  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1403 }
1404 
1405 void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
1406 {
1407  // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
1408  ValidateNumInputsOutputs(layerParam, 1, 1);
1409 
1410  const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1411 
1412  string name = layerParam.name();
1413 
1414  ScaleParameter param = layerParam.scale_param();
1415  if (param.axis() != 1)
1416  {
1417  // Would have to use something other than BatchNormalizationLayer in this case
1418  throw ParseException(
1419  boost::str(
1420  boost::format(
1421  "Loading Scale Layer: Only axis 1 is supported currently. "
1422  "Layer=%1% Axis=%2% %3%") %
1423  layerParam.name() %
1424  param.axis() %
1425  CHECK_LOCATION().AsString()));
1426  }
1427 
1428  unsigned int channels = inputInfo.GetShape()[1];
1429  unsigned int shape[] = {channels};
1430 
1432  desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
1433  vector<float> meanData(channels, 0.0f);
1434  vector<float> varianceData(channels, 1.0f);
1435  vector<float> betaData(channels, 0.0f);
1436  vector<float> gammaData(channels);
1437 
1438  GetDataFromBlob(layerParam, gammaData, 0);
1439 
1440  if(param.has_bias_term())
1441  {
1442  GetDataFromBlob(layerParam, betaData, 1);
1443  }
1444 
1445  ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1446  ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1447  ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1448  ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1449 
1450  armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1451  mean, variance, beta, gamma, name.c_str());
1452  GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1453  batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1454  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1455 }
1456 
1457 void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
1458 {
1459  // Used in caffe to duplicate memory - not necessary in armnn.
1460  if (layerParam.bottom_size() != 1)
1461  {
1462  throw ParseException(
1463  boost::str(
1464  boost::format(
1465  "Split layer '%1%' should have exactly 1 bottom. "
1466  "#bottoms=%2% %3%") %
1467  layerParam.name() %
1468  layerParam.bottom_size() %
1469  CHECK_LOCATION().AsString()));
1470  }
1471  armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1472  for (int i = 0; i < layerParam.top_size(); i++)
1473  {
1474  SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1475  }
1476 }
1477 
1478 void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
1479 {
1480  // Ignored for inference, so patch the single input to its single output.
1481  if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1482  {
1483  throw ParseException(
1484  boost::str(
1485  boost::format(
1486  "Dropout layer '%1%' should have exactly 1 bottom and 1 top. "
1487  "#bottoms=%2% #tops=%3% %4%") %
1488  layerParam.name() %
1489  layerParam.bottom_size() %
1490  layerParam.top_size() %
1491  CHECK_LOCATION().AsString()));
1492  }
1493  SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1494 }
1495 
1498  const armnn::TensorInfo& tensorInfo)
1499 {
1500  return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1501 }
1502 
1505  const armnn::TensorInfo& tensorInfo)
1506 {
1507  return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1508 }
1509 
1512  const armnn::TensorInfo& tensorInfo,
1513  const char* bindingPointDesc,
1514  std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1515 {
1516  const std::string layerName = layer->GetName();
1517  auto it = nameToBindingInfo.find(layerName);
1518  if (it == nameToBindingInfo.end())
1519  {
1520  nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1521  }
1522  else
1523  {
1524  throw ParseException(
1525  boost::str(
1526  boost::format(
1527  "Id %1% used by more than one %2% layer %3%") %
1528  id %
1529  bindingPointDesc %
1530  CHECK_LOCATION().AsString()));
1531  }
1532 }
1533 
1535 {
1536  auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1537  if (it != m_ArmnnOutputSlotForCaffeTop.end())
1538  {
1539  return *it->second;
1540  }
1541  else
1542  {
1543  throw ParseException(
1544  boost::str(
1545  boost::format(
1546  "Could not find armnn output slot for Caffe top '%1%' %2%") %
1547  caffeTopName %
1548  CHECK_LOCATION().AsString()));
1549  }
1550 }
1551 
1553  const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
1554 {
1555  auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1556  if (it == m_ArmnnOutputSlotForCaffeTop.end())
1557  {
1558  m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1559  }
1560  else
1561  {
1562  throw ParseException(
1563  boost::str(
1564  boost::format(
1565  "Attempting to add duplicate entry for Caffe top '%1%' %2%") %
1566  caffeTopName %
1567  CHECK_LOCATION().AsString()));
1568  }
1569 }
1570 
1571 // Note: can move to CaffeParser when/if we optimise the text/string format
1572 // to load on a layer by layer basis
1573 void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
1574 {
1575  // Finds layers with the same top.
1576  std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1577  for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1578  {
1579  caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
1580  std::string name = layer.name();
1581  for (int i = 0; i < layer.top_size(); ++i)
1582  {
1583  layersByTop[layer.top(i)].push_back(&layer);
1584  }
1585  }
1586 
1587  // For each set of layers with the same top, resolves them to a linear chain rather than in-place layers.
1588  // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1589  for (auto layersWithSameTopIt : layersByTop)
1590  {
1591  const std::string& top = layersWithSameTopIt.first;
1592  const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1593 
1594  // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
1595  // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1596  for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1597  {
1598  caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1599  caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1600  if (layer1.top_size() != 1)
1601  {
1602  throw ParseException(
1603  boost::str(
1604  boost::format(
1605  "Node '%1%' is an in-place layer but doesn't have exactly one "
1606  "top. It has %2% instead. %3%") %
1607  layer1.name() %
1608  layer1.top_size() %
1609  CHECK_LOCATION().AsString()));
1610  }
1611  std::string newTop = layer1.name() + "_top";
1612  layer1.set_top(0, newTop);
1613  if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1614  {
1615  throw ParseException(
1616  boost::str(
1617  boost::format(
1618  "Node '%1%' is an in-place layer but "
1619  "doesn't have exactly one bottom, or it doesn't match its top. "
1620  "#bottoms=%2%, first bottom is %3%, top is %4% %5%") %
1621  layer2.name() %
1622  layer2.bottom(0) %
1623  top %
1624  CHECK_LOCATION().AsString()));
1625  }
1626  layer2.set_bottom(0, newTop);
1627  }
1628  }
1629 }
1630 
1631 // Note: can move to CaffeParser when/if we optimise the text/string format
1632 // to load on a layer by layer basis
1633 void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
1634 {
1635  // Caffe models sometimes have an implicit input layer.
1636  // In that case, add an explicit one.
1637  if (netParameter.input_size() > 0)
1638  {
1639  LayerParameter* newLayer = netParameter.add_layer();
1640 
1641  newLayer->set_type("Input");
1642  newLayer->set_name(netParameter.input(0));
1643  newLayer->add_top(netParameter.input(0));
1644 
1645  InputParameter* inputParam = newLayer->mutable_input_param();
1646  BlobShape* shape = inputParam->add_shape();
1647 
1648  int dim_size = netParameter.input_dim_size();
1649  for (int i = 0; i < dim_size; ++i)
1650  {
1651  shape->add_dim(netParameter.input_dim(i));
1652  }
1653  }
1654 
1655  // Replaces in-place layers with regular ones to make the rest of the parsing easier.
1656  ResolveInPlaceLayers(netParameter);
1657 
1658  // Creates a lookup of Caffe layers by name.
1659  for (int i = 0; i < netParameter.layer_size(); ++i)
1660  {
1661  const caffe::LayerParameter& layer = netParameter.layer(i);
1662  for (int i = 0; i < layer.top_size(); ++i)
1663  {
1664  m_CaffeLayersByTopName[layer.top(i)] = &layer;
1665  }
1666  }
1667 
1668  // Finds the output layers the user requested.
1669  std::vector<const caffe::LayerParameter*> targetLayers;
1670  for (const std::string& requestedOutputName : m_RequestedOutputs)
1671  {
1672  auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1673  if (nodeIt == m_CaffeLayersByTopName.end())
1674  {
1675  throw ParseException(
1676  boost::str(
1677  boost::format(
1678  "Couldn't find requested output layer '%1%' in graph %2%") %
1679  requestedOutputName %
1680  CHECK_LOCATION().AsString()));
1681  }
1682  targetLayers.push_back(nodeIt->second);
1683  }
1684 
1685  // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
1686  std::vector<const caffe::LayerParameter*> sortedNodes;
1687  if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1688  targetLayers,
1689  [this](const caffe::LayerParameter* node)
1690  {
1691  return GetInputs(*node);
1692  },
1693  sortedNodes))
1694  {
1695  throw ParseException(
1696  boost::str(
1697  boost::format(
1698  "Cycle detected in graph. #nodes: %1% %2%") %
1699  sortedNodes.size() %
1700  CHECK_LOCATION().AsString()));
1701  }
1702 
1703  // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
1704  for (const caffe::LayerParameter* current : sortedNodes)
1705  {
1706  auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1707  if (it == ms_CaffeLayerNameToParsingFunctions.end())
1708  {
1709  throw ParseException(
1710  boost::str(
1711  boost::format("Unsupported layer type: '%1%' for layer %2% %3%") %
1712  current->type() %
1713  current->name() %
1714  CHECK_LOCATION().AsString()));
1715  }
1716  auto func = it->second;
1717  (this->*func)(*current);
1718  }
1719 
1720  // Adds ArmNN output layers connected to each requested output.
1721  for (const std::string& requestedOutput : m_RequestedOutputs)
1722  {
1723  armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1724 
1727  armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1728  outputSlot.Connect(outputLayer->GetInputSlot(0));
1729 
1730  TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1731  }
1732 }
1733 
1735  const std::map<std::string, armnn::TensorShape>& inputShapes,
1736  const std::vector<std::string>& requestedOutputs)
1737 {
1738  FILE* fd = fopen(graphFile, "r");
1739 
1740  if (fd == nullptr)
1741  {
1742  throw FileNotFoundException(
1743  boost::str(
1744  boost::format(
1745  "Failed to open graph file: %1% %2%") %
1746  graphFile %
1747  CHECK_LOCATION().AsString()));
1748  }
1749 
1750  // Parses the file into a message.
1751  NetParameter netParam;
1752  auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1753  bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1754  delete input;
1755  fclose(fd);
1756 
1757  if (!success)
1758  {
1759  throw ParseException(
1760  boost::str(
1761  boost::format(
1762  "Failed to parse graph file: %1% %2%") %
1763  graphFile %
1764  CHECK_LOCATION().AsString()));
1765  }
1766 
1767  return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1768 }
1769 
1771  const std::map<std::string, armnn::TensorShape>& inputShapes,
1772  const std::vector<std::string>& requestedOutputs)
1773 {
1774  // Parses the string into a message.
1775  NetParameter netParam;
1776  bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1777 
1778  if (!success)
1779  {
1780  throw ParseException(
1781  boost::str(
1782  boost::format(
1783  "Failed to parse graph string %1%") %
1784  CHECK_LOCATION().AsString()));
1785  }
1786 
1787  return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1788 }
1789 
1791  const std::map<std::string, armnn::TensorShape>& inputShapes,
1792  const std::vector<std::string>& requestedOutputs)
1793 {
1794  FILE* fd = fopen(graphFile, "rb");
1795 
1796  if (fd == nullptr)
1797  {
1798  throw FileNotFoundException(
1799  boost::str(
1800  boost::format(
1801  "Failed to open graph file at: %1% %2%") %
1802  graphFile %
1803  CHECK_LOCATION().AsString()));
1804  }
1805 
1806  // Parses the file into a message.
1807  NetParameter netParam;
1808 
1809  FileInputStream inStream(fileno(fd));
1810  CodedInputStream codedStream(&inStream);
1811  codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
1812  bool success = netParam.ParseFromCodedStream(&codedStream);
1813  fclose(fd);
1814 
1815  if (!success)
1816  {
1817  throw ParseException(
1818  boost::str(
1819  boost::format(
1820  "Failed to parse protobuf file: %1% %2%") %
1821  graphFile %
1822  CHECK_LOCATION().AsString()));
1823  }
1824 
1825  return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1826 }
1827 
1828 // Note: can move to CaffeParser when/if we optimise the text/string format
1829 // to load on a layer by layer basis
1831  const std::map<std::string, armnn::TensorShape>& inputShapes,
1832  const std::vector<std::string>& requestedOutputs)
1833 {
1836 
1838 
1839  m_InputShapes = inputShapes;
1840  if (requestedOutputs.size() == 0)
1841  {
1842  throw ParseException("requestedOutputs must have at least one entry");
1843  }
1844  m_RequestedOutputs = requestedOutputs;
1845 
1846  try
1847  {
1848  LoadNetParam(netParam);
1849  }
1850  catch (const ParseException& e)
1851  {
1852  Cleanup();
1853  throw e;
1854  }
1855 
1856  Cleanup();
1857 
1858  return move(m_Network);
1859 }
1860 
1862  // cleanup, in case we reuse this parser
1863  m_InputShapes.clear();
1864  m_RequestedOutputs.clear();
1866  // NOTE: when we get the text/string format
1867  // optimised for memory then this data structure can
1868  // also move to the CaffeParser class
1869  m_CaffeLayersByTopName.clear();
1870 }
1871 
1872 }
uint32_t m_PadBottom
Padding bottom value in the height dimension.
+
bool m_BiasEnabled
Enable/disable bias.
+
virtual unsigned int GetNumOutputSlots() const =0
Returns the number of connectable output slots.
+
A ViewsDescriptor for the SplitterLayer.
+
Interface for a layer that is connectable to other layers via InputSlots and OutputSlots.
Definition: INetwork.hpp:61
+ +
void ParseConvLayer(const caffe::LayerParameter &layerParam)
+
uint32_t m_PadBottom
Padding bottom value in the height dimension.
+
virtual BindingPointInfo GetNetworkInputBindingInfo(const std::string &name) const override
Retrieves binding info (layer id and tensor info) for the network input identified by the given layer...
+
float m_K
Kappa value used for the across channel normalization equation.
+
int m_Axis
Scalar, defaulted to the last index (-1), specifying the dimension the activation will be performed o...
+
const TensorShape & GetShape() const
Definition: Tensor.hpp:187
+
static void Destroy(ICaffeParser *parser)
+
uint32_t m_PadLeft
Padding left value in the width dimension.
+ + +
armnn::IOutputSlot & GetArmnnOutputSlotForCaffeTop(const std::string &caffeTopName) const
Retrieves the Armnn IOutputSlot representing the given Caffe top.
+ +
virtual armnn::INetworkPtr CreateNetworkFromString(const char *protoText, const std::map< std::string, armnn::TensorShape > &inputShapes, const std::vector< std::string > &requestedOutputs) override
Creates the network directly from protobuf text in a string. Useful for debugging/testing.
+
bool m_TransposeWeightMatrix
Enable/disable transpose weight matrix.
+ +
uint32_t m_PoolWidth
Pooling width value.
+
A Convolution2dDescriptor for the Convolution2dLayer.
+
float m_Alpha
Alpha value for the normalization equation.
+
uint32_t m_PadLeft
Padding left value in the width dimension.
+
static const std::map< std::string, OperationParsingFunction > ms_CaffeLayerNameToParsingFunctions
Maps Caffe layer names to parsing member functions.
+ +
float m_Eps
Value to add to the variance. Used to avoid dividing by zero.
+
PaddingMethod m_PaddingMethod
The padding method to be used. (Exclude, IgnoreValue).
+
void LoadNetParam(caffe::NetParameter &netParameter)
does the actual conversion from caffe::NetParameter to armnn::INetwork
+
void ParseInputLayer(const caffe::LayerParameter &layerParam)
Adds an armnn layer to m_Network given a Caffe LayerParameter of the correct type and is responsible ...
+ +
uint32_t m_PadTop
Padding top value in the height dimension.
+
uint32_t m_PadRight
Padding right value in the width dimension.
+
Copyright (c) 2020 ARM Limited.
+ +
static std::pair< armnn::LayerBindingId, armnn::TensorInfo > GetBindingInfo(const std::string &layerName, const char *bindingPointDesc, const std::unordered_map< std::string, BindingPointInfo > &bindingInfos)
+
std::vector< std::string > m_RequestedOutputs
+
Caffe networks are loaded from protobuf files (binary or text) using the protobuf library and the gen...
+ +
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
+
int LayerBindingId
Type of identifiers for bindable layers (inputs, outputs).
Definition: Types.hpp:194
+
virtual void SetTensorInfo(const TensorInfo &tensorInfo)=0
+
NormalizationAlgorithmMethod m_NormMethodType
Normalization method algorithm to use (LocalBrightness, LocalContrast).
+
virtual armnn::INetworkPtr CreateNetworkFromTextFile(const char *graphFile, const std::map< std::string, armnn::TensorShape > &inputShapes, const std::vector< std::string > &requestedOutputs) override
Create the network from a protobuf text file on disk.
+
void SetShape(const TensorShape &newShape)
Definition: Tensor.hpp:189
+
void ParseBatchNormLayer(const caffe::LayerParameter &layerParam)
+ +
void TrackOutputBinding(armnn::IConnectableLayer *layer, armnn::LayerBindingId id, const armnn::TensorInfo &tensorInfo)
+
uint32_t m_PoolHeight
Pooling height value.
+
uint32_t m_PadTop
Padding top value in the height dimension.
+ +
uint32_t m_StrideX
Stride value when proceeding through input for the width dimension.
+ +
void ResolveInPlaceLayers(caffe::NetParameter &netParameter)
Modifies the Caffe network to replace "in-place" layers (whose top() and bottom() are both the same) ...
+ +
uint32_t m_PadRight
Padding right value in the width dimension.
+
Status SetViewSize(uint32_t view, uint32_t coord, uint32_t value)
Set the size of the views.
+
static ICaffeParserPtr Create()
+
An output connection slot for a layer.
Definition: INetwork.hpp:37
+ +
An OriginsDescriptor for the ConcatLayer.
+
std::unordered_map< std::string, armnn::IOutputSlot * > m_ArmnnOutputSlotForCaffeTop
As we add armnn layers we store the armnn IOutputSlot which corresponds to the Caffe tops...
+
A FullyConnectedDescriptor for the FullyConnectedLayer.
+
bool m_BiasEnabled
Enable/disable bias.
+
A tensor defined by a TensorInfo (shape and data type) and an immutable backing store.
Definition: Tensor.hpp:298
+
std::unordered_map< std::string, BindingPointInfo > m_NetworkInputsBindingInfo
maps input layer names to their corresponding ids and tensor infos
+ +
virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(const char *graphFile, const std::map< std::string, armnn::TensorShape > &inputShapes, const std::vector< std::string > &requestedOutputs) override
Create the network from a protobuf binary file on disk.
+ +
#define ARMNN_ASSERT(COND)
Definition: Assert.hpp:14
+ +
#define GET_OPTIONAL_WITH_FALLBACK(PARAM, PARAM_TYPE, OPTIONAL_VALUE, FALLBACK_VALUE, VALUE_TYPE, DEFAULT_VALUE)
+
std::enable_if_t< std::is_unsigned< Source >::value &&std::is_unsigned< Dest >::value, Dest > numeric_cast(Source source)
Definition: NumericCast.hpp:33
+
std::unordered_map< std::string, BindingPointInfo > m_NetworkOutputsBindingInfo
maps output layer names to their corresponding ids and tensor infos
+
std::unique_ptr< ICaffeParser, void(*)(ICaffeParser *parser)> ICaffeParserPtr
+
armnn::TensorInfo BlobShapeToTensorInfo(const caffe::BlobShape &blobShape) const
Converts Caffe&#39;s protobuf tensor shape format to ArmNN&#39;s.
+
std::map< std::string, armnn::TensorShape > m_InputShapes
+
An ActivationDescriptor for the ActivationLayer.
Definition: Descriptors.hpp:20
+ +
#define CHECK_LOCATION()
Definition: Exceptions.hpp:197
+
void ParseInnerProductLayer(const caffe::LayerParameter &layerParam)
+
void ParseSoftmaxLayer(const caffe::LayerParameter &layerParam)
+
armnn::INetworkPtr CreateNetworkFromNetParameter(caffe::NetParameter &netParam, const std::map< std::string, armnn::TensorShape > &inputShapes, const std::vector< std::string > &requestedOutputs)
Parses a NetParameter loaded into memory from one of the other CreateNetwork*.
+
void ParseReluLayer(const caffe::LayerParameter &layerParam)
+
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
+
void AddConvLayerWithSplits(const caffe::LayerParameter &layerParam, const armnn::Convolution2dDescriptor &desc, unsigned int kernelW, unsigned int kernelH)
ParseConv may use these helpers depending on the group parameter.
+ + +
static void TrackBindingPoint(armnn::IConnectableLayer *layer, armnn::LayerBindingId id, const armnn::TensorInfo &tensorInfo, const char *bindingPointDesc, std::unordered_map< std::string, BindingPointInfo > &nameToBindingInfo)
+
void ParseScaleLayer(const caffe::LayerParameter &layerParam)
+
void ParseDropoutLayer(const caffe::LayerParameter &layerParam)
+
NormalizationAlgorithmChannel m_NormChannelType
Normalization channel algorithm to use (Across, Within).
+
float m_A
Alpha upper bound value used by the activation functions. (BoundedReLu, Linear, TanH, Elu).
Definition: Descriptors.hpp:45
+
virtual BindingPointInfo GetNetworkOutputBindingInfo(const std::string &name) const override
Retrieves binding info (layer id and tensor info) for the network output identified by the given laye...
+ +
EmptyOptional is used to initialize the Optional class in case we want to have default value for an O...
Definition: Optional.hpp:32
+ +
std::pair< armnn::LayerBindingId, armnn::TensorInfo > BindingPointInfo
Definition: Tensor.hpp:245
+
void TrackInputBinding(armnn::IConnectableLayer *layer, armnn::LayerBindingId id, const armnn::TensorInfo &tensorInfo)
+
PoolingAlgorithm m_PoolType
The pooling algorithm to use (Max. Average, L2).
+
void ParseEltwiseLayer(const caffe::LayerParameter &layerParam)
+
static ICaffeParser * CreateRaw()
+ + +
OutputShapeRounding m_OutputShapeRounding
The rounding method for the output shape. (Floor, Ceiling).
+
std::vector< const caffe::LayerParameter * > GetInputs(const caffe::LayerParameter &layerParam)
Find the Caffe layers listed as inputs (bottoms) for a given layer.
+
virtual const IInputSlot & GetInputSlot(unsigned int index) const =0
Get a const input slot handle by slot index.
+
armnn::TensorInfo GetTensorInfo(unsigned int numberOfBatches, unsigned int numberOfChannels, unsigned int height, unsigned int width, const armnn::DataLayout dataLayout, const armnn::DataType dataType)
Definition: TensorUtils.cpp:38
+ + +
virtual const IOutputSlot & GetOutputSlot(unsigned int index) const =0
Get the const output slot handle by slot index.
+
void ParseLRNLayer(const caffe::LayerParameter &layerParam)
+
virtual const char * GetName() const =0
Returns the name of the layer.
+
#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, PARAM_TYPE, OPTIONAL_VALUE, FALLBACK_VECTOR, VALUE_TYPE, DEFAULT_VALUE)
+
void Connect(armnn::IConnectableLayer *from, armnn::IConnectableLayer *to, const armnn::TensorInfo &tensorInfo, unsigned int fromIndex, unsigned int toIndex)
Definition: TestUtils.cpp:12
+
std::unique_ptr< INetwork, void(*)(INetwork *network)> INetworkPtr
Definition: INetwork.hpp:101
+
virtual int Connect(IInputSlot &destination)=0
+
A Pooling2dDescriptor for the Pooling2dLayer.
+
void SetArmnnOutputSlotForCaffeTop(const std::string &caffeTopName, armnn::IOutputSlot &armnnOutputSlot)
+
A NormalizationDescriptor for the NormalizationLayer.
+
static INetworkPtr Create(NetworkOptions networkOptions={})
Definition: Network.cpp:50
+
unsigned int GetNumDimensions() const
Definition: Tensor.hpp:191
+
void AddConvLayerWithDepthwiseConv(const caffe::LayerParameter &layerParam, const armnn::Convolution2dDescriptor &desc, unsigned int kernelW, unsigned int kernelH)
+
A SoftmaxDescriptor for the SoftmaxLayer.
+
float m_Beta
Beta value for the normalization equation.
+
void ParsePoolingLayer(const caffe::LayerParameter &layerParam)
+
uint32_t m_NormSize
Depth radius value.
+
Status SetViewOriginCoord(uint32_t view, uint32_t coord, uint32_t value)
Set the view origin coordinates.
+
std::map< std::string, const caffe::LayerParameter * > m_CaffeLayersByTopName
+
ActivationFunction m_Function
The activation function to use (Sigmoid, TanH, Linear, ReLu, BoundedReLu, SoftReLu, LeakyReLu, Abs, Sqrt, Square, Elu).
Definition: Descriptors.hpp:43
+ +
void ParseConcatLayer(const caffe::LayerParameter &layerParam)
+
uint32_t m_StrideY
Stride value when proceeding through input for the height dimension.
+
A DepthwiseConvolution2dDescriptor for the DepthwiseConvolution2dLayer.
+
A BatchNormalizationDescriptor for the BatchNormalizationLayer.
+
uint32_t m_PadLeft
Padding left value in the width dimension.
+
BlobShape TensorDescToBlobShape(const TensorInfo &desc)
+
void ParseSplitLayer(const caffe::LayerParameter &layerParam)
+
Status SetViewOriginCoord(uint32_t view, uint32_t coord, uint32_t value)
Set the view origin coordinates.
+
+
+ + + + -- cgit v1.2.1