blob: d11da466b8541197645679f6d1c41064487db258 [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
James Ward58dec6b2020-09-11 17:32:44 +010019#include <fmt/format.h>
telsoa014fcda012018-03-09 14:13:49 +000020
21// Caffe
22#include "caffe/proto/caffe.pb.h"
23
24// ProtoBuf
25#include <google/protobuf/io/coded_stream.h>
26#include <google/protobuf/io/zero_copy_stream.h>
27#include <google/protobuf/io/zero_copy_stream_impl.h>
28#include <google/protobuf/text_format.h>
29#include <google/protobuf/stubs/common.h>
30#include <google/protobuf/stubs/once.h>
31#include <google/protobuf/io/coded_stream.h>
telsoa014fcda012018-03-09 14:13:49 +000032#include <google/protobuf/descriptor.h>
33#include <google/protobuf/generated_message_reflection.h>
34#include <google/protobuf/reflection_ops.h>
35#include <google/protobuf/wire_format.h>
36
37#include <cmath>
38#include <sstream>
39#include <queue>
40#include <fcntl.h>
41
42/// Caffe networks are loaded from protobuf files (binary or text) using the protobuf library and the generated
43/// code from caffe.pb.h. This gives us a caffe::NetParameter which is an in-memory version of the file.
44/// This contains a flat list of Caffe 'layers' (e.g. convolution, pooling etc.).
45/// Each layer has inputs (called "bottoms") and outputs (called "tops"). Data flows from bottom to top.
46/// The bottoms of a layer refer to the tops of other layers, not their names.
telsoa01c577f2c2018-08-31 09:22:23 +010047/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't
48/// need any other changes).
telsoa014fcda012018-03-09 14:13:49 +000049///
50/// Some layers (e.g. Relu) can be configured so that their top and bottom are both the same. This is called an
51/// "in-place" layer and is a Caffe runtime feature used to reduce memory usage by modifying tensors in-place.
52/// This isn't relevant to the parser and so we preprocess these layers to convert them to regular layers, to result
53/// in a consistent graph structure.
54
55namespace armnnCaffeParser
56{
57
58using namespace armnn;
59using namespace caffe;
60using namespace std;
61using namespace google::protobuf::io;
62
telsoa01c577f2c2018-08-31 09:22:23 +010063namespace
telsoa014fcda012018-03-09 14:13:49 +000064{
65
telsoa01c577f2c2018-08-31 09:22:23 +010066const float* GetArrayPtrFromBlob(const LayerParameter& layerParam, unsigned int blobIndex)
telsoa014fcda012018-03-09 14:13:49 +000067{
telsoa01c577f2c2018-08-31 09:22:23 +010068 auto nBlobs = layerParam.blobs_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +010069 if (blobIndex >= armnn::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000070 {
telsoa01c577f2c2018-08-31 09:22:23 +010071 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +010072 fmt::format("Expected data blob at index {} in layer {} not found. nBlobs={}. {}",
73 blobIndex,
74 layerParam.name(),
75 nBlobs,
76 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000077 }
78
Matthew Sloyan589e3e82020-09-11 16:17:48 +010079 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(blobIndex));
telsoa014fcda012018-03-09 14:13:49 +000080
telsoa01c577f2c2018-08-31 09:22:23 +010081 const float* arrayPtr = blob.data().data();
82 return arrayPtr;
83}
84
85void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
86{
87 auto nBlobs = layerParam.blobs_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +010088 if (blobIndex >= armnn::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000089 {
telsoa01c577f2c2018-08-31 09:22:23 +010090 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +010091 fmt::format("Expected data blob at index {} in layer {} not found. {}",
92 blobIndex,
93 layerParam.name(),
94 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000095 }
96
Matthew Sloyan589e3e82020-09-11 16:17:48 +010097 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(blobIndex));
telsoa01c577f2c2018-08-31 09:22:23 +010098
Matthew Sloyan589e3e82020-09-11 16:17:48 +010099 size_t blobSize = armnn::numeric_cast<size_t>(blob.data_size());
telsoa01c577f2c2018-08-31 09:22:23 +0100100 if (blobSize != outData.size())
telsoa014fcda012018-03-09 14:13:49 +0000101 {
telsoa01c577f2c2018-08-31 09:22:23 +0100102 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100103 fmt::format("Data blob at index {} in layer {} has an unexpected size. "
104 "Expected {} elements but got {} elements. {}",
105 blobIndex,
106 layerParam.name(),
107 outData.size(),
108 blobSize,
109 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100110 }
111
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100112 int outSizeInt = armnn::numeric_cast<int>(outData.size());
telsoa01c577f2c2018-08-31 09:22:23 +0100113 for (int i = 0; i < outSizeInt; ++i)
114 {
115 outData[static_cast<size_t>(i)] = blob.data(i);
telsoa014fcda012018-03-09 14:13:49 +0000116 }
117}
118
telsoa014fcda012018-03-09 14:13:49 +0000119template <typename T>
120size_t SizeOfVectorData(const vector<T>& vec)
121{
122 return vec.size() * sizeof(T);
123}
124
125void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
126 unsigned int numInputs,
127 unsigned int numOutputs)
128{
129 int numInputsActual = layerParameter.bottom_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100130 if (numInputs != armnn::numeric_cast<unsigned int>(numInputsActual))
telsoa014fcda012018-03-09 14:13:49 +0000131 {
telsoa01c577f2c2018-08-31 09:22:23 +0100132 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100133 fmt::format("Invalid number of inputs requested {} for layer {} "
134 "while only {} present. {}",
135 numInputs,
136 layerParameter.name(),
137 numInputsActual,
138 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000139 }
140
141 int numOutputsActual = layerParameter.top_size();
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100142 if (numOutputs != armnn::numeric_cast<unsigned int>(numOutputsActual))
telsoa014fcda012018-03-09 14:13:49 +0000143 {
telsoa01c577f2c2018-08-31 09:22:23 +0100144 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100145 fmt::format("Invalid number of outputs requested {} for layer {} "
146 "while only {} present. {}",
147 numOutputs,
148 layerParameter.name(),
149 numOutputsActual,
150 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000151 }
152}
153
telsoa01c577f2c2018-08-31 09:22:23 +0100154template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
155ValueType GetOptionalWithFallback(const ParamType& param,
156 ExtractOptional extractOptional,
157 ExtractFallback extractFallback,
158 ValueType defaultValue)
159{
160 auto optValue = extractOptional(param, defaultValue);
161 if (optValue.first)
162 {
163 return optValue.second;
164 }
165 auto fallbackValue = extractFallback(param, defaultValue);
166 return fallbackValue.second;
167}
168
169#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
170 PARAM_TYPE, \
171 OPTIONAL_VALUE, \
172 FALLBACK_VECTOR, \
173 VALUE_TYPE, \
174 DEFAULT_VALUE) \
175 GetOptionalWithFallback( \
176 PARAM, \
177 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
178 { \
179 if (param.has_##OPTIONAL_VALUE ()) \
180 { \
181 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
182 } \
183 else \
184 { \
185 return std::make_pair(false, defaultValue); \
186 } \
187 }, \
188 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
189 { \
190 if (param.FALLBACK_VECTOR##_size() > 0) \
191 { \
192 return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
193 } \
194 else \
195 { \
196 return std::make_pair(false, defaultValue); \
197 } \
198 }, \
199 DEFAULT_VALUE)
200
201#define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
202 PARAM_TYPE, \
203 OPTIONAL_VALUE, \
204 FALLBACK_VALUE, \
205 VALUE_TYPE, \
206 DEFAULT_VALUE) \
207 GetOptionalWithFallback( \
208 PARAM, \
209 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
210 { \
211 if (param.has_##OPTIONAL_VALUE ()) \
212 { \
213 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
214 } \
215 else \
216 { \
217 return std::make_pair(false, defaultValue); \
218 } \
219 }, \
220 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
221 { \
222 if (param.has_##FALLBACK_VALUE ()) \
223 { \
224 return std::make_pair(true, param.FALLBACK_VALUE ()); \
225 } \
226 else \
227 { \
228 return std::make_pair(false, defaultValue); \
229 } \
230 }, \
231 DEFAULT_VALUE)
232
telsoa01c577f2c2018-08-31 09:22:23 +0100233} // namespace <anonymous>
234
235const std::map<std::string, CaffeParserBase::OperationParsingFunction>
236 CaffeParserBase::ms_CaffeLayerNameToParsingFunctions = {
237 { "Input", &CaffeParserBase::ParseInputLayer },
238 { "Convolution", &CaffeParserBase::ParseConvLayer },
239 { "Pooling", &CaffeParserBase::ParsePoolingLayer },
240 { "ReLU", &CaffeParserBase::ParseReluLayer },
241 { "LRN", &CaffeParserBase::ParseLRNLayer },
242 { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
243 { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
244 { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
245 { "Concat", &CaffeParserBase::ParseConcatLayer },
246 { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
247 { "Scale", &CaffeParserBase::ParseScaleLayer },
248 { "Split", &CaffeParserBase::ParseSplitLayer },
249 { "Dropout", &CaffeParserBase::ParseDropoutLayer},
250};
251
252ICaffeParser* ICaffeParser::CreateRaw()
253{
254 return new RecordByRecordCaffeParser();
255}
256
257ICaffeParserPtr ICaffeParser::Create()
258{
259 return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
260}
261
262void ICaffeParser::Destroy(ICaffeParser* parser)
263{
264 delete parser;
265}
266
267CaffeParserBase::CaffeParserBase()
268 : m_Network(nullptr, nullptr)
269{
270
271}
272
273CaffeParser::CaffeParser()
274: CaffeParserBase()
275{
276
277}
278
279BindingPointInfo CaffeParserBase::GetNetworkInputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000280{
281 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
282}
283
telsoa01c577f2c2018-08-31 09:22:23 +0100284BindingPointInfo CaffeParserBase::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000285{
286 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
287}
288
telsoa01c577f2c2018-08-31 09:22:23 +0100289std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
telsoa014fcda012018-03-09 14:13:49 +0000290 const char* bindingPointDesc,
291 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
292{
293 auto it = nameToBindingInfo.find(layerName);
294 if (it == nameToBindingInfo.end())
295 {
telsoa01c577f2c2018-08-31 09:22:23 +0100296 throw InvalidArgumentException(
James Ward58dec6b2020-09-11 17:32:44 +0100297 fmt::format("Unknown binding {} for layer '{}'. {}",
298 bindingPointDesc,
299 layerName,
300 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000301 }
302 return it->second;
303}
304
telsoa01c577f2c2018-08-31 09:22:23 +0100305TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
telsoa014fcda012018-03-09 14:13:49 +0000306{
307 std::vector<unsigned int> shape;
308 for (int j = 0; j < blobShape.dim_size(); ++j)
309 {
310 shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
311 }
312
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100313 return TensorInfo(armnn::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
telsoa014fcda012018-03-09 14:13:49 +0000314}
315
316BlobShape TensorDescToBlobShape(const TensorInfo& desc)
317{
318 BlobShape ret;
319 for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
320 {
321 ret.add_dim(i);
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100322 ret.set_dim(armnn::numeric_cast<int>(i), desc.GetShape()[i]);
telsoa014fcda012018-03-09 14:13:49 +0000323 }
324
325 return ret;
326}
327
telsoa01c577f2c2018-08-31 09:22:23 +0100328// Note: can move to CaffeParser when/if we optimise the text/string format
329// to load on a layer by layer basis
330vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000331{
332 std::vector<const caffe::LayerParameter*> ret;
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100333 ret.reserve(armnn::numeric_cast<size_t>(layerParam.bottom_size()));
telsoa014fcda012018-03-09 14:13:49 +0000334 for (int j = 0; j < layerParam.bottom_size(); ++j)
335 {
336 std::string inputName = layerParam.bottom(j);
337 auto inputIt = m_CaffeLayersByTopName.find(inputName);
338 if (inputIt == m_CaffeLayersByTopName.end())
339 {
340 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100341 fmt::format("Can't find Caffe layer with top called '{}', "
342 "which is listed as an input of '{}'. {}",
343 inputName,
344 layerParam.name(),
345 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000346 }
347 ret.push_back(inputIt->second);
348 }
349
350 return ret;
351}
352
telsoa01c577f2c2018-08-31 09:22:23 +0100353void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000354{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100355 ARMNN_ASSERT(layerParam.type() == "Input");
telsoa014fcda012018-03-09 14:13:49 +0000356 ValidateNumInputsOutputs(layerParam, 0, 1);
357
358 const InputParameter& param = layerParam.input_param();
359
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100360 const armnn::LayerBindingId inputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa01c577f2c2018-08-31 09:22:23 +0100361 m_NetworkInputsBindingInfo.size());
telsoa014fcda012018-03-09 14:13:49 +0000362 armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
363
telsoa01c577f2c2018-08-31 09:22:23 +0100364 // Decides the tensor info for this input. This can be specified in the Caffe network but can also
telsoa014fcda012018-03-09 14:13:49 +0000365 // be overriden by user input (m_inputShapes).
366 armnn::TensorInfo inputTensorInfo;
367
368 const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
369 &param.shape(0) : nullptr;
370 if (originalShape)
371 {
372 inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
373 }
374
375 auto overrideIt = m_InputShapes.find(layerParam.name());
376 if (overrideIt != m_InputShapes.end())
377 {
378 const TensorShape& overrideShape = overrideIt->second;
379 if (originalShape &&
380 ( originalShape->dim(1) != overrideShape[1]
381 || originalShape->dim(2) != overrideShape[2]
382 || originalShape->dim(3) != overrideShape[3]))
383 {
telsoa01c577f2c2018-08-31 09:22:23 +0100384 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100385 fmt::format("Parsed input shape for '{}' is incompatible with the override provided. {}",
386 layerParam.name(),
387 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000388 }
389 inputTensorInfo.SetShape(overrideShape);
390 }
391 else if (!originalShape)
392 {
telsoa01c577f2c2018-08-31 09:22:23 +0100393 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100394 fmt::format("No input descriptor given for '{}' and no input shape found in caffe model. {}",
395 layerParam.name(),
396 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000397 }
398
399 TrackInputBinding(inputLayer, inputId, inputTensorInfo);
400 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
401 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
402}
403
telsoa01c577f2c2018-08-31 09:22:23 +0100404void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
405 const armnn::Convolution2dDescriptor& desc,
406 unsigned int kernelW,
407 unsigned int kernelH)
telsoa014fcda012018-03-09 14:13:49 +0000408{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100409 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa014fcda012018-03-09 14:13:49 +0000410 ValidateNumInputsOutputs(layerParam, 1, 1);
411
telsoa01c577f2c2018-08-31 09:22:23 +0100412 ConvolutionParameter convParam = layerParam.convolution_param();
telsoa014fcda012018-03-09 14:13:49 +0000413 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa01c577f2c2018-08-31 09:22:23 +0100414 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
telsoa014fcda012018-03-09 14:13:49 +0000415
telsoa01c577f2c2018-08-31 09:22:23 +0100416 // asusme these were already verified by the caller ParseConvLayer() function
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100417 ARMNN_ASSERT(numGroups < inputShape.dim(1));
418 ARMNN_ASSERT(numGroups > 1);
telsoa014fcda012018-03-09 14:13:49 +0000419
420 // Handle grouping
telsoa014fcda012018-03-09 14:13:49 +0000421 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
422
423 vector<string> convLayerNames(numGroups);
424 vector<armnn::IConnectableLayer*> convLayers(numGroups);
425 convLayerNames[0] = layerParam.name();
426
telsoa01c577f2c2018-08-31 09:22:23 +0100427 // This convolution is to be applied to chunks of the input data so add a splitter layer
428
429 // Redirect the convolution input to the splitter
430 unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
431 static_cast<unsigned int>(inputShape.dim(1)),
432 static_cast<unsigned int>(inputShape.dim(2)),
433 static_cast<unsigned int>(inputShape.dim(3))};
434
435 // Split dimension 1 of the splitter output shape and conv input shapes
436 // according to the number of groups
437
438 splitterDimSizes[1] /= numGroups;
439 inputShape.set_dim(1, splitterDimSizes[1]);
440
441 // This is used to describe how the input is to be split
442 ViewsDescriptor splitterDesc(numGroups);
443
444 // Create an output node for each group, giving each a unique name
445 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000446 {
telsoa01c577f2c2018-08-31 09:22:23 +0100447 // Work out the names of the splitter layers child convolutions
448 stringstream ss;
449 ss << layerParam.name() << "_" << g;
450 convLayerNames[g] = ss.str();
telsoa014fcda012018-03-09 14:13:49 +0000451
telsoa01c577f2c2018-08-31 09:22:23 +0100452 splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
telsoa014fcda012018-03-09 14:13:49 +0000453
telsoa01c577f2c2018-08-31 09:22:23 +0100454 // Set the size of the views.
455 for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
telsoa014fcda012018-03-09 14:13:49 +0000456 {
telsoa01c577f2c2018-08-31 09:22:23 +0100457 splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
telsoa014fcda012018-03-09 14:13:49 +0000458 }
459 }
460
telsoa01c577f2c2018-08-31 09:22:23 +0100461 const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
462 armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
telsoa014fcda012018-03-09 14:13:49 +0000463
telsoa01c577f2c2018-08-31 09:22:23 +0100464 inputConnection.Connect(splitterLayer->GetInputSlot(0));
465 for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
466 {
467 splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
468 }
telsoa014fcda012018-03-09 14:13:49 +0000469
470 unsigned int numFilters = convParam.num_output();
471
telsoa01c577f2c2018-08-31 09:22:23 +0100472 // Populates convolution output tensor descriptor dimensions.
telsoa014fcda012018-03-09 14:13:49 +0000473 BlobShape outputShape;
474 outputShape.add_dim(0);
475 outputShape.set_dim(0, inputShape.dim(0));
476 outputShape.add_dim(1);
telsoa01c577f2c2018-08-31 09:22:23 +0100477 // Ensures that dimension 1 of the convolution output is split according to the number of groups.
telsoa014fcda012018-03-09 14:13:49 +0000478 outputShape.set_dim(1, numFilters / numGroups);
479 outputShape.add_dim(2);
480 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100481 2, (static_cast<int>(
482 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
483 static_cast<float>(desc.m_StrideY)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000484 outputShape.add_dim(3);
485 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100486 3, (static_cast<int>(
487 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
488 static_cast<float>(desc.m_StrideX)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000489
490 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100491 vector<float> weightData(armnn::numeric_cast<size_t>(numGroups *
telsoa01c577f2c2018-08-31 09:22:23 +0100492 inputShape.dim(1) * // number of input channels
493 outputShape.dim(1) * // number of output channels
494 kernelH *
495 kernelW));
telsoa014fcda012018-03-09 14:13:49 +0000496 GetDataFromBlob(layerParam, weightData, 0);
497
498 const unsigned int weightDimSizes[4] = {
telsoa01c577f2c2018-08-31 09:22:23 +0100499 static_cast<unsigned int>(outputShape.dim(1)),
500 static_cast<unsigned int>(inputShape.dim(1)),
501 kernelH,
502 kernelW};
telsoa014fcda012018-03-09 14:13:49 +0000503
telsoa014fcda012018-03-09 14:13:49 +0000504 TensorInfo biasInfo;
505 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100506
507 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000508 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100509 biasData.resize(armnn::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
telsoa014fcda012018-03-09 14:13:49 +0000510 GetDataFromBlob(layerParam, biasData, 1);
511
512 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
513 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
514 }
515
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100516 const unsigned int numWeightsPerGroup = armnn::numeric_cast<unsigned int>(weightData.size()) / numGroups;
517 const unsigned int numBiasesPerGroup = armnn::numeric_cast<unsigned int>(biasData.size()) / numGroups;
telsoa014fcda012018-03-09 14:13:49 +0000518
telsoa014fcda012018-03-09 14:13:49 +0000519 for (unsigned int g = 0; g < numGroups; ++g)
520 {
telsoa01c577f2c2018-08-31 09:22:23 +0100521 // Sets the slot index, group 0 should be connected to the 0th output of the splitter
522 // group 1 should be connected to the 1st output of the splitter.
telsoa014fcda012018-03-09 14:13:49 +0000523
telsoa01c577f2c2018-08-31 09:22:23 +0100524 // Pulls out the weights for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000525 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
526 weightData.data() + numWeightsPerGroup * g);
527
528 IConnectableLayer* convLayer = nullptr;
Matteo Martincighfc598e12019-05-14 10:36:13 +0100529 Optional<ConstTensor> optionalBiases;
telsoa01c577f2c2018-08-31 09:22:23 +0100530 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000531 {
telsoa01c577f2c2018-08-31 09:22:23 +0100532 // Pulls out the biases for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000533 ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
Matteo Martincighfc598e12019-05-14 10:36:13 +0100534 optionalBiases = Optional<ConstTensor>(biases);
telsoa014fcda012018-03-09 14:13:49 +0000535 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100536 convLayer = m_Network->AddConvolution2dLayer(desc,
537 weights,
538 optionalBiases,
539 convLayerNames[g].c_str());
telsoa014fcda012018-03-09 14:13:49 +0000540 convLayers[g] = convLayer;
541
542 // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
543 // otherwise it's the regular input to this layer.
telsoa01c577f2c2018-08-31 09:22:23 +0100544 armnn::IOutputSlot& splitterInputConnection =
545 splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
telsoa014fcda012018-03-09 14:13:49 +0000546 splitterInputConnection.Connect(convLayer->GetInputSlot(0));
547 convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
telsoa014fcda012018-03-09 14:13:49 +0000548 }
549
Jim Flynne242f2d2019-05-22 14:24:13 +0100550 // If the convolution was performed in chunks, add a layer to concatenate the results
telsoa01c577f2c2018-08-31 09:22:23 +0100551
552 // The merge input shape matches that of the convolution output
Jim Flynne242f2d2019-05-22 14:24:13 +0100553 unsigned int concatDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
554 static_cast<unsigned int>(outputShape.dim(1)),
555 static_cast<unsigned int>(outputShape.dim(2)),
556 static_cast<unsigned int>(outputShape.dim(3))};
telsoa01c577f2c2018-08-31 09:22:23 +0100557
Jim Flynne242f2d2019-05-22 14:24:13 +0100558 // This is used to describe how the input is to be concatenated
559 OriginsDescriptor concatDesc(numGroups);
telsoa01c577f2c2018-08-31 09:22:23 +0100560
561 // Now create an input node for each group, using the name from
562 // the output of the corresponding convolution
563 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000564 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100565 concatDesc.SetViewOriginCoord(g, 1, concatDimSizes[1] * g);
telsoa01c577f2c2018-08-31 09:22:23 +0100566 }
telsoa014fcda012018-03-09 14:13:49 +0000567
Jim Flynne242f2d2019-05-22 14:24:13 +0100568 // Make sure the output from the concat is the correct size to hold the data for all groups
569 concatDimSizes[1] *= numGroups;
570 outputShape.set_dim(1, concatDimSizes[1]);
telsoa014fcda012018-03-09 14:13:49 +0000571
Jim Flynne242f2d2019-05-22 14:24:13 +0100572 // Finally add the concat layer
573 IConnectableLayer* concatLayer = m_Network->AddConcatLayer(concatDesc, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000574
Jim Flynne242f2d2019-05-22 14:24:13 +0100575 if (!concatLayer)
telsoa01c577f2c2018-08-31 09:22:23 +0100576 {
577 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100578 fmt::format("Failed to create final concat layer for Split+Convolution+Concat. "
579 "Layer={} #groups={} #filters={} {}",
580 layerParam.name(),
581 numGroups,
582 numFilters,
583 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100584 }
telsoa014fcda012018-03-09 14:13:49 +0000585
telsoa01c577f2c2018-08-31 09:22:23 +0100586 for (unsigned int g = 0; g < numGroups; ++g)
587 {
Jim Flynne242f2d2019-05-22 14:24:13 +0100588 convLayers[g]->GetOutputSlot(0).Connect(concatLayer->GetInputSlot(g));
telsoa01c577f2c2018-08-31 09:22:23 +0100589 }
Jim Flynne242f2d2019-05-22 14:24:13 +0100590 concatLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, concatDimSizes, DataType::Float32));
591 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatLayer->GetOutputSlot(0));
telsoa01c577f2c2018-08-31 09:22:23 +0100592}
telsoa014fcda012018-03-09 14:13:49 +0000593
telsoa01c577f2c2018-08-31 09:22:23 +0100594void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
595 const armnn::Convolution2dDescriptor& convDesc,
596 unsigned int kernelW,
597 unsigned int kernelH)
598{
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100599 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100600 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000601
telsoa01c577f2c2018-08-31 09:22:23 +0100602 ConvolutionParameter convParam = layerParam.convolution_param();
603 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa014fcda012018-03-09 14:13:49 +0000604
telsoa01c577f2c2018-08-31 09:22:23 +0100605 DepthwiseConvolution2dDescriptor desc;
606 desc.m_PadLeft = convDesc.m_PadLeft;
607 desc.m_PadRight = convDesc.m_PadRight;
608 desc.m_PadTop = convDesc.m_PadTop;
609 desc.m_PadBottom = convDesc.m_PadBottom;
610 desc.m_StrideX = convDesc.m_StrideX;
611 desc.m_StrideY = convDesc.m_StrideY;
612 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
telsoa014fcda012018-03-09 14:13:49 +0000613
telsoa01c577f2c2018-08-31 09:22:23 +0100614 unsigned int numFilters = convParam.num_output();
615
616 BlobShape outputShape;
617 outputShape.add_dim(0);
618 outputShape.set_dim(0, inputShape.dim(0));
619 outputShape.add_dim(1);
620 outputShape.set_dim(1, numFilters);
621 outputShape.add_dim(2);
622 outputShape.set_dim(
623 2, (static_cast<int>(
624 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
625 static_cast<float>(desc.m_StrideY)) + 1));
626 outputShape.add_dim(3);
627 outputShape.set_dim(
628 3, (static_cast<int>(
629 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
630 static_cast<float>(desc.m_StrideX)) + 1));
631
632 // Load the weight data
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100633 size_t allWeightsSize = armnn::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
telsoa01c577f2c2018-08-31 09:22:23 +0100634 vector<float> weightData(allWeightsSize);
635
636 GetDataFromBlob(layerParam, weightData, 0);
637
638 // depth multiplier will be 1 for the depthwise convolution
639 const unsigned int weightDimSizes[4] = {
640 static_cast<unsigned int>(1), // depth multiplier
641 static_cast<unsigned int>(inputShape.dim(1)), // #channels
642 kernelH,
643 kernelW};
644
645 armnn::IConnectableLayer* returnLayer = nullptr;
646 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100647 Optional<ConstTensor> optionalBiases;
648 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100649 if (desc.m_BiasEnabled)
650 {
651 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100652
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100653 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100654 GetDataFromBlob(layerParam, biasData, 1);
655
656 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
657 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
658
659 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100660 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100661 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100662 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc,
663 weights,
664 optionalBiases,
665 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000666
surmeh013537c2c2018-05-18 16:31:43 +0100667 if (!returnLayer)
668 {
telsoa01c577f2c2018-08-31 09:22:23 +0100669 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100670 fmt::format("Failed to create depthwise convolution layer. "
671 "Layer={} #filters={} {}",
672 layerParam.name(),
673 numFilters,
674 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100675 }
676 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
677 inputConnection.Connect(returnLayer->GetInputSlot(0));
678 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
679 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
680}
681
682void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
683{
684 // Ignored Caffe Parameters
685 // * Dilation Size
686 // * Weight Filler
687 // * Bias Filler
688 // * Engine
689 // * Force nd_im2col
690 // * Axis
691
692 // Not Available ArmNN Interface Parameters
693 // * Rounding policy;
694
Narumol Prangnawaratac2770a2020-04-01 16:51:23 +0100695 ARMNN_ASSERT(layerParam.type() == "Convolution");
telsoa01c577f2c2018-08-31 09:22:23 +0100696 ValidateNumInputsOutputs(layerParam, 1, 1);
697
698 ConvolutionParameter convParam = layerParam.convolution_param();
699 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
700 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
701 unsigned int numFilters = convParam.num_output();
702
703 const auto notFound = std::numeric_limits<unsigned int>::max();
704
705 unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
706 kernel_h, kernel_size, unsigned int, notFound);
707 unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
708 kernel_w, kernel_size, unsigned int, notFound);
709
710 unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
711 stride_h, stride, unsigned int, 1u);
712 unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
713 stride_w, stride, unsigned int, 1u);
714
715 unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
716 pad_h, pad, unsigned int, 0u);
717 unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
718 pad_w, pad, unsigned int, 0u);
719
telsoa01c577f2c2018-08-31 09:22:23 +0100720 Convolution2dDescriptor convolution2dDescriptor;
721 convolution2dDescriptor.m_PadLeft = padW;
722 convolution2dDescriptor.m_PadRight = padW;
723 convolution2dDescriptor.m_PadTop = padH;
724 convolution2dDescriptor.m_PadBottom = padH;
725 convolution2dDescriptor.m_StrideX = strideW;
726 convolution2dDescriptor.m_StrideY = strideH;
727 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
728
729 if (numGroups > numFilters)
730 {
731 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100732 fmt::format("Error parsing Convolution: {}. "
733 "The 'group'={} parameter cannot be larger than the "
734 "number of filters supplied ='{}'. {}",
735 layerParam.name(),
736 numGroups,
737 numFilters,
738 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100739 }
740
741 if (inputShape.dim_size() != 4)
742 {
743 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100744 fmt::format("Convolution input shape is expected to have 4 dimensions. "
745 "{}'s input has only {}. {}",
746 layerParam.name(),
747 inputShape.dim_size(),
748 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100749 }
750
751 if (numGroups > 1)
752 {
753 if (numGroups > inputShape.dim(1))
754 {
755 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100756 fmt::format("Error parsing Convolution: {}. "
757 "The 'group'={} parameter cannot be larger than the "
758 "channel of the input shape={} (in NCHW format). {}",
759 layerParam.name(),
760 numGroups,
761 inputShape.dim(1),
762 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100763 }
764 else if (numGroups == inputShape.dim(1))
765 {
766 // we use a depthwise convolution here, because the number of groups equals to the
767 // input channels
768 AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
769 return;
770 }
771 else
772 {
773 // we split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +0100774 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +0100775 AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
776 return;
777 }
778 }
779
780 // NOTE: at this point we only need to handle #group=1 case, all other cases should be
781 // handled by the AddConvLayer* helpers
782
783 // Populate convolution output tensor descriptor dimensions
784 BlobShape outputShape;
785 outputShape.add_dim(0);
786 outputShape.set_dim(0, inputShape.dim(0));
787 outputShape.add_dim(1);
788 outputShape.set_dim(1, numFilters);
789 outputShape.add_dim(2);
790 outputShape.set_dim(
791 2, (static_cast<int>(
792 static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
793 static_cast<float>(strideH)) + 1));
794 outputShape.add_dim(3);
795 outputShape.set_dim(
796 3, (static_cast<int>(
797 static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
798 static_cast<float>(strideW)) + 1));
799
800 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100801 vector<float> weightData(armnn::numeric_cast<size_t>(inputShape.dim(1) *
telsoa01c577f2c2018-08-31 09:22:23 +0100802 outputShape.dim(1) *
803 kernelH *
804 kernelW));
805 GetDataFromBlob(layerParam, weightData, 0);
806
807 const unsigned int weightDimSizes[4] = {
808 static_cast<unsigned int>(outputShape.dim(1)), // output channels
809 static_cast<unsigned int>(inputShape.dim(1)), // input channels
810 kernelH,
811 kernelW};
812
813 armnn::IConnectableLayer* returnLayer = nullptr;
814
815 // Pull out the weights for this group from that loaded from the model file earlier
816 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100817 Optional<ConstTensor> optionalBiases;
818 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100819 if (convolution2dDescriptor.m_BiasEnabled)
820 {
821 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100822
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100823 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100824 GetDataFromBlob(layerParam, biasData, 1);
825
826 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
827 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
828
829 // Pull out the biases for this group from that loaded from the model file earlier
830 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100831 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100832 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100833 returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
834 weights,
835 optionalBiases,
836 layerParam.name().c_str());
telsoa01c577f2c2018-08-31 09:22:23 +0100837
838 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
839 inputConnection.Connect(returnLayer->GetInputSlot(0));
840 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
841
842 if (!returnLayer)
843 {
844 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100845 fmt::format("Failed to create Convolution layer. "
846 "Layer={} #groups={} #filters={} {}",
847 layerParam.name(),
848 numGroups,
849 numFilters,
850 CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100851 }
852
telsoa014fcda012018-03-09 14:13:49 +0000853 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
854}
855
telsoa01c577f2c2018-08-31 09:22:23 +0100856void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000857{
telsoa01c577f2c2018-08-31 09:22:23 +0100858 // Ignored Caffe Parameters
859 // Stochastic Pooling
860 // Engine
861
telsoa014fcda012018-03-09 14:13:49 +0000862 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000863 PoolingParameter param = layerParam.pooling_param();
telsoa014fcda012018-03-09 14:13:49 +0000864 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
865
telsoa01c577f2c2018-08-31 09:22:23 +0100866 const auto notFound = std::numeric_limits<unsigned int>::max();
867
868 unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
869 kernel_h, kernel_size, unsigned int, notFound);
870 unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
871 kernel_w, kernel_size, unsigned int, notFound);
872
873 if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000874 {
875 kernel_h = inputInfo.GetShape()[2];
876 kernel_w = inputInfo.GetShape()[3];
877 }
telsoa01c577f2c2018-08-31 09:22:23 +0100878
telsoa01c577f2c2018-08-31 09:22:23 +0100879 unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
880 stride_h, stride, unsigned int, notFound);
881 unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
882 stride_h, stride, unsigned int, notFound);
883
884 if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000885 {
telsoa01c577f2c2018-08-31 09:22:23 +0100886 stride_h = 1;
887 stride_w = 1;
telsoa014fcda012018-03-09 14:13:49 +0000888 }
889
telsoa01c577f2c2018-08-31 09:22:23 +0100890 unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
891 pad_h, pad, unsigned int, 0u);
892 unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
893 pad_w, pad, unsigned int, 0u);
telsoa014fcda012018-03-09 14:13:49 +0000894
telsoa014fcda012018-03-09 14:13:49 +0000895 // Populate Weight and Bias Filter Descriptor
896 Pooling2dDescriptor pooling2dDescriptor;
897 if (param.has_pool())
898 {
899 PoolingParameter_PoolMethod p = param.pool();
900 switch (p)
901 {
902 case PoolingParameter_PoolMethod_MAX:
903 {
904 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
905 break;
906 }
907 case PoolingParameter_PoolMethod_AVE:
908 {
909 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
910 break;
911 }
912 case PoolingParameter_PoolMethod_STOCHASTIC:
913 {
telsoa01c577f2c2018-08-31 09:22:23 +0100914 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100915 fmt::format("Pooling Layer: Stochastic Pooling Not Supported. Layer={} {}",
916 layerParam.name(),
917 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000918 }
919 default:
920 {
telsoa01c577f2c2018-08-31 09:22:23 +0100921 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100922 fmt::format("Pooling Layer: unknown pooling method: {} for layer: {} {}",
923 p,
924 layerParam.name(),
925 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000926 }
927 }
928 }
929 else
930 {
telsoa01c577f2c2018-08-31 09:22:23 +0100931 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100932 fmt::format("No Pooling Method Defined for {} {}",
933 layerParam.name(),
934 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000935 }
936
937 pooling2dDescriptor.m_PadLeft = pad_w;
938 pooling2dDescriptor.m_PadRight = pad_w;
939 pooling2dDescriptor.m_PadTop = pad_h;
940 pooling2dDescriptor.m_PadBottom = pad_h;
941 pooling2dDescriptor.m_StrideX = stride_w;
942 pooling2dDescriptor.m_StrideY = stride_h;
943 pooling2dDescriptor.m_PoolWidth = kernel_w;
944 pooling2dDescriptor.m_PoolHeight = kernel_h;
945
946 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
947 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
948
949 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
950 layerParam.name().c_str());
951
telsoa014fcda012018-03-09 14:13:49 +0000952 TensorInfo outputInfo(
953 { inputInfo.GetShape()[0],
954 inputInfo.GetShape()[1],
955 static_cast<unsigned int>(ceil(
956 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
Matthew Sloyan24ac8592020-09-23 16:57:23 +0100957 armnn::numeric_cast<float>(stride_h))) + 1,
telsoa014fcda012018-03-09 14:13:49 +0000958 static_cast<unsigned int>(ceil(
959 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
Matthew Sloyan24ac8592020-09-23 16:57:23 +0100960 armnn::numeric_cast<float>(stride_w))) + 1 },
telsoa014fcda012018-03-09 14:13:49 +0000961 DataType::Float32);
962
963 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
964 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
965 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
966}
967
telsoa01c577f2c2018-08-31 09:22:23 +0100968void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000969{
970 ValidateNumInputsOutputs(layerParam, 1, 1);
971
972 const string& name = layerParam.name();
973 const ReLUParameter& param = layerParam.relu_param();
974
975 ActivationDescriptor activationDescriptor;
976 const float negativeSlope = param.negative_slope();
977 if (negativeSlope == 0.0f)
978 {
979 activationDescriptor.m_Function = ActivationFunction::ReLu;
980 }
981 else
982 {
983 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
984 activationDescriptor.m_A = negativeSlope;
985 }
986
987 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
988 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
989 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
990 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
991 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
992}
993
telsoa01c577f2c2018-08-31 09:22:23 +0100994void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000995{
996 ValidateNumInputsOutputs(layerParam, 1, 1);
997
998 LRNParameter param = layerParam.lrn_param();
999
1000 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1001
telsoa01c577f2c2018-08-31 09:22:23 +01001002 // Ignored BATCH NORMALIZATION Caffe Parameters.
1003 // Ignored MVN Caffe Parameters.
1004 // Ignored LRN Caffe Parameters.
telsoa014fcda012018-03-09 14:13:49 +00001005 // Engine
1006
1007 NormalizationDescriptor normalizationDescriptor;
1008 if (param.has_norm_region())
1009 {
1010 LRNParameter_NormRegion n = param.norm_region();
1011 switch (n)
1012 {
1013 case LRNParameter_NormRegion_ACROSS_CHANNELS:
1014 {
1015 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1016 break;
1017 }
1018 case LRNParameter_NormRegion_WITHIN_CHANNEL:
1019 {
1020 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1021 break;
1022 }
1023 default:
telsoa01c577f2c2018-08-31 09:22:23 +01001024 {
1025 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001026 fmt::format("Unknown region {} for LRN layer {} {}",
1027 n,
1028 layerParam.name(),
1029 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001030 }
telsoa014fcda012018-03-09 14:13:49 +00001031 }
1032 }
1033 else
1034 {
telsoa01c577f2c2018-08-31 09:22:23 +01001035 // Caffe defaults to normalization across channels.
telsoa014fcda012018-03-09 14:13:49 +00001036 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1037 }
1038
1039 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1040 if (param.has_local_size())
1041 {
1042 normalizationDescriptor.m_NormSize = param.local_size();
1043 }
1044 else
1045 {
telsoa01c577f2c2018-08-31 09:22:23 +01001046 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001047 fmt::format("local_size not defined for LRN layer {} {}",
1048 layerParam.name(),
1049 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001050 }
1051
1052 if (param.has_alpha())
1053 {
1054 normalizationDescriptor.m_Alpha = param.alpha();
Matthew Sloyan24ac8592020-09-23 16:57:23 +01001055 normalizationDescriptor.m_Alpha /= armnn::numeric_cast<float>(param.local_size());
telsoa014fcda012018-03-09 14:13:49 +00001056 }
1057 else
1058 {
telsoa01c577f2c2018-08-31 09:22:23 +01001059 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001060 fmt::format("Alpha not defined for LRN layer {} {}",
1061 layerParam.name(),
1062 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001063 }
1064 if (param.has_beta())
1065 {
1066 normalizationDescriptor.m_Beta = param.beta();
1067 }
1068 else
1069 {
telsoa01c577f2c2018-08-31 09:22:23 +01001070 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001071 fmt::format("Beta not defined for LRN layer {} {}",
1072 layerParam.name(),
1073 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001074 }
telsoa01c577f2c2018-08-31 09:22:23 +01001075
telsoa014fcda012018-03-09 14:13:49 +00001076 if (param.has_k())
1077 {
1078 normalizationDescriptor.m_K = param.k();
1079 }
1080 else
telsoa01c577f2c2018-08-31 09:22:23 +01001081 {
telsoa014fcda012018-03-09 14:13:49 +00001082 normalizationDescriptor.m_K = 1;
telsoa01c577f2c2018-08-31 09:22:23 +01001083 }
telsoa014fcda012018-03-09 14:13:49 +00001084
1085 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1086 layerParam.name().c_str());
1087 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1088 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1089
1090 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1091}
1092
telsoa01c577f2c2018-08-31 09:22:23 +01001093void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001094{
1095 InnerProductParameter param = layerParam.inner_product_param();
1096
1097 ValidateNumInputsOutputs(layerParam, 1, 1);
1098
1099 unsigned int outputSize = param.num_output();
1100
telsoa01c577f2c2018-08-31 09:22:23 +01001101 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001102 // Weight Filler
1103 // Bias Filler
1104 // Engine
1105 // Axis
1106
1107 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1108
1109 if (param.has_transpose())
1110 {
telsoa01c577f2c2018-08-31 09:22:23 +01001111 // If true, assumes transposed weights.
telsoa014fcda012018-03-09 14:13:49 +00001112 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1113 }
1114 else
1115 {
telsoa01c577f2c2018-08-31 09:22:23 +01001116 // Caffe defaults to transposed.
telsoa014fcda012018-03-09 14:13:49 +00001117 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1118 }
1119
1120 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1121
1122 TensorInfo weightInfo;
1123 TensorInfo biasInfo;
1124
telsoa01c577f2c2018-08-31 09:22:23 +01001125 // Allows implicit flattening of extra dimensions.
telsoa014fcda012018-03-09 14:13:49 +00001126 unsigned int inputSize = inputInfo.GetShape()[1];
1127 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1128 {
1129 inputSize *= inputInfo.GetShape()[i];
1130 }
1131
telsoa01c577f2c2018-08-31 09:22:23 +01001132 const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
telsoa014fcda012018-03-09 14:13:49 +00001133 const unsigned int swTD[2] = { outputSize, inputSize };
telsoa01c577f2c2018-08-31 09:22:23 +01001134 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001135
1136 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
telsoa01c577f2c2018-08-31 09:22:23 +01001137 // Todo: check whether bias enabled.
telsoa014fcda012018-03-09 14:13:49 +00001138 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1139 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1140 {
1141 // BIAS VALUE
telsoa01c577f2c2018-08-31 09:22:23 +01001142 const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
telsoa014fcda012018-03-09 14:13:49 +00001143
1144 const unsigned int sbTD[1] = { outputSize };
1145
telsoa01c577f2c2018-08-31 09:22:23 +01001146 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001147
Matteo Martincighfc598e12019-05-14 10:36:13 +01001148 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1149 weights,
1150 Optional<ConstTensor>(biases),
1151 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001152 }
1153 else
1154 {
Matteo Martincighfc598e12019-05-14 10:36:13 +01001155 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1156 weights,
1157 EmptyOptional(),
1158 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001159 }
1160
1161 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1162 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1163 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1164 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1165}
1166
telsoa01c577f2c2018-08-31 09:22:23 +01001167void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001168{
1169 ValidateNumInputsOutputs(layerParam, 1, 1);
1170
1171 SoftmaxParameter param = layerParam.softmax_param();
1172
1173 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1174
telsoa01c577f2c2018-08-31 09:22:23 +01001175 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001176 // axis
1177 // Engine
1178
1179 armnn::SoftmaxDescriptor softmaxDescriptor;
Teresa Charlin4320c922020-08-12 16:04:41 +01001180 softmaxDescriptor.m_Axis = 1;
telsoa014fcda012018-03-09 14:13:49 +00001181 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1182 softmaxDescriptor,
1183 layerParam.name().c_str());
1184 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1185 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1186 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1187}
1188
telsoa01c577f2c2018-08-31 09:22:23 +01001189void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001190{
1191 ValidateNumInputsOutputs(layerParam, 2, 1);
1192
1193 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1194
telsoa01c577f2c2018-08-31 09:22:23 +01001195 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001196 // coeff
1197
telsoa01c577f2c2018-08-31 09:22:23 +01001198 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
telsoa014fcda012018-03-09 14:13:49 +00001199
1200 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1201 {
1202 operation = layerParam.eltwise_param().operation();
1203 }
1204
1205 armnn::IConnectableLayer* newLayer = nullptr;
1206 switch (operation)
1207 {
1208 case EltwiseParameter_EltwiseOp_SUM:
1209 {
1210 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1211 break;
1212 }
1213 case EltwiseParameter_EltwiseOp_PROD:
1214 {
1215 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1216 break;
1217 }
1218 default:
1219 {
telsoa01c577f2c2018-08-31 09:22:23 +01001220 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001221 fmt::format("Unsupported operation {} in Eltwise layer {} {}",
1222 operation,
1223 layerParam.name(),
1224 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001225 }
1226 }
1227
1228 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1229 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1230 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1231 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1232}
1233
telsoa01c577f2c2018-08-31 09:22:23 +01001234void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001235{
1236 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
telsoa01c577f2c2018-08-31 09:22:23 +01001237 // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
telsoa014fcda012018-03-09 14:13:49 +00001238 unsigned int concatDim = 1;
1239 unsigned int numOfDims = 4;
1240
telsoa01c577f2c2018-08-31 09:22:23 +01001241 // we only consider 4-D tensor here
1242 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
telsoa014fcda012018-03-09 14:13:49 +00001243 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1244
1245 unsigned int mergeDim = 0;
1246 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1247 {
1248 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001249 layerParam.bottom(armnn::numeric_cast<int>(viewIndex))).GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001250 // Checks whether the dimensions of the input tensors are actually 4.
telsoa014fcda012018-03-09 14:13:49 +00001251 if (inputInfo.GetNumDimensions()!=4)
1252 {
telsoa01c577f2c2018-08-31 09:22:23 +01001253 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001254 fmt::format("The number of dimensions for input tensors of "
1255 "the concatenation op should be 4. Inputs of {} has "
1256 "{} dimensions. {}",
1257 layerParam.name(),
1258 inputInfo.GetNumDimensions(),
1259 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001260 }
1261
1262 mergeDimSizes[0] = inputInfo.GetShape()[0];
1263 mergeDimSizes[1] = inputInfo.GetShape()[1];
1264 mergeDimSizes[2] = inputInfo.GetShape()[2];
1265 mergeDimSizes[3] = inputInfo.GetShape()[3];
1266
1267 for (unsigned int j = 0; j < concatDim; ++j)
1268 {
1269 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1270 }
1271
1272 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1273 mergeDim += mergeDimSizes[concatDim];
1274
1275 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1276 {
1277 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1278 }
1279 }
1280 mergeDimSizes[concatDim] = mergeDim;
1281
Jim Flynn906f9462019-05-10 13:55:21 +01001282 armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001283 for (unsigned int i = 0; i < numInputs; ++i)
1284 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001285 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(armnn::numeric_cast<int>(i)));
telsoa014fcda012018-03-09 14:13:49 +00001286 outputSlot.Connect(concatlayer->GetInputSlot(i));
1287 }
1288
1289 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1290 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1291}
1292
telsoa01c577f2c2018-08-31 09:22:23 +01001293void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001294{
1295 ValidateNumInputsOutputs(layerParam, 1, 1);
1296
1297 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1298
1299 string name = layerParam.name();
1300
1301 BatchNormParameter param = layerParam.batch_norm_param();
1302 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1303 // when the network is in the testing phase).
1304 if (param.has_use_global_stats())
1305 {
1306 if (!param.use_global_stats())
1307 {
telsoa01c577f2c2018-08-31 09:22:23 +01001308 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001309 fmt::format("Error parsing Batch Norm layer '{}': "
1310 "Parameter 'use_global_stats' is set to false, which is "
1311 "unsupported (value used for training). {}",
1312 name,
1313 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001314 }
1315 }
1316
1317 BatchNormalizationDescriptor desc;
1318 desc.m_Eps = param.eps();
1319
1320 unsigned int channels = inputInfo.GetShape()[1];
1321 unsigned int shape[] = {channels};
1322
1323 vector<float> meanData(channels);
1324 GetDataFromBlob(layerParam, meanData, 0);
1325
1326 vector<float> varianceData(channels);
1327 GetDataFromBlob(layerParam, varianceData, 1);
1328
telsoa01c577f2c2018-08-31 09:22:23 +01001329 // Reads moving average factor and applies scaling (if required).
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001330 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(2));
1331 const float movingAverageFactor = blob.data(armnn::numeric_cast<int>(0));
surmeh013537c2c2018-05-18 16:31:43 +01001332 if(movingAverageFactor != 0.0f)
1333 {
1334 const float scaleFactor = 1.0f / movingAverageFactor;
1335 auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1336
1337 std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1338 std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1339 }
1340
telsoa01c577f2c2018-08-31 09:22:23 +01001341 // Identifies scale operation.
telsoa014fcda012018-03-09 14:13:49 +00001342 vector<float> betaData(channels, 0.0f);
1343 vector<float> gammaData(channels, 1.0f);
1344
1345 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1346 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1347 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1348 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1349
1350 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1351 mean, variance, beta, gamma, name.c_str());
1352 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1353 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1354 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1355}
1356
telsoa01c577f2c2018-08-31 09:22:23 +01001357void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001358{
telsoa01c577f2c2018-08-31 09:22:23 +01001359 // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
telsoa014fcda012018-03-09 14:13:49 +00001360 ValidateNumInputsOutputs(layerParam, 1, 1);
1361
1362 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1363
1364 string name = layerParam.name();
1365
1366 ScaleParameter param = layerParam.scale_param();
1367 if (param.axis() != 1)
1368 {
1369 // Would have to use something other than BatchNormalizationLayer in this case
telsoa01c577f2c2018-08-31 09:22:23 +01001370 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001371 fmt::format("Loading Scale Layer: Only axis 1 is supported currently. "
1372 "Layer={} Axis={} {}",
1373 layerParam.name(),
1374 param.axis(),
1375 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001376 }
1377
1378 unsigned int channels = inputInfo.GetShape()[1];
1379 unsigned int shape[] = {channels};
1380
1381 BatchNormalizationDescriptor desc;
telsoa01c577f2c2018-08-31 09:22:23 +01001382 desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
telsoa014fcda012018-03-09 14:13:49 +00001383 vector<float> meanData(channels, 0.0f);
1384 vector<float> varianceData(channels, 1.0f);
1385 vector<float> betaData(channels, 0.0f);
1386 vector<float> gammaData(channels);
1387
1388 GetDataFromBlob(layerParam, gammaData, 0);
1389
1390 if(param.has_bias_term())
1391 {
1392 GetDataFromBlob(layerParam, betaData, 1);
1393 }
1394
1395 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1396 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1397 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1398 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1399
1400 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1401 mean, variance, beta, gamma, name.c_str());
1402 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1403 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1404 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1405}
1406
telsoa01c577f2c2018-08-31 09:22:23 +01001407void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001408{
telsoa01c577f2c2018-08-31 09:22:23 +01001409 // Used in caffe to duplicate memory - not necessary in armnn.
telsoa014fcda012018-03-09 14:13:49 +00001410 if (layerParam.bottom_size() != 1)
1411 {
telsoa01c577f2c2018-08-31 09:22:23 +01001412 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001413 fmt::format("Split layer '{}' should have exactly 1 bottom. "
1414 "#bottoms={} {}",
1415 layerParam.name(),
1416 layerParam.bottom_size(),
1417 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001418 }
1419 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1420 for (int i = 0; i < layerParam.top_size(); i++)
1421 {
1422 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1423 }
1424}
1425
telsoa01c577f2c2018-08-31 09:22:23 +01001426void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001427{
telsoa01c577f2c2018-08-31 09:22:23 +01001428 // Ignored for inference, so patch the single input to its single output.
telsoa014fcda012018-03-09 14:13:49 +00001429 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1430 {
telsoa01c577f2c2018-08-31 09:22:23 +01001431 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001432 fmt::format("Dropout layer '{}' should have exactly 1 bottom and 1 top. "
1433 "#bottoms={} #tops={} {}",
1434 layerParam.name(),
1435 layerParam.bottom_size(),
1436 layerParam.top_size(),
1437 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001438 }
1439 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1440}
1441
telsoa01c577f2c2018-08-31 09:22:23 +01001442void CaffeParserBase::TrackInputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001443 armnn::LayerBindingId id,
1444 const armnn::TensorInfo& tensorInfo)
1445{
1446 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1447}
1448
telsoa01c577f2c2018-08-31 09:22:23 +01001449void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001450 armnn::LayerBindingId id,
1451 const armnn::TensorInfo& tensorInfo)
1452{
1453 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1454}
1455
telsoa01c577f2c2018-08-31 09:22:23 +01001456void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001457 armnn::LayerBindingId id,
1458 const armnn::TensorInfo& tensorInfo,
1459 const char* bindingPointDesc,
1460 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1461{
1462 const std::string layerName = layer->GetName();
1463 auto it = nameToBindingInfo.find(layerName);
1464 if (it == nameToBindingInfo.end())
1465 {
1466 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1467 }
1468 else
1469 {
telsoa01c577f2c2018-08-31 09:22:23 +01001470 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001471 fmt::format("Id {} used by more than one {} layer {}",
1472 id,
1473 bindingPointDesc,
1474 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001475 }
1476}
1477
telsoa01c577f2c2018-08-31 09:22:23 +01001478armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
telsoa014fcda012018-03-09 14:13:49 +00001479{
1480 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1481 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1482 {
1483 return *it->second;
1484 }
1485 else
1486 {
telsoa01c577f2c2018-08-31 09:22:23 +01001487 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001488 fmt::format("Could not find armnn output slot for Caffe top '{}' {}",
1489 caffeTopName,
1490 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001491 }
1492}
1493
telsoa01c577f2c2018-08-31 09:22:23 +01001494void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
1495 const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
telsoa014fcda012018-03-09 14:13:49 +00001496{
1497 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1498 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1499 {
1500 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1501 }
1502 else
1503 {
telsoa01c577f2c2018-08-31 09:22:23 +01001504 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001505 fmt::format("Attempting to add duplicate entry for Caffe top '{}' {}",
1506 caffeTopName,
1507 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001508 }
1509}
1510
telsoa01c577f2c2018-08-31 09:22:23 +01001511// Note: can move to CaffeParser when/if we optimise the text/string format
1512// to load on a layer by layer basis
1513void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001514{
telsoa01c577f2c2018-08-31 09:22:23 +01001515 // Finds layers with the same top.
telsoa014fcda012018-03-09 14:13:49 +00001516 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1517 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1518 {
1519 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
telsoa01c577f2c2018-08-31 09:22:23 +01001520 std::string name = layer.name();
telsoa014fcda012018-03-09 14:13:49 +00001521 for (int i = 0; i < layer.top_size(); ++i)
1522 {
1523 layersByTop[layer.top(i)].push_back(&layer);
1524 }
1525 }
1526
telsoa01c577f2c2018-08-31 09:22:23 +01001527 // 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 +00001528 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1529 for (auto layersWithSameTopIt : layersByTop)
1530 {
1531 const std::string& top = layersWithSameTopIt.first;
1532 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1533
telsoa01c577f2c2018-08-31 09:22:23 +01001534 // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
telsoa014fcda012018-03-09 14:13:49 +00001535 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1536 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1537 {
1538 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1539 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1540 if (layer1.top_size() != 1)
1541 {
telsoa01c577f2c2018-08-31 09:22:23 +01001542 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001543 fmt::format("Node '{}' is an in-place layer but doesn't have exactly one "
1544 "top. It has {} instead. {}",
1545 layer1.name(),
1546 layer1.top_size(),
1547 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001548 }
1549 std::string newTop = layer1.name() + "_top";
1550 layer1.set_top(0, newTop);
1551 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1552 {
telsoa01c577f2c2018-08-31 09:22:23 +01001553 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001554 fmt::format("Node '{}' is an in-place layer but "
1555 "doesn't have exactly one bottom, or it doesn't match its top. "
1556 "#bottoms={}, first bottom is {}, top is {} {}",
1557 layer2.name(),
1558 layer2.bottom(0),
1559 top,
1560 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001561 }
1562 layer2.set_bottom(0, newTop);
1563 }
1564 }
1565}
1566
telsoa01c577f2c2018-08-31 09:22:23 +01001567// Note: can move to CaffeParser when/if we optimise the text/string format
1568// to load on a layer by layer basis
1569void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001570{
telsoa01c577f2c2018-08-31 09:22:23 +01001571 // Caffe models sometimes have an implicit input layer.
1572 // In that case, add an explicit one.
telsoa014fcda012018-03-09 14:13:49 +00001573 if (netParameter.input_size() > 0)
1574 {
1575 LayerParameter* newLayer = netParameter.add_layer();
1576
1577 newLayer->set_type("Input");
1578 newLayer->set_name(netParameter.input(0));
1579 newLayer->add_top(netParameter.input(0));
1580
1581 InputParameter* inputParam = newLayer->mutable_input_param();
1582 BlobShape* shape = inputParam->add_shape();
1583
1584 int dim_size = netParameter.input_dim_size();
1585 for (int i = 0; i < dim_size; ++i)
1586 {
1587 shape->add_dim(netParameter.input_dim(i));
1588 }
1589 }
1590
telsoa01c577f2c2018-08-31 09:22:23 +01001591 // Replaces in-place layers with regular ones to make the rest of the parsing easier.
telsoa014fcda012018-03-09 14:13:49 +00001592 ResolveInPlaceLayers(netParameter);
1593
telsoa01c577f2c2018-08-31 09:22:23 +01001594 // Creates a lookup of Caffe layers by name.
telsoa014fcda012018-03-09 14:13:49 +00001595 for (int i = 0; i < netParameter.layer_size(); ++i)
1596 {
1597 const caffe::LayerParameter& layer = netParameter.layer(i);
1598 for (int i = 0; i < layer.top_size(); ++i)
1599 {
1600 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1601 }
1602 }
1603
telsoa01c577f2c2018-08-31 09:22:23 +01001604 // Finds the output layers the user requested.
telsoa014fcda012018-03-09 14:13:49 +00001605 std::vector<const caffe::LayerParameter*> targetLayers;
1606 for (const std::string& requestedOutputName : m_RequestedOutputs)
1607 {
1608 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1609 if (nodeIt == m_CaffeLayersByTopName.end())
1610 {
telsoa01c577f2c2018-08-31 09:22:23 +01001611 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001612 fmt::format("Couldn't find requested output layer '{}' in graph {}",
1613 requestedOutputName,
1614 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001615 }
1616 targetLayers.push_back(nodeIt->second);
1617 }
1618
telsoa01c577f2c2018-08-31 09:22:23 +01001619 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001620 std::vector<const caffe::LayerParameter*> sortedNodes;
1621 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1622 targetLayers,
1623 [this](const caffe::LayerParameter* node)
1624 {
1625 return GetInputs(*node);
1626 },
1627 sortedNodes))
1628 {
telsoa01c577f2c2018-08-31 09:22:23 +01001629 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001630 fmt::format("Cycle detected in graph. #nodes: {} {}",
1631 sortedNodes.size(),
1632 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001633 }
1634
telsoa01c577f2c2018-08-31 09:22:23 +01001635 // 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 +00001636 for (const caffe::LayerParameter* current : sortedNodes)
1637 {
1638 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1639 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1640 {
telsoa01c577f2c2018-08-31 09:22:23 +01001641 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001642 fmt::format("Unsupported layer type: '{}' for layer {} {}",
1643 current->type(),
1644 current->name(),
1645 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001646 }
1647 auto func = it->second;
1648 (this->*func)(*current);
1649 }
1650
telsoa01c577f2c2018-08-31 09:22:23 +01001651 // Adds ArmNN output layers connected to each requested output.
telsoa014fcda012018-03-09 14:13:49 +00001652 for (const std::string& requestedOutput : m_RequestedOutputs)
1653 {
1654 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1655
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001656 const armnn::LayerBindingId outputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa014fcda012018-03-09 14:13:49 +00001657 m_NetworkOutputsBindingInfo.size());
1658 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1659 outputSlot.Connect(outputLayer->GetInputSlot(0));
1660
1661 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1662 }
1663}
1664
telsoa01c577f2c2018-08-31 09:22:23 +01001665INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
telsoa014fcda012018-03-09 14:13:49 +00001666 const std::map<std::string, armnn::TensorShape>& inputShapes,
1667 const std::vector<std::string>& requestedOutputs)
1668{
1669 FILE* fd = fopen(graphFile, "r");
1670
1671 if (fd == nullptr)
1672 {
telsoa01c577f2c2018-08-31 09:22:23 +01001673 throw FileNotFoundException(
James Ward58dec6b2020-09-11 17:32:44 +01001674 fmt::format("Failed to open graph file: {} {}",
1675 graphFile,
1676 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001677 }
1678
telsoa01c577f2c2018-08-31 09:22:23 +01001679 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001680 NetParameter netParam;
1681 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1682 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1683 delete input;
1684 fclose(fd);
1685
1686 if (!success)
1687 {
telsoa01c577f2c2018-08-31 09:22:23 +01001688 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001689 fmt::format("Failed to parse graph file: {} {}",
1690 graphFile,
1691 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001692 }
1693
1694 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1695}
1696
telsoa01c577f2c2018-08-31 09:22:23 +01001697INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
telsoa014fcda012018-03-09 14:13:49 +00001698 const std::map<std::string, armnn::TensorShape>& inputShapes,
1699 const std::vector<std::string>& requestedOutputs)
1700{
telsoa01c577f2c2018-08-31 09:22:23 +01001701 // Parses the string into a message.
telsoa014fcda012018-03-09 14:13:49 +00001702 NetParameter netParam;
1703 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1704
1705 if (!success)
1706 {
telsoa01c577f2c2018-08-31 09:22:23 +01001707 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001708 fmt::format("Failed to parse graph string {}",
1709 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001710 }
1711
1712 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1713}
1714
1715INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1716 const std::map<std::string, armnn::TensorShape>& inputShapes,
1717 const std::vector<std::string>& requestedOutputs)
1718{
1719 FILE* fd = fopen(graphFile, "rb");
1720
1721 if (fd == nullptr)
1722 {
telsoa01c577f2c2018-08-31 09:22:23 +01001723 throw FileNotFoundException(
James Ward58dec6b2020-09-11 17:32:44 +01001724 fmt::format("Failed to open graph file at: {} {}",
1725 graphFile,
1726 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001727 }
1728
telsoa01c577f2c2018-08-31 09:22:23 +01001729 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001730 NetParameter netParam;
1731
1732 FileInputStream inStream(fileno(fd));
1733 CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +01001734 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa014fcda012018-03-09 14:13:49 +00001735 bool success = netParam.ParseFromCodedStream(&codedStream);
1736 fclose(fd);
1737
1738 if (!success)
1739 {
telsoa01c577f2c2018-08-31 09:22:23 +01001740 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001741 fmt::format("Failed to parse protobuf file: {} {}",
1742 graphFile,
1743 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001744 }
1745
1746 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1747}
1748
telsoa01c577f2c2018-08-31 09:22:23 +01001749// Note: can move to CaffeParser when/if we optimise the text/string format
1750// to load on a layer by layer basis
1751INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
telsoa014fcda012018-03-09 14:13:49 +00001752 const std::map<std::string, armnn::TensorShape>& inputShapes,
1753 const std::vector<std::string>& requestedOutputs)
1754{
1755 m_NetworkInputsBindingInfo.clear();
1756 m_NetworkOutputsBindingInfo.clear();
1757
1758 m_Network = INetwork::Create();
1759
1760 m_InputShapes = inputShapes;
1761 if (requestedOutputs.size() == 0)
1762 {
1763 throw ParseException("requestedOutputs must have at least one entry");
1764 }
1765 m_RequestedOutputs = requestedOutputs;
1766
1767 try
1768 {
1769 LoadNetParam(netParam);
1770 }
1771 catch (const ParseException& e)
1772 {
1773 Cleanup();
1774 throw e;
1775 }
1776
1777 Cleanup();
1778
1779 return move(m_Network);
1780}
1781
telsoa01c577f2c2018-08-31 09:22:23 +01001782void CaffeParserBase::Cleanup() {
telsoa014fcda012018-03-09 14:13:49 +00001783 // cleanup, in case we reuse this parser
telsoa014fcda012018-03-09 14:13:49 +00001784 m_InputShapes.clear();
1785 m_RequestedOutputs.clear();
1786 m_ArmnnOutputSlotForCaffeTop.clear();
telsoa01c577f2c2018-08-31 09:22:23 +01001787 // NOTE: when we get the text/string format
1788 // optimised for memory then this data structure can
1789 // also move to the CaffeParser class
1790 m_CaffeLayersByTopName.clear();
telsoa014fcda012018-03-09 14:13:49 +00001791}
1792
1793}