blob: 0a6a6c5348d6d30eae1e48fe12786e3617c03337 [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>
James Ward58dec6b2020-09-11 17:32:44 +010020#include <fmt/format.h>
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(
James Ward58dec6b2020-09-11 17:32:44 +010073 fmt::format("Expected data blob at index {} in layer {} not found. nBlobs={}. {}",
74 blobIndex,
75 layerParam.name(),
76 nBlobs,
77 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000078 }
79
Matthew Sloyan589e3e82020-09-11 16:17:48 +010080 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(blobIndex));
telsoa014fcda012018-03-09 14:13:49 +000081
telsoa01c577f2c2018-08-31 09:22:23 +010082 const float* arrayPtr = blob.data().data();
83 return arrayPtr;
84}
85
86void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
87{
88 auto nBlobs = layerParam.blobs_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +010089 if (blobIndex >= armnn::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000090 {
telsoa01c577f2c2018-08-31 09:22:23 +010091 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +010092 fmt::format("Expected data blob at index {} in layer {} not found. {}",
93 blobIndex,
94 layerParam.name(),
95 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000096 }
97
Matthew Sloyan589e3e82020-09-11 16:17:48 +010098 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(blobIndex));
telsoa01c577f2c2018-08-31 09:22:23 +010099
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100100 size_t blobSize = armnn::numeric_cast<size_t>(blob.data_size());
telsoa01c577f2c2018-08-31 09:22:23 +0100101 if (blobSize != outData.size())
telsoa014fcda012018-03-09 14:13:49 +0000102 {
telsoa01c577f2c2018-08-31 09:22:23 +0100103 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100104 fmt::format("Data blob at index {} in layer {} has an unexpected size. "
105 "Expected {} elements but got {} elements. {}",
106 blobIndex,
107 layerParam.name(),
108 outData.size(),
109 blobSize,
110 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100111 }
112
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100113 int outSizeInt = armnn::numeric_cast<int>(outData.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100114 for (int i = 0; i < outSizeInt; ++i)
115 {
116 outData[static_cast<size_t>(i)] = blob.data(i);
telsoa014fcda012018-03-09 14:13:49 +0000117 }
118}
119
telsoa014fcda012018-03-09 14:13:49 +0000120template <typename T>
121size_t SizeOfVectorData(const vector<T>& vec)
122{
123 return vec.size() * sizeof(T);
124}
125
126void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
127 unsigned int numInputs,
128 unsigned int numOutputs)
129{
130 int numInputsActual = layerParameter.bottom_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100131 if (numInputs != armnn::numeric_cast<unsigned int>(numInputsActual))
telsoa014fcda012018-03-09 14:13:49 +0000132 {
telsoa01c577f2c2018-08-31 09:22:23 +0100133 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100134 fmt::format("Invalid number of inputs requested {} for layer {} "
135 "while only {} present. {}",
136 numInputs,
137 layerParameter.name(),
138 numInputsActual,
139 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000140 }
141
142 int numOutputsActual = layerParameter.top_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100143 if (numOutputs != armnn::numeric_cast<unsigned int>(numOutputsActual))
telsoa014fcda012018-03-09 14:13:49 +0000144 {
telsoa01c577f2c2018-08-31 09:22:23 +0100145 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100146 fmt::format("Invalid number of outputs requested {} for layer {} "
147 "while only {} present. {}",
148 numOutputs,
149 layerParameter.name(),
150 numOutputsActual,
151 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000152 }
153}
154
telsoa01c577f2c2018-08-31 09:22:23 +0100155template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
156ValueType GetOptionalWithFallback(const ParamType& param,
157 ExtractOptional extractOptional,
158 ExtractFallback extractFallback,
159 ValueType defaultValue)
160{
161 auto optValue = extractOptional(param, defaultValue);
162 if (optValue.first)
163 {
164 return optValue.second;
165 }
166 auto fallbackValue = extractFallback(param, defaultValue);
167 return fallbackValue.second;
168}
169
170#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
171 PARAM_TYPE, \
172 OPTIONAL_VALUE, \
173 FALLBACK_VECTOR, \
174 VALUE_TYPE, \
175 DEFAULT_VALUE) \
176 GetOptionalWithFallback( \
177 PARAM, \
178 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
179 { \
180 if (param.has_##OPTIONAL_VALUE ()) \
181 { \
182 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
183 } \
184 else \
185 { \
186 return std::make_pair(false, defaultValue); \
187 } \
188 }, \
189 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
190 { \
191 if (param.FALLBACK_VECTOR##_size() > 0) \
192 { \
193 return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
194 } \
195 else \
196 { \
197 return std::make_pair(false, defaultValue); \
198 } \
199 }, \
200 DEFAULT_VALUE)
201
202#define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
203 PARAM_TYPE, \
204 OPTIONAL_VALUE, \
205 FALLBACK_VALUE, \
206 VALUE_TYPE, \
207 DEFAULT_VALUE) \
208 GetOptionalWithFallback( \
209 PARAM, \
210 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
211 { \
212 if (param.has_##OPTIONAL_VALUE ()) \
213 { \
214 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
215 } \
216 else \
217 { \
218 return std::make_pair(false, defaultValue); \
219 } \
220 }, \
221 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
222 { \
223 if (param.has_##FALLBACK_VALUE ()) \
224 { \
225 return std::make_pair(true, param.FALLBACK_VALUE ()); \
226 } \
227 else \
228 { \
229 return std::make_pair(false, defaultValue); \
230 } \
231 }, \
232 DEFAULT_VALUE)
233
telsoa01c577f2c2018-08-31 09:22:23 +0100234} // namespace <anonymous>
235
236const std::map<std::string, CaffeParserBase::OperationParsingFunction>
237 CaffeParserBase::ms_CaffeLayerNameToParsingFunctions = {
238 { "Input", &CaffeParserBase::ParseInputLayer },
239 { "Convolution", &CaffeParserBase::ParseConvLayer },
240 { "Pooling", &CaffeParserBase::ParsePoolingLayer },
241 { "ReLU", &CaffeParserBase::ParseReluLayer },
242 { "LRN", &CaffeParserBase::ParseLRNLayer },
243 { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
244 { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
245 { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
246 { "Concat", &CaffeParserBase::ParseConcatLayer },
247 { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
248 { "Scale", &CaffeParserBase::ParseScaleLayer },
249 { "Split", &CaffeParserBase::ParseSplitLayer },
250 { "Dropout", &CaffeParserBase::ParseDropoutLayer},
251};
252
253ICaffeParser* ICaffeParser::CreateRaw()
254{
255 return new RecordByRecordCaffeParser();
256}
257
258ICaffeParserPtr ICaffeParser::Create()
259{
260 return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
261}
262
263void ICaffeParser::Destroy(ICaffeParser* parser)
264{
265 delete parser;
266}
267
268CaffeParserBase::CaffeParserBase()
269 : m_Network(nullptr, nullptr)
270{
271
272}
273
274CaffeParser::CaffeParser()
275: CaffeParserBase()
276{
277
278}
279
280BindingPointInfo CaffeParserBase::GetNetworkInputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000281{
282 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
283}
284
telsoa01c577f2c2018-08-31 09:22:23 +0100285BindingPointInfo CaffeParserBase::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000286{
287 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
288}
289
telsoa01c577f2c2018-08-31 09:22:23 +0100290std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
telsoa014fcda012018-03-09 14:13:49 +0000291 const char* bindingPointDesc,
292 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
293{
294 auto it = nameToBindingInfo.find(layerName);
295 if (it == nameToBindingInfo.end())
296 {
telsoa01c577f2c2018-08-31 09:22:23 +0100297 throw InvalidArgumentException(
James Ward58dec6b2020-09-11 17:32:44 +0100298 fmt::format("Unknown binding {} for layer '{}'. {}",
299 bindingPointDesc,
300 layerName,
301 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000302 }
303 return it->second;
304}
305
telsoa01c577f2c2018-08-31 09:22:23 +0100306TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
telsoa014fcda012018-03-09 14:13:49 +0000307{
308 std::vector<unsigned int> shape;
309 for (int j = 0; j < blobShape.dim_size(); ++j)
310 {
311 shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
312 }
313
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100314 return TensorInfo(armnn::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
telsoa014fcda012018-03-09 14:13:49 +0000315}
316
317BlobShape TensorDescToBlobShape(const TensorInfo& desc)
318{
319 BlobShape ret;
320 for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
321 {
322 ret.add_dim(i);
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100323 ret.set_dim(armnn::numeric_cast<int>(i), desc.GetShape()[i]);
telsoa014fcda012018-03-09 14:13:49 +0000324 }
325
326 return ret;
327}
328
telsoa01c577f2c2018-08-31 09:22:23 +0100329// Note: can move to CaffeParser when/if we optimise the text/string format
330// to load on a layer by layer basis
331vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000332{
333 std::vector<const caffe::LayerParameter*> ret;
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100334 ret.reserve(armnn::numeric_cast<size_t>(layerParam.bottom_size()));
telsoa014fcda012018-03-09 14:13:49 +0000335 for (int j = 0; j < layerParam.bottom_size(); ++j)
336 {
337 std::string inputName = layerParam.bottom(j);
338 auto inputIt = m_CaffeLayersByTopName.find(inputName);
339 if (inputIt == m_CaffeLayersByTopName.end())
340 {
341 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100342 fmt::format("Can't find Caffe layer with top called '{}', "
343 "which is listed as an input of '{}'. {}",
344 inputName,
345 layerParam.name(),
346 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000347 }
348 ret.push_back(inputIt->second);
349 }
350
351 return ret;
352}
353
telsoa01c577f2c2018-08-31 09:22:23 +0100354void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000355{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100356 ARMNN_ASSERT(layerParam.type() == "Input");
telsoa014fcda012018-03-09 14:13:49 +0000357 ValidateNumInputsOutputs(layerParam, 0, 1);
358
359 const InputParameter& param = layerParam.input_param();
360
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100361 const armnn::LayerBindingId inputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa01c577f2c2018-08-31 09:22:23 +0100362 m_NetworkInputsBindingInfo.size());
telsoa014fcda012018-03-09 14:13:49 +0000363 armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
364
telsoa01c577f2c2018-08-31 09:22:23 +0100365 // Decides the tensor info for this input. This can be specified in the Caffe network but can also
telsoa014fcda012018-03-09 14:13:49 +0000366 // be overriden by user input (m_inputShapes).
367 armnn::TensorInfo inputTensorInfo;
368
369 const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
370 &param.shape(0) : nullptr;
371 if (originalShape)
372 {
373 inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
374 }
375
376 auto overrideIt = m_InputShapes.find(layerParam.name());
377 if (overrideIt != m_InputShapes.end())
378 {
379 const TensorShape& overrideShape = overrideIt->second;
380 if (originalShape &&
381 ( originalShape->dim(1) != overrideShape[1]
382 || originalShape->dim(2) != overrideShape[2]
383 || originalShape->dim(3) != overrideShape[3]))
384 {
telsoa01c577f2c2018-08-31 09:22:23 +0100385 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100386 fmt::format("Parsed input shape for '{}' is incompatible with the override provided. {}",
387 layerParam.name(),
388 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000389 }
390 inputTensorInfo.SetShape(overrideShape);
391 }
392 else if (!originalShape)
393 {
telsoa01c577f2c2018-08-31 09:22:23 +0100394 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100395 fmt::format("No input descriptor given for '{}' and no input shape found in caffe model. {}",
396 layerParam.name(),
397 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000398 }
399
400 TrackInputBinding(inputLayer, inputId, inputTensorInfo);
401 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
402 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
403}
404
telsoa01c577f2c2018-08-31 09:22:23 +0100405void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
406 const armnn::Convolution2dDescriptor& desc,
407 unsigned int kernelW,
408 unsigned int kernelH)
telsoa014fcda012018-03-09 14:13:49 +0000409{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100410 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa014fcda012018-03-09 14:13:49 +0000411 ValidateNumInputsOutputs(layerParam, 1, 1);
412
telsoa01c577f2c2018-08-31 09:22:23 +0100413 ConvolutionParameter convParam = layerParam.convolution_param();
telsoa014fcda012018-03-09 14:13:49 +0000414 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa01c577f2c2018-08-31 09:22:23 +0100415 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
telsoa014fcda012018-03-09 14:13:49 +0000416
telsoa01c577f2c2018-08-31 09:22:23 +0100417 // asusme these were already verified by the caller ParseConvLayer() function
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100418 ARMNN_ASSERT(numGroups < inputShape.dim(1));
419 ARMNN_ASSERT(numGroups > 1);
telsoa014fcda012018-03-09 14:13:49 +0000420
421 // Handle grouping
telsoa014fcda012018-03-09 14:13:49 +0000422 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
423
424 vector<string> convLayerNames(numGroups);
425 vector<armnn::IConnectableLayer*> convLayers(numGroups);
426 convLayerNames[0] = layerParam.name();
427
telsoa01c577f2c2018-08-31 09:22:23 +0100428 // This convolution is to be applied to chunks of the input data so add a splitter layer
429
430 // Redirect the convolution input to the splitter
431 unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
432 static_cast<unsigned int>(inputShape.dim(1)),
433 static_cast<unsigned int>(inputShape.dim(2)),
434 static_cast<unsigned int>(inputShape.dim(3))};
435
436 // Split dimension 1 of the splitter output shape and conv input shapes
437 // according to the number of groups
438
439 splitterDimSizes[1] /= numGroups;
440 inputShape.set_dim(1, splitterDimSizes[1]);
441
442 // This is used to describe how the input is to be split
443 ViewsDescriptor splitterDesc(numGroups);
444
445 // Create an output node for each group, giving each a unique name
446 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000447 {
telsoa01c577f2c2018-08-31 09:22:23 +0100448 // Work out the names of the splitter layers child convolutions
449 stringstream ss;
450 ss << layerParam.name() << "_" << g;
451 convLayerNames[g] = ss.str();
telsoa014fcda012018-03-09 14:13:49 +0000452
telsoa01c577f2c2018-08-31 09:22:23 +0100453 splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
telsoa014fcda012018-03-09 14:13:49 +0000454
telsoa01c577f2c2018-08-31 09:22:23 +0100455 // Set the size of the views.
456 for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
telsoa014fcda012018-03-09 14:13:49 +0000457 {
telsoa01c577f2c2018-08-31 09:22:23 +0100458 splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
telsoa014fcda012018-03-09 14:13:49 +0000459 }
460 }
461
telsoa01c577f2c2018-08-31 09:22:23 +0100462 const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
463 armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
telsoa014fcda012018-03-09 14:13:49 +0000464
telsoa01c577f2c2018-08-31 09:22:23 +0100465 inputConnection.Connect(splitterLayer->GetInputSlot(0));
466 for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
467 {
468 splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
469 }
telsoa014fcda012018-03-09 14:13:49 +0000470
471 unsigned int numFilters = convParam.num_output();
472
telsoa01c577f2c2018-08-31 09:22:23 +0100473 // Populates convolution output tensor descriptor dimensions.
telsoa014fcda012018-03-09 14:13:49 +0000474 BlobShape outputShape;
475 outputShape.add_dim(0);
476 outputShape.set_dim(0, inputShape.dim(0));
477 outputShape.add_dim(1);
telsoa01c577f2c2018-08-31 09:22:23 +0100478 // Ensures that dimension 1 of the convolution output is split according to the number of groups.
telsoa014fcda012018-03-09 14:13:49 +0000479 outputShape.set_dim(1, numFilters / numGroups);
480 outputShape.add_dim(2);
481 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100482 2, (static_cast<int>(
483 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
484 static_cast<float>(desc.m_StrideY)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000485 outputShape.add_dim(3);
486 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100487 3, (static_cast<int>(
488 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
489 static_cast<float>(desc.m_StrideX)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000490
491 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100492 vector<float> weightData(armnn::numeric_cast<size_t>(numGroups *
telsoa01c577f2c2018-08-31 09:22:23 +0100493 inputShape.dim(1) * // number of input channels
494 outputShape.dim(1) * // number of output channels
495 kernelH *
496 kernelW));
telsoa014fcda012018-03-09 14:13:49 +0000497 GetDataFromBlob(layerParam, weightData, 0);
498
499 const unsigned int weightDimSizes[4] = {
telsoa01c577f2c2018-08-31 09:22:23 +0100500 static_cast<unsigned int>(outputShape.dim(1)),
501 static_cast<unsigned int>(inputShape.dim(1)),
502 kernelH,
503 kernelW};
telsoa014fcda012018-03-09 14:13:49 +0000504
telsoa014fcda012018-03-09 14:13:49 +0000505 TensorInfo biasInfo;
506 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100507
508 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000509 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100510 biasData.resize(armnn::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
telsoa014fcda012018-03-09 14:13:49 +0000511 GetDataFromBlob(layerParam, biasData, 1);
512
513 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
514 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
515 }
516
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100517 const unsigned int numWeightsPerGroup = armnn::numeric_cast<unsigned int>(weightData.size()) / numGroups;
518 const unsigned int numBiasesPerGroup = armnn::numeric_cast<unsigned int>(biasData.size()) / numGroups;
telsoa014fcda012018-03-09 14:13:49 +0000519
telsoa014fcda012018-03-09 14:13:49 +0000520 for (unsigned int g = 0; g < numGroups; ++g)
521 {
telsoa01c577f2c2018-08-31 09:22:23 +0100522 // Sets the slot index, group 0 should be connected to the 0th output of the splitter
523 // group 1 should be connected to the 1st output of the splitter.
telsoa014fcda012018-03-09 14:13:49 +0000524
telsoa01c577f2c2018-08-31 09:22:23 +0100525 // Pulls out the weights for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000526 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
527 weightData.data() + numWeightsPerGroup * g);
528
529 IConnectableLayer* convLayer = nullptr;
Matteo Martincighfc598e12019-05-14 10:36:13 +0100530 Optional<ConstTensor> optionalBiases;
telsoa01c577f2c2018-08-31 09:22:23 +0100531 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000532 {
telsoa01c577f2c2018-08-31 09:22:23 +0100533 // Pulls out the biases for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000534 ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
Matteo Martincighfc598e12019-05-14 10:36:13 +0100535 optionalBiases = Optional<ConstTensor>(biases);
telsoa014fcda012018-03-09 14:13:49 +0000536 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100537 convLayer = m_Network->AddConvolution2dLayer(desc,
538 weights,
539 optionalBiases,
540 convLayerNames[g].c_str());
telsoa014fcda012018-03-09 14:13:49 +0000541 convLayers[g] = convLayer;
542
543 // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
544 // otherwise it's the regular input to this layer.
telsoa01c577f2c2018-08-31 09:22:23 +0100545 armnn::IOutputSlot& splitterInputConnection =
546 splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
telsoa014fcda012018-03-09 14:13:49 +0000547 splitterInputConnection.Connect(convLayer->GetInputSlot(0));
548 convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
telsoa014fcda012018-03-09 14:13:49 +0000549 }
550
Jim Flynne242f2d2019-05-22 14:24:13 +0100551 // If the convolution was performed in chunks, add a layer to concatenate the results
telsoa01c577f2c2018-08-31 09:22:23 +0100552
553 // The merge input shape matches that of the convolution output
Jim Flynne242f2d2019-05-22 14:24:13 +0100554 unsigned int concatDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
555 static_cast<unsigned int>(outputShape.dim(1)),
556 static_cast<unsigned int>(outputShape.dim(2)),
557 static_cast<unsigned int>(outputShape.dim(3))};
telsoa01c577f2c2018-08-31 09:22:23 +0100558
Jim Flynne242f2d2019-05-22 14:24:13 +0100559 // This is used to describe how the input is to be concatenated
560 OriginsDescriptor concatDesc(numGroups);
telsoa01c577f2c2018-08-31 09:22:23 +0100561
562 // Now create an input node for each group, using the name from
563 // the output of the corresponding convolution
564 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000565 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100566 concatDesc.SetViewOriginCoord(g, 1, concatDimSizes[1] * g);
telsoa01c577f2c2018-08-31 09:22:23 +0100567 }
telsoa014fcda012018-03-09 14:13:49 +0000568
Jim Flynne242f2d2019-05-22 14:24:13 +0100569 // Make sure the output from the concat is the correct size to hold the data for all groups
570 concatDimSizes[1] *= numGroups;
571 outputShape.set_dim(1, concatDimSizes[1]);
telsoa014fcda012018-03-09 14:13:49 +0000572
Jim Flynne242f2d2019-05-22 14:24:13 +0100573 // Finally add the concat layer
574 IConnectableLayer* concatLayer = m_Network->AddConcatLayer(concatDesc, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000575
Jim Flynne242f2d2019-05-22 14:24:13 +0100576 if (!concatLayer)
telsoa01c577f2c2018-08-31 09:22:23 +0100577 {
578 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100579 fmt::format("Failed to create final concat layer for Split+Convolution+Concat. "
580 "Layer={} #groups={} #filters={} {}",
581 layerParam.name(),
582 numGroups,
583 numFilters,
584 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100585 }
telsoa014fcda012018-03-09 14:13:49 +0000586
telsoa01c577f2c2018-08-31 09:22:23 +0100587 for (unsigned int g = 0; g < numGroups; ++g)
588 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100589 convLayers[g]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(g));
telsoa01c577f2c2018-08-31 09:22:23 +0100590 }
Jim Flynne242f2d2019-05-22 14:24:13 +0100591 concatLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, concatDimSizes, DataType::Float32));
592 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatLayer->GetOutputSlot(0));
telsoa01c577f2c2018-08-31 09:22:23 +0100593}
telsoa014fcda012018-03-09 14:13:49 +0000594
telsoa01c577f2c2018-08-31 09:22:23 +0100595void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
596 const armnn::Convolution2dDescriptor& convDesc,
597 unsigned int kernelW,
598 unsigned int kernelH)
599{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100600 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100601 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000602
telsoa01c577f2c2018-08-31 09:22:23 +0100603 ConvolutionParameter convParam = layerParam.convolution_param();
604 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa014fcda012018-03-09 14:13:49 +0000605
telsoa01c577f2c2018-08-31 09:22:23 +0100606 DepthwiseConvolution2dDescriptor desc;
607 desc.m_PadLeft = convDesc.m_PadLeft;
608 desc.m_PadRight = convDesc.m_PadRight;
609 desc.m_PadTop = convDesc.m_PadTop;
610 desc.m_PadBottom = convDesc.m_PadBottom;
611 desc.m_StrideX = convDesc.m_StrideX;
612 desc.m_StrideY = convDesc.m_StrideY;
613 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
telsoa014fcda012018-03-09 14:13:49 +0000614
telsoa01c577f2c2018-08-31 09:22:23 +0100615 unsigned int numFilters = convParam.num_output();
616
617 BlobShape outputShape;
618 outputShape.add_dim(0);
619 outputShape.set_dim(0, inputShape.dim(0));
620 outputShape.add_dim(1);
621 outputShape.set_dim(1, numFilters);
622 outputShape.add_dim(2);
623 outputShape.set_dim(
624 2, (static_cast<int>(
625 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
626 static_cast<float>(desc.m_StrideY)) + 1));
627 outputShape.add_dim(3);
628 outputShape.set_dim(
629 3, (static_cast<int>(
630 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
631 static_cast<float>(desc.m_StrideX)) + 1));
632
633 // Load the weight data
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100634 size_t allWeightsSize = armnn::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
telsoa01c577f2c2018-08-31 09:22:23 +0100635 vector<float> weightData(allWeightsSize);
636
637 GetDataFromBlob(layerParam, weightData, 0);
638
639 // depth multiplier will be 1 for the depthwise convolution
640 const unsigned int weightDimSizes[4] = {
641 static_cast<unsigned int>(1), // depth multiplier
642 static_cast<unsigned int>(inputShape.dim(1)), // #channels
643 kernelH,
644 kernelW};
645
646 armnn::IConnectableLayer* returnLayer = nullptr;
647 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100648 Optional<ConstTensor> optionalBiases;
649 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100650 if (desc.m_BiasEnabled)
651 {
652 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100653
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100654 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100655 GetDataFromBlob(layerParam, biasData, 1);
656
657 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
658 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
659
660 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100661 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100662 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100663 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc,
664 weights,
665 optionalBiases,
666 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000667
surmeh013537c2c2018-05-18 16:31:43 +0100668 if (!returnLayer)
669 {
telsoa01c577f2c2018-08-31 09:22:23 +0100670 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100671 fmt::format("Failed to create depthwise convolution layer. "
672 "Layer={} #filters={} {}",
673 layerParam.name(),
674 numFilters,
675 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100676 }
677 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
678 inputConnection.Connect(returnLayer->GetInputSlot(0));
679 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
680 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
681}
682
683void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
684{
685 // Ignored Caffe Parameters
686 // * Dilation Size
687 // * Weight Filler
688 // * Bias Filler
689 // * Engine
690 // * Force nd_im2col
691 // * Axis
692
693 // Not Available ArmNN Interface Parameters
694 // * Rounding policy;
695
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100696 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100697 ValidateNumInputsOutputs(layerParam, 1, 1);
698
699 ConvolutionParameter convParam = layerParam.convolution_param();
700 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
701 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
702 unsigned int numFilters = convParam.num_output();
703
704 const auto notFound = std::numeric_limits<unsigned int>::max();
705
706 unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
707 kernel_h, kernel_size, unsigned int, notFound);
708 unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
709 kernel_w, kernel_size, unsigned int, notFound);
710
711 unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
712 stride_h, stride, unsigned int, 1u);
713 unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
714 stride_w, stride, unsigned int, 1u);
715
716 unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
717 pad_h, pad, unsigned int, 0u);
718 unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
719 pad_w, pad, unsigned int, 0u);
720
telsoa01c577f2c2018-08-31 09:22:23 +0100721 Convolution2dDescriptor convolution2dDescriptor;
722 convolution2dDescriptor.m_PadLeft = padW;
723 convolution2dDescriptor.m_PadRight = padW;
724 convolution2dDescriptor.m_PadTop = padH;
725 convolution2dDescriptor.m_PadBottom = padH;
726 convolution2dDescriptor.m_StrideX = strideW;
727 convolution2dDescriptor.m_StrideY = strideH;
728 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
729
730 if (numGroups > numFilters)
731 {
732 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100733 fmt::format("Error parsing Convolution: {}. "
734 "The 'group'={} parameter cannot be larger than the "
735 "number of filters supplied ='{}'. {}",
736 layerParam.name(),
737 numGroups,
738 numFilters,
739 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100740 }
741
742 if (inputShape.dim_size() != 4)
743 {
744 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100745 fmt::format("Convolution input shape is expected to have 4 dimensions. "
746 "{}'s input has only {}. {}",
747 layerParam.name(),
748 inputShape.dim_size(),
749 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100750 }
751
752 if (numGroups > 1)
753 {
754 if (numGroups > inputShape.dim(1))
755 {
756 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100757 fmt::format("Error parsing Convolution: {}. "
758 "The 'group'={} parameter cannot be larger than the "
759 "channel of the input shape={} (in NCHW format). {}",
760 layerParam.name(),
761 numGroups,
762 inputShape.dim(1),
763 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100764 }
765 else if (numGroups == inputShape.dim(1))
766 {
767 // we use a depthwise convolution here, because the number of groups equals to the
768 // input channels
769 AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
770 return;
771 }
772 else
773 {
774 // we split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +0100775 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +0100776 AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
777 return;
778 }
779 }
780
781 // NOTE: at this point we only need to handle #group=1 case, all other cases should be
782 // handled by the AddConvLayer* helpers
783
784 // Populate convolution output tensor descriptor dimensions
785 BlobShape outputShape;
786 outputShape.add_dim(0);
787 outputShape.set_dim(0, inputShape.dim(0));
788 outputShape.add_dim(1);
789 outputShape.set_dim(1, numFilters);
790 outputShape.add_dim(2);
791 outputShape.set_dim(
792 2, (static_cast<int>(
793 static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
794 static_cast<float>(strideH)) + 1));
795 outputShape.add_dim(3);
796 outputShape.set_dim(
797 3, (static_cast<int>(
798 static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
799 static_cast<float>(strideW)) + 1));
800
801 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100802 vector<float> weightData(armnn::numeric_cast<size_t>(inputShape.dim(1) *
telsoa01c577f2c2018-08-31 09:22:23 +0100803 outputShape.dim(1) *
804 kernelH *
805 kernelW));
806 GetDataFromBlob(layerParam, weightData, 0);
807
808 const unsigned int weightDimSizes[4] = {
809 static_cast<unsigned int>(outputShape.dim(1)), // output channels
810 static_cast<unsigned int>(inputShape.dim(1)), // input channels
811 kernelH,
812 kernelW};
813
814 armnn::IConnectableLayer* returnLayer = nullptr;
815
816 // Pull out the weights for this group from that loaded from the model file earlier
817 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100818 Optional<ConstTensor> optionalBiases;
819 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100820 if (convolution2dDescriptor.m_BiasEnabled)
821 {
822 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100823
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100824 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100825 GetDataFromBlob(layerParam, biasData, 1);
826
827 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
828 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
829
830 // Pull out the biases for this group from that loaded from the model file earlier
831 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100832 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100833 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100834 returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
835 weights,
836 optionalBiases,
837 layerParam.name().c_str());
telsoa01c577f2c2018-08-31 09:22:23 +0100838
839 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
840 inputConnection.Connect(returnLayer->GetInputSlot(0));
841 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
842
843 if (!returnLayer)
844 {
845 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100846 fmt::format("Failed to create Convolution layer. "
847 "Layer={} #groups={} #filters={} {}",
848 layerParam.name(),
849 numGroups,
850 numFilters,
851 CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100852 }
853
telsoa014fcda012018-03-09 14:13:49 +0000854 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
855}
856
telsoa01c577f2c2018-08-31 09:22:23 +0100857void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000858{
telsoa01c577f2c2018-08-31 09:22:23 +0100859 // Ignored Caffe Parameters
860 // Stochastic Pooling
861 // Engine
862
telsoa014fcda012018-03-09 14:13:49 +0000863 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000864 PoolingParameter param = layerParam.pooling_param();
telsoa014fcda012018-03-09 14:13:49 +0000865 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
866
telsoa01c577f2c2018-08-31 09:22:23 +0100867 const auto notFound = std::numeric_limits<unsigned int>::max();
868
869 unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
870 kernel_h, kernel_size, unsigned int, notFound);
871 unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
872 kernel_w, kernel_size, unsigned int, notFound);
873
874 if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000875 {
876 kernel_h = inputInfo.GetShape()[2];
877 kernel_w = inputInfo.GetShape()[3];
878 }
telsoa01c577f2c2018-08-31 09:22:23 +0100879
telsoa01c577f2c2018-08-31 09:22:23 +0100880 unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
881 stride_h, stride, unsigned int, notFound);
882 unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
883 stride_h, stride, unsigned int, notFound);
884
885 if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000886 {
telsoa01c577f2c2018-08-31 09:22:23 +0100887 stride_h = 1;
888 stride_w = 1;
telsoa014fcda012018-03-09 14:13:49 +0000889 }
890
telsoa01c577f2c2018-08-31 09:22:23 +0100891 unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
892 pad_h, pad, unsigned int, 0u);
893 unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
894 pad_w, pad, unsigned int, 0u);
telsoa014fcda012018-03-09 14:13:49 +0000895
telsoa014fcda012018-03-09 14:13:49 +0000896 // Populate Weight and Bias Filter Descriptor
897 Pooling2dDescriptor pooling2dDescriptor;
898 if (param.has_pool())
899 {
900 PoolingParameter_PoolMethod p = param.pool();
901 switch (p)
902 {
903 case PoolingParameter_PoolMethod_MAX:
904 {
905 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
906 break;
907 }
908 case PoolingParameter_PoolMethod_AVE:
909 {
910 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
911 break;
912 }
913 case PoolingParameter_PoolMethod_STOCHASTIC:
914 {
telsoa01c577f2c2018-08-31 09:22:23 +0100915 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100916 fmt::format("Pooling Layer: Stochastic Pooling Not Supported. Layer={} {}",
917 layerParam.name(),
918 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000919 }
920 default:
921 {
telsoa01c577f2c2018-08-31 09:22:23 +0100922 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100923 fmt::format("Pooling Layer: unknown pooling method: {} for layer: {} {}",
924 p,
925 layerParam.name(),
926 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000927 }
928 }
929 }
930 else
931 {
telsoa01c577f2c2018-08-31 09:22:23 +0100932 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100933 fmt::format("No Pooling Method Defined for {} {}",
934 layerParam.name(),
935 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000936 }
937
938 pooling2dDescriptor.m_PadLeft = pad_w;
939 pooling2dDescriptor.m_PadRight = pad_w;
940 pooling2dDescriptor.m_PadTop = pad_h;
941 pooling2dDescriptor.m_PadBottom = pad_h;
942 pooling2dDescriptor.m_StrideX = stride_w;
943 pooling2dDescriptor.m_StrideY = stride_h;
944 pooling2dDescriptor.m_PoolWidth = kernel_w;
945 pooling2dDescriptor.m_PoolHeight = kernel_h;
946
947 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
948 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
949
950 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
951 layerParam.name().c_str());
952
telsoa014fcda012018-03-09 14:13:49 +0000953 TensorInfo outputInfo(
954 { inputInfo.GetShape()[0],
955 inputInfo.GetShape()[1],
956 static_cast<unsigned int>(ceil(
957 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
958 boost::numeric_cast<float>(stride_h))) + 1,
959 static_cast<unsigned int>(ceil(
960 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
961 boost::numeric_cast<float>(stride_w))) + 1 },
962 DataType::Float32);
963
964 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
965 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
966 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
967}
968
telsoa01c577f2c2018-08-31 09:22:23 +0100969void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000970{
971 ValidateNumInputsOutputs(layerParam, 1, 1);
972
973 const string& name = layerParam.name();
974 const ReLUParameter& param = layerParam.relu_param();
975
976 ActivationDescriptor activationDescriptor;
977 const float negativeSlope = param.negative_slope();
978 if (negativeSlope == 0.0f)
979 {
980 activationDescriptor.m_Function = ActivationFunction::ReLu;
981 }
982 else
983 {
984 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
985 activationDescriptor.m_A = negativeSlope;
986 }
987
988 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
989 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
990 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
991 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
992 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
993}
994
telsoa01c577f2c2018-08-31 09:22:23 +0100995void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000996{
997 ValidateNumInputsOutputs(layerParam, 1, 1);
998
999 LRNParameter param = layerParam.lrn_param();
1000
1001 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1002
telsoa01c577f2c2018-08-31 09:22:23 +01001003 // Ignored BATCH NORMALIZATION Caffe Parameters.
1004 // Ignored MVN Caffe Parameters.
1005 // Ignored LRN Caffe Parameters.
telsoa014fcda012018-03-09 14:13:49 +00001006 // Engine
1007
1008 NormalizationDescriptor normalizationDescriptor;
1009 if (param.has_norm_region())
1010 {
1011 LRNParameter_NormRegion n = param.norm_region();
1012 switch (n)
1013 {
1014 case LRNParameter_NormRegion_ACROSS_CHANNELS:
1015 {
1016 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1017 break;
1018 }
1019 case LRNParameter_NormRegion_WITHIN_CHANNEL:
1020 {
1021 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1022 break;
1023 }
1024 default:
telsoa01c577f2c2018-08-31 09:22:23 +01001025 {
1026 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001027 fmt::format("Unknown region {} for LRN layer {} {}",
1028 n,
1029 layerParam.name(),
1030 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001031 }
telsoa014fcda012018-03-09 14:13:49 +00001032 }
1033 }
1034 else
1035 {
telsoa01c577f2c2018-08-31 09:22:23 +01001036 // Caffe defaults to normalization across channels.
telsoa014fcda012018-03-09 14:13:49 +00001037 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1038 }
1039
1040 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1041 if (param.has_local_size())
1042 {
1043 normalizationDescriptor.m_NormSize = param.local_size();
1044 }
1045 else
1046 {
telsoa01c577f2c2018-08-31 09:22:23 +01001047 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001048 fmt::format("local_size not defined for LRN layer {} {}",
1049 layerParam.name(),
1050 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001051 }
1052
1053 if (param.has_alpha())
1054 {
1055 normalizationDescriptor.m_Alpha = param.alpha();
1056 normalizationDescriptor.m_Alpha /= boost::numeric_cast<float>(param.local_size());
1057 }
1058 else
1059 {
telsoa01c577f2c2018-08-31 09:22:23 +01001060 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001061 fmt::format("Alpha not defined for LRN layer {} {}",
1062 layerParam.name(),
1063 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001064 }
1065 if (param.has_beta())
1066 {
1067 normalizationDescriptor.m_Beta = param.beta();
1068 }
1069 else
1070 {
telsoa01c577f2c2018-08-31 09:22:23 +01001071 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001072 fmt::format("Beta not defined for LRN layer {} {}",
1073 layerParam.name(),
1074 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001075 }
telsoa01c577f2c2018-08-31 09:22:23 +01001076
telsoa014fcda012018-03-09 14:13:49 +00001077 if (param.has_k())
1078 {
1079 normalizationDescriptor.m_K = param.k();
1080 }
1081 else
telsoa01c577f2c2018-08-31 09:22:23 +01001082 {
telsoa014fcda012018-03-09 14:13:49 +00001083 normalizationDescriptor.m_K = 1;
telsoa01c577f2c2018-08-31 09:22:23 +01001084 }
telsoa014fcda012018-03-09 14:13:49 +00001085
1086 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1087 layerParam.name().c_str());
1088 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1089 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1090
1091 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1092}
1093
telsoa01c577f2c2018-08-31 09:22:23 +01001094void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001095{
1096 InnerProductParameter param = layerParam.inner_product_param();
1097
1098 ValidateNumInputsOutputs(layerParam, 1, 1);
1099
1100 unsigned int outputSize = param.num_output();
1101
telsoa01c577f2c2018-08-31 09:22:23 +01001102 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001103 // Weight Filler
1104 // Bias Filler
1105 // Engine
1106 // Axis
1107
1108 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1109
1110 if (param.has_transpose())
1111 {
telsoa01c577f2c2018-08-31 09:22:23 +01001112 // If true, assumes transposed weights.
telsoa014fcda012018-03-09 14:13:49 +00001113 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1114 }
1115 else
1116 {
telsoa01c577f2c2018-08-31 09:22:23 +01001117 // Caffe defaults to transposed.
telsoa014fcda012018-03-09 14:13:49 +00001118 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1119 }
1120
1121 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1122
1123 TensorInfo weightInfo;
1124 TensorInfo biasInfo;
1125
telsoa01c577f2c2018-08-31 09:22:23 +01001126 // Allows implicit flattening of extra dimensions.
telsoa014fcda012018-03-09 14:13:49 +00001127 unsigned int inputSize = inputInfo.GetShape()[1];
1128 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1129 {
1130 inputSize *= inputInfo.GetShape()[i];
1131 }
1132
telsoa01c577f2c2018-08-31 09:22:23 +01001133 const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
telsoa014fcda012018-03-09 14:13:49 +00001134 const unsigned int swTD[2] = { outputSize, inputSize };
telsoa01c577f2c2018-08-31 09:22:23 +01001135 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001136
1137 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
telsoa01c577f2c2018-08-31 09:22:23 +01001138 // Todo: check whether bias enabled.
telsoa014fcda012018-03-09 14:13:49 +00001139 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1140 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1141 {
1142 // BIAS VALUE
telsoa01c577f2c2018-08-31 09:22:23 +01001143 const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
telsoa014fcda012018-03-09 14:13:49 +00001144
1145 const unsigned int sbTD[1] = { outputSize };
1146
telsoa01c577f2c2018-08-31 09:22:23 +01001147 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001148
Matteo Martincighfc598e12019-05-14 10:36:13 +01001149 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1150 weights,
1151 Optional<ConstTensor>(biases),
1152 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001153 }
1154 else
1155 {
Matteo Martincighfc598e12019-05-14 10:36:13 +01001156 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1157 weights,
1158 EmptyOptional(),
1159 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001160 }
1161
1162 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1163 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1164 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1165 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1166}
1167
telsoa01c577f2c2018-08-31 09:22:23 +01001168void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001169{
1170 ValidateNumInputsOutputs(layerParam, 1, 1);
1171
1172 SoftmaxParameter param = layerParam.softmax_param();
1173
1174 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1175
telsoa01c577f2c2018-08-31 09:22:23 +01001176 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001177 // axis
1178 // Engine
1179
1180 armnn::SoftmaxDescriptor softmaxDescriptor;
Teresa Charlin4320c922020-08-12 16:04:41 +01001181 softmaxDescriptor.m_Axis = 1;
telsoa014fcda012018-03-09 14:13:49 +00001182 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1183 softmaxDescriptor,
1184 layerParam.name().c_str());
1185 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1186 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1187 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1188}
1189
telsoa01c577f2c2018-08-31 09:22:23 +01001190void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001191{
1192 ValidateNumInputsOutputs(layerParam, 2, 1);
1193
1194 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1195
telsoa01c577f2c2018-08-31 09:22:23 +01001196 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001197 // coeff
1198
telsoa01c577f2c2018-08-31 09:22:23 +01001199 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
telsoa014fcda012018-03-09 14:13:49 +00001200
1201 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1202 {
1203 operation = layerParam.eltwise_param().operation();
1204 }
1205
1206 armnn::IConnectableLayer* newLayer = nullptr;
1207 switch (operation)
1208 {
1209 case EltwiseParameter_EltwiseOp_SUM:
1210 {
1211 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1212 break;
1213 }
1214 case EltwiseParameter_EltwiseOp_PROD:
1215 {
1216 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1217 break;
1218 }
1219 default:
1220 {
telsoa01c577f2c2018-08-31 09:22:23 +01001221 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001222 fmt::format("Unsupported operation {} in Eltwise layer {} {}",
1223 operation,
1224 layerParam.name(),
1225 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001226 }
1227 }
1228
1229 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1230 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1231 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1232 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1233}
1234
telsoa01c577f2c2018-08-31 09:22:23 +01001235void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001236{
1237 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
telsoa01c577f2c2018-08-31 09:22:23 +01001238 // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
telsoa014fcda012018-03-09 14:13:49 +00001239 unsigned int concatDim = 1;
1240 unsigned int numOfDims = 4;
1241
telsoa01c577f2c2018-08-31 09:22:23 +01001242 // we only consider 4-D tensor here
1243 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
telsoa014fcda012018-03-09 14:13:49 +00001244 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1245
1246 unsigned int mergeDim = 0;
1247 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1248 {
1249 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001250 layerParam.bottom(armnn::numeric_cast<int>(viewIndex))).GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001251 // Checks whether the dimensions of the input tensors are actually 4.
telsoa014fcda012018-03-09 14:13:49 +00001252 if (inputInfo.GetNumDimensions()!=4)
1253 {
telsoa01c577f2c2018-08-31 09:22:23 +01001254 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001255 fmt::format("The number of dimensions for input tensors of "
1256 "the concatenation op should be 4. Inputs of {} has "
1257 "{} dimensions. {}",
1258 layerParam.name(),
1259 inputInfo.GetNumDimensions(),
1260 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001261 }
1262
1263 mergeDimSizes[0] = inputInfo.GetShape()[0];
1264 mergeDimSizes[1] = inputInfo.GetShape()[1];
1265 mergeDimSizes[2] = inputInfo.GetShape()[2];
1266 mergeDimSizes[3] = inputInfo.GetShape()[3];
1267
1268 for (unsigned int j = 0; j < concatDim; ++j)
1269 {
1270 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1271 }
1272
1273 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1274 mergeDim += mergeDimSizes[concatDim];
1275
1276 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1277 {
1278 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1279 }
1280 }
1281 mergeDimSizes[concatDim] = mergeDim;
1282
Jim Flynn906f9462019-05-10 13:55:21 +01001283 armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001284 for (unsigned int i = 0; i < numInputs; ++i)
1285 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001286 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(armnn::numeric_cast<int>(i)));
telsoa014fcda012018-03-09 14:13:49 +00001287 outputSlot.Connect(concatlayer->GetInputSlot(i));
1288 }
1289
1290 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1291 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1292}
1293
telsoa01c577f2c2018-08-31 09:22:23 +01001294void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001295{
1296 ValidateNumInputsOutputs(layerParam, 1, 1);
1297
1298 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1299
1300 string name = layerParam.name();
1301
1302 BatchNormParameter param = layerParam.batch_norm_param();
1303 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1304 // when the network is in the testing phase).
1305 if (param.has_use_global_stats())
1306 {
1307 if (!param.use_global_stats())
1308 {
telsoa01c577f2c2018-08-31 09:22:23 +01001309 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001310 fmt::format("Error parsing Batch Norm layer '{}': "
1311 "Parameter 'use_global_stats' is set to false, which is "
1312 "unsupported (value used for training). {}",
1313 name,
1314 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001315 }
1316 }
1317
1318 BatchNormalizationDescriptor desc;
1319 desc.m_Eps = param.eps();
1320
1321 unsigned int channels = inputInfo.GetShape()[1];
1322 unsigned int shape[] = {channels};
1323
1324 vector<float> meanData(channels);
1325 GetDataFromBlob(layerParam, meanData, 0);
1326
1327 vector<float> varianceData(channels);
1328 GetDataFromBlob(layerParam, varianceData, 1);
1329
telsoa01c577f2c2018-08-31 09:22:23 +01001330 // Reads moving average factor and applies scaling (if required).
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001331 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(2));
1332 const float movingAverageFactor = blob.data(armnn::numeric_cast<int>(0));
surmeh013537c2c2018-05-18 16:31:43 +01001333 if(movingAverageFactor != 0.0f)
1334 {
1335 const float scaleFactor = 1.0f / movingAverageFactor;
1336 auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1337
1338 std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1339 std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1340 }
1341
telsoa01c577f2c2018-08-31 09:22:23 +01001342 // Identifies scale operation.
telsoa014fcda012018-03-09 14:13:49 +00001343 vector<float> betaData(channels, 0.0f);
1344 vector<float> gammaData(channels, 1.0f);
1345
1346 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1347 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1348 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1349 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1350
1351 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1352 mean, variance, beta, gamma, name.c_str());
1353 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1354 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1355 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1356}
1357
telsoa01c577f2c2018-08-31 09:22:23 +01001358void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001359{
telsoa01c577f2c2018-08-31 09:22:23 +01001360 // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
telsoa014fcda012018-03-09 14:13:49 +00001361 ValidateNumInputsOutputs(layerParam, 1, 1);
1362
1363 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1364
1365 string name = layerParam.name();
1366
1367 ScaleParameter param = layerParam.scale_param();
1368 if (param.axis() != 1)
1369 {
1370 // Would have to use something other than BatchNormalizationLayer in this case
telsoa01c577f2c2018-08-31 09:22:23 +01001371 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001372 fmt::format("Loading Scale Layer: Only axis 1 is supported currently. "
1373 "Layer={} Axis={} {}",
1374 layerParam.name(),
1375 param.axis(),
1376 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001377 }
1378
1379 unsigned int channels = inputInfo.GetShape()[1];
1380 unsigned int shape[] = {channels};
1381
1382 BatchNormalizationDescriptor desc;
telsoa01c577f2c2018-08-31 09:22:23 +01001383 desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
telsoa014fcda012018-03-09 14:13:49 +00001384 vector<float> meanData(channels, 0.0f);
1385 vector<float> varianceData(channels, 1.0f);
1386 vector<float> betaData(channels, 0.0f);
1387 vector<float> gammaData(channels);
1388
1389 GetDataFromBlob(layerParam, gammaData, 0);
1390
1391 if(param.has_bias_term())
1392 {
1393 GetDataFromBlob(layerParam, betaData, 1);
1394 }
1395
1396 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1397 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1398 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1399 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1400
1401 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1402 mean, variance, beta, gamma, name.c_str());
1403 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1404 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1405 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1406}
1407
telsoa01c577f2c2018-08-31 09:22:23 +01001408void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001409{
telsoa01c577f2c2018-08-31 09:22:23 +01001410 // Used in caffe to duplicate memory - not necessary in armnn.
telsoa014fcda012018-03-09 14:13:49 +00001411 if (layerParam.bottom_size() != 1)
1412 {
telsoa01c577f2c2018-08-31 09:22:23 +01001413 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001414 fmt::format("Split layer '{}' should have exactly 1 bottom. "
1415 "#bottoms={} {}",
1416 layerParam.name(),
1417 layerParam.bottom_size(),
1418 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001419 }
1420 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1421 for (int i = 0; i < layerParam.top_size(); i++)
1422 {
1423 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1424 }
1425}
1426
telsoa01c577f2c2018-08-31 09:22:23 +01001427void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001428{
telsoa01c577f2c2018-08-31 09:22:23 +01001429 // Ignored for inference, so patch the single input to its single output.
telsoa014fcda012018-03-09 14:13:49 +00001430 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1431 {
telsoa01c577f2c2018-08-31 09:22:23 +01001432 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001433 fmt::format("Dropout layer '{}' should have exactly 1 bottom and 1 top. "
1434 "#bottoms={} #tops={} {}",
1435 layerParam.name(),
1436 layerParam.bottom_size(),
1437 layerParam.top_size(),
1438 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001439 }
1440 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1441}
1442
telsoa01c577f2c2018-08-31 09:22:23 +01001443void CaffeParserBase::TrackInputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001444 armnn::LayerBindingId id,
1445 const armnn::TensorInfo& tensorInfo)
1446{
1447 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1448}
1449
telsoa01c577f2c2018-08-31 09:22:23 +01001450void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001451 armnn::LayerBindingId id,
1452 const armnn::TensorInfo& tensorInfo)
1453{
1454 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1455}
1456
telsoa01c577f2c2018-08-31 09:22:23 +01001457void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001458 armnn::LayerBindingId id,
1459 const armnn::TensorInfo& tensorInfo,
1460 const char* bindingPointDesc,
1461 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1462{
1463 const std::string layerName = layer->GetName();
1464 auto it = nameToBindingInfo.find(layerName);
1465 if (it == nameToBindingInfo.end())
1466 {
1467 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1468 }
1469 else
1470 {
telsoa01c577f2c2018-08-31 09:22:23 +01001471 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001472 fmt::format("Id {} used by more than one {} layer {}",
1473 id,
1474 bindingPointDesc,
1475 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001476 }
1477}
1478
telsoa01c577f2c2018-08-31 09:22:23 +01001479armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
telsoa014fcda012018-03-09 14:13:49 +00001480{
1481 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1482 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1483 {
1484 return *it->second;
1485 }
1486 else
1487 {
telsoa01c577f2c2018-08-31 09:22:23 +01001488 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001489 fmt::format("Could not find armnn output slot for Caffe top '{}' {}",
1490 caffeTopName,
1491 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001492 }
1493}
1494
telsoa01c577f2c2018-08-31 09:22:23 +01001495void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
1496 const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
telsoa014fcda012018-03-09 14:13:49 +00001497{
1498 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1499 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1500 {
1501 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1502 }
1503 else
1504 {
telsoa01c577f2c2018-08-31 09:22:23 +01001505 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001506 fmt::format("Attempting to add duplicate entry for Caffe top '{}' {}",
1507 caffeTopName,
1508 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001509 }
1510}
1511
telsoa01c577f2c2018-08-31 09:22:23 +01001512// Note: can move to CaffeParser when/if we optimise the text/string format
1513// to load on a layer by layer basis
1514void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001515{
telsoa01c577f2c2018-08-31 09:22:23 +01001516 // Finds layers with the same top.
telsoa014fcda012018-03-09 14:13:49 +00001517 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1518 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1519 {
1520 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
telsoa01c577f2c2018-08-31 09:22:23 +01001521 std::string name = layer.name();
telsoa014fcda012018-03-09 14:13:49 +00001522 for (int i = 0; i < layer.top_size(); ++i)
1523 {
1524 layersByTop[layer.top(i)].push_back(&layer);
1525 }
1526 }
1527
telsoa01c577f2c2018-08-31 09:22:23 +01001528 // 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 +00001529 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1530 for (auto layersWithSameTopIt : layersByTop)
1531 {
1532 const std::string& top = layersWithSameTopIt.first;
1533 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1534
telsoa01c577f2c2018-08-31 09:22:23 +01001535 // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
telsoa014fcda012018-03-09 14:13:49 +00001536 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1537 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1538 {
1539 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1540 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1541 if (layer1.top_size() != 1)
1542 {
telsoa01c577f2c2018-08-31 09:22:23 +01001543 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001544 fmt::format("Node '{}' is an in-place layer but doesn't have exactly one "
1545 "top. It has {} instead. {}",
1546 layer1.name(),
1547 layer1.top_size(),
1548 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001549 }
1550 std::string newTop = layer1.name() + "_top";
1551 layer1.set_top(0, newTop);
1552 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1553 {
telsoa01c577f2c2018-08-31 09:22:23 +01001554 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001555 fmt::format("Node '{}' is an in-place layer but "
1556 "doesn't have exactly one bottom, or it doesn't match its top. "
1557 "#bottoms={}, first bottom is {}, top is {} {}",
1558 layer2.name(),
1559 layer2.bottom(0),
1560 top,
1561 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001562 }
1563 layer2.set_bottom(0, newTop);
1564 }
1565 }
1566}
1567
telsoa01c577f2c2018-08-31 09:22:23 +01001568// Note: can move to CaffeParser when/if we optimise the text/string format
1569// to load on a layer by layer basis
1570void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001571{
telsoa01c577f2c2018-08-31 09:22:23 +01001572 // Caffe models sometimes have an implicit input layer.
1573 // In that case, add an explicit one.
telsoa014fcda012018-03-09 14:13:49 +00001574 if (netParameter.input_size() > 0)
1575 {
1576 LayerParameter* newLayer = netParameter.add_layer();
1577
1578 newLayer->set_type("Input");
1579 newLayer->set_name(netParameter.input(0));
1580 newLayer->add_top(netParameter.input(0));
1581
1582 InputParameter* inputParam = newLayer->mutable_input_param();
1583 BlobShape* shape = inputParam->add_shape();
1584
1585 int dim_size = netParameter.input_dim_size();
1586 for (int i = 0; i < dim_size; ++i)
1587 {
1588 shape->add_dim(netParameter.input_dim(i));
1589 }
1590 }
1591
telsoa01c577f2c2018-08-31 09:22:23 +01001592 // Replaces in-place layers with regular ones to make the rest of the parsing easier.
telsoa014fcda012018-03-09 14:13:49 +00001593 ResolveInPlaceLayers(netParameter);
1594
telsoa01c577f2c2018-08-31 09:22:23 +01001595 // Creates a lookup of Caffe layers by name.
telsoa014fcda012018-03-09 14:13:49 +00001596 for (int i = 0; i < netParameter.layer_size(); ++i)
1597 {
1598 const caffe::LayerParameter& layer = netParameter.layer(i);
1599 for (int i = 0; i < layer.top_size(); ++i)
1600 {
1601 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1602 }
1603 }
1604
telsoa01c577f2c2018-08-31 09:22:23 +01001605 // Finds the output layers the user requested.
telsoa014fcda012018-03-09 14:13:49 +00001606 std::vector<const caffe::LayerParameter*> targetLayers;
1607 for (const std::string& requestedOutputName : m_RequestedOutputs)
1608 {
1609 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1610 if (nodeIt == m_CaffeLayersByTopName.end())
1611 {
telsoa01c577f2c2018-08-31 09:22:23 +01001612 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001613 fmt::format("Couldn't find requested output layer '{}' in graph {}",
1614 requestedOutputName,
1615 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001616 }
1617 targetLayers.push_back(nodeIt->second);
1618 }
1619
telsoa01c577f2c2018-08-31 09:22:23 +01001620 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001621 std::vector<const caffe::LayerParameter*> sortedNodes;
1622 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1623 targetLayers,
1624 [this](const caffe::LayerParameter* node)
1625 {
1626 return GetInputs(*node);
1627 },
1628 sortedNodes))
1629 {
telsoa01c577f2c2018-08-31 09:22:23 +01001630 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001631 fmt::format("Cycle detected in graph. #nodes: {} {}",
1632 sortedNodes.size(),
1633 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001634 }
1635
telsoa01c577f2c2018-08-31 09:22:23 +01001636 // 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 +00001637 for (const caffe::LayerParameter* current : sortedNodes)
1638 {
1639 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1640 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1641 {
telsoa01c577f2c2018-08-31 09:22:23 +01001642 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001643 fmt::format("Unsupported layer type: '{}' for layer {} {}",
1644 current->type(),
1645 current->name(),
1646 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001647 }
1648 auto func = it->second;
1649 (this->*func)(*current);
1650 }
1651
telsoa01c577f2c2018-08-31 09:22:23 +01001652 // Adds ArmNN output layers connected to each requested output.
telsoa014fcda012018-03-09 14:13:49 +00001653 for (const std::string& requestedOutput : m_RequestedOutputs)
1654 {
1655 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1656
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001657 const armnn::LayerBindingId outputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa014fcda012018-03-09 14:13:49 +00001658 m_NetworkOutputsBindingInfo.size());
1659 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1660 outputSlot.Connect(outputLayer->GetInputSlot(0));
1661
1662 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1663 }
1664}
1665
telsoa01c577f2c2018-08-31 09:22:23 +01001666INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
telsoa014fcda012018-03-09 14:13:49 +00001667 const std::map<std::string, armnn::TensorShape>& inputShapes,
1668 const std::vector<std::string>& requestedOutputs)
1669{
1670 FILE* fd = fopen(graphFile, "r");
1671
1672 if (fd == nullptr)
1673 {
telsoa01c577f2c2018-08-31 09:22:23 +01001674 throw FileNotFoundException(
James Ward58dec6b2020-09-11 17:32:44 +01001675 fmt::format("Failed to open graph file: {} {}",
1676 graphFile,
1677 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001678 }
1679
telsoa01c577f2c2018-08-31 09:22:23 +01001680 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001681 NetParameter netParam;
1682 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1683 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1684 delete input;
1685 fclose(fd);
1686
1687 if (!success)
1688 {
telsoa01c577f2c2018-08-31 09:22:23 +01001689 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001690 fmt::format("Failed to parse graph file: {} {}",
1691 graphFile,
1692 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001693 }
1694
1695 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1696}
1697
telsoa01c577f2c2018-08-31 09:22:23 +01001698INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
telsoa014fcda012018-03-09 14:13:49 +00001699 const std::map<std::string, armnn::TensorShape>& inputShapes,
1700 const std::vector<std::string>& requestedOutputs)
1701{
telsoa01c577f2c2018-08-31 09:22:23 +01001702 // Parses the string into a message.
telsoa014fcda012018-03-09 14:13:49 +00001703 NetParameter netParam;
1704 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1705
1706 if (!success)
1707 {
telsoa01c577f2c2018-08-31 09:22:23 +01001708 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001709 fmt::format("Failed to parse graph string {}",
1710 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001711 }
1712
1713 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1714}
1715
1716INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1717 const std::map<std::string, armnn::TensorShape>& inputShapes,
1718 const std::vector<std::string>& requestedOutputs)
1719{
1720 FILE* fd = fopen(graphFile, "rb");
1721
1722 if (fd == nullptr)
1723 {
telsoa01c577f2c2018-08-31 09:22:23 +01001724 throw FileNotFoundException(
James Ward58dec6b2020-09-11 17:32:44 +01001725 fmt::format("Failed to open graph file at: {} {}",
1726 graphFile,
1727 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001728 }
1729
telsoa01c577f2c2018-08-31 09:22:23 +01001730 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001731 NetParameter netParam;
1732
1733 FileInputStream inStream(fileno(fd));
1734 CodedInputStream codedStream(&inStream);
1735 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
1736 bool success = netParam.ParseFromCodedStream(&codedStream);
1737 fclose(fd);
1738
1739 if (!success)
1740 {
telsoa01c577f2c2018-08-31 09:22:23 +01001741 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001742 fmt::format("Failed to parse protobuf file: {} {}",
1743 graphFile,
1744 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001745 }
1746
1747 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1748}
1749
telsoa01c577f2c2018-08-31 09:22:23 +01001750// Note: can move to CaffeParser when/if we optimise the text/string format
1751// to load on a layer by layer basis
1752INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
telsoa014fcda012018-03-09 14:13:49 +00001753 const std::map<std::string, armnn::TensorShape>& inputShapes,
1754 const std::vector<std::string>& requestedOutputs)
1755{
1756 m_NetworkInputsBindingInfo.clear();
1757 m_NetworkOutputsBindingInfo.clear();
1758
1759 m_Network = INetwork::Create();
1760
1761 m_InputShapes = inputShapes;
1762 if (requestedOutputs.size() == 0)
1763 {
1764 throw ParseException("requestedOutputs must have at least one entry");
1765 }
1766 m_RequestedOutputs = requestedOutputs;
1767
1768 try
1769 {
1770 LoadNetParam(netParam);
1771 }
1772 catch (const ParseException& e)
1773 {
1774 Cleanup();
1775 throw e;
1776 }
1777
1778 Cleanup();
1779
1780 return move(m_Network);
1781}
1782
telsoa01c577f2c2018-08-31 09:22:23 +01001783void CaffeParserBase::Cleanup() {
telsoa014fcda012018-03-09 14:13:49 +00001784 // cleanup, in case we reuse this parser
telsoa014fcda012018-03-09 14:13:49 +00001785 m_InputShapes.clear();
1786 m_RequestedOutputs.clear();
1787 m_ArmnnOutputSlotForCaffeTop.clear();
telsoa01c577f2c2018-08-31 09:22:23 +01001788 // NOTE: when we get the text/string format
1789 // optimised for memory then this data structure can
1790 // also move to the CaffeParser class
1791 m_CaffeLayersByTopName.clear();
telsoa014fcda012018-03-09 14:13:49 +00001792}
1793
1794}