blob: b8ce47050543bc5d4c6bed96a8131d5c9e9c7317 [file] [log] [blame]
Laurent Carlier749294b2020-06-01 09:03:17 +01001//
telsoa014fcda012018-03-09 14:13:49 +00002// Copyright © 2017 Arm Ltd. All rights reserved.
David Beckecb56cd2018-09-05 12:52:57 +01003// SPDX-License-Identifier: MIT
telsoa014fcda012018-03-09 14:13:49 +00004//
5#include "CaffeParser.hpp"
telsoa01c577f2c2018-08-31 09:22:23 +01006#include "RecordByRecordCaffeParser.hpp"
telsoa014fcda012018-03-09 14:13:49 +00007
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"
telsoa01c577f2c2018-08-31 09:22:23 +010014#include "VerificationHelpers.hpp"
telsoa014fcda012018-03-09 14:13:49 +000015
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +010016#include <armnn/utility/Assert.hpp>
17
telsoa014fcda012018-03-09 14:13:49 +000018#include <boost/numeric/conversion/cast.hpp>
telsoa014fcda012018-03-09 14:13:49 +000019#include <boost/format.hpp>
telsoa014fcda012018-03-09 14:13:49 +000020
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>
telsoa014fcda012018-03-09 14:13:49 +000032#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.
telsoa01c577f2c2018-08-31 09:22:23 +010047/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't
48/// need any other changes).
telsoa014fcda012018-03-09 14:13:49 +000049///
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
55namespace armnnCaffeParser
56{
57
58using namespace armnn;
59using namespace caffe;
60using namespace std;
61using namespace google::protobuf::io;
62
telsoa01c577f2c2018-08-31 09:22:23 +010063namespace
telsoa014fcda012018-03-09 14:13:49 +000064{
65
telsoa01c577f2c2018-08-31 09:22:23 +010066const float* GetArrayPtrFromBlob(const LayerParameter& layerParam, unsigned int blobIndex)
telsoa014fcda012018-03-09 14:13:49 +000067{
telsoa01c577f2c2018-08-31 09:22:23 +010068 auto nBlobs = layerParam.blobs_size();
69 if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000070 {
telsoa01c577f2c2018-08-31 09:22:23 +010071 throw ParseException(
72 boost::str(
73 boost::format(
74 "Expected data blob at index %1% in layer %2% not found. nBlobs=%2%. %4%") %
75 blobIndex %
76 layerParam.name() %
77 nBlobs %
78 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000079 }
80
81 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
82
telsoa01c577f2c2018-08-31 09:22:23 +010083 const float* arrayPtr = blob.data().data();
84 return arrayPtr;
85}
86
87void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
88{
89 auto nBlobs = layerParam.blobs_size();
90 if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000091 {
telsoa01c577f2c2018-08-31 09:22:23 +010092 throw ParseException(
93 boost::str(
94 boost::format(
95 "Expected data blob at index %1% in layer %2% not found. %3%") %
96 blobIndex %
97 layerParam.name() %
98 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000099 }
100
telsoa01c577f2c2018-08-31 09:22:23 +0100101 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
102
103 size_t blobSize = boost::numeric_cast<size_t>(blob.data_size());
104 if (blobSize != outData.size())
telsoa014fcda012018-03-09 14:13:49 +0000105 {
telsoa01c577f2c2018-08-31 09:22:23 +0100106 throw ParseException(
107 boost::str(
108 boost::format(
109 "Data blob at index %1% in layer %2% has an unexpected size. "
110 "Expected %3% elements but got %4% elements. %5%") %
111 blobIndex %
112 layerParam.name() %
113 outData.size() %
114 blobSize %
115 CHECK_LOCATION().AsString()));
116 }
117
118 int outSizeInt = boost::numeric_cast<int>(outData.size());
119 for (int i = 0; i < outSizeInt; ++i)
120 {
121 outData[static_cast<size_t>(i)] = blob.data(i);
telsoa014fcda012018-03-09 14:13:49 +0000122 }
123}
124
telsoa014fcda012018-03-09 14:13:49 +0000125template <typename T>
126size_t SizeOfVectorData(const vector<T>& vec)
127{
128 return vec.size() * sizeof(T);
129}
130
131void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
132 unsigned int numInputs,
133 unsigned int numOutputs)
134{
135 int numInputsActual = layerParameter.bottom_size();
136 if (numInputs != boost::numeric_cast<unsigned int>(numInputsActual))
137 {
telsoa01c577f2c2018-08-31 09:22:23 +0100138 throw ParseException(
139 boost::str(
140 boost::format("Invalid number of inputs requested %1% for layer %2% "
141 "while only %3% present. %4%") %
142 numInputs %
143 layerParameter.name() %
144 numInputsActual %
145 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000146 }
147
148 int numOutputsActual = layerParameter.top_size();
149 if (numOutputs != boost::numeric_cast<unsigned int>(numOutputsActual))
150 {
telsoa01c577f2c2018-08-31 09:22:23 +0100151 throw ParseException(
152 boost::str(
153 boost::format("Invalid number of outputs requested %1% for layer %2% "
154 "while only %3% present. %4%") %
155 numOutputs %
156 layerParameter.name() %
157 numOutputsActual %
158 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000159 }
160}
161
telsoa01c577f2c2018-08-31 09:22:23 +0100162template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
163ValueType GetOptionalWithFallback(const ParamType& param,
164 ExtractOptional extractOptional,
165 ExtractFallback extractFallback,
166 ValueType defaultValue)
167{
168 auto optValue = extractOptional(param, defaultValue);
169 if (optValue.first)
170 {
171 return optValue.second;
172 }
173 auto fallbackValue = extractFallback(param, defaultValue);
174 return fallbackValue.second;
175}
176
177#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
178 PARAM_TYPE, \
179 OPTIONAL_VALUE, \
180 FALLBACK_VECTOR, \
181 VALUE_TYPE, \
182 DEFAULT_VALUE) \
183 GetOptionalWithFallback( \
184 PARAM, \
185 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
186 { \
187 if (param.has_##OPTIONAL_VALUE ()) \
188 { \
189 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
190 } \
191 else \
192 { \
193 return std::make_pair(false, defaultValue); \
194 } \
195 }, \
196 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
197 { \
198 if (param.FALLBACK_VECTOR##_size() > 0) \
199 { \
200 return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
201 } \
202 else \
203 { \
204 return std::make_pair(false, defaultValue); \
205 } \
206 }, \
207 DEFAULT_VALUE)
208
209#define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
210 PARAM_TYPE, \
211 OPTIONAL_VALUE, \
212 FALLBACK_VALUE, \
213 VALUE_TYPE, \
214 DEFAULT_VALUE) \
215 GetOptionalWithFallback( \
216 PARAM, \
217 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
218 { \
219 if (param.has_##OPTIONAL_VALUE ()) \
220 { \
221 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
222 } \
223 else \
224 { \
225 return std::make_pair(false, defaultValue); \
226 } \
227 }, \
228 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
229 { \
230 if (param.has_##FALLBACK_VALUE ()) \
231 { \
232 return std::make_pair(true, param.FALLBACK_VALUE ()); \
233 } \
234 else \
235 { \
236 return std::make_pair(false, defaultValue); \
237 } \
238 }, \
239 DEFAULT_VALUE)
240
telsoa01c577f2c2018-08-31 09:22:23 +0100241} // namespace <anonymous>
242
243const std::map<std::string, CaffeParserBase::OperationParsingFunction>
244 CaffeParserBase::ms_CaffeLayerNameToParsingFunctions = {
245 { "Input", &CaffeParserBase::ParseInputLayer },
246 { "Convolution", &CaffeParserBase::ParseConvLayer },
247 { "Pooling", &CaffeParserBase::ParsePoolingLayer },
248 { "ReLU", &CaffeParserBase::ParseReluLayer },
249 { "LRN", &CaffeParserBase::ParseLRNLayer },
250 { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
251 { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
252 { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
253 { "Concat", &CaffeParserBase::ParseConcatLayer },
254 { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
255 { "Scale", &CaffeParserBase::ParseScaleLayer },
256 { "Split", &CaffeParserBase::ParseSplitLayer },
257 { "Dropout", &CaffeParserBase::ParseDropoutLayer},
258};
259
260ICaffeParser* ICaffeParser::CreateRaw()
261{
262 return new RecordByRecordCaffeParser();
263}
264
265ICaffeParserPtr ICaffeParser::Create()
266{
267 return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
268}
269
270void ICaffeParser::Destroy(ICaffeParser* parser)
271{
272 delete parser;
273}
274
275CaffeParserBase::CaffeParserBase()
276 : m_Network(nullptr, nullptr)
277{
278
279}
280
281CaffeParser::CaffeParser()
282: CaffeParserBase()
283{
284
285}
286
287BindingPointInfo CaffeParserBase::GetNetworkInputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000288{
289 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
290}
291
telsoa01c577f2c2018-08-31 09:22:23 +0100292BindingPointInfo CaffeParserBase::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000293{
294 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
295}
296
telsoa01c577f2c2018-08-31 09:22:23 +0100297std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
telsoa014fcda012018-03-09 14:13:49 +0000298 const char* bindingPointDesc,
299 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
300{
301 auto it = nameToBindingInfo.find(layerName);
302 if (it == nameToBindingInfo.end())
303 {
telsoa01c577f2c2018-08-31 09:22:23 +0100304 throw InvalidArgumentException(
305 boost::str(
306 boost::format(
307 "Unknown binding %1% for layer '%2%'. %3%") %
308 bindingPointDesc %
309 layerName %
310 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000311 }
312 return it->second;
313}
314
telsoa01c577f2c2018-08-31 09:22:23 +0100315TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
telsoa014fcda012018-03-09 14:13:49 +0000316{
317 std::vector<unsigned int> shape;
318 for (int j = 0; j < blobShape.dim_size(); ++j)
319 {
320 shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
321 }
322
323 return TensorInfo(boost::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
324}
325
326BlobShape TensorDescToBlobShape(const TensorInfo& desc)
327{
328 BlobShape ret;
329 for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
330 {
331 ret.add_dim(i);
332 ret.set_dim(boost::numeric_cast<int>(i), desc.GetShape()[i]);
333 }
334
335 return ret;
336}
337
telsoa01c577f2c2018-08-31 09:22:23 +0100338// Note: can move to CaffeParser when/if we optimise the text/string format
339// to load on a layer by layer basis
340vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000341{
342 std::vector<const caffe::LayerParameter*> ret;
343 ret.reserve(boost::numeric_cast<size_t>(layerParam.bottom_size()));
344 for (int j = 0; j < layerParam.bottom_size(); ++j)
345 {
346 std::string inputName = layerParam.bottom(j);
347 auto inputIt = m_CaffeLayersByTopName.find(inputName);
348 if (inputIt == m_CaffeLayersByTopName.end())
349 {
350 throw ParseException(
telsoa01c577f2c2018-08-31 09:22:23 +0100351 boost::str(
352 boost::format(
353 "Can't find Caffe layer with top called '%1%', "
354 "which is listed as an input of '%2%'. %3%") %
355 inputName %
356 layerParam.name() %
357 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000358 }
359 ret.push_back(inputIt->second);
360 }
361
362 return ret;
363}
364
telsoa01c577f2c2018-08-31 09:22:23 +0100365void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000366{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100367 ARMNN_ASSERT(layerParam.type() == "Input");
telsoa014fcda012018-03-09 14:13:49 +0000368 ValidateNumInputsOutputs(layerParam, 0, 1);
369
370 const InputParameter& param = layerParam.input_param();
371
telsoa01c577f2c2018-08-31 09:22:23 +0100372 const armnn::LayerBindingId inputId = boost::numeric_cast<armnn::LayerBindingId>(
373 m_NetworkInputsBindingInfo.size());
telsoa014fcda012018-03-09 14:13:49 +0000374 armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
375
telsoa01c577f2c2018-08-31 09:22:23 +0100376 // Decides the tensor info for this input. This can be specified in the Caffe network but can also
telsoa014fcda012018-03-09 14:13:49 +0000377 // be overriden by user input (m_inputShapes).
378 armnn::TensorInfo inputTensorInfo;
379
380 const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
381 &param.shape(0) : nullptr;
382 if (originalShape)
383 {
384 inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
385 }
386
387 auto overrideIt = m_InputShapes.find(layerParam.name());
388 if (overrideIt != m_InputShapes.end())
389 {
390 const TensorShape& overrideShape = overrideIt->second;
391 if (originalShape &&
392 ( originalShape->dim(1) != overrideShape[1]
393 || originalShape->dim(2) != overrideShape[2]
394 || originalShape->dim(3) != overrideShape[3]))
395 {
telsoa01c577f2c2018-08-31 09:22:23 +0100396 throw ParseException(
397 boost::str(
398 boost::format(
399 "Parsed input shape for '%1%' is incompatible with the override provided. %2%") %
400 layerParam.name() %
401 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000402 }
403 inputTensorInfo.SetShape(overrideShape);
404 }
405 else if (!originalShape)
406 {
telsoa01c577f2c2018-08-31 09:22:23 +0100407 throw ParseException(
408 boost::str(
409 boost::format(
410 "No input descriptor given for '%1%' and no input shape found in caffe model. %2%") %
411 layerParam.name() %
412 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000413 }
414
415 TrackInputBinding(inputLayer, inputId, inputTensorInfo);
416 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
417 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
418}
419
telsoa01c577f2c2018-08-31 09:22:23 +0100420void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
421 const armnn::Convolution2dDescriptor& desc,
422 unsigned int kernelW,
423 unsigned int kernelH)
telsoa014fcda012018-03-09 14:13:49 +0000424{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100425 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa014fcda012018-03-09 14:13:49 +0000426 ValidateNumInputsOutputs(layerParam, 1, 1);
427
telsoa01c577f2c2018-08-31 09:22:23 +0100428 ConvolutionParameter convParam = layerParam.convolution_param();
telsoa014fcda012018-03-09 14:13:49 +0000429 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa01c577f2c2018-08-31 09:22:23 +0100430 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
telsoa014fcda012018-03-09 14:13:49 +0000431
telsoa01c577f2c2018-08-31 09:22:23 +0100432 // asusme these were already verified by the caller ParseConvLayer() function
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100433 ARMNN_ASSERT(numGroups < inputShape.dim(1));
434 ARMNN_ASSERT(numGroups > 1);
telsoa014fcda012018-03-09 14:13:49 +0000435
436 // Handle grouping
telsoa014fcda012018-03-09 14:13:49 +0000437 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
438
439 vector<string> convLayerNames(numGroups);
440 vector<armnn::IConnectableLayer*> convLayers(numGroups);
441 convLayerNames[0] = layerParam.name();
442
telsoa01c577f2c2018-08-31 09:22:23 +0100443 // This convolution is to be applied to chunks of the input data so add a splitter layer
444
445 // Redirect the convolution input to the splitter
446 unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
447 static_cast<unsigned int>(inputShape.dim(1)),
448 static_cast<unsigned int>(inputShape.dim(2)),
449 static_cast<unsigned int>(inputShape.dim(3))};
450
451 // Split dimension 1 of the splitter output shape and conv input shapes
452 // according to the number of groups
453
454 splitterDimSizes[1] /= numGroups;
455 inputShape.set_dim(1, splitterDimSizes[1]);
456
457 // This is used to describe how the input is to be split
458 ViewsDescriptor splitterDesc(numGroups);
459
460 // Create an output node for each group, giving each a unique name
461 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000462 {
telsoa01c577f2c2018-08-31 09:22:23 +0100463 // Work out the names of the splitter layers child convolutions
464 stringstream ss;
465 ss << layerParam.name() << "_" << g;
466 convLayerNames[g] = ss.str();
telsoa014fcda012018-03-09 14:13:49 +0000467
telsoa01c577f2c2018-08-31 09:22:23 +0100468 splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
telsoa014fcda012018-03-09 14:13:49 +0000469
telsoa01c577f2c2018-08-31 09:22:23 +0100470 // Set the size of the views.
471 for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
telsoa014fcda012018-03-09 14:13:49 +0000472 {
telsoa01c577f2c2018-08-31 09:22:23 +0100473 splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
telsoa014fcda012018-03-09 14:13:49 +0000474 }
475 }
476
telsoa01c577f2c2018-08-31 09:22:23 +0100477 const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
478 armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
telsoa014fcda012018-03-09 14:13:49 +0000479
telsoa01c577f2c2018-08-31 09:22:23 +0100480 inputConnection.Connect(splitterLayer->GetInputSlot(0));
481 for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
482 {
483 splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
484 }
telsoa014fcda012018-03-09 14:13:49 +0000485
486 unsigned int numFilters = convParam.num_output();
487
telsoa01c577f2c2018-08-31 09:22:23 +0100488 // Populates convolution output tensor descriptor dimensions.
telsoa014fcda012018-03-09 14:13:49 +0000489 BlobShape outputShape;
490 outputShape.add_dim(0);
491 outputShape.set_dim(0, inputShape.dim(0));
492 outputShape.add_dim(1);
telsoa01c577f2c2018-08-31 09:22:23 +0100493 // Ensures that dimension 1 of the convolution output is split according to the number of groups.
telsoa014fcda012018-03-09 14:13:49 +0000494 outputShape.set_dim(1, numFilters / numGroups);
495 outputShape.add_dim(2);
496 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100497 2, (static_cast<int>(
498 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
499 static_cast<float>(desc.m_StrideY)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000500 outputShape.add_dim(3);
501 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100502 3, (static_cast<int>(
503 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
504 static_cast<float>(desc.m_StrideX)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000505
506 // Load the weight data for ALL groups
telsoa01c577f2c2018-08-31 09:22:23 +0100507 vector<float> weightData(boost::numeric_cast<size_t>(numGroups *
508 inputShape.dim(1) * // number of input channels
509 outputShape.dim(1) * // number of output channels
510 kernelH *
511 kernelW));
telsoa014fcda012018-03-09 14:13:49 +0000512 GetDataFromBlob(layerParam, weightData, 0);
513
514 const unsigned int weightDimSizes[4] = {
telsoa01c577f2c2018-08-31 09:22:23 +0100515 static_cast<unsigned int>(outputShape.dim(1)),
516 static_cast<unsigned int>(inputShape.dim(1)),
517 kernelH,
518 kernelW};
telsoa014fcda012018-03-09 14:13:49 +0000519
telsoa014fcda012018-03-09 14:13:49 +0000520 TensorInfo biasInfo;
521 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100522
523 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000524 {
525 biasData.resize(boost::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
526 GetDataFromBlob(layerParam, biasData, 1);
527
528 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
529 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
530 }
531
532 const unsigned int numWeightsPerGroup = boost::numeric_cast<unsigned int>(weightData.size()) / numGroups;
533 const unsigned int numBiasesPerGroup = boost::numeric_cast<unsigned int>(biasData.size()) / numGroups;
534
telsoa014fcda012018-03-09 14:13:49 +0000535 for (unsigned int g = 0; g < numGroups; ++g)
536 {
telsoa01c577f2c2018-08-31 09:22:23 +0100537 // Sets the slot index, group 0 should be connected to the 0th output of the splitter
538 // group 1 should be connected to the 1st output of the splitter.
telsoa014fcda012018-03-09 14:13:49 +0000539
telsoa01c577f2c2018-08-31 09:22:23 +0100540 // Pulls out the weights for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000541 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
542 weightData.data() + numWeightsPerGroup * g);
543
544 IConnectableLayer* convLayer = nullptr;
Matteo Martincighfc598e12019-05-14 10:36:13 +0100545 Optional<ConstTensor> optionalBiases;
telsoa01c577f2c2018-08-31 09:22:23 +0100546 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000547 {
telsoa01c577f2c2018-08-31 09:22:23 +0100548 // Pulls out the biases for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000549 ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
Matteo Martincighfc598e12019-05-14 10:36:13 +0100550 optionalBiases = Optional<ConstTensor>(biases);
telsoa014fcda012018-03-09 14:13:49 +0000551 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100552 convLayer = m_Network->AddConvolution2dLayer(desc,
553 weights,
554 optionalBiases,
555 convLayerNames[g].c_str());
telsoa014fcda012018-03-09 14:13:49 +0000556 convLayers[g] = convLayer;
557
558 // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
559 // otherwise it's the regular input to this layer.
telsoa01c577f2c2018-08-31 09:22:23 +0100560 armnn::IOutputSlot& splitterInputConnection =
561 splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
telsoa014fcda012018-03-09 14:13:49 +0000562 splitterInputConnection.Connect(convLayer->GetInputSlot(0));
563 convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
telsoa014fcda012018-03-09 14:13:49 +0000564 }
565
Jim Flynne242f2d2019-05-22 14:24:13 +0100566 // If the convolution was performed in chunks, add a layer to concatenate the results
telsoa01c577f2c2018-08-31 09:22:23 +0100567
568 // The merge input shape matches that of the convolution output
Jim Flynne242f2d2019-05-22 14:24:13 +0100569 unsigned int concatDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
570 static_cast<unsigned int>(outputShape.dim(1)),
571 static_cast<unsigned int>(outputShape.dim(2)),
572 static_cast<unsigned int>(outputShape.dim(3))};
telsoa01c577f2c2018-08-31 09:22:23 +0100573
Jim Flynne242f2d2019-05-22 14:24:13 +0100574 // This is used to describe how the input is to be concatenated
575 OriginsDescriptor concatDesc(numGroups);
telsoa01c577f2c2018-08-31 09:22:23 +0100576
577 // Now create an input node for each group, using the name from
578 // the output of the corresponding convolution
579 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000580 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100581 concatDesc.SetViewOriginCoord(g, 1, concatDimSizes[1] * g);
telsoa01c577f2c2018-08-31 09:22:23 +0100582 }
telsoa014fcda012018-03-09 14:13:49 +0000583
Jim Flynne242f2d2019-05-22 14:24:13 +0100584 // Make sure the output from the concat is the correct size to hold the data for all groups
585 concatDimSizes[1] *= numGroups;
586 outputShape.set_dim(1, concatDimSizes[1]);
telsoa014fcda012018-03-09 14:13:49 +0000587
Jim Flynne242f2d2019-05-22 14:24:13 +0100588 // Finally add the concat layer
589 IConnectableLayer* concatLayer = m_Network->AddConcatLayer(concatDesc, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000590
Jim Flynne242f2d2019-05-22 14:24:13 +0100591 if (!concatLayer)
telsoa01c577f2c2018-08-31 09:22:23 +0100592 {
593 throw ParseException(
594 boost::str(
595 boost::format(
Jim Flynne242f2d2019-05-22 14:24:13 +0100596 "Failed to create final concat layer for Split+Convolution+Concat. "
telsoa01c577f2c2018-08-31 09:22:23 +0100597 "Layer=%1% #groups=%2% #filters=%3% %4%") %
598 layerParam.name() %
599 numGroups %
600 numFilters %
601 CHECK_LOCATION().AsString()));
602 }
telsoa014fcda012018-03-09 14:13:49 +0000603
telsoa01c577f2c2018-08-31 09:22:23 +0100604 for (unsigned int g = 0; g < numGroups; ++g)
605 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100606 convLayers[g]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(g));
telsoa01c577f2c2018-08-31 09:22:23 +0100607 }
Jim Flynne242f2d2019-05-22 14:24:13 +0100608 concatLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, concatDimSizes, DataType::Float32));
609 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatLayer->GetOutputSlot(0));
telsoa01c577f2c2018-08-31 09:22:23 +0100610}
telsoa014fcda012018-03-09 14:13:49 +0000611
telsoa01c577f2c2018-08-31 09:22:23 +0100612void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
613 const armnn::Convolution2dDescriptor& convDesc,
614 unsigned int kernelW,
615 unsigned int kernelH)
616{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100617 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100618 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000619
telsoa01c577f2c2018-08-31 09:22:23 +0100620 ConvolutionParameter convParam = layerParam.convolution_param();
621 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa014fcda012018-03-09 14:13:49 +0000622
telsoa01c577f2c2018-08-31 09:22:23 +0100623 DepthwiseConvolution2dDescriptor desc;
624 desc.m_PadLeft = convDesc.m_PadLeft;
625 desc.m_PadRight = convDesc.m_PadRight;
626 desc.m_PadTop = convDesc.m_PadTop;
627 desc.m_PadBottom = convDesc.m_PadBottom;
628 desc.m_StrideX = convDesc.m_StrideX;
629 desc.m_StrideY = convDesc.m_StrideY;
630 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
telsoa014fcda012018-03-09 14:13:49 +0000631
telsoa01c577f2c2018-08-31 09:22:23 +0100632 unsigned int numFilters = convParam.num_output();
633
634 BlobShape outputShape;
635 outputShape.add_dim(0);
636 outputShape.set_dim(0, inputShape.dim(0));
637 outputShape.add_dim(1);
638 outputShape.set_dim(1, numFilters);
639 outputShape.add_dim(2);
640 outputShape.set_dim(
641 2, (static_cast<int>(
642 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
643 static_cast<float>(desc.m_StrideY)) + 1));
644 outputShape.add_dim(3);
645 outputShape.set_dim(
646 3, (static_cast<int>(
647 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
648 static_cast<float>(desc.m_StrideX)) + 1));
649
650 // Load the weight data
651 size_t allWeightsSize = boost::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
652 vector<float> weightData(allWeightsSize);
653
654 GetDataFromBlob(layerParam, weightData, 0);
655
656 // depth multiplier will be 1 for the depthwise convolution
657 const unsigned int weightDimSizes[4] = {
658 static_cast<unsigned int>(1), // depth multiplier
659 static_cast<unsigned int>(inputShape.dim(1)), // #channels
660 kernelH,
661 kernelW};
662
663 armnn::IConnectableLayer* returnLayer = nullptr;
664 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100665 Optional<ConstTensor> optionalBiases;
666 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100667 if (desc.m_BiasEnabled)
668 {
669 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100670
671 biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
672 GetDataFromBlob(layerParam, biasData, 1);
673
674 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
675 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
676
677 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100678 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100679 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100680 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc,
681 weights,
682 optionalBiases,
683 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000684
surmeh013537c2c2018-05-18 16:31:43 +0100685 if (!returnLayer)
686 {
telsoa01c577f2c2018-08-31 09:22:23 +0100687 throw ParseException(
688 boost::str(
689 boost::format(
690 "Failed to create depthwise convolution layer. "
691 "Layer=%1% #filters=%2% %3%") %
692 layerParam.name() %
693 numFilters %
694 CHECK_LOCATION().AsString()));
695 }
696 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
697 inputConnection.Connect(returnLayer->GetInputSlot(0));
698 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
699 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
700}
701
702void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
703{
704 // Ignored Caffe Parameters
705 // * Dilation Size
706 // * Weight Filler
707 // * Bias Filler
708 // * Engine
709 // * Force nd_im2col
710 // * Axis
711
712 // Not Available ArmNN Interface Parameters
713 // * Rounding policy;
714
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100715 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100716 ValidateNumInputsOutputs(layerParam, 1, 1);
717
718 ConvolutionParameter convParam = layerParam.convolution_param();
719 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
720 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
721 unsigned int numFilters = convParam.num_output();
722
723 const auto notFound = std::numeric_limits<unsigned int>::max();
724
725 unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
726 kernel_h, kernel_size, unsigned int, notFound);
727 unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
728 kernel_w, kernel_size, unsigned int, notFound);
729
730 unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
731 stride_h, stride, unsigned int, 1u);
732 unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
733 stride_w, stride, unsigned int, 1u);
734
735 unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
736 pad_h, pad, unsigned int, 0u);
737 unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
738 pad_w, pad, unsigned int, 0u);
739
telsoa01c577f2c2018-08-31 09:22:23 +0100740 Convolution2dDescriptor convolution2dDescriptor;
741 convolution2dDescriptor.m_PadLeft = padW;
742 convolution2dDescriptor.m_PadRight = padW;
743 convolution2dDescriptor.m_PadTop = padH;
744 convolution2dDescriptor.m_PadBottom = padH;
745 convolution2dDescriptor.m_StrideX = strideW;
746 convolution2dDescriptor.m_StrideY = strideH;
747 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
748
749 if (numGroups > numFilters)
750 {
751 throw ParseException(
752 boost::str(
753 boost::format(
754 "Error parsing Convolution: %1%. "
755 "The 'group'=%2% parameter cannot be larger than the "
756 "number of filters supplied ='%3%'. %4%") %
757 layerParam.name() %
758 numGroups %
759 numFilters %
760 CHECK_LOCATION().AsString()));
761 }
762
763 if (inputShape.dim_size() != 4)
764 {
765 throw ParseException(
766 boost::str(
767 boost::format(
768 "Convolution input shape is expected to have 4 dimensions. "
769 "%1%'s input has only %2%. %3%") %
770 layerParam.name() %
771 inputShape.dim_size() %
772 CHECK_LOCATION().AsString()));
773 }
774
775 if (numGroups > 1)
776 {
777 if (numGroups > inputShape.dim(1))
778 {
779 throw ParseException(
780 boost::str(
781 boost::format(
782 "Error parsing Convolution: %1%. "
783 "The 'group'=%2% parameter cannot be larger than the "
784 "channel of the input shape=%3% (in NCHW format). %4%") %
785 layerParam.name() %
786 numGroups %
787 inputShape.dim(1) %
788 CHECK_LOCATION().AsString()));
789 }
790 else if (numGroups == inputShape.dim(1))
791 {
792 // we use a depthwise convolution here, because the number of groups equals to the
793 // input channels
794 AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
795 return;
796 }
797 else
798 {
799 // we split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +0100800 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +0100801 AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
802 return;
803 }
804 }
805
806 // NOTE: at this point we only need to handle #group=1 case, all other cases should be
807 // handled by the AddConvLayer* helpers
808
809 // Populate convolution output tensor descriptor dimensions
810 BlobShape outputShape;
811 outputShape.add_dim(0);
812 outputShape.set_dim(0, inputShape.dim(0));
813 outputShape.add_dim(1);
814 outputShape.set_dim(1, numFilters);
815 outputShape.add_dim(2);
816 outputShape.set_dim(
817 2, (static_cast<int>(
818 static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
819 static_cast<float>(strideH)) + 1));
820 outputShape.add_dim(3);
821 outputShape.set_dim(
822 3, (static_cast<int>(
823 static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
824 static_cast<float>(strideW)) + 1));
825
826 // Load the weight data for ALL groups
827 vector<float> weightData(boost::numeric_cast<size_t>(inputShape.dim(1) *
828 outputShape.dim(1) *
829 kernelH *
830 kernelW));
831 GetDataFromBlob(layerParam, weightData, 0);
832
833 const unsigned int weightDimSizes[4] = {
834 static_cast<unsigned int>(outputShape.dim(1)), // output channels
835 static_cast<unsigned int>(inputShape.dim(1)), // input channels
836 kernelH,
837 kernelW};
838
839 armnn::IConnectableLayer* returnLayer = nullptr;
840
841 // Pull out the weights for this group from that loaded from the model file earlier
842 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100843 Optional<ConstTensor> optionalBiases;
844 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100845 if (convolution2dDescriptor.m_BiasEnabled)
846 {
847 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100848
849 biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
850 GetDataFromBlob(layerParam, biasData, 1);
851
852 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
853 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
854
855 // Pull out the biases for this group from that loaded from the model file earlier
856 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100857 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100858 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100859 returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
860 weights,
861 optionalBiases,
862 layerParam.name().c_str());
telsoa01c577f2c2018-08-31 09:22:23 +0100863
864 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
865 inputConnection.Connect(returnLayer->GetInputSlot(0));
866 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
867
868 if (!returnLayer)
869 {
870 throw ParseException(
871 boost::str(
872 boost::format(
873 "Failed to create Convolution layer. "
874 "Layer=%1% #groups=%2% #filters=%3% %4%") %
875 layerParam.name() %
876 numGroups %
877 numFilters %
878 CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100879 }
880
telsoa014fcda012018-03-09 14:13:49 +0000881 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
882}
883
telsoa01c577f2c2018-08-31 09:22:23 +0100884void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000885{
telsoa01c577f2c2018-08-31 09:22:23 +0100886 // Ignored Caffe Parameters
887 // Stochastic Pooling
888 // Engine
889
telsoa014fcda012018-03-09 14:13:49 +0000890 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000891 PoolingParameter param = layerParam.pooling_param();
telsoa014fcda012018-03-09 14:13:49 +0000892 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
893
telsoa01c577f2c2018-08-31 09:22:23 +0100894 const auto notFound = std::numeric_limits<unsigned int>::max();
895
896 unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
897 kernel_h, kernel_size, unsigned int, notFound);
898 unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
899 kernel_w, kernel_size, unsigned int, notFound);
900
901 if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000902 {
903 kernel_h = inputInfo.GetShape()[2];
904 kernel_w = inputInfo.GetShape()[3];
905 }
telsoa01c577f2c2018-08-31 09:22:23 +0100906
telsoa01c577f2c2018-08-31 09:22:23 +0100907 unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
908 stride_h, stride, unsigned int, notFound);
909 unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
910 stride_h, stride, unsigned int, notFound);
911
912 if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000913 {
telsoa01c577f2c2018-08-31 09:22:23 +0100914 stride_h = 1;
915 stride_w = 1;
telsoa014fcda012018-03-09 14:13:49 +0000916 }
917
telsoa01c577f2c2018-08-31 09:22:23 +0100918 unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
919 pad_h, pad, unsigned int, 0u);
920 unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
921 pad_w, pad, unsigned int, 0u);
telsoa014fcda012018-03-09 14:13:49 +0000922
telsoa014fcda012018-03-09 14:13:49 +0000923 // Populate Weight and Bias Filter Descriptor
924 Pooling2dDescriptor pooling2dDescriptor;
925 if (param.has_pool())
926 {
927 PoolingParameter_PoolMethod p = param.pool();
928 switch (p)
929 {
930 case PoolingParameter_PoolMethod_MAX:
931 {
932 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
933 break;
934 }
935 case PoolingParameter_PoolMethod_AVE:
936 {
937 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
938 break;
939 }
940 case PoolingParameter_PoolMethod_STOCHASTIC:
941 {
telsoa01c577f2c2018-08-31 09:22:23 +0100942 throw ParseException(
943 boost::str(
944 boost::format(
945 "Pooling Layer: Stochastic Pooling Not Supported. Layer=%1% %2%") %
946 layerParam.name() %
947 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000948 }
949 default:
950 {
telsoa01c577f2c2018-08-31 09:22:23 +0100951 throw ParseException(
952 boost::str(
953 boost::format(
954 "Pooling Layer: unknown pooling method: %1% for layer: %2% %3%") %
955 p %
956 layerParam.name() %
957 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000958 }
959 }
960 }
961 else
962 {
telsoa01c577f2c2018-08-31 09:22:23 +0100963 throw ParseException(
964 boost::str(
965 boost::format(
966 "No Pooling Method Defined for %1% %2%") %
967 layerParam.name() %
968 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000969 }
970
971 pooling2dDescriptor.m_PadLeft = pad_w;
972 pooling2dDescriptor.m_PadRight = pad_w;
973 pooling2dDescriptor.m_PadTop = pad_h;
974 pooling2dDescriptor.m_PadBottom = pad_h;
975 pooling2dDescriptor.m_StrideX = stride_w;
976 pooling2dDescriptor.m_StrideY = stride_h;
977 pooling2dDescriptor.m_PoolWidth = kernel_w;
978 pooling2dDescriptor.m_PoolHeight = kernel_h;
979
980 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
981 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
982
983 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
984 layerParam.name().c_str());
985
telsoa014fcda012018-03-09 14:13:49 +0000986 TensorInfo outputInfo(
987 { inputInfo.GetShape()[0],
988 inputInfo.GetShape()[1],
989 static_cast<unsigned int>(ceil(
990 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
991 boost::numeric_cast<float>(stride_h))) + 1,
992 static_cast<unsigned int>(ceil(
993 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
994 boost::numeric_cast<float>(stride_w))) + 1 },
995 DataType::Float32);
996
997 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
998 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
999 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
1000}
1001
telsoa01c577f2c2018-08-31 09:22:23 +01001002void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001003{
1004 ValidateNumInputsOutputs(layerParam, 1, 1);
1005
1006 const string& name = layerParam.name();
1007 const ReLUParameter& param = layerParam.relu_param();
1008
1009 ActivationDescriptor activationDescriptor;
1010 const float negativeSlope = param.negative_slope();
1011 if (negativeSlope == 0.0f)
1012 {
1013 activationDescriptor.m_Function = ActivationFunction::ReLu;
1014 }
1015 else
1016 {
1017 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
1018 activationDescriptor.m_A = negativeSlope;
1019 }
1020
1021 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1022 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
1023 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
1024 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1025 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
1026}
1027
telsoa01c577f2c2018-08-31 09:22:23 +01001028void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001029{
1030 ValidateNumInputsOutputs(layerParam, 1, 1);
1031
1032 LRNParameter param = layerParam.lrn_param();
1033
1034 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1035
telsoa01c577f2c2018-08-31 09:22:23 +01001036 // Ignored BATCH NORMALIZATION Caffe Parameters.
1037 // Ignored MVN Caffe Parameters.
1038 // Ignored LRN Caffe Parameters.
telsoa014fcda012018-03-09 14:13:49 +00001039 // Engine
1040
1041 NormalizationDescriptor normalizationDescriptor;
1042 if (param.has_norm_region())
1043 {
1044 LRNParameter_NormRegion n = param.norm_region();
1045 switch (n)
1046 {
1047 case LRNParameter_NormRegion_ACROSS_CHANNELS:
1048 {
1049 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1050 break;
1051 }
1052 case LRNParameter_NormRegion_WITHIN_CHANNEL:
1053 {
1054 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1055 break;
1056 }
1057 default:
telsoa01c577f2c2018-08-31 09:22:23 +01001058 {
1059 throw ParseException(
1060 boost::str(
1061 boost::format(
1062 "Unknown region %1% for LRN layer %2% %3%") %
1063 n %
1064 layerParam.name() %
1065 CHECK_LOCATION().AsString()));
1066 }
telsoa014fcda012018-03-09 14:13:49 +00001067 }
1068 }
1069 else
1070 {
telsoa01c577f2c2018-08-31 09:22:23 +01001071 // Caffe defaults to normalization across channels.
telsoa014fcda012018-03-09 14:13:49 +00001072 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1073 }
1074
1075 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1076 if (param.has_local_size())
1077 {
1078 normalizationDescriptor.m_NormSize = param.local_size();
1079 }
1080 else
1081 {
telsoa01c577f2c2018-08-31 09:22:23 +01001082 throw ParseException(
1083 boost::str(
1084 boost::format(
1085 "local_size not defined for LRN layer %1% %2%") %
1086 layerParam.name() %
1087 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001088 }
1089
1090 if (param.has_alpha())
1091 {
1092 normalizationDescriptor.m_Alpha = param.alpha();
1093 normalizationDescriptor.m_Alpha /= boost::numeric_cast<float>(param.local_size());
1094 }
1095 else
1096 {
telsoa01c577f2c2018-08-31 09:22:23 +01001097 throw ParseException(
1098 boost::str(
1099 boost::format(
1100 "Alpha not defined for LRN layer %1% %2%") %
1101 layerParam.name() %
1102 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001103 }
1104 if (param.has_beta())
1105 {
1106 normalizationDescriptor.m_Beta = param.beta();
1107 }
1108 else
1109 {
telsoa01c577f2c2018-08-31 09:22:23 +01001110 throw ParseException(
1111 boost::str(
1112 boost::format(
1113 "Beta not defined for LRN layer %1% %2%") %
1114 layerParam.name() %
1115 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001116 }
telsoa01c577f2c2018-08-31 09:22:23 +01001117
telsoa014fcda012018-03-09 14:13:49 +00001118 if (param.has_k())
1119 {
1120 normalizationDescriptor.m_K = param.k();
1121 }
1122 else
telsoa01c577f2c2018-08-31 09:22:23 +01001123 {
telsoa014fcda012018-03-09 14:13:49 +00001124 normalizationDescriptor.m_K = 1;
telsoa01c577f2c2018-08-31 09:22:23 +01001125 }
telsoa014fcda012018-03-09 14:13:49 +00001126
1127 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1128 layerParam.name().c_str());
1129 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1130 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1131
1132 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1133}
1134
telsoa01c577f2c2018-08-31 09:22:23 +01001135void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001136{
1137 InnerProductParameter param = layerParam.inner_product_param();
1138
1139 ValidateNumInputsOutputs(layerParam, 1, 1);
1140
1141 unsigned int outputSize = param.num_output();
1142
telsoa01c577f2c2018-08-31 09:22:23 +01001143 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001144 // Weight Filler
1145 // Bias Filler
1146 // Engine
1147 // Axis
1148
1149 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1150
1151 if (param.has_transpose())
1152 {
telsoa01c577f2c2018-08-31 09:22:23 +01001153 // If true, assumes transposed weights.
telsoa014fcda012018-03-09 14:13:49 +00001154 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1155 }
1156 else
1157 {
telsoa01c577f2c2018-08-31 09:22:23 +01001158 // Caffe defaults to transposed.
telsoa014fcda012018-03-09 14:13:49 +00001159 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1160 }
1161
1162 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1163
1164 TensorInfo weightInfo;
1165 TensorInfo biasInfo;
1166
telsoa01c577f2c2018-08-31 09:22:23 +01001167 // Allows implicit flattening of extra dimensions.
telsoa014fcda012018-03-09 14:13:49 +00001168 unsigned int inputSize = inputInfo.GetShape()[1];
1169 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1170 {
1171 inputSize *= inputInfo.GetShape()[i];
1172 }
1173
telsoa01c577f2c2018-08-31 09:22:23 +01001174 const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
telsoa014fcda012018-03-09 14:13:49 +00001175 const unsigned int swTD[2] = { outputSize, inputSize };
telsoa01c577f2c2018-08-31 09:22:23 +01001176 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001177
1178 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
telsoa01c577f2c2018-08-31 09:22:23 +01001179 // Todo: check whether bias enabled.
telsoa014fcda012018-03-09 14:13:49 +00001180 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1181 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1182 {
1183 // BIAS VALUE
telsoa01c577f2c2018-08-31 09:22:23 +01001184 const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
telsoa014fcda012018-03-09 14:13:49 +00001185
1186 const unsigned int sbTD[1] = { outputSize };
1187
telsoa01c577f2c2018-08-31 09:22:23 +01001188 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001189
Matteo Martincighfc598e12019-05-14 10:36:13 +01001190 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1191 weights,
1192 Optional<ConstTensor>(biases),
1193 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001194 }
1195 else
1196 {
Matteo Martincighfc598e12019-05-14 10:36:13 +01001197 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1198 weights,
1199 EmptyOptional(),
1200 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001201 }
1202
1203 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1204 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1205 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1206 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1207}
1208
telsoa01c577f2c2018-08-31 09:22:23 +01001209void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001210{
1211 ValidateNumInputsOutputs(layerParam, 1, 1);
1212
1213 SoftmaxParameter param = layerParam.softmax_param();
1214
1215 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1216
telsoa01c577f2c2018-08-31 09:22:23 +01001217 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001218 // axis
1219 // Engine
1220
1221 armnn::SoftmaxDescriptor softmaxDescriptor;
Teresa Charlin4320c922020-08-12 16:04:41 +01001222 softmaxDescriptor.m_Axis = 1;
telsoa014fcda012018-03-09 14:13:49 +00001223 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1224 softmaxDescriptor,
1225 layerParam.name().c_str());
1226 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1227 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1228 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1229}
1230
telsoa01c577f2c2018-08-31 09:22:23 +01001231void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001232{
1233 ValidateNumInputsOutputs(layerParam, 2, 1);
1234
1235 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1236
telsoa01c577f2c2018-08-31 09:22:23 +01001237 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001238 // coeff
1239
telsoa01c577f2c2018-08-31 09:22:23 +01001240 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
telsoa014fcda012018-03-09 14:13:49 +00001241
1242 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1243 {
1244 operation = layerParam.eltwise_param().operation();
1245 }
1246
1247 armnn::IConnectableLayer* newLayer = nullptr;
1248 switch (operation)
1249 {
1250 case EltwiseParameter_EltwiseOp_SUM:
1251 {
1252 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1253 break;
1254 }
1255 case EltwiseParameter_EltwiseOp_PROD:
1256 {
1257 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1258 break;
1259 }
1260 default:
1261 {
telsoa01c577f2c2018-08-31 09:22:23 +01001262 throw ParseException(
1263 boost::str(
1264 boost::format(
1265 "Unsupported operation %1% in Eltwise layer %2% %3%") %
1266 operation %
1267 layerParam.name() %
1268 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001269 }
1270 }
1271
1272 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1273 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1274 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1275 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1276}
1277
telsoa01c577f2c2018-08-31 09:22:23 +01001278void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001279{
1280 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
telsoa01c577f2c2018-08-31 09:22:23 +01001281 // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
telsoa014fcda012018-03-09 14:13:49 +00001282 unsigned int concatDim = 1;
1283 unsigned int numOfDims = 4;
1284
telsoa01c577f2c2018-08-31 09:22:23 +01001285 // we only consider 4-D tensor here
1286 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
telsoa014fcda012018-03-09 14:13:49 +00001287 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1288
1289 unsigned int mergeDim = 0;
1290 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1291 {
1292 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
1293 layerParam.bottom(boost::numeric_cast<int>(viewIndex))).GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001294 // Checks whether the dimensions of the input tensors are actually 4.
telsoa014fcda012018-03-09 14:13:49 +00001295 if (inputInfo.GetNumDimensions()!=4)
1296 {
telsoa01c577f2c2018-08-31 09:22:23 +01001297 throw ParseException(
1298 boost::str(
1299 boost::format(
1300 "The number of dimensions for input tensors of "
1301 "the concatenation op should be 4. Inputs of %1% has "
1302 "%2% dimensions. %3%") %
1303 layerParam.name() %
1304 inputInfo.GetNumDimensions() %
1305 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001306 }
1307
1308 mergeDimSizes[0] = inputInfo.GetShape()[0];
1309 mergeDimSizes[1] = inputInfo.GetShape()[1];
1310 mergeDimSizes[2] = inputInfo.GetShape()[2];
1311 mergeDimSizes[3] = inputInfo.GetShape()[3];
1312
1313 for (unsigned int j = 0; j < concatDim; ++j)
1314 {
1315 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1316 }
1317
1318 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1319 mergeDim += mergeDimSizes[concatDim];
1320
1321 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1322 {
1323 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1324 }
1325 }
1326 mergeDimSizes[concatDim] = mergeDim;
1327
Jim Flynn906f9462019-05-10 13:55:21 +01001328 armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001329 for (unsigned int i = 0; i < numInputs; ++i)
1330 {
1331 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(boost::numeric_cast<int>(i)));
1332 outputSlot.Connect(concatlayer->GetInputSlot(i));
1333 }
1334
1335 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1336 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1337}
1338
telsoa01c577f2c2018-08-31 09:22:23 +01001339void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001340{
1341 ValidateNumInputsOutputs(layerParam, 1, 1);
1342
1343 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1344
1345 string name = layerParam.name();
1346
1347 BatchNormParameter param = layerParam.batch_norm_param();
1348 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1349 // when the network is in the testing phase).
1350 if (param.has_use_global_stats())
1351 {
1352 if (!param.use_global_stats())
1353 {
telsoa01c577f2c2018-08-31 09:22:23 +01001354 throw ParseException(
1355 boost::str(
1356 boost::format(
1357 "Error parsing Batch Norm layer '%1%': "
1358 "Parameter 'use_global_stats' is set to false, which is "
1359 "unsupported (value used for training). %2%") %
1360 name %
1361 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001362 }
1363 }
1364
1365 BatchNormalizationDescriptor desc;
1366 desc.m_Eps = param.eps();
1367
1368 unsigned int channels = inputInfo.GetShape()[1];
1369 unsigned int shape[] = {channels};
1370
1371 vector<float> meanData(channels);
1372 GetDataFromBlob(layerParam, meanData, 0);
1373
1374 vector<float> varianceData(channels);
1375 GetDataFromBlob(layerParam, varianceData, 1);
1376
telsoa01c577f2c2018-08-31 09:22:23 +01001377 // Reads moving average factor and applies scaling (if required).
surmeh013537c2c2018-05-18 16:31:43 +01001378 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(2));
1379 const float movingAverageFactor = blob.data(boost::numeric_cast<int>(0));
1380 if(movingAverageFactor != 0.0f)
1381 {
1382 const float scaleFactor = 1.0f / movingAverageFactor;
1383 auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1384
1385 std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1386 std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1387 }
1388
telsoa01c577f2c2018-08-31 09:22:23 +01001389 // Identifies scale operation.
telsoa014fcda012018-03-09 14:13:49 +00001390 vector<float> betaData(channels, 0.0f);
1391 vector<float> gammaData(channels, 1.0f);
1392
1393 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1394 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1395 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1396 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1397
1398 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1399 mean, variance, beta, gamma, name.c_str());
1400 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1401 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1402 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1403}
1404
telsoa01c577f2c2018-08-31 09:22:23 +01001405void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001406{
telsoa01c577f2c2018-08-31 09:22:23 +01001407 // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
telsoa014fcda012018-03-09 14:13:49 +00001408 ValidateNumInputsOutputs(layerParam, 1, 1);
1409
1410 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1411
1412 string name = layerParam.name();
1413
1414 ScaleParameter param = layerParam.scale_param();
1415 if (param.axis() != 1)
1416 {
1417 // Would have to use something other than BatchNormalizationLayer in this case
telsoa01c577f2c2018-08-31 09:22:23 +01001418 throw ParseException(
1419 boost::str(
1420 boost::format(
1421 "Loading Scale Layer: Only axis 1 is supported currently. "
1422 "Layer=%1% Axis=%2% %3%") %
1423 layerParam.name() %
1424 param.axis() %
1425 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001426 }
1427
1428 unsigned int channels = inputInfo.GetShape()[1];
1429 unsigned int shape[] = {channels};
1430
1431 BatchNormalizationDescriptor desc;
telsoa01c577f2c2018-08-31 09:22:23 +01001432 desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
telsoa014fcda012018-03-09 14:13:49 +00001433 vector<float> meanData(channels, 0.0f);
1434 vector<float> varianceData(channels, 1.0f);
1435 vector<float> betaData(channels, 0.0f);
1436 vector<float> gammaData(channels);
1437
1438 GetDataFromBlob(layerParam, gammaData, 0);
1439
1440 if(param.has_bias_term())
1441 {
1442 GetDataFromBlob(layerParam, betaData, 1);
1443 }
1444
1445 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1446 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1447 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1448 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1449
1450 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1451 mean, variance, beta, gamma, name.c_str());
1452 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1453 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1454 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1455}
1456
telsoa01c577f2c2018-08-31 09:22:23 +01001457void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001458{
telsoa01c577f2c2018-08-31 09:22:23 +01001459 // Used in caffe to duplicate memory - not necessary in armnn.
telsoa014fcda012018-03-09 14:13:49 +00001460 if (layerParam.bottom_size() != 1)
1461 {
telsoa01c577f2c2018-08-31 09:22:23 +01001462 throw ParseException(
1463 boost::str(
1464 boost::format(
1465 "Split layer '%1%' should have exactly 1 bottom. "
1466 "#bottoms=%2% %3%") %
1467 layerParam.name() %
1468 layerParam.bottom_size() %
1469 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001470 }
1471 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1472 for (int i = 0; i < layerParam.top_size(); i++)
1473 {
1474 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1475 }
1476}
1477
telsoa01c577f2c2018-08-31 09:22:23 +01001478void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001479{
telsoa01c577f2c2018-08-31 09:22:23 +01001480 // Ignored for inference, so patch the single input to its single output.
telsoa014fcda012018-03-09 14:13:49 +00001481 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1482 {
telsoa01c577f2c2018-08-31 09:22:23 +01001483 throw ParseException(
1484 boost::str(
1485 boost::format(
1486 "Dropout layer '%1%' should have exactly 1 bottom and 1 top. "
1487 "#bottoms=%2% #tops=%3% %4%") %
1488 layerParam.name() %
1489 layerParam.bottom_size() %
1490 layerParam.top_size() %
1491 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001492 }
1493 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1494}
1495
telsoa01c577f2c2018-08-31 09:22:23 +01001496void CaffeParserBase::TrackInputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001497 armnn::LayerBindingId id,
1498 const armnn::TensorInfo& tensorInfo)
1499{
1500 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1501}
1502
telsoa01c577f2c2018-08-31 09:22:23 +01001503void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001504 armnn::LayerBindingId id,
1505 const armnn::TensorInfo& tensorInfo)
1506{
1507 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1508}
1509
telsoa01c577f2c2018-08-31 09:22:23 +01001510void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001511 armnn::LayerBindingId id,
1512 const armnn::TensorInfo& tensorInfo,
1513 const char* bindingPointDesc,
1514 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1515{
1516 const std::string layerName = layer->GetName();
1517 auto it = nameToBindingInfo.find(layerName);
1518 if (it == nameToBindingInfo.end())
1519 {
1520 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1521 }
1522 else
1523 {
telsoa01c577f2c2018-08-31 09:22:23 +01001524 throw ParseException(
1525 boost::str(
1526 boost::format(
1527 "Id %1% used by more than one %2% layer %3%") %
1528 id %
1529 bindingPointDesc %
1530 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001531 }
1532}
1533
telsoa01c577f2c2018-08-31 09:22:23 +01001534armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
telsoa014fcda012018-03-09 14:13:49 +00001535{
1536 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1537 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1538 {
1539 return *it->second;
1540 }
1541 else
1542 {
telsoa01c577f2c2018-08-31 09:22:23 +01001543 throw ParseException(
1544 boost::str(
1545 boost::format(
1546 "Could not find armnn output slot for Caffe top '%1%' %2%") %
1547 caffeTopName %
1548 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001549 }
1550}
1551
telsoa01c577f2c2018-08-31 09:22:23 +01001552void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
1553 const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
telsoa014fcda012018-03-09 14:13:49 +00001554{
1555 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1556 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1557 {
1558 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1559 }
1560 else
1561 {
telsoa01c577f2c2018-08-31 09:22:23 +01001562 throw ParseException(
1563 boost::str(
1564 boost::format(
1565 "Attempting to add duplicate entry for Caffe top '%1%' %2%") %
1566 caffeTopName %
1567 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001568 }
1569}
1570
telsoa01c577f2c2018-08-31 09:22:23 +01001571// Note: can move to CaffeParser when/if we optimise the text/string format
1572// to load on a layer by layer basis
1573void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001574{
telsoa01c577f2c2018-08-31 09:22:23 +01001575 // Finds layers with the same top.
telsoa014fcda012018-03-09 14:13:49 +00001576 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1577 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1578 {
1579 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
telsoa01c577f2c2018-08-31 09:22:23 +01001580 std::string name = layer.name();
telsoa014fcda012018-03-09 14:13:49 +00001581 for (int i = 0; i < layer.top_size(); ++i)
1582 {
1583 layersByTop[layer.top(i)].push_back(&layer);
1584 }
1585 }
1586
telsoa01c577f2c2018-08-31 09:22:23 +01001587 // For each set of layers with the same top, resolves them to a linear chain rather than in-place layers.
telsoa014fcda012018-03-09 14:13:49 +00001588 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1589 for (auto layersWithSameTopIt : layersByTop)
1590 {
1591 const std::string& top = layersWithSameTopIt.first;
1592 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1593
telsoa01c577f2c2018-08-31 09:22:23 +01001594 // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
telsoa014fcda012018-03-09 14:13:49 +00001595 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1596 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1597 {
1598 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1599 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1600 if (layer1.top_size() != 1)
1601 {
telsoa01c577f2c2018-08-31 09:22:23 +01001602 throw ParseException(
1603 boost::str(
1604 boost::format(
1605 "Node '%1%' is an in-place layer but doesn't have exactly one "
1606 "top. It has %2% instead. %3%") %
1607 layer1.name() %
1608 layer1.top_size() %
1609 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001610 }
1611 std::string newTop = layer1.name() + "_top";
1612 layer1.set_top(0, newTop);
1613 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1614 {
telsoa01c577f2c2018-08-31 09:22:23 +01001615 throw ParseException(
1616 boost::str(
1617 boost::format(
1618 "Node '%1%' is an in-place layer but "
1619 "doesn't have exactly one bottom, or it doesn't match its top. "
1620 "#bottoms=%2%, first bottom is %3%, top is %4% %5%") %
1621 layer2.name() %
1622 layer2.bottom(0) %
1623 top %
1624 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001625 }
1626 layer2.set_bottom(0, newTop);
1627 }
1628 }
1629}
1630
telsoa01c577f2c2018-08-31 09:22:23 +01001631// Note: can move to CaffeParser when/if we optimise the text/string format
1632// to load on a layer by layer basis
1633void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001634{
telsoa01c577f2c2018-08-31 09:22:23 +01001635 // Caffe models sometimes have an implicit input layer.
1636 // In that case, add an explicit one.
telsoa014fcda012018-03-09 14:13:49 +00001637 if (netParameter.input_size() > 0)
1638 {
1639 LayerParameter* newLayer = netParameter.add_layer();
1640
1641 newLayer->set_type("Input");
1642 newLayer->set_name(netParameter.input(0));
1643 newLayer->add_top(netParameter.input(0));
1644
1645 InputParameter* inputParam = newLayer->mutable_input_param();
1646 BlobShape* shape = inputParam->add_shape();
1647
1648 int dim_size = netParameter.input_dim_size();
1649 for (int i = 0; i < dim_size; ++i)
1650 {
1651 shape->add_dim(netParameter.input_dim(i));
1652 }
1653 }
1654
telsoa01c577f2c2018-08-31 09:22:23 +01001655 // Replaces in-place layers with regular ones to make the rest of the parsing easier.
telsoa014fcda012018-03-09 14:13:49 +00001656 ResolveInPlaceLayers(netParameter);
1657
telsoa01c577f2c2018-08-31 09:22:23 +01001658 // Creates a lookup of Caffe layers by name.
telsoa014fcda012018-03-09 14:13:49 +00001659 for (int i = 0; i < netParameter.layer_size(); ++i)
1660 {
1661 const caffe::LayerParameter& layer = netParameter.layer(i);
1662 for (int i = 0; i < layer.top_size(); ++i)
1663 {
1664 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1665 }
1666 }
1667
telsoa01c577f2c2018-08-31 09:22:23 +01001668 // Finds the output layers the user requested.
telsoa014fcda012018-03-09 14:13:49 +00001669 std::vector<const caffe::LayerParameter*> targetLayers;
1670 for (const std::string& requestedOutputName : m_RequestedOutputs)
1671 {
1672 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1673 if (nodeIt == m_CaffeLayersByTopName.end())
1674 {
telsoa01c577f2c2018-08-31 09:22:23 +01001675 throw ParseException(
1676 boost::str(
1677 boost::format(
1678 "Couldn't find requested output layer '%1%' in graph %2%") %
1679 requestedOutputName %
1680 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001681 }
1682 targetLayers.push_back(nodeIt->second);
1683 }
1684
telsoa01c577f2c2018-08-31 09:22:23 +01001685 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001686 std::vector<const caffe::LayerParameter*> sortedNodes;
1687 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1688 targetLayers,
1689 [this](const caffe::LayerParameter* node)
1690 {
1691 return GetInputs(*node);
1692 },
1693 sortedNodes))
1694 {
telsoa01c577f2c2018-08-31 09:22:23 +01001695 throw ParseException(
1696 boost::str(
1697 boost::format(
1698 "Cycle detected in graph. #nodes: %1% %2%") %
1699 sortedNodes.size() %
1700 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001701 }
1702
telsoa01c577f2c2018-08-31 09:22:23 +01001703 // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001704 for (const caffe::LayerParameter* current : sortedNodes)
1705 {
1706 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1707 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1708 {
telsoa01c577f2c2018-08-31 09:22:23 +01001709 throw ParseException(
1710 boost::str(
1711 boost::format("Unsupported layer type: '%1%' for layer %2% %3%") %
1712 current->type() %
1713 current->name() %
1714 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001715 }
1716 auto func = it->second;
1717 (this->*func)(*current);
1718 }
1719
telsoa01c577f2c2018-08-31 09:22:23 +01001720 // Adds ArmNN output layers connected to each requested output.
telsoa014fcda012018-03-09 14:13:49 +00001721 for (const std::string& requestedOutput : m_RequestedOutputs)
1722 {
1723 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1724
1725 const armnn::LayerBindingId outputId = boost::numeric_cast<armnn::LayerBindingId>(
1726 m_NetworkOutputsBindingInfo.size());
1727 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1728 outputSlot.Connect(outputLayer->GetInputSlot(0));
1729
1730 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1731 }
1732}
1733
telsoa01c577f2c2018-08-31 09:22:23 +01001734INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
telsoa014fcda012018-03-09 14:13:49 +00001735 const std::map<std::string, armnn::TensorShape>& inputShapes,
1736 const std::vector<std::string>& requestedOutputs)
1737{
1738 FILE* fd = fopen(graphFile, "r");
1739
1740 if (fd == nullptr)
1741 {
telsoa01c577f2c2018-08-31 09:22:23 +01001742 throw FileNotFoundException(
1743 boost::str(
1744 boost::format(
1745 "Failed to open graph file: %1% %2%") %
1746 graphFile %
1747 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001748 }
1749
telsoa01c577f2c2018-08-31 09:22:23 +01001750 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001751 NetParameter netParam;
1752 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1753 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1754 delete input;
1755 fclose(fd);
1756
1757 if (!success)
1758 {
telsoa01c577f2c2018-08-31 09:22:23 +01001759 throw ParseException(
1760 boost::str(
1761 boost::format(
1762 "Failed to parse graph file: %1% %2%") %
1763 graphFile %
1764 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001765 }
1766
1767 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1768}
1769
telsoa01c577f2c2018-08-31 09:22:23 +01001770INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
telsoa014fcda012018-03-09 14:13:49 +00001771 const std::map<std::string, armnn::TensorShape>& inputShapes,
1772 const std::vector<std::string>& requestedOutputs)
1773{
telsoa01c577f2c2018-08-31 09:22:23 +01001774 // Parses the string into a message.
telsoa014fcda012018-03-09 14:13:49 +00001775 NetParameter netParam;
1776 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1777
1778 if (!success)
1779 {
telsoa01c577f2c2018-08-31 09:22:23 +01001780 throw ParseException(
1781 boost::str(
1782 boost::format(
1783 "Failed to parse graph string %1%") %
1784 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001785 }
1786
1787 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1788}
1789
1790INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1791 const std::map<std::string, armnn::TensorShape>& inputShapes,
1792 const std::vector<std::string>& requestedOutputs)
1793{
1794 FILE* fd = fopen(graphFile, "rb");
1795
1796 if (fd == nullptr)
1797 {
telsoa01c577f2c2018-08-31 09:22:23 +01001798 throw FileNotFoundException(
1799 boost::str(
1800 boost::format(
1801 "Failed to open graph file at: %1% %2%") %
1802 graphFile %
1803 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001804 }
1805
telsoa01c577f2c2018-08-31 09:22:23 +01001806 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001807 NetParameter netParam;
1808
1809 FileInputStream inStream(fileno(fd));
1810 CodedInputStream codedStream(&inStream);
1811 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
1812 bool success = netParam.ParseFromCodedStream(&codedStream);
1813 fclose(fd);
1814
1815 if (!success)
1816 {
telsoa01c577f2c2018-08-31 09:22:23 +01001817 throw ParseException(
1818 boost::str(
1819 boost::format(
1820 "Failed to parse protobuf file: %1% %2%") %
1821 graphFile %
1822 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001823 }
1824
1825 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1826}
1827
telsoa01c577f2c2018-08-31 09:22:23 +01001828// Note: can move to CaffeParser when/if we optimise the text/string format
1829// to load on a layer by layer basis
1830INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
telsoa014fcda012018-03-09 14:13:49 +00001831 const std::map<std::string, armnn::TensorShape>& inputShapes,
1832 const std::vector<std::string>& requestedOutputs)
1833{
1834 m_NetworkInputsBindingInfo.clear();
1835 m_NetworkOutputsBindingInfo.clear();
1836
1837 m_Network = INetwork::Create();
1838
1839 m_InputShapes = inputShapes;
1840 if (requestedOutputs.size() == 0)
1841 {
1842 throw ParseException("requestedOutputs must have at least one entry");
1843 }
1844 m_RequestedOutputs = requestedOutputs;
1845
1846 try
1847 {
1848 LoadNetParam(netParam);
1849 }
1850 catch (const ParseException& e)
1851 {
1852 Cleanup();
1853 throw e;
1854 }
1855
1856 Cleanup();
1857
1858 return move(m_Network);
1859}
1860
telsoa01c577f2c2018-08-31 09:22:23 +01001861void CaffeParserBase::Cleanup() {
telsoa014fcda012018-03-09 14:13:49 +00001862 // cleanup, in case we reuse this parser
telsoa014fcda012018-03-09 14:13:49 +00001863 m_InputShapes.clear();
1864 m_RequestedOutputs.clear();
1865 m_ArmnnOutputSlotForCaffeTop.clear();
telsoa01c577f2c2018-08-31 09:22:23 +01001866 // NOTE: when we get the text/string format
1867 // optimised for memory then this data structure can
1868 // also move to the CaffeParser class
1869 m_CaffeLayersByTopName.clear();
telsoa014fcda012018-03-09 14:13:49 +00001870}
1871
1872}