blob: d50846abab5d4f821154cc72582c14f85e66a02a [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>
Matthew Sloyan589e3e82020-09-11 16:17:48 +010017#include <armnn/utility/NumericCast.hpp>
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +010018
telsoa014fcda012018-03-09 14:13:49 +000019#include <boost/numeric/conversion/cast.hpp>
telsoa014fcda012018-03-09 14:13:49 +000020#include <boost/format.hpp>
telsoa014fcda012018-03-09 14:13:49 +000021
22// Caffe
23#include "caffe/proto/caffe.pb.h"
24
25// ProtoBuf
26#include <google/protobuf/io/coded_stream.h>
27#include <google/protobuf/io/zero_copy_stream.h>
28#include <google/protobuf/io/zero_copy_stream_impl.h>
29#include <google/protobuf/text_format.h>
30#include <google/protobuf/stubs/common.h>
31#include <google/protobuf/stubs/once.h>
32#include <google/protobuf/io/coded_stream.h>
telsoa014fcda012018-03-09 14:13:49 +000033#include <google/protobuf/descriptor.h>
34#include <google/protobuf/generated_message_reflection.h>
35#include <google/protobuf/reflection_ops.h>
36#include <google/protobuf/wire_format.h>
37
38#include <cmath>
39#include <sstream>
40#include <queue>
41#include <fcntl.h>
42
43/// Caffe networks are loaded from protobuf files (binary or text) using the protobuf library and the generated
44/// code from caffe.pb.h. This gives us a caffe::NetParameter which is an in-memory version of the file.
45/// This contains a flat list of Caffe 'layers' (e.g. convolution, pooling etc.).
46/// Each layer has inputs (called "bottoms") and outputs (called "tops"). Data flows from bottom to top.
47/// The bottoms of a layer refer to the tops of other layers, not their names.
telsoa01c577f2c2018-08-31 09:22:23 +010048/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't
49/// need any other changes).
telsoa014fcda012018-03-09 14:13:49 +000050///
51/// Some layers (e.g. Relu) can be configured so that their top and bottom are both the same. This is called an
52/// "in-place" layer and is a Caffe runtime feature used to reduce memory usage by modifying tensors in-place.
53/// This isn't relevant to the parser and so we preprocess these layers to convert them to regular layers, to result
54/// in a consistent graph structure.
55
56namespace armnnCaffeParser
57{
58
59using namespace armnn;
60using namespace caffe;
61using namespace std;
62using namespace google::protobuf::io;
63
telsoa01c577f2c2018-08-31 09:22:23 +010064namespace
telsoa014fcda012018-03-09 14:13:49 +000065{
66
telsoa01c577f2c2018-08-31 09:22:23 +010067const float* GetArrayPtrFromBlob(const LayerParameter& layerParam, unsigned int blobIndex)
telsoa014fcda012018-03-09 14:13:49 +000068{
telsoa01c577f2c2018-08-31 09:22:23 +010069 auto nBlobs = layerParam.blobs_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +010070 if (blobIndex >= armnn::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000071 {
telsoa01c577f2c2018-08-31 09:22:23 +010072 throw ParseException(
73 boost::str(
74 boost::format(
75 "Expected data blob at index %1% in layer %2% not found. nBlobs=%2%. %4%") %
76 blobIndex %
77 layerParam.name() %
78 nBlobs %
79 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000080 }
81
Matthew Sloyan589e3e82020-09-11 16:17:48 +010082 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(blobIndex));
telsoa014fcda012018-03-09 14:13:49 +000083
telsoa01c577f2c2018-08-31 09:22:23 +010084 const float* arrayPtr = blob.data().data();
85 return arrayPtr;
86}
87
88void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
89{
90 auto nBlobs = layerParam.blobs_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +010091 if (blobIndex >= armnn::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000092 {
telsoa01c577f2c2018-08-31 09:22:23 +010093 throw ParseException(
94 boost::str(
95 boost::format(
96 "Expected data blob at index %1% in layer %2% not found. %3%") %
97 blobIndex %
98 layerParam.name() %
99 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000100 }
101
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100102 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(blobIndex));
telsoa01c577f2c2018-08-31 09:22:23 +0100103
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100104 size_t blobSize = armnn::numeric_cast<size_t>(blob.data_size());
telsoa01c577f2c2018-08-31 09:22:23 +0100105 if (blobSize != outData.size())
telsoa014fcda012018-03-09 14:13:49 +0000106 {
telsoa01c577f2c2018-08-31 09:22:23 +0100107 throw ParseException(
108 boost::str(
109 boost::format(
110 "Data blob at index %1% in layer %2% has an unexpected size. "
111 "Expected %3% elements but got %4% elements. %5%") %
112 blobIndex %
113 layerParam.name() %
114 outData.size() %
115 blobSize %
116 CHECK_LOCATION().AsString()));
117 }
118
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100119 int outSizeInt = armnn::numeric_cast<int>(outData.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100120 for (int i = 0; i < outSizeInt; ++i)
121 {
122 outData[static_cast<size_t>(i)] = blob.data(i);
telsoa014fcda012018-03-09 14:13:49 +0000123 }
124}
125
telsoa014fcda012018-03-09 14:13:49 +0000126template <typename T>
127size_t SizeOfVectorData(const vector<T>& vec)
128{
129 return vec.size() * sizeof(T);
130}
131
132void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
133 unsigned int numInputs,
134 unsigned int numOutputs)
135{
136 int numInputsActual = layerParameter.bottom_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100137 if (numInputs != armnn::numeric_cast<unsigned int>(numInputsActual))
telsoa014fcda012018-03-09 14:13:49 +0000138 {
telsoa01c577f2c2018-08-31 09:22:23 +0100139 throw ParseException(
140 boost::str(
141 boost::format("Invalid number of inputs requested %1% for layer %2% "
142 "while only %3% present. %4%") %
143 numInputs %
144 layerParameter.name() %
145 numInputsActual %
146 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000147 }
148
149 int numOutputsActual = layerParameter.top_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100150 if (numOutputs != armnn::numeric_cast<unsigned int>(numOutputsActual))
telsoa014fcda012018-03-09 14:13:49 +0000151 {
telsoa01c577f2c2018-08-31 09:22:23 +0100152 throw ParseException(
153 boost::str(
154 boost::format("Invalid number of outputs requested %1% for layer %2% "
155 "while only %3% present. %4%") %
156 numOutputs %
157 layerParameter.name() %
158 numOutputsActual %
159 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000160 }
161}
162
telsoa01c577f2c2018-08-31 09:22:23 +0100163template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
164ValueType GetOptionalWithFallback(const ParamType& param,
165 ExtractOptional extractOptional,
166 ExtractFallback extractFallback,
167 ValueType defaultValue)
168{
169 auto optValue = extractOptional(param, defaultValue);
170 if (optValue.first)
171 {
172 return optValue.second;
173 }
174 auto fallbackValue = extractFallback(param, defaultValue);
175 return fallbackValue.second;
176}
177
178#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
179 PARAM_TYPE, \
180 OPTIONAL_VALUE, \
181 FALLBACK_VECTOR, \
182 VALUE_TYPE, \
183 DEFAULT_VALUE) \
184 GetOptionalWithFallback( \
185 PARAM, \
186 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
187 { \
188 if (param.has_##OPTIONAL_VALUE ()) \
189 { \
190 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
191 } \
192 else \
193 { \
194 return std::make_pair(false, defaultValue); \
195 } \
196 }, \
197 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
198 { \
199 if (param.FALLBACK_VECTOR##_size() > 0) \
200 { \
201 return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
202 } \
203 else \
204 { \
205 return std::make_pair(false, defaultValue); \
206 } \
207 }, \
208 DEFAULT_VALUE)
209
210#define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
211 PARAM_TYPE, \
212 OPTIONAL_VALUE, \
213 FALLBACK_VALUE, \
214 VALUE_TYPE, \
215 DEFAULT_VALUE) \
216 GetOptionalWithFallback( \
217 PARAM, \
218 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
219 { \
220 if (param.has_##OPTIONAL_VALUE ()) \
221 { \
222 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
223 } \
224 else \
225 { \
226 return std::make_pair(false, defaultValue); \
227 } \
228 }, \
229 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
230 { \
231 if (param.has_##FALLBACK_VALUE ()) \
232 { \
233 return std::make_pair(true, param.FALLBACK_VALUE ()); \
234 } \
235 else \
236 { \
237 return std::make_pair(false, defaultValue); \
238 } \
239 }, \
240 DEFAULT_VALUE)
241
telsoa01c577f2c2018-08-31 09:22:23 +0100242} // namespace <anonymous>
243
244const std::map<std::string, CaffeParserBase::OperationParsingFunction>
245 CaffeParserBase::ms_CaffeLayerNameToParsingFunctions = {
246 { "Input", &CaffeParserBase::ParseInputLayer },
247 { "Convolution", &CaffeParserBase::ParseConvLayer },
248 { "Pooling", &CaffeParserBase::ParsePoolingLayer },
249 { "ReLU", &CaffeParserBase::ParseReluLayer },
250 { "LRN", &CaffeParserBase::ParseLRNLayer },
251 { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
252 { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
253 { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
254 { "Concat", &CaffeParserBase::ParseConcatLayer },
255 { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
256 { "Scale", &CaffeParserBase::ParseScaleLayer },
257 { "Split", &CaffeParserBase::ParseSplitLayer },
258 { "Dropout", &CaffeParserBase::ParseDropoutLayer},
259};
260
261ICaffeParser* ICaffeParser::CreateRaw()
262{
263 return new RecordByRecordCaffeParser();
264}
265
266ICaffeParserPtr ICaffeParser::Create()
267{
268 return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
269}
270
271void ICaffeParser::Destroy(ICaffeParser* parser)
272{
273 delete parser;
274}
275
276CaffeParserBase::CaffeParserBase()
277 : m_Network(nullptr, nullptr)
278{
279
280}
281
282CaffeParser::CaffeParser()
283: CaffeParserBase()
284{
285
286}
287
288BindingPointInfo CaffeParserBase::GetNetworkInputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000289{
290 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
291}
292
telsoa01c577f2c2018-08-31 09:22:23 +0100293BindingPointInfo CaffeParserBase::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000294{
295 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
296}
297
telsoa01c577f2c2018-08-31 09:22:23 +0100298std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
telsoa014fcda012018-03-09 14:13:49 +0000299 const char* bindingPointDesc,
300 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
301{
302 auto it = nameToBindingInfo.find(layerName);
303 if (it == nameToBindingInfo.end())
304 {
telsoa01c577f2c2018-08-31 09:22:23 +0100305 throw InvalidArgumentException(
306 boost::str(
307 boost::format(
308 "Unknown binding %1% for layer '%2%'. %3%") %
309 bindingPointDesc %
310 layerName %
311 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000312 }
313 return it->second;
314}
315
telsoa01c577f2c2018-08-31 09:22:23 +0100316TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
telsoa014fcda012018-03-09 14:13:49 +0000317{
318 std::vector<unsigned int> shape;
319 for (int j = 0; j < blobShape.dim_size(); ++j)
320 {
321 shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
322 }
323
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100324 return TensorInfo(armnn::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
telsoa014fcda012018-03-09 14:13:49 +0000325}
326
327BlobShape TensorDescToBlobShape(const TensorInfo& desc)
328{
329 BlobShape ret;
330 for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
331 {
332 ret.add_dim(i);
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100333 ret.set_dim(armnn::numeric_cast<int>(i), desc.GetShape()[i]);
telsoa014fcda012018-03-09 14:13:49 +0000334 }
335
336 return ret;
337}
338
telsoa01c577f2c2018-08-31 09:22:23 +0100339// Note: can move to CaffeParser when/if we optimise the text/string format
340// to load on a layer by layer basis
341vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000342{
343 std::vector<const caffe::LayerParameter*> ret;
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100344 ret.reserve(armnn::numeric_cast<size_t>(layerParam.bottom_size()));
telsoa014fcda012018-03-09 14:13:49 +0000345 for (int j = 0; j < layerParam.bottom_size(); ++j)
346 {
347 std::string inputName = layerParam.bottom(j);
348 auto inputIt = m_CaffeLayersByTopName.find(inputName);
349 if (inputIt == m_CaffeLayersByTopName.end())
350 {
351 throw ParseException(
telsoa01c577f2c2018-08-31 09:22:23 +0100352 boost::str(
353 boost::format(
354 "Can't find Caffe layer with top called '%1%', "
355 "which is listed as an input of '%2%'. %3%") %
356 inputName %
357 layerParam.name() %
358 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000359 }
360 ret.push_back(inputIt->second);
361 }
362
363 return ret;
364}
365
telsoa01c577f2c2018-08-31 09:22:23 +0100366void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000367{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100368 ARMNN_ASSERT(layerParam.type() == "Input");
telsoa014fcda012018-03-09 14:13:49 +0000369 ValidateNumInputsOutputs(layerParam, 0, 1);
370
371 const InputParameter& param = layerParam.input_param();
372
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100373 const armnn::LayerBindingId inputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa01c577f2c2018-08-31 09:22:23 +0100374 m_NetworkInputsBindingInfo.size());
telsoa014fcda012018-03-09 14:13:49 +0000375 armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
376
telsoa01c577f2c2018-08-31 09:22:23 +0100377 // Decides the tensor info for this input. This can be specified in the Caffe network but can also
telsoa014fcda012018-03-09 14:13:49 +0000378 // be overriden by user input (m_inputShapes).
379 armnn::TensorInfo inputTensorInfo;
380
381 const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
382 &param.shape(0) : nullptr;
383 if (originalShape)
384 {
385 inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
386 }
387
388 auto overrideIt = m_InputShapes.find(layerParam.name());
389 if (overrideIt != m_InputShapes.end())
390 {
391 const TensorShape& overrideShape = overrideIt->second;
392 if (originalShape &&
393 ( originalShape->dim(1) != overrideShape[1]
394 || originalShape->dim(2) != overrideShape[2]
395 || originalShape->dim(3) != overrideShape[3]))
396 {
telsoa01c577f2c2018-08-31 09:22:23 +0100397 throw ParseException(
398 boost::str(
399 boost::format(
400 "Parsed input shape for '%1%' is incompatible with the override provided. %2%") %
401 layerParam.name() %
402 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000403 }
404 inputTensorInfo.SetShape(overrideShape);
405 }
406 else if (!originalShape)
407 {
telsoa01c577f2c2018-08-31 09:22:23 +0100408 throw ParseException(
409 boost::str(
410 boost::format(
411 "No input descriptor given for '%1%' and no input shape found in caffe model. %2%") %
412 layerParam.name() %
413 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000414 }
415
416 TrackInputBinding(inputLayer, inputId, inputTensorInfo);
417 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
418 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
419}
420
telsoa01c577f2c2018-08-31 09:22:23 +0100421void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
422 const armnn::Convolution2dDescriptor& desc,
423 unsigned int kernelW,
424 unsigned int kernelH)
telsoa014fcda012018-03-09 14:13:49 +0000425{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100426 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa014fcda012018-03-09 14:13:49 +0000427 ValidateNumInputsOutputs(layerParam, 1, 1);
428
telsoa01c577f2c2018-08-31 09:22:23 +0100429 ConvolutionParameter convParam = layerParam.convolution_param();
telsoa014fcda012018-03-09 14:13:49 +0000430 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa01c577f2c2018-08-31 09:22:23 +0100431 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
telsoa014fcda012018-03-09 14:13:49 +0000432
telsoa01c577f2c2018-08-31 09:22:23 +0100433 // asusme these were already verified by the caller ParseConvLayer() function
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100434 ARMNN_ASSERT(numGroups < inputShape.dim(1));
435 ARMNN_ASSERT(numGroups > 1);
telsoa014fcda012018-03-09 14:13:49 +0000436
437 // Handle grouping
telsoa014fcda012018-03-09 14:13:49 +0000438 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
439
440 vector<string> convLayerNames(numGroups);
441 vector<armnn::IConnectableLayer*> convLayers(numGroups);
442 convLayerNames[0] = layerParam.name();
443
telsoa01c577f2c2018-08-31 09:22:23 +0100444 // This convolution is to be applied to chunks of the input data so add a splitter layer
445
446 // Redirect the convolution input to the splitter
447 unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
448 static_cast<unsigned int>(inputShape.dim(1)),
449 static_cast<unsigned int>(inputShape.dim(2)),
450 static_cast<unsigned int>(inputShape.dim(3))};
451
452 // Split dimension 1 of the splitter output shape and conv input shapes
453 // according to the number of groups
454
455 splitterDimSizes[1] /= numGroups;
456 inputShape.set_dim(1, splitterDimSizes[1]);
457
458 // This is used to describe how the input is to be split
459 ViewsDescriptor splitterDesc(numGroups);
460
461 // Create an output node for each group, giving each a unique name
462 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000463 {
telsoa01c577f2c2018-08-31 09:22:23 +0100464 // Work out the names of the splitter layers child convolutions
465 stringstream ss;
466 ss << layerParam.name() << "_" << g;
467 convLayerNames[g] = ss.str();
telsoa014fcda012018-03-09 14:13:49 +0000468
telsoa01c577f2c2018-08-31 09:22:23 +0100469 splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
telsoa014fcda012018-03-09 14:13:49 +0000470
telsoa01c577f2c2018-08-31 09:22:23 +0100471 // Set the size of the views.
472 for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
telsoa014fcda012018-03-09 14:13:49 +0000473 {
telsoa01c577f2c2018-08-31 09:22:23 +0100474 splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
telsoa014fcda012018-03-09 14:13:49 +0000475 }
476 }
477
telsoa01c577f2c2018-08-31 09:22:23 +0100478 const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
479 armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
telsoa014fcda012018-03-09 14:13:49 +0000480
telsoa01c577f2c2018-08-31 09:22:23 +0100481 inputConnection.Connect(splitterLayer->GetInputSlot(0));
482 for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
483 {
484 splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
485 }
telsoa014fcda012018-03-09 14:13:49 +0000486
487 unsigned int numFilters = convParam.num_output();
488
telsoa01c577f2c2018-08-31 09:22:23 +0100489 // Populates convolution output tensor descriptor dimensions.
telsoa014fcda012018-03-09 14:13:49 +0000490 BlobShape outputShape;
491 outputShape.add_dim(0);
492 outputShape.set_dim(0, inputShape.dim(0));
493 outputShape.add_dim(1);
telsoa01c577f2c2018-08-31 09:22:23 +0100494 // Ensures that dimension 1 of the convolution output is split according to the number of groups.
telsoa014fcda012018-03-09 14:13:49 +0000495 outputShape.set_dim(1, numFilters / numGroups);
496 outputShape.add_dim(2);
497 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100498 2, (static_cast<int>(
499 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
500 static_cast<float>(desc.m_StrideY)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000501 outputShape.add_dim(3);
502 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100503 3, (static_cast<int>(
504 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
505 static_cast<float>(desc.m_StrideX)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000506
507 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100508 vector<float> weightData(armnn::numeric_cast<size_t>(numGroups *
telsoa01c577f2c2018-08-31 09:22:23 +0100509 inputShape.dim(1) * // number of input channels
510 outputShape.dim(1) * // number of output channels
511 kernelH *
512 kernelW));
telsoa014fcda012018-03-09 14:13:49 +0000513 GetDataFromBlob(layerParam, weightData, 0);
514
515 const unsigned int weightDimSizes[4] = {
telsoa01c577f2c2018-08-31 09:22:23 +0100516 static_cast<unsigned int>(outputShape.dim(1)),
517 static_cast<unsigned int>(inputShape.dim(1)),
518 kernelH,
519 kernelW};
telsoa014fcda012018-03-09 14:13:49 +0000520
telsoa014fcda012018-03-09 14:13:49 +0000521 TensorInfo biasInfo;
522 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100523
524 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000525 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100526 biasData.resize(armnn::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
telsoa014fcda012018-03-09 14:13:49 +0000527 GetDataFromBlob(layerParam, biasData, 1);
528
529 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
530 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
531 }
532
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100533 const unsigned int numWeightsPerGroup = armnn::numeric_cast<unsigned int>(weightData.size()) / numGroups;
534 const unsigned int numBiasesPerGroup = armnn::numeric_cast<unsigned int>(biasData.size()) / numGroups;
telsoa014fcda012018-03-09 14:13:49 +0000535
telsoa014fcda012018-03-09 14:13:49 +0000536 for (unsigned int g = 0; g < numGroups; ++g)
537 {
telsoa01c577f2c2018-08-31 09:22:23 +0100538 // Sets the slot index, group 0 should be connected to the 0th output of the splitter
539 // group 1 should be connected to the 1st output of the splitter.
telsoa014fcda012018-03-09 14:13:49 +0000540
telsoa01c577f2c2018-08-31 09:22:23 +0100541 // Pulls out the weights for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000542 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
543 weightData.data() + numWeightsPerGroup * g);
544
545 IConnectableLayer* convLayer = nullptr;
Matteo Martincighfc598e12019-05-14 10:36:13 +0100546 Optional<ConstTensor> optionalBiases;
telsoa01c577f2c2018-08-31 09:22:23 +0100547 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000548 {
telsoa01c577f2c2018-08-31 09:22:23 +0100549 // Pulls out the biases for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000550 ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
Matteo Martincighfc598e12019-05-14 10:36:13 +0100551 optionalBiases = Optional<ConstTensor>(biases);
telsoa014fcda012018-03-09 14:13:49 +0000552 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100553 convLayer = m_Network->AddConvolution2dLayer(desc,
554 weights,
555 optionalBiases,
556 convLayerNames[g].c_str());
telsoa014fcda012018-03-09 14:13:49 +0000557 convLayers[g] = convLayer;
558
559 // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
560 // otherwise it's the regular input to this layer.
telsoa01c577f2c2018-08-31 09:22:23 +0100561 armnn::IOutputSlot& splitterInputConnection =
562 splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
telsoa014fcda012018-03-09 14:13:49 +0000563 splitterInputConnection.Connect(convLayer->GetInputSlot(0));
564 convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
telsoa014fcda012018-03-09 14:13:49 +0000565 }
566
Jim Flynne242f2d2019-05-22 14:24:13 +0100567 // If the convolution was performed in chunks, add a layer to concatenate the results
telsoa01c577f2c2018-08-31 09:22:23 +0100568
569 // The merge input shape matches that of the convolution output
Jim Flynne242f2d2019-05-22 14:24:13 +0100570 unsigned int concatDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
571 static_cast<unsigned int>(outputShape.dim(1)),
572 static_cast<unsigned int>(outputShape.dim(2)),
573 static_cast<unsigned int>(outputShape.dim(3))};
telsoa01c577f2c2018-08-31 09:22:23 +0100574
Jim Flynne242f2d2019-05-22 14:24:13 +0100575 // This is used to describe how the input is to be concatenated
576 OriginsDescriptor concatDesc(numGroups);
telsoa01c577f2c2018-08-31 09:22:23 +0100577
578 // Now create an input node for each group, using the name from
579 // the output of the corresponding convolution
580 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000581 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100582 concatDesc.SetViewOriginCoord(g, 1, concatDimSizes[1] * g);
telsoa01c577f2c2018-08-31 09:22:23 +0100583 }
telsoa014fcda012018-03-09 14:13:49 +0000584
Jim Flynne242f2d2019-05-22 14:24:13 +0100585 // Make sure the output from the concat is the correct size to hold the data for all groups
586 concatDimSizes[1] *= numGroups;
587 outputShape.set_dim(1, concatDimSizes[1]);
telsoa014fcda012018-03-09 14:13:49 +0000588
Jim Flynne242f2d2019-05-22 14:24:13 +0100589 // Finally add the concat layer
590 IConnectableLayer* concatLayer = m_Network->AddConcatLayer(concatDesc, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000591
Jim Flynne242f2d2019-05-22 14:24:13 +0100592 if (!concatLayer)
telsoa01c577f2c2018-08-31 09:22:23 +0100593 {
594 throw ParseException(
595 boost::str(
596 boost::format(
Jim Flynne242f2d2019-05-22 14:24:13 +0100597 "Failed to create final concat layer for Split+Convolution+Concat. "
telsoa01c577f2c2018-08-31 09:22:23 +0100598 "Layer=%1% #groups=%2% #filters=%3% %4%") %
599 layerParam.name() %
600 numGroups %
601 numFilters %
602 CHECK_LOCATION().AsString()));
603 }
telsoa014fcda012018-03-09 14:13:49 +0000604
telsoa01c577f2c2018-08-31 09:22:23 +0100605 for (unsigned int g = 0; g < numGroups; ++g)
606 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100607 convLayers[g]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(g));
telsoa01c577f2c2018-08-31 09:22:23 +0100608 }
Jim Flynne242f2d2019-05-22 14:24:13 +0100609 concatLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, concatDimSizes, DataType::Float32));
610 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatLayer->GetOutputSlot(0));
telsoa01c577f2c2018-08-31 09:22:23 +0100611}
telsoa014fcda012018-03-09 14:13:49 +0000612
telsoa01c577f2c2018-08-31 09:22:23 +0100613void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
614 const armnn::Convolution2dDescriptor& convDesc,
615 unsigned int kernelW,
616 unsigned int kernelH)
617{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100618 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100619 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000620
telsoa01c577f2c2018-08-31 09:22:23 +0100621 ConvolutionParameter convParam = layerParam.convolution_param();
622 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa014fcda012018-03-09 14:13:49 +0000623
telsoa01c577f2c2018-08-31 09:22:23 +0100624 DepthwiseConvolution2dDescriptor desc;
625 desc.m_PadLeft = convDesc.m_PadLeft;
626 desc.m_PadRight = convDesc.m_PadRight;
627 desc.m_PadTop = convDesc.m_PadTop;
628 desc.m_PadBottom = convDesc.m_PadBottom;
629 desc.m_StrideX = convDesc.m_StrideX;
630 desc.m_StrideY = convDesc.m_StrideY;
631 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
telsoa014fcda012018-03-09 14:13:49 +0000632
telsoa01c577f2c2018-08-31 09:22:23 +0100633 unsigned int numFilters = convParam.num_output();
634
635 BlobShape outputShape;
636 outputShape.add_dim(0);
637 outputShape.set_dim(0, inputShape.dim(0));
638 outputShape.add_dim(1);
639 outputShape.set_dim(1, numFilters);
640 outputShape.add_dim(2);
641 outputShape.set_dim(
642 2, (static_cast<int>(
643 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
644 static_cast<float>(desc.m_StrideY)) + 1));
645 outputShape.add_dim(3);
646 outputShape.set_dim(
647 3, (static_cast<int>(
648 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
649 static_cast<float>(desc.m_StrideX)) + 1));
650
651 // Load the weight data
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100652 size_t allWeightsSize = armnn::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
telsoa01c577f2c2018-08-31 09:22:23 +0100653 vector<float> weightData(allWeightsSize);
654
655 GetDataFromBlob(layerParam, weightData, 0);
656
657 // depth multiplier will be 1 for the depthwise convolution
658 const unsigned int weightDimSizes[4] = {
659 static_cast<unsigned int>(1), // depth multiplier
660 static_cast<unsigned int>(inputShape.dim(1)), // #channels
661 kernelH,
662 kernelW};
663
664 armnn::IConnectableLayer* returnLayer = nullptr;
665 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100666 Optional<ConstTensor> optionalBiases;
667 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100668 if (desc.m_BiasEnabled)
669 {
670 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100671
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100672 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100673 GetDataFromBlob(layerParam, biasData, 1);
674
675 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
676 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
677
678 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100679 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100680 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100681 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc,
682 weights,
683 optionalBiases,
684 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000685
surmeh013537c2c2018-05-18 16:31:43 +0100686 if (!returnLayer)
687 {
telsoa01c577f2c2018-08-31 09:22:23 +0100688 throw ParseException(
689 boost::str(
690 boost::format(
691 "Failed to create depthwise convolution layer. "
692 "Layer=%1% #filters=%2% %3%") %
693 layerParam.name() %
694 numFilters %
695 CHECK_LOCATION().AsString()));
696 }
697 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
698 inputConnection.Connect(returnLayer->GetInputSlot(0));
699 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
700 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
701}
702
703void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
704{
705 // Ignored Caffe Parameters
706 // * Dilation Size
707 // * Weight Filler
708 // * Bias Filler
709 // * Engine
710 // * Force nd_im2col
711 // * Axis
712
713 // Not Available ArmNN Interface Parameters
714 // * Rounding policy;
715
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100716 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100717 ValidateNumInputsOutputs(layerParam, 1, 1);
718
719 ConvolutionParameter convParam = layerParam.convolution_param();
720 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
721 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
722 unsigned int numFilters = convParam.num_output();
723
724 const auto notFound = std::numeric_limits<unsigned int>::max();
725
726 unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
727 kernel_h, kernel_size, unsigned int, notFound);
728 unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
729 kernel_w, kernel_size, unsigned int, notFound);
730
731 unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
732 stride_h, stride, unsigned int, 1u);
733 unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
734 stride_w, stride, unsigned int, 1u);
735
736 unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
737 pad_h, pad, unsigned int, 0u);
738 unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
739 pad_w, pad, unsigned int, 0u);
740
telsoa01c577f2c2018-08-31 09:22:23 +0100741 Convolution2dDescriptor convolution2dDescriptor;
742 convolution2dDescriptor.m_PadLeft = padW;
743 convolution2dDescriptor.m_PadRight = padW;
744 convolution2dDescriptor.m_PadTop = padH;
745 convolution2dDescriptor.m_PadBottom = padH;
746 convolution2dDescriptor.m_StrideX = strideW;
747 convolution2dDescriptor.m_StrideY = strideH;
748 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
749
750 if (numGroups > numFilters)
751 {
752 throw ParseException(
753 boost::str(
754 boost::format(
755 "Error parsing Convolution: %1%. "
756 "The 'group'=%2% parameter cannot be larger than the "
757 "number of filters supplied ='%3%'. %4%") %
758 layerParam.name() %
759 numGroups %
760 numFilters %
761 CHECK_LOCATION().AsString()));
762 }
763
764 if (inputShape.dim_size() != 4)
765 {
766 throw ParseException(
767 boost::str(
768 boost::format(
769 "Convolution input shape is expected to have 4 dimensions. "
770 "%1%'s input has only %2%. %3%") %
771 layerParam.name() %
772 inputShape.dim_size() %
773 CHECK_LOCATION().AsString()));
774 }
775
776 if (numGroups > 1)
777 {
778 if (numGroups > inputShape.dim(1))
779 {
780 throw ParseException(
781 boost::str(
782 boost::format(
783 "Error parsing Convolution: %1%. "
784 "The 'group'=%2% parameter cannot be larger than the "
785 "channel of the input shape=%3% (in NCHW format). %4%") %
786 layerParam.name() %
787 numGroups %
788 inputShape.dim(1) %
789 CHECK_LOCATION().AsString()));
790 }
791 else if (numGroups == inputShape.dim(1))
792 {
793 // we use a depthwise convolution here, because the number of groups equals to the
794 // input channels
795 AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
796 return;
797 }
798 else
799 {
800 // we split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +0100801 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +0100802 AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
803 return;
804 }
805 }
806
807 // NOTE: at this point we only need to handle #group=1 case, all other cases should be
808 // handled by the AddConvLayer* helpers
809
810 // Populate convolution output tensor descriptor dimensions
811 BlobShape outputShape;
812 outputShape.add_dim(0);
813 outputShape.set_dim(0, inputShape.dim(0));
814 outputShape.add_dim(1);
815 outputShape.set_dim(1, numFilters);
816 outputShape.add_dim(2);
817 outputShape.set_dim(
818 2, (static_cast<int>(
819 static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
820 static_cast<float>(strideH)) + 1));
821 outputShape.add_dim(3);
822 outputShape.set_dim(
823 3, (static_cast<int>(
824 static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
825 static_cast<float>(strideW)) + 1));
826
827 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100828 vector<float> weightData(armnn::numeric_cast<size_t>(inputShape.dim(1) *
telsoa01c577f2c2018-08-31 09:22:23 +0100829 outputShape.dim(1) *
830 kernelH *
831 kernelW));
832 GetDataFromBlob(layerParam, weightData, 0);
833
834 const unsigned int weightDimSizes[4] = {
835 static_cast<unsigned int>(outputShape.dim(1)), // output channels
836 static_cast<unsigned int>(inputShape.dim(1)), // input channels
837 kernelH,
838 kernelW};
839
840 armnn::IConnectableLayer* returnLayer = nullptr;
841
842 // Pull out the weights for this group from that loaded from the model file earlier
843 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100844 Optional<ConstTensor> optionalBiases;
845 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100846 if (convolution2dDescriptor.m_BiasEnabled)
847 {
848 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100849
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100850 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100851 GetDataFromBlob(layerParam, biasData, 1);
852
853 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
854 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
855
856 // Pull out the biases for this group from that loaded from the model file earlier
857 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100858 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100859 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100860 returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
861 weights,
862 optionalBiases,
863 layerParam.name().c_str());
telsoa01c577f2c2018-08-31 09:22:23 +0100864
865 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
866 inputConnection.Connect(returnLayer->GetInputSlot(0));
867 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
868
869 if (!returnLayer)
870 {
871 throw ParseException(
872 boost::str(
873 boost::format(
874 "Failed to create Convolution layer. "
875 "Layer=%1% #groups=%2% #filters=%3% %4%") %
876 layerParam.name() %
877 numGroups %
878 numFilters %
879 CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100880 }
881
telsoa014fcda012018-03-09 14:13:49 +0000882 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
883}
884
telsoa01c577f2c2018-08-31 09:22:23 +0100885void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000886{
telsoa01c577f2c2018-08-31 09:22:23 +0100887 // Ignored Caffe Parameters
888 // Stochastic Pooling
889 // Engine
890
telsoa014fcda012018-03-09 14:13:49 +0000891 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000892 PoolingParameter param = layerParam.pooling_param();
telsoa014fcda012018-03-09 14:13:49 +0000893 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
894
telsoa01c577f2c2018-08-31 09:22:23 +0100895 const auto notFound = std::numeric_limits<unsigned int>::max();
896
897 unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
898 kernel_h, kernel_size, unsigned int, notFound);
899 unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
900 kernel_w, kernel_size, unsigned int, notFound);
901
902 if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000903 {
904 kernel_h = inputInfo.GetShape()[2];
905 kernel_w = inputInfo.GetShape()[3];
906 }
telsoa01c577f2c2018-08-31 09:22:23 +0100907
telsoa01c577f2c2018-08-31 09:22:23 +0100908 unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
909 stride_h, stride, unsigned int, notFound);
910 unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
911 stride_h, stride, unsigned int, notFound);
912
913 if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000914 {
telsoa01c577f2c2018-08-31 09:22:23 +0100915 stride_h = 1;
916 stride_w = 1;
telsoa014fcda012018-03-09 14:13:49 +0000917 }
918
telsoa01c577f2c2018-08-31 09:22:23 +0100919 unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
920 pad_h, pad, unsigned int, 0u);
921 unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
922 pad_w, pad, unsigned int, 0u);
telsoa014fcda012018-03-09 14:13:49 +0000923
telsoa014fcda012018-03-09 14:13:49 +0000924 // Populate Weight and Bias Filter Descriptor
925 Pooling2dDescriptor pooling2dDescriptor;
926 if (param.has_pool())
927 {
928 PoolingParameter_PoolMethod p = param.pool();
929 switch (p)
930 {
931 case PoolingParameter_PoolMethod_MAX:
932 {
933 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
934 break;
935 }
936 case PoolingParameter_PoolMethod_AVE:
937 {
938 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
939 break;
940 }
941 case PoolingParameter_PoolMethod_STOCHASTIC:
942 {
telsoa01c577f2c2018-08-31 09:22:23 +0100943 throw ParseException(
944 boost::str(
945 boost::format(
946 "Pooling Layer: Stochastic Pooling Not Supported. Layer=%1% %2%") %
947 layerParam.name() %
948 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000949 }
950 default:
951 {
telsoa01c577f2c2018-08-31 09:22:23 +0100952 throw ParseException(
953 boost::str(
954 boost::format(
955 "Pooling Layer: unknown pooling method: %1% for layer: %2% %3%") %
956 p %
957 layerParam.name() %
958 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000959 }
960 }
961 }
962 else
963 {
telsoa01c577f2c2018-08-31 09:22:23 +0100964 throw ParseException(
965 boost::str(
966 boost::format(
967 "No Pooling Method Defined for %1% %2%") %
968 layerParam.name() %
969 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000970 }
971
972 pooling2dDescriptor.m_PadLeft = pad_w;
973 pooling2dDescriptor.m_PadRight = pad_w;
974 pooling2dDescriptor.m_PadTop = pad_h;
975 pooling2dDescriptor.m_PadBottom = pad_h;
976 pooling2dDescriptor.m_StrideX = stride_w;
977 pooling2dDescriptor.m_StrideY = stride_h;
978 pooling2dDescriptor.m_PoolWidth = kernel_w;
979 pooling2dDescriptor.m_PoolHeight = kernel_h;
980
981 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
982 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
983
984 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
985 layerParam.name().c_str());
986
telsoa014fcda012018-03-09 14:13:49 +0000987 TensorInfo outputInfo(
988 { inputInfo.GetShape()[0],
989 inputInfo.GetShape()[1],
990 static_cast<unsigned int>(ceil(
991 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
992 boost::numeric_cast<float>(stride_h))) + 1,
993 static_cast<unsigned int>(ceil(
994 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
995 boost::numeric_cast<float>(stride_w))) + 1 },
996 DataType::Float32);
997
998 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
999 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1000 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
1001}
1002
telsoa01c577f2c2018-08-31 09:22:23 +01001003void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001004{
1005 ValidateNumInputsOutputs(layerParam, 1, 1);
1006
1007 const string& name = layerParam.name();
1008 const ReLUParameter& param = layerParam.relu_param();
1009
1010 ActivationDescriptor activationDescriptor;
1011 const float negativeSlope = param.negative_slope();
1012 if (negativeSlope == 0.0f)
1013 {
1014 activationDescriptor.m_Function = ActivationFunction::ReLu;
1015 }
1016 else
1017 {
1018 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
1019 activationDescriptor.m_A = negativeSlope;
1020 }
1021
1022 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1023 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
1024 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
1025 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1026 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
1027}
1028
telsoa01c577f2c2018-08-31 09:22:23 +01001029void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001030{
1031 ValidateNumInputsOutputs(layerParam, 1, 1);
1032
1033 LRNParameter param = layerParam.lrn_param();
1034
1035 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1036
telsoa01c577f2c2018-08-31 09:22:23 +01001037 // Ignored BATCH NORMALIZATION Caffe Parameters.
1038 // Ignored MVN Caffe Parameters.
1039 // Ignored LRN Caffe Parameters.
telsoa014fcda012018-03-09 14:13:49 +00001040 // Engine
1041
1042 NormalizationDescriptor normalizationDescriptor;
1043 if (param.has_norm_region())
1044 {
1045 LRNParameter_NormRegion n = param.norm_region();
1046 switch (n)
1047 {
1048 case LRNParameter_NormRegion_ACROSS_CHANNELS:
1049 {
1050 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1051 break;
1052 }
1053 case LRNParameter_NormRegion_WITHIN_CHANNEL:
1054 {
1055 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1056 break;
1057 }
1058 default:
telsoa01c577f2c2018-08-31 09:22:23 +01001059 {
1060 throw ParseException(
1061 boost::str(
1062 boost::format(
1063 "Unknown region %1% for LRN layer %2% %3%") %
1064 n %
1065 layerParam.name() %
1066 CHECK_LOCATION().AsString()));
1067 }
telsoa014fcda012018-03-09 14:13:49 +00001068 }
1069 }
1070 else
1071 {
telsoa01c577f2c2018-08-31 09:22:23 +01001072 // Caffe defaults to normalization across channels.
telsoa014fcda012018-03-09 14:13:49 +00001073 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1074 }
1075
1076 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1077 if (param.has_local_size())
1078 {
1079 normalizationDescriptor.m_NormSize = param.local_size();
1080 }
1081 else
1082 {
telsoa01c577f2c2018-08-31 09:22:23 +01001083 throw ParseException(
1084 boost::str(
1085 boost::format(
1086 "local_size not defined for LRN layer %1% %2%") %
1087 layerParam.name() %
1088 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001089 }
1090
1091 if (param.has_alpha())
1092 {
1093 normalizationDescriptor.m_Alpha = param.alpha();
1094 normalizationDescriptor.m_Alpha /= boost::numeric_cast<float>(param.local_size());
1095 }
1096 else
1097 {
telsoa01c577f2c2018-08-31 09:22:23 +01001098 throw ParseException(
1099 boost::str(
1100 boost::format(
1101 "Alpha not defined for LRN layer %1% %2%") %
1102 layerParam.name() %
1103 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001104 }
1105 if (param.has_beta())
1106 {
1107 normalizationDescriptor.m_Beta = param.beta();
1108 }
1109 else
1110 {
telsoa01c577f2c2018-08-31 09:22:23 +01001111 throw ParseException(
1112 boost::str(
1113 boost::format(
1114 "Beta not defined for LRN layer %1% %2%") %
1115 layerParam.name() %
1116 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001117 }
telsoa01c577f2c2018-08-31 09:22:23 +01001118
telsoa014fcda012018-03-09 14:13:49 +00001119 if (param.has_k())
1120 {
1121 normalizationDescriptor.m_K = param.k();
1122 }
1123 else
telsoa01c577f2c2018-08-31 09:22:23 +01001124 {
telsoa014fcda012018-03-09 14:13:49 +00001125 normalizationDescriptor.m_K = 1;
telsoa01c577f2c2018-08-31 09:22:23 +01001126 }
telsoa014fcda012018-03-09 14:13:49 +00001127
1128 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1129 layerParam.name().c_str());
1130 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1131 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1132
1133 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1134}
1135
telsoa01c577f2c2018-08-31 09:22:23 +01001136void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001137{
1138 InnerProductParameter param = layerParam.inner_product_param();
1139
1140 ValidateNumInputsOutputs(layerParam, 1, 1);
1141
1142 unsigned int outputSize = param.num_output();
1143
telsoa01c577f2c2018-08-31 09:22:23 +01001144 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001145 // Weight Filler
1146 // Bias Filler
1147 // Engine
1148 // Axis
1149
1150 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1151
1152 if (param.has_transpose())
1153 {
telsoa01c577f2c2018-08-31 09:22:23 +01001154 // If true, assumes transposed weights.
telsoa014fcda012018-03-09 14:13:49 +00001155 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1156 }
1157 else
1158 {
telsoa01c577f2c2018-08-31 09:22:23 +01001159 // Caffe defaults to transposed.
telsoa014fcda012018-03-09 14:13:49 +00001160 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1161 }
1162
1163 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1164
1165 TensorInfo weightInfo;
1166 TensorInfo biasInfo;
1167
telsoa01c577f2c2018-08-31 09:22:23 +01001168 // Allows implicit flattening of extra dimensions.
telsoa014fcda012018-03-09 14:13:49 +00001169 unsigned int inputSize = inputInfo.GetShape()[1];
1170 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1171 {
1172 inputSize *= inputInfo.GetShape()[i];
1173 }
1174
telsoa01c577f2c2018-08-31 09:22:23 +01001175 const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
telsoa014fcda012018-03-09 14:13:49 +00001176 const unsigned int swTD[2] = { outputSize, inputSize };
telsoa01c577f2c2018-08-31 09:22:23 +01001177 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001178
1179 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
telsoa01c577f2c2018-08-31 09:22:23 +01001180 // Todo: check whether bias enabled.
telsoa014fcda012018-03-09 14:13:49 +00001181 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1182 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1183 {
1184 // BIAS VALUE
telsoa01c577f2c2018-08-31 09:22:23 +01001185 const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
telsoa014fcda012018-03-09 14:13:49 +00001186
1187 const unsigned int sbTD[1] = { outputSize };
1188
telsoa01c577f2c2018-08-31 09:22:23 +01001189 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001190
Matteo Martincighfc598e12019-05-14 10:36:13 +01001191 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1192 weights,
1193 Optional<ConstTensor>(biases),
1194 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001195 }
1196 else
1197 {
Matteo Martincighfc598e12019-05-14 10:36:13 +01001198 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1199 weights,
1200 EmptyOptional(),
1201 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001202 }
1203
1204 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1205 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1206 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1207 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1208}
1209
telsoa01c577f2c2018-08-31 09:22:23 +01001210void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001211{
1212 ValidateNumInputsOutputs(layerParam, 1, 1);
1213
1214 SoftmaxParameter param = layerParam.softmax_param();
1215
1216 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1217
telsoa01c577f2c2018-08-31 09:22:23 +01001218 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001219 // axis
1220 // Engine
1221
1222 armnn::SoftmaxDescriptor softmaxDescriptor;
Teresa Charlin4320c922020-08-12 16:04:41 +01001223 softmaxDescriptor.m_Axis = 1;
telsoa014fcda012018-03-09 14:13:49 +00001224 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1225 softmaxDescriptor,
1226 layerParam.name().c_str());
1227 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1228 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1229 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1230}
1231
telsoa01c577f2c2018-08-31 09:22:23 +01001232void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001233{
1234 ValidateNumInputsOutputs(layerParam, 2, 1);
1235
1236 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1237
telsoa01c577f2c2018-08-31 09:22:23 +01001238 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001239 // coeff
1240
telsoa01c577f2c2018-08-31 09:22:23 +01001241 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
telsoa014fcda012018-03-09 14:13:49 +00001242
1243 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1244 {
1245 operation = layerParam.eltwise_param().operation();
1246 }
1247
1248 armnn::IConnectableLayer* newLayer = nullptr;
1249 switch (operation)
1250 {
1251 case EltwiseParameter_EltwiseOp_SUM:
1252 {
1253 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1254 break;
1255 }
1256 case EltwiseParameter_EltwiseOp_PROD:
1257 {
1258 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1259 break;
1260 }
1261 default:
1262 {
telsoa01c577f2c2018-08-31 09:22:23 +01001263 throw ParseException(
1264 boost::str(
1265 boost::format(
1266 "Unsupported operation %1% in Eltwise layer %2% %3%") %
1267 operation %
1268 layerParam.name() %
1269 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001270 }
1271 }
1272
1273 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1274 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1275 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1276 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1277}
1278
telsoa01c577f2c2018-08-31 09:22:23 +01001279void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001280{
1281 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
telsoa01c577f2c2018-08-31 09:22:23 +01001282 // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
telsoa014fcda012018-03-09 14:13:49 +00001283 unsigned int concatDim = 1;
1284 unsigned int numOfDims = 4;
1285
telsoa01c577f2c2018-08-31 09:22:23 +01001286 // we only consider 4-D tensor here
1287 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
telsoa014fcda012018-03-09 14:13:49 +00001288 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1289
1290 unsigned int mergeDim = 0;
1291 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1292 {
1293 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001294 layerParam.bottom(armnn::numeric_cast<int>(viewIndex))).GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001295 // Checks whether the dimensions of the input tensors are actually 4.
telsoa014fcda012018-03-09 14:13:49 +00001296 if (inputInfo.GetNumDimensions()!=4)
1297 {
telsoa01c577f2c2018-08-31 09:22:23 +01001298 throw ParseException(
1299 boost::str(
1300 boost::format(
1301 "The number of dimensions for input tensors of "
1302 "the concatenation op should be 4. Inputs of %1% has "
1303 "%2% dimensions. %3%") %
1304 layerParam.name() %
1305 inputInfo.GetNumDimensions() %
1306 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001307 }
1308
1309 mergeDimSizes[0] = inputInfo.GetShape()[0];
1310 mergeDimSizes[1] = inputInfo.GetShape()[1];
1311 mergeDimSizes[2] = inputInfo.GetShape()[2];
1312 mergeDimSizes[3] = inputInfo.GetShape()[3];
1313
1314 for (unsigned int j = 0; j < concatDim; ++j)
1315 {
1316 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1317 }
1318
1319 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1320 mergeDim += mergeDimSizes[concatDim];
1321
1322 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1323 {
1324 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1325 }
1326 }
1327 mergeDimSizes[concatDim] = mergeDim;
1328
Jim Flynn906f9462019-05-10 13:55:21 +01001329 armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001330 for (unsigned int i = 0; i < numInputs; ++i)
1331 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001332 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(armnn::numeric_cast<int>(i)));
telsoa014fcda012018-03-09 14:13:49 +00001333 outputSlot.Connect(concatlayer->GetInputSlot(i));
1334 }
1335
1336 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1337 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1338}
1339
telsoa01c577f2c2018-08-31 09:22:23 +01001340void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001341{
1342 ValidateNumInputsOutputs(layerParam, 1, 1);
1343
1344 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1345
1346 string name = layerParam.name();
1347
1348 BatchNormParameter param = layerParam.batch_norm_param();
1349 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1350 // when the network is in the testing phase).
1351 if (param.has_use_global_stats())
1352 {
1353 if (!param.use_global_stats())
1354 {
telsoa01c577f2c2018-08-31 09:22:23 +01001355 throw ParseException(
1356 boost::str(
1357 boost::format(
1358 "Error parsing Batch Norm layer '%1%': "
1359 "Parameter 'use_global_stats' is set to false, which is "
1360 "unsupported (value used for training). %2%") %
1361 name %
1362 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001363 }
1364 }
1365
1366 BatchNormalizationDescriptor desc;
1367 desc.m_Eps = param.eps();
1368
1369 unsigned int channels = inputInfo.GetShape()[1];
1370 unsigned int shape[] = {channels};
1371
1372 vector<float> meanData(channels);
1373 GetDataFromBlob(layerParam, meanData, 0);
1374
1375 vector<float> varianceData(channels);
1376 GetDataFromBlob(layerParam, varianceData, 1);
1377
telsoa01c577f2c2018-08-31 09:22:23 +01001378 // Reads moving average factor and applies scaling (if required).
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001379 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(2));
1380 const float movingAverageFactor = blob.data(armnn::numeric_cast<int>(0));
surmeh013537c2c2018-05-18 16:31:43 +01001381 if(movingAverageFactor != 0.0f)
1382 {
1383 const float scaleFactor = 1.0f / movingAverageFactor;
1384 auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1385
1386 std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1387 std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1388 }
1389
telsoa01c577f2c2018-08-31 09:22:23 +01001390 // Identifies scale operation.
telsoa014fcda012018-03-09 14:13:49 +00001391 vector<float> betaData(channels, 0.0f);
1392 vector<float> gammaData(channels, 1.0f);
1393
1394 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1395 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1396 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1397 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1398
1399 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1400 mean, variance, beta, gamma, name.c_str());
1401 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1402 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1403 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1404}
1405
telsoa01c577f2c2018-08-31 09:22:23 +01001406void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001407{
telsoa01c577f2c2018-08-31 09:22:23 +01001408 // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
telsoa014fcda012018-03-09 14:13:49 +00001409 ValidateNumInputsOutputs(layerParam, 1, 1);
1410
1411 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1412
1413 string name = layerParam.name();
1414
1415 ScaleParameter param = layerParam.scale_param();
1416 if (param.axis() != 1)
1417 {
1418 // Would have to use something other than BatchNormalizationLayer in this case
telsoa01c577f2c2018-08-31 09:22:23 +01001419 throw ParseException(
1420 boost::str(
1421 boost::format(
1422 "Loading Scale Layer: Only axis 1 is supported currently. "
1423 "Layer=%1% Axis=%2% %3%") %
1424 layerParam.name() %
1425 param.axis() %
1426 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001427 }
1428
1429 unsigned int channels = inputInfo.GetShape()[1];
1430 unsigned int shape[] = {channels};
1431
1432 BatchNormalizationDescriptor desc;
telsoa01c577f2c2018-08-31 09:22:23 +01001433 desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
telsoa014fcda012018-03-09 14:13:49 +00001434 vector<float> meanData(channels, 0.0f);
1435 vector<float> varianceData(channels, 1.0f);
1436 vector<float> betaData(channels, 0.0f);
1437 vector<float> gammaData(channels);
1438
1439 GetDataFromBlob(layerParam, gammaData, 0);
1440
1441 if(param.has_bias_term())
1442 {
1443 GetDataFromBlob(layerParam, betaData, 1);
1444 }
1445
1446 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1447 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1448 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1449 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1450
1451 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1452 mean, variance, beta, gamma, name.c_str());
1453 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1454 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1455 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1456}
1457
telsoa01c577f2c2018-08-31 09:22:23 +01001458void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001459{
telsoa01c577f2c2018-08-31 09:22:23 +01001460 // Used in caffe to duplicate memory - not necessary in armnn.
telsoa014fcda012018-03-09 14:13:49 +00001461 if (layerParam.bottom_size() != 1)
1462 {
telsoa01c577f2c2018-08-31 09:22:23 +01001463 throw ParseException(
1464 boost::str(
1465 boost::format(
1466 "Split layer '%1%' should have exactly 1 bottom. "
1467 "#bottoms=%2% %3%") %
1468 layerParam.name() %
1469 layerParam.bottom_size() %
1470 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001471 }
1472 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1473 for (int i = 0; i < layerParam.top_size(); i++)
1474 {
1475 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1476 }
1477}
1478
telsoa01c577f2c2018-08-31 09:22:23 +01001479void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001480{
telsoa01c577f2c2018-08-31 09:22:23 +01001481 // Ignored for inference, so patch the single input to its single output.
telsoa014fcda012018-03-09 14:13:49 +00001482 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1483 {
telsoa01c577f2c2018-08-31 09:22:23 +01001484 throw ParseException(
1485 boost::str(
1486 boost::format(
1487 "Dropout layer '%1%' should have exactly 1 bottom and 1 top. "
1488 "#bottoms=%2% #tops=%3% %4%") %
1489 layerParam.name() %
1490 layerParam.bottom_size() %
1491 layerParam.top_size() %
1492 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001493 }
1494 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1495}
1496
telsoa01c577f2c2018-08-31 09:22:23 +01001497void CaffeParserBase::TrackInputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001498 armnn::LayerBindingId id,
1499 const armnn::TensorInfo& tensorInfo)
1500{
1501 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1502}
1503
telsoa01c577f2c2018-08-31 09:22:23 +01001504void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001505 armnn::LayerBindingId id,
1506 const armnn::TensorInfo& tensorInfo)
1507{
1508 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1509}
1510
telsoa01c577f2c2018-08-31 09:22:23 +01001511void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001512 armnn::LayerBindingId id,
1513 const armnn::TensorInfo& tensorInfo,
1514 const char* bindingPointDesc,
1515 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1516{
1517 const std::string layerName = layer->GetName();
1518 auto it = nameToBindingInfo.find(layerName);
1519 if (it == nameToBindingInfo.end())
1520 {
1521 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1522 }
1523 else
1524 {
telsoa01c577f2c2018-08-31 09:22:23 +01001525 throw ParseException(
1526 boost::str(
1527 boost::format(
1528 "Id %1% used by more than one %2% layer %3%") %
1529 id %
1530 bindingPointDesc %
1531 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001532 }
1533}
1534
telsoa01c577f2c2018-08-31 09:22:23 +01001535armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
telsoa014fcda012018-03-09 14:13:49 +00001536{
1537 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1538 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1539 {
1540 return *it->second;
1541 }
1542 else
1543 {
telsoa01c577f2c2018-08-31 09:22:23 +01001544 throw ParseException(
1545 boost::str(
1546 boost::format(
1547 "Could not find armnn output slot for Caffe top '%1%' %2%") %
1548 caffeTopName %
1549 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001550 }
1551}
1552
telsoa01c577f2c2018-08-31 09:22:23 +01001553void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
1554 const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
telsoa014fcda012018-03-09 14:13:49 +00001555{
1556 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1557 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1558 {
1559 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1560 }
1561 else
1562 {
telsoa01c577f2c2018-08-31 09:22:23 +01001563 throw ParseException(
1564 boost::str(
1565 boost::format(
1566 "Attempting to add duplicate entry for Caffe top '%1%' %2%") %
1567 caffeTopName %
1568 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001569 }
1570}
1571
telsoa01c577f2c2018-08-31 09:22:23 +01001572// Note: can move to CaffeParser when/if we optimise the text/string format
1573// to load on a layer by layer basis
1574void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001575{
telsoa01c577f2c2018-08-31 09:22:23 +01001576 // Finds layers with the same top.
telsoa014fcda012018-03-09 14:13:49 +00001577 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1578 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1579 {
1580 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
telsoa01c577f2c2018-08-31 09:22:23 +01001581 std::string name = layer.name();
telsoa014fcda012018-03-09 14:13:49 +00001582 for (int i = 0; i < layer.top_size(); ++i)
1583 {
1584 layersByTop[layer.top(i)].push_back(&layer);
1585 }
1586 }
1587
telsoa01c577f2c2018-08-31 09:22:23 +01001588 // 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 +00001589 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1590 for (auto layersWithSameTopIt : layersByTop)
1591 {
1592 const std::string& top = layersWithSameTopIt.first;
1593 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1594
telsoa01c577f2c2018-08-31 09:22:23 +01001595 // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
telsoa014fcda012018-03-09 14:13:49 +00001596 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1597 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1598 {
1599 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1600 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1601 if (layer1.top_size() != 1)
1602 {
telsoa01c577f2c2018-08-31 09:22:23 +01001603 throw ParseException(
1604 boost::str(
1605 boost::format(
1606 "Node '%1%' is an in-place layer but doesn't have exactly one "
1607 "top. It has %2% instead. %3%") %
1608 layer1.name() %
1609 layer1.top_size() %
1610 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001611 }
1612 std::string newTop = layer1.name() + "_top";
1613 layer1.set_top(0, newTop);
1614 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1615 {
telsoa01c577f2c2018-08-31 09:22:23 +01001616 throw ParseException(
1617 boost::str(
1618 boost::format(
1619 "Node '%1%' is an in-place layer but "
1620 "doesn't have exactly one bottom, or it doesn't match its top. "
1621 "#bottoms=%2%, first bottom is %3%, top is %4% %5%") %
1622 layer2.name() %
1623 layer2.bottom(0) %
1624 top %
1625 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001626 }
1627 layer2.set_bottom(0, newTop);
1628 }
1629 }
1630}
1631
telsoa01c577f2c2018-08-31 09:22:23 +01001632// Note: can move to CaffeParser when/if we optimise the text/string format
1633// to load on a layer by layer basis
1634void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001635{
telsoa01c577f2c2018-08-31 09:22:23 +01001636 // Caffe models sometimes have an implicit input layer.
1637 // In that case, add an explicit one.
telsoa014fcda012018-03-09 14:13:49 +00001638 if (netParameter.input_size() > 0)
1639 {
1640 LayerParameter* newLayer = netParameter.add_layer();
1641
1642 newLayer->set_type("Input");
1643 newLayer->set_name(netParameter.input(0));
1644 newLayer->add_top(netParameter.input(0));
1645
1646 InputParameter* inputParam = newLayer->mutable_input_param();
1647 BlobShape* shape = inputParam->add_shape();
1648
1649 int dim_size = netParameter.input_dim_size();
1650 for (int i = 0; i < dim_size; ++i)
1651 {
1652 shape->add_dim(netParameter.input_dim(i));
1653 }
1654 }
1655
telsoa01c577f2c2018-08-31 09:22:23 +01001656 // Replaces in-place layers with regular ones to make the rest of the parsing easier.
telsoa014fcda012018-03-09 14:13:49 +00001657 ResolveInPlaceLayers(netParameter);
1658
telsoa01c577f2c2018-08-31 09:22:23 +01001659 // Creates a lookup of Caffe layers by name.
telsoa014fcda012018-03-09 14:13:49 +00001660 for (int i = 0; i < netParameter.layer_size(); ++i)
1661 {
1662 const caffe::LayerParameter& layer = netParameter.layer(i);
1663 for (int i = 0; i < layer.top_size(); ++i)
1664 {
1665 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1666 }
1667 }
1668
telsoa01c577f2c2018-08-31 09:22:23 +01001669 // Finds the output layers the user requested.
telsoa014fcda012018-03-09 14:13:49 +00001670 std::vector<const caffe::LayerParameter*> targetLayers;
1671 for (const std::string& requestedOutputName : m_RequestedOutputs)
1672 {
1673 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1674 if (nodeIt == m_CaffeLayersByTopName.end())
1675 {
telsoa01c577f2c2018-08-31 09:22:23 +01001676 throw ParseException(
1677 boost::str(
1678 boost::format(
1679 "Couldn't find requested output layer '%1%' in graph %2%") %
1680 requestedOutputName %
1681 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001682 }
1683 targetLayers.push_back(nodeIt->second);
1684 }
1685
telsoa01c577f2c2018-08-31 09:22:23 +01001686 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001687 std::vector<const caffe::LayerParameter*> sortedNodes;
1688 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1689 targetLayers,
1690 [this](const caffe::LayerParameter* node)
1691 {
1692 return GetInputs(*node);
1693 },
1694 sortedNodes))
1695 {
telsoa01c577f2c2018-08-31 09:22:23 +01001696 throw ParseException(
1697 boost::str(
1698 boost::format(
1699 "Cycle detected in graph. #nodes: %1% %2%") %
1700 sortedNodes.size() %
1701 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001702 }
1703
telsoa01c577f2c2018-08-31 09:22:23 +01001704 // 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 +00001705 for (const caffe::LayerParameter* current : sortedNodes)
1706 {
1707 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1708 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1709 {
telsoa01c577f2c2018-08-31 09:22:23 +01001710 throw ParseException(
1711 boost::str(
1712 boost::format("Unsupported layer type: '%1%' for layer %2% %3%") %
1713 current->type() %
1714 current->name() %
1715 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001716 }
1717 auto func = it->second;
1718 (this->*func)(*current);
1719 }
1720
telsoa01c577f2c2018-08-31 09:22:23 +01001721 // Adds ArmNN output layers connected to each requested output.
telsoa014fcda012018-03-09 14:13:49 +00001722 for (const std::string& requestedOutput : m_RequestedOutputs)
1723 {
1724 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1725
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001726 const armnn::LayerBindingId outputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa014fcda012018-03-09 14:13:49 +00001727 m_NetworkOutputsBindingInfo.size());
1728 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1729 outputSlot.Connect(outputLayer->GetInputSlot(0));
1730
1731 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1732 }
1733}
1734
telsoa01c577f2c2018-08-31 09:22:23 +01001735INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
telsoa014fcda012018-03-09 14:13:49 +00001736 const std::map<std::string, armnn::TensorShape>& inputShapes,
1737 const std::vector<std::string>& requestedOutputs)
1738{
1739 FILE* fd = fopen(graphFile, "r");
1740
1741 if (fd == nullptr)
1742 {
telsoa01c577f2c2018-08-31 09:22:23 +01001743 throw FileNotFoundException(
1744 boost::str(
1745 boost::format(
1746 "Failed to open graph file: %1% %2%") %
1747 graphFile %
1748 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001749 }
1750
telsoa01c577f2c2018-08-31 09:22:23 +01001751 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001752 NetParameter netParam;
1753 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1754 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1755 delete input;
1756 fclose(fd);
1757
1758 if (!success)
1759 {
telsoa01c577f2c2018-08-31 09:22:23 +01001760 throw ParseException(
1761 boost::str(
1762 boost::format(
1763 "Failed to parse graph file: %1% %2%") %
1764 graphFile %
1765 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001766 }
1767
1768 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1769}
1770
telsoa01c577f2c2018-08-31 09:22:23 +01001771INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
telsoa014fcda012018-03-09 14:13:49 +00001772 const std::map<std::string, armnn::TensorShape>& inputShapes,
1773 const std::vector<std::string>& requestedOutputs)
1774{
telsoa01c577f2c2018-08-31 09:22:23 +01001775 // Parses the string into a message.
telsoa014fcda012018-03-09 14:13:49 +00001776 NetParameter netParam;
1777 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1778
1779 if (!success)
1780 {
telsoa01c577f2c2018-08-31 09:22:23 +01001781 throw ParseException(
1782 boost::str(
1783 boost::format(
1784 "Failed to parse graph string %1%") %
1785 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001786 }
1787
1788 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1789}
1790
1791INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1792 const std::map<std::string, armnn::TensorShape>& inputShapes,
1793 const std::vector<std::string>& requestedOutputs)
1794{
1795 FILE* fd = fopen(graphFile, "rb");
1796
1797 if (fd == nullptr)
1798 {
telsoa01c577f2c2018-08-31 09:22:23 +01001799 throw FileNotFoundException(
1800 boost::str(
1801 boost::format(
1802 "Failed to open graph file at: %1% %2%") %
1803 graphFile %
1804 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001805 }
1806
telsoa01c577f2c2018-08-31 09:22:23 +01001807 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001808 NetParameter netParam;
1809
1810 FileInputStream inStream(fileno(fd));
1811 CodedInputStream codedStream(&inStream);
1812 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
1813 bool success = netParam.ParseFromCodedStream(&codedStream);
1814 fclose(fd);
1815
1816 if (!success)
1817 {
telsoa01c577f2c2018-08-31 09:22:23 +01001818 throw ParseException(
1819 boost::str(
1820 boost::format(
1821 "Failed to parse protobuf file: %1% %2%") %
1822 graphFile %
1823 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001824 }
1825
1826 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1827}
1828
telsoa01c577f2c2018-08-31 09:22:23 +01001829// Note: can move to CaffeParser when/if we optimise the text/string format
1830// to load on a layer by layer basis
1831INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
telsoa014fcda012018-03-09 14:13:49 +00001832 const std::map<std::string, armnn::TensorShape>& inputShapes,
1833 const std::vector<std::string>& requestedOutputs)
1834{
1835 m_NetworkInputsBindingInfo.clear();
1836 m_NetworkOutputsBindingInfo.clear();
1837
1838 m_Network = INetwork::Create();
1839
1840 m_InputShapes = inputShapes;
1841 if (requestedOutputs.size() == 0)
1842 {
1843 throw ParseException("requestedOutputs must have at least one entry");
1844 }
1845 m_RequestedOutputs = requestedOutputs;
1846
1847 try
1848 {
1849 LoadNetParam(netParam);
1850 }
1851 catch (const ParseException& e)
1852 {
1853 Cleanup();
1854 throw e;
1855 }
1856
1857 Cleanup();
1858
1859 return move(m_Network);
1860}
1861
telsoa01c577f2c2018-08-31 09:22:23 +01001862void CaffeParserBase::Cleanup() {
telsoa014fcda012018-03-09 14:13:49 +00001863 // cleanup, in case we reuse this parser
telsoa014fcda012018-03-09 14:13:49 +00001864 m_InputShapes.clear();
1865 m_RequestedOutputs.clear();
1866 m_ArmnnOutputSlotForCaffeTop.clear();
telsoa01c577f2c2018-08-31 09:22:23 +01001867 // NOTE: when we get the text/string format
1868 // optimised for memory then this data structure can
1869 // also move to the CaffeParser class
1870 m_CaffeLayersByTopName.clear();
telsoa014fcda012018-03-09 14:13:49 +00001871}
1872
1873}