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