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.