blob: 51b58ccea3fa7dbc128c31f89ec8c523ad96fae9 [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>(
Keith Mok7dc18202020-12-20 13:45:51 -0800482 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - (desc.m_DilationX * (kernelH - 1) + 1)) /
telsoa01c577f2c2018-08-31 09:22:23 +0100483 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>(
Keith Mok7dc18202020-12-20 13:45:51 -0800487 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - (desc.m_DilationY * (kernelW - 1) + 1)) /
telsoa01c577f2c2018-08-31 09:22:23 +0100488 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;
Keith Mok7dc18202020-12-20 13:45:51 -0800612 desc.m_DilationX = convDesc.m_DilationX;
613 desc.m_DilationY = convDesc.m_DilationY;
telsoa01c577f2c2018-08-31 09:22:23 +0100614 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
telsoa014fcda012018-03-09 14:13:49 +0000615
telsoa01c577f2c2018-08-31 09:22:23 +0100616 unsigned int numFilters = convParam.num_output();
617
618 BlobShape outputShape;
619 outputShape.add_dim(0);
620 outputShape.set_dim(0, inputShape.dim(0));
621 outputShape.add_dim(1);
622 outputShape.set_dim(1, numFilters);
623 outputShape.add_dim(2);
624 outputShape.set_dim(
625 2, (static_cast<int>(
Keith Mok7dc18202020-12-20 13:45:51 -0800626 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - (desc.m_DilationX * (kernelH - 1) + 1)) /
telsoa01c577f2c2018-08-31 09:22:23 +0100627 static_cast<float>(desc.m_StrideY)) + 1));
628 outputShape.add_dim(3);
629 outputShape.set_dim(
630 3, (static_cast<int>(
Keith Mok7dc18202020-12-20 13:45:51 -0800631 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - (desc.m_DilationY * (kernelW - 1) + 1)) /
telsoa01c577f2c2018-08-31 09:22:23 +0100632 static_cast<float>(desc.m_StrideX)) + 1));
633
634 // Load the weight data
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100635 size_t allWeightsSize = armnn::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
telsoa01c577f2c2018-08-31 09:22:23 +0100636 vector<float> weightData(allWeightsSize);
637
638 GetDataFromBlob(layerParam, weightData, 0);
639
640 // depth multiplier will be 1 for the depthwise convolution
641 const unsigned int weightDimSizes[4] = {
642 static_cast<unsigned int>(1), // depth multiplier
643 static_cast<unsigned int>(inputShape.dim(1)), // #channels
644 kernelH,
645 kernelW};
646
647 armnn::IConnectableLayer* returnLayer = nullptr;
648 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100649 Optional<ConstTensor> optionalBiases;
650 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100651 if (desc.m_BiasEnabled)
652 {
653 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100654
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100655 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100656 GetDataFromBlob(layerParam, biasData, 1);
657
658 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
659 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
660
661 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100662 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100663 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100664 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc,
665 weights,
666 optionalBiases,
667 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000668
surmeh013537c2c2018-05-18 16:31:43 +0100669 if (!returnLayer)
670 {
telsoa01c577f2c2018-08-31 09:22:23 +0100671 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100672 fmt::format("Failed to create depthwise convolution layer. "
673 "Layer={} #filters={} {}",
674 layerParam.name(),
675 numFilters,
676 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100677 }
678 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
679 inputConnection.Connect(returnLayer->GetInputSlot(0));
680 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
681 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
682}
683
684void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
685{
686 // Ignored Caffe Parameters
telsoa01c577f2c2018-08-31 09:22:23 +0100687 // * 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
Keith Mok7dc18202020-12-20 13:45:51 -0800721 unsigned int dilationH = convParam.dilation_size() > 0 ? convParam.dilation(0) : 1;
722 unsigned int dilationW = convParam.dilation_size() > 1 ? convParam.dilation(1) :
723 convParam.dilation_size() > 0 ? convParam.dilation(0) : 1;
724
telsoa01c577f2c2018-08-31 09:22:23 +0100725 Convolution2dDescriptor convolution2dDescriptor;
726 convolution2dDescriptor.m_PadLeft = padW;
727 convolution2dDescriptor.m_PadRight = padW;
728 convolution2dDescriptor.m_PadTop = padH;
729 convolution2dDescriptor.m_PadBottom = padH;
730 convolution2dDescriptor.m_StrideX = strideW;
731 convolution2dDescriptor.m_StrideY = strideH;
Keith Mok7dc18202020-12-20 13:45:51 -0800732 convolution2dDescriptor.m_DilationX = dilationW;
733 convolution2dDescriptor.m_DilationY = dilationH;
telsoa01c577f2c2018-08-31 09:22:23 +0100734 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
735
736 if (numGroups > numFilters)
737 {
738 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100739 fmt::format("Error parsing Convolution: {}. "
740 "The 'group'={} parameter cannot be larger than the "
741 "number of filters supplied ='{}'. {}",
742 layerParam.name(),
743 numGroups,
744 numFilters,
745 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100746 }
747
748 if (inputShape.dim_size() != 4)
749 {
750 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100751 fmt::format("Convolution input shape is expected to have 4 dimensions. "
752 "{}'s input has only {}. {}",
753 layerParam.name(),
754 inputShape.dim_size(),
755 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100756 }
757
758 if (numGroups > 1)
759 {
760 if (numGroups > inputShape.dim(1))
761 {
762 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100763 fmt::format("Error parsing Convolution: {}. "
764 "The 'group'={} parameter cannot be larger than the "
765 "channel of the input shape={} (in NCHW format). {}",
766 layerParam.name(),
767 numGroups,
768 inputShape.dim(1),
769 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +0100770 }
771 else if (numGroups == inputShape.dim(1))
772 {
773 // we use a depthwise convolution here, because the number of groups equals to the
774 // input channels
775 AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
776 return;
777 }
778 else
779 {
780 // we split the input by channels into channels/groups separate convolutions
Jim Flynne242f2d2019-05-22 14:24:13 +0100781 // and concatenate the results afterwards
telsoa01c577f2c2018-08-31 09:22:23 +0100782 AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
783 return;
784 }
785 }
786
787 // NOTE: at this point we only need to handle #group=1 case, all other cases should be
788 // handled by the AddConvLayer* helpers
789
790 // Populate convolution output tensor descriptor dimensions
791 BlobShape outputShape;
792 outputShape.add_dim(0);
793 outputShape.set_dim(0, inputShape.dim(0));
794 outputShape.add_dim(1);
795 outputShape.set_dim(1, numFilters);
796 outputShape.add_dim(2);
797 outputShape.set_dim(
798 2, (static_cast<int>(
Keith Mok7dc18202020-12-20 13:45:51 -0800799 static_cast<float>(inputShape.dim(2) + 2 * padH - (dilationH * (kernelH - 1) + 1)) /
telsoa01c577f2c2018-08-31 09:22:23 +0100800 static_cast<float>(strideH)) + 1));
801 outputShape.add_dim(3);
802 outputShape.set_dim(
803 3, (static_cast<int>(
Keith Mok7dc18202020-12-20 13:45:51 -0800804 static_cast<float>(inputShape.dim(3) + 2 * padW - (dilationW * (kernelW - 1) + 1)) /
telsoa01c577f2c2018-08-31 09:22:23 +0100805 static_cast<float>(strideW)) + 1));
806
807 // Load the weight data for ALL groups
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100808 vector<float> weightData(armnn::numeric_cast<size_t>(inputShape.dim(1) *
telsoa01c577f2c2018-08-31 09:22:23 +0100809 outputShape.dim(1) *
810 kernelH *
811 kernelW));
812 GetDataFromBlob(layerParam, weightData, 0);
813
814 const unsigned int weightDimSizes[4] = {
815 static_cast<unsigned int>(outputShape.dim(1)), // output channels
816 static_cast<unsigned int>(inputShape.dim(1)), // input channels
817 kernelH,
818 kernelW};
819
820 armnn::IConnectableLayer* returnLayer = nullptr;
821
822 // Pull out the weights for this group from that loaded from the model file earlier
823 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100824 Optional<ConstTensor> optionalBiases;
825 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100826 if (convolution2dDescriptor.m_BiasEnabled)
827 {
828 TensorInfo biasInfo;
telsoa01c577f2c2018-08-31 09:22:23 +0100829
Matthew Sloyan589e3e82020-09-11 16:17:48 +0100830 biasData.resize(armnn::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
telsoa01c577f2c2018-08-31 09:22:23 +0100831 GetDataFromBlob(layerParam, biasData, 1);
832
833 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
834 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
835
836 // Pull out the biases for this group from that loaded from the model file earlier
837 ConstTensor biases(biasInfo, biasData.data());
Matteo Martincighfc598e12019-05-14 10:36:13 +0100838 optionalBiases = Optional<ConstTensor>(biases);
telsoa01c577f2c2018-08-31 09:22:23 +0100839 }
Matteo Martincighfc598e12019-05-14 10:36:13 +0100840 returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
841 weights,
842 optionalBiases,
843 layerParam.name().c_str());
telsoa01c577f2c2018-08-31 09:22:23 +0100844
845 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
846 inputConnection.Connect(returnLayer->GetInputSlot(0));
847 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
848
849 if (!returnLayer)
850 {
851 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100852 fmt::format("Failed to create Convolution layer. "
853 "Layer={} #groups={} #filters={} {}",
854 layerParam.name(),
855 numGroups,
856 numFilters,
857 CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100858 }
859
telsoa014fcda012018-03-09 14:13:49 +0000860 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
861}
862
telsoa01c577f2c2018-08-31 09:22:23 +0100863void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000864{
telsoa01c577f2c2018-08-31 09:22:23 +0100865 // Ignored Caffe Parameters
866 // Stochastic Pooling
867 // Engine
868
telsoa014fcda012018-03-09 14:13:49 +0000869 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000870 PoolingParameter param = layerParam.pooling_param();
telsoa014fcda012018-03-09 14:13:49 +0000871 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
872
telsoa01c577f2c2018-08-31 09:22:23 +0100873 const auto notFound = std::numeric_limits<unsigned int>::max();
874
875 unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
876 kernel_h, kernel_size, unsigned int, notFound);
877 unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
878 kernel_w, kernel_size, unsigned int, notFound);
879
880 if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000881 {
882 kernel_h = inputInfo.GetShape()[2];
883 kernel_w = inputInfo.GetShape()[3];
884 }
telsoa01c577f2c2018-08-31 09:22:23 +0100885
telsoa01c577f2c2018-08-31 09:22:23 +0100886 unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
887 stride_h, stride, unsigned int, notFound);
888 unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
889 stride_h, stride, unsigned int, notFound);
890
891 if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000892 {
telsoa01c577f2c2018-08-31 09:22:23 +0100893 stride_h = 1;
894 stride_w = 1;
telsoa014fcda012018-03-09 14:13:49 +0000895 }
896
telsoa01c577f2c2018-08-31 09:22:23 +0100897 unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
898 pad_h, pad, unsigned int, 0u);
899 unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
900 pad_w, pad, unsigned int, 0u);
telsoa014fcda012018-03-09 14:13:49 +0000901
telsoa014fcda012018-03-09 14:13:49 +0000902 // Populate Weight and Bias Filter Descriptor
903 Pooling2dDescriptor pooling2dDescriptor;
904 if (param.has_pool())
905 {
906 PoolingParameter_PoolMethod p = param.pool();
907 switch (p)
908 {
909 case PoolingParameter_PoolMethod_MAX:
910 {
911 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
912 break;
913 }
914 case PoolingParameter_PoolMethod_AVE:
915 {
916 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
917 break;
918 }
919 case PoolingParameter_PoolMethod_STOCHASTIC:
920 {
telsoa01c577f2c2018-08-31 09:22:23 +0100921 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100922 fmt::format("Pooling Layer: Stochastic Pooling Not Supported. Layer={} {}",
923 layerParam.name(),
924 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000925 }
926 default:
927 {
telsoa01c577f2c2018-08-31 09:22:23 +0100928 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100929 fmt::format("Pooling Layer: unknown pooling method: {} for layer: {} {}",
930 p,
931 layerParam.name(),
932 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000933 }
934 }
935 }
936 else
937 {
telsoa01c577f2c2018-08-31 09:22:23 +0100938 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +0100939 fmt::format("No Pooling Method Defined for {} {}",
940 layerParam.name(),
941 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000942 }
943
944 pooling2dDescriptor.m_PadLeft = pad_w;
945 pooling2dDescriptor.m_PadRight = pad_w;
946 pooling2dDescriptor.m_PadTop = pad_h;
947 pooling2dDescriptor.m_PadBottom = pad_h;
948 pooling2dDescriptor.m_StrideX = stride_w;
949 pooling2dDescriptor.m_StrideY = stride_h;
950 pooling2dDescriptor.m_PoolWidth = kernel_w;
951 pooling2dDescriptor.m_PoolHeight = kernel_h;
952
953 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
954 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
955
956 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
957 layerParam.name().c_str());
958
telsoa014fcda012018-03-09 14:13:49 +0000959 TensorInfo outputInfo(
960 { inputInfo.GetShape()[0],
961 inputInfo.GetShape()[1],
962 static_cast<unsigned int>(ceil(
963 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
Matthew Sloyan24ac8592020-09-23 16:57:23 +0100964 armnn::numeric_cast<float>(stride_h))) + 1,
telsoa014fcda012018-03-09 14:13:49 +0000965 static_cast<unsigned int>(ceil(
966 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
Matthew Sloyan24ac8592020-09-23 16:57:23 +0100967 armnn::numeric_cast<float>(stride_w))) + 1 },
telsoa014fcda012018-03-09 14:13:49 +0000968 DataType::Float32);
969
970 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
971 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
972 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
973}
974
telsoa01c577f2c2018-08-31 09:22:23 +0100975void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000976{
977 ValidateNumInputsOutputs(layerParam, 1, 1);
978
979 const string& name = layerParam.name();
980 const ReLUParameter& param = layerParam.relu_param();
981
982 ActivationDescriptor activationDescriptor;
983 const float negativeSlope = param.negative_slope();
984 if (negativeSlope == 0.0f)
985 {
986 activationDescriptor.m_Function = ActivationFunction::ReLu;
987 }
988 else
989 {
990 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
991 activationDescriptor.m_A = negativeSlope;
992 }
993
994 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
995 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
996 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
997 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
998 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
999}
1000
telsoa01c577f2c2018-08-31 09:22:23 +01001001void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001002{
1003 ValidateNumInputsOutputs(layerParam, 1, 1);
1004
1005 LRNParameter param = layerParam.lrn_param();
1006
1007 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1008
telsoa01c577f2c2018-08-31 09:22:23 +01001009 // Ignored BATCH NORMALIZATION Caffe Parameters.
1010 // Ignored MVN Caffe Parameters.
1011 // Ignored LRN Caffe Parameters.
telsoa014fcda012018-03-09 14:13:49 +00001012 // Engine
1013
1014 NormalizationDescriptor normalizationDescriptor;
1015 if (param.has_norm_region())
1016 {
1017 LRNParameter_NormRegion n = param.norm_region();
1018 switch (n)
1019 {
1020 case LRNParameter_NormRegion_ACROSS_CHANNELS:
1021 {
1022 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1023 break;
1024 }
1025 case LRNParameter_NormRegion_WITHIN_CHANNEL:
1026 {
1027 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1028 break;
1029 }
1030 default:
telsoa01c577f2c2018-08-31 09:22:23 +01001031 {
1032 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001033 fmt::format("Unknown region {} for LRN layer {} {}",
1034 n,
1035 layerParam.name(),
1036 CHECK_LOCATION().AsString()));
telsoa01c577f2c2018-08-31 09:22:23 +01001037 }
telsoa014fcda012018-03-09 14:13:49 +00001038 }
1039 }
1040 else
1041 {
telsoa01c577f2c2018-08-31 09:22:23 +01001042 // Caffe defaults to normalization across channels.
telsoa014fcda012018-03-09 14:13:49 +00001043 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1044 }
1045
1046 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1047 if (param.has_local_size())
1048 {
1049 normalizationDescriptor.m_NormSize = param.local_size();
1050 }
1051 else
1052 {
telsoa01c577f2c2018-08-31 09:22:23 +01001053 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001054 fmt::format("local_size not defined for LRN layer {} {}",
1055 layerParam.name(),
1056 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001057 }
1058
1059 if (param.has_alpha())
1060 {
1061 normalizationDescriptor.m_Alpha = param.alpha();
Matthew Sloyan24ac8592020-09-23 16:57:23 +01001062 normalizationDescriptor.m_Alpha /= armnn::numeric_cast<float>(param.local_size());
telsoa014fcda012018-03-09 14:13:49 +00001063 }
1064 else
1065 {
telsoa01c577f2c2018-08-31 09:22:23 +01001066 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001067 fmt::format("Alpha not defined for LRN layer {} {}",
1068 layerParam.name(),
1069 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001070 }
1071 if (param.has_beta())
1072 {
1073 normalizationDescriptor.m_Beta = param.beta();
1074 }
1075 else
1076 {
telsoa01c577f2c2018-08-31 09:22:23 +01001077 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001078 fmt::format("Beta not defined for LRN layer {} {}",
1079 layerParam.name(),
1080 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001081 }
telsoa01c577f2c2018-08-31 09:22:23 +01001082
telsoa014fcda012018-03-09 14:13:49 +00001083 if (param.has_k())
1084 {
1085 normalizationDescriptor.m_K = param.k();
1086 }
1087 else
telsoa01c577f2c2018-08-31 09:22:23 +01001088 {
telsoa014fcda012018-03-09 14:13:49 +00001089 normalizationDescriptor.m_K = 1;
telsoa01c577f2c2018-08-31 09:22:23 +01001090 }
telsoa014fcda012018-03-09 14:13:49 +00001091
1092 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1093 layerParam.name().c_str());
1094 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1095 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1096
1097 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1098}
1099
telsoa01c577f2c2018-08-31 09:22:23 +01001100void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001101{
1102 InnerProductParameter param = layerParam.inner_product_param();
1103
1104 ValidateNumInputsOutputs(layerParam, 1, 1);
1105
1106 unsigned int outputSize = param.num_output();
1107
telsoa01c577f2c2018-08-31 09:22:23 +01001108 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001109 // Weight Filler
1110 // Bias Filler
1111 // Engine
1112 // Axis
1113
1114 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1115
1116 if (param.has_transpose())
1117 {
telsoa01c577f2c2018-08-31 09:22:23 +01001118 // If true, assumes transposed weights.
telsoa014fcda012018-03-09 14:13:49 +00001119 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1120 }
1121 else
1122 {
telsoa01c577f2c2018-08-31 09:22:23 +01001123 // Caffe defaults to transposed.
telsoa014fcda012018-03-09 14:13:49 +00001124 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1125 }
1126
1127 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1128
1129 TensorInfo weightInfo;
1130 TensorInfo biasInfo;
1131
telsoa01c577f2c2018-08-31 09:22:23 +01001132 // Allows implicit flattening of extra dimensions.
telsoa014fcda012018-03-09 14:13:49 +00001133 unsigned int inputSize = inputInfo.GetShape()[1];
1134 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1135 {
1136 inputSize *= inputInfo.GetShape()[i];
1137 }
1138
telsoa01c577f2c2018-08-31 09:22:23 +01001139 const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
telsoa014fcda012018-03-09 14:13:49 +00001140 const unsigned int swTD[2] = { outputSize, inputSize };
telsoa01c577f2c2018-08-31 09:22:23 +01001141 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001142
1143 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
telsoa01c577f2c2018-08-31 09:22:23 +01001144 // Todo: check whether bias enabled.
telsoa014fcda012018-03-09 14:13:49 +00001145 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1146 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1147 {
1148 // BIAS VALUE
telsoa01c577f2c2018-08-31 09:22:23 +01001149 const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
telsoa014fcda012018-03-09 14:13:49 +00001150
1151 const unsigned int sbTD[1] = { outputSize };
1152
telsoa01c577f2c2018-08-31 09:22:23 +01001153 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001154
Matteo Martincighfc598e12019-05-14 10:36:13 +01001155 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1156 weights,
1157 Optional<ConstTensor>(biases),
1158 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001159 }
1160 else
1161 {
Matteo Martincighfc598e12019-05-14 10:36:13 +01001162 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor,
1163 weights,
1164 EmptyOptional(),
1165 layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001166 }
1167
1168 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1169 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1170 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1171 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1172}
1173
telsoa01c577f2c2018-08-31 09:22:23 +01001174void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001175{
1176 ValidateNumInputsOutputs(layerParam, 1, 1);
1177
1178 SoftmaxParameter param = layerParam.softmax_param();
1179
1180 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1181
telsoa01c577f2c2018-08-31 09:22:23 +01001182 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001183 // axis
1184 // Engine
1185
1186 armnn::SoftmaxDescriptor softmaxDescriptor;
Teresa Charlin4320c922020-08-12 16:04:41 +01001187 softmaxDescriptor.m_Axis = 1;
telsoa014fcda012018-03-09 14:13:49 +00001188 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1189 softmaxDescriptor,
1190 layerParam.name().c_str());
1191 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1192 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1193 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1194}
1195
telsoa01c577f2c2018-08-31 09:22:23 +01001196void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001197{
1198 ValidateNumInputsOutputs(layerParam, 2, 1);
1199
1200 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1201
telsoa01c577f2c2018-08-31 09:22:23 +01001202 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001203 // coeff
1204
telsoa01c577f2c2018-08-31 09:22:23 +01001205 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
telsoa014fcda012018-03-09 14:13:49 +00001206
1207 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1208 {
1209 operation = layerParam.eltwise_param().operation();
1210 }
1211
1212 armnn::IConnectableLayer* newLayer = nullptr;
1213 switch (operation)
1214 {
1215 case EltwiseParameter_EltwiseOp_SUM:
1216 {
1217 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1218 break;
1219 }
1220 case EltwiseParameter_EltwiseOp_PROD:
1221 {
1222 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1223 break;
1224 }
1225 default:
1226 {
telsoa01c577f2c2018-08-31 09:22:23 +01001227 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001228 fmt::format("Unsupported operation {} in Eltwise layer {} {}",
1229 operation,
1230 layerParam.name(),
1231 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001232 }
1233 }
1234
1235 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1236 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1237 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1238 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1239}
1240
telsoa01c577f2c2018-08-31 09:22:23 +01001241void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001242{
1243 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
telsoa01c577f2c2018-08-31 09:22:23 +01001244 // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
telsoa014fcda012018-03-09 14:13:49 +00001245 unsigned int concatDim = 1;
1246 unsigned int numOfDims = 4;
1247
telsoa01c577f2c2018-08-31 09:22:23 +01001248 // we only consider 4-D tensor here
1249 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
telsoa014fcda012018-03-09 14:13:49 +00001250 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1251
1252 unsigned int mergeDim = 0;
1253 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1254 {
1255 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001256 layerParam.bottom(armnn::numeric_cast<int>(viewIndex))).GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001257 // Checks whether the dimensions of the input tensors are actually 4.
telsoa014fcda012018-03-09 14:13:49 +00001258 if (inputInfo.GetNumDimensions()!=4)
1259 {
telsoa01c577f2c2018-08-31 09:22:23 +01001260 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001261 fmt::format("The number of dimensions for input tensors of "
1262 "the concatenation op should be 4. Inputs of {} has "
1263 "{} dimensions. {}",
1264 layerParam.name(),
1265 inputInfo.GetNumDimensions(),
1266 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001267 }
1268
1269 mergeDimSizes[0] = inputInfo.GetShape()[0];
1270 mergeDimSizes[1] = inputInfo.GetShape()[1];
1271 mergeDimSizes[2] = inputInfo.GetShape()[2];
1272 mergeDimSizes[3] = inputInfo.GetShape()[3];
1273
1274 for (unsigned int j = 0; j < concatDim; ++j)
1275 {
1276 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1277 }
1278
1279 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1280 mergeDim += mergeDimSizes[concatDim];
1281
1282 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1283 {
1284 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1285 }
1286 }
1287 mergeDimSizes[concatDim] = mergeDim;
1288
Jim Flynn906f9462019-05-10 13:55:21 +01001289 armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001290 for (unsigned int i = 0; i < numInputs; ++i)
1291 {
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001292 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(armnn::numeric_cast<int>(i)));
telsoa014fcda012018-03-09 14:13:49 +00001293 outputSlot.Connect(concatlayer->GetInputSlot(i));
1294 }
1295
1296 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1297 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1298}
1299
telsoa01c577f2c2018-08-31 09:22:23 +01001300void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001301{
1302 ValidateNumInputsOutputs(layerParam, 1, 1);
1303
1304 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1305
1306 string name = layerParam.name();
1307
1308 BatchNormParameter param = layerParam.batch_norm_param();
1309 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1310 // when the network is in the testing phase).
1311 if (param.has_use_global_stats())
1312 {
1313 if (!param.use_global_stats())
1314 {
telsoa01c577f2c2018-08-31 09:22:23 +01001315 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001316 fmt::format("Error parsing Batch Norm layer '{}': "
1317 "Parameter 'use_global_stats' is set to false, which is "
1318 "unsupported (value used for training). {}",
1319 name,
1320 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001321 }
1322 }
1323
1324 BatchNormalizationDescriptor desc;
1325 desc.m_Eps = param.eps();
1326
1327 unsigned int channels = inputInfo.GetShape()[1];
1328 unsigned int shape[] = {channels};
1329
1330 vector<float> meanData(channels);
1331 GetDataFromBlob(layerParam, meanData, 0);
1332
1333 vector<float> varianceData(channels);
1334 GetDataFromBlob(layerParam, varianceData, 1);
1335
telsoa01c577f2c2018-08-31 09:22:23 +01001336 // Reads moving average factor and applies scaling (if required).
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001337 const BlobProto& blob = layerParam.blobs(armnn::numeric_cast<int>(2));
1338 const float movingAverageFactor = blob.data(armnn::numeric_cast<int>(0));
surmeh013537c2c2018-05-18 16:31:43 +01001339 if(movingAverageFactor != 0.0f)
1340 {
1341 const float scaleFactor = 1.0f / movingAverageFactor;
1342 auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1343
1344 std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1345 std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1346 }
1347
telsoa01c577f2c2018-08-31 09:22:23 +01001348 // Identifies scale operation.
telsoa014fcda012018-03-09 14:13:49 +00001349 vector<float> betaData(channels, 0.0f);
1350 vector<float> gammaData(channels, 1.0f);
1351
1352 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1353 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1354 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1355 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1356
1357 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1358 mean, variance, beta, gamma, name.c_str());
1359 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1360 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1361 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1362}
1363
telsoa01c577f2c2018-08-31 09:22:23 +01001364void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001365{
telsoa01c577f2c2018-08-31 09:22:23 +01001366 // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
telsoa014fcda012018-03-09 14:13:49 +00001367 ValidateNumInputsOutputs(layerParam, 1, 1);
1368
1369 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1370
1371 string name = layerParam.name();
1372
1373 ScaleParameter param = layerParam.scale_param();
1374 if (param.axis() != 1)
1375 {
1376 // Would have to use something other than BatchNormalizationLayer in this case
telsoa01c577f2c2018-08-31 09:22:23 +01001377 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001378 fmt::format("Loading Scale Layer: Only axis 1 is supported currently. "
1379 "Layer={} Axis={} {}",
1380 layerParam.name(),
1381 param.axis(),
1382 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001383 }
1384
1385 unsigned int channels = inputInfo.GetShape()[1];
1386 unsigned int shape[] = {channels};
1387
1388 BatchNormalizationDescriptor desc;
telsoa01c577f2c2018-08-31 09:22:23 +01001389 desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
telsoa014fcda012018-03-09 14:13:49 +00001390 vector<float> meanData(channels, 0.0f);
1391 vector<float> varianceData(channels, 1.0f);
1392 vector<float> betaData(channels, 0.0f);
1393 vector<float> gammaData(channels);
1394
1395 GetDataFromBlob(layerParam, gammaData, 0);
1396
1397 if(param.has_bias_term())
1398 {
1399 GetDataFromBlob(layerParam, betaData, 1);
1400 }
1401
1402 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1403 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1404 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1405 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1406
1407 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1408 mean, variance, beta, gamma, name.c_str());
1409 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1410 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1411 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1412}
1413
telsoa01c577f2c2018-08-31 09:22:23 +01001414void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001415{
telsoa01c577f2c2018-08-31 09:22:23 +01001416 // Used in caffe to duplicate memory - not necessary in armnn.
telsoa014fcda012018-03-09 14:13:49 +00001417 if (layerParam.bottom_size() != 1)
1418 {
telsoa01c577f2c2018-08-31 09:22:23 +01001419 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001420 fmt::format("Split layer '{}' should have exactly 1 bottom. "
1421 "#bottoms={} {}",
1422 layerParam.name(),
1423 layerParam.bottom_size(),
1424 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001425 }
1426 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1427 for (int i = 0; i < layerParam.top_size(); i++)
1428 {
1429 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1430 }
1431}
1432
telsoa01c577f2c2018-08-31 09:22:23 +01001433void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001434{
telsoa01c577f2c2018-08-31 09:22:23 +01001435 // Ignored for inference, so patch the single input to its single output.
telsoa014fcda012018-03-09 14:13:49 +00001436 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1437 {
telsoa01c577f2c2018-08-31 09:22:23 +01001438 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001439 fmt::format("Dropout layer '{}' should have exactly 1 bottom and 1 top. "
1440 "#bottoms={} #tops={} {}",
1441 layerParam.name(),
1442 layerParam.bottom_size(),
1443 layerParam.top_size(),
1444 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001445 }
1446 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1447}
1448
telsoa01c577f2c2018-08-31 09:22:23 +01001449void CaffeParserBase::TrackInputBinding(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_NetworkInputsBindingInfo);
1454}
1455
telsoa01c577f2c2018-08-31 09:22:23 +01001456void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001457 armnn::LayerBindingId id,
1458 const armnn::TensorInfo& tensorInfo)
1459{
1460 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1461}
1462
telsoa01c577f2c2018-08-31 09:22:23 +01001463void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001464 armnn::LayerBindingId id,
1465 const armnn::TensorInfo& tensorInfo,
1466 const char* bindingPointDesc,
1467 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1468{
1469 const std::string layerName = layer->GetName();
1470 auto it = nameToBindingInfo.find(layerName);
1471 if (it == nameToBindingInfo.end())
1472 {
1473 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1474 }
1475 else
1476 {
telsoa01c577f2c2018-08-31 09:22:23 +01001477 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001478 fmt::format("Id {} used by more than one {} layer {}",
1479 id,
1480 bindingPointDesc,
1481 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001482 }
1483}
1484
telsoa01c577f2c2018-08-31 09:22:23 +01001485armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
telsoa014fcda012018-03-09 14:13:49 +00001486{
1487 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1488 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1489 {
1490 return *it->second;
1491 }
1492 else
1493 {
telsoa01c577f2c2018-08-31 09:22:23 +01001494 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001495 fmt::format("Could not find armnn output slot for Caffe top '{}' {}",
1496 caffeTopName,
1497 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001498 }
1499}
1500
telsoa01c577f2c2018-08-31 09:22:23 +01001501void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
1502 const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
telsoa014fcda012018-03-09 14:13:49 +00001503{
1504 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1505 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1506 {
1507 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1508 }
1509 else
1510 {
telsoa01c577f2c2018-08-31 09:22:23 +01001511 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001512 fmt::format("Attempting to add duplicate entry for Caffe top '{}' {}",
1513 caffeTopName,
1514 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001515 }
1516}
1517
telsoa01c577f2c2018-08-31 09:22:23 +01001518// Note: can move to CaffeParser when/if we optimise the text/string format
1519// to load on a layer by layer basis
1520void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001521{
telsoa01c577f2c2018-08-31 09:22:23 +01001522 // Finds layers with the same top.
telsoa014fcda012018-03-09 14:13:49 +00001523 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1524 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1525 {
1526 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
telsoa01c577f2c2018-08-31 09:22:23 +01001527 std::string name = layer.name();
telsoa014fcda012018-03-09 14:13:49 +00001528 for (int i = 0; i < layer.top_size(); ++i)
1529 {
1530 layersByTop[layer.top(i)].push_back(&layer);
1531 }
1532 }
1533
telsoa01c577f2c2018-08-31 09:22:23 +01001534 // 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 +00001535 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1536 for (auto layersWithSameTopIt : layersByTop)
1537 {
1538 const std::string& top = layersWithSameTopIt.first;
1539 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1540
telsoa01c577f2c2018-08-31 09:22:23 +01001541 // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
telsoa014fcda012018-03-09 14:13:49 +00001542 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1543 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1544 {
1545 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1546 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1547 if (layer1.top_size() != 1)
1548 {
telsoa01c577f2c2018-08-31 09:22:23 +01001549 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001550 fmt::format("Node '{}' is an in-place layer but doesn't have exactly one "
1551 "top. It has {} instead. {}",
1552 layer1.name(),
1553 layer1.top_size(),
1554 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001555 }
1556 std::string newTop = layer1.name() + "_top";
1557 layer1.set_top(0, newTop);
1558 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1559 {
telsoa01c577f2c2018-08-31 09:22:23 +01001560 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001561 fmt::format("Node '{}' is an in-place layer but "
1562 "doesn't have exactly one bottom, or it doesn't match its top. "
1563 "#bottoms={}, first bottom is {}, top is {} {}",
1564 layer2.name(),
1565 layer2.bottom(0),
1566 top,
1567 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001568 }
1569 layer2.set_bottom(0, newTop);
1570 }
1571 }
1572}
1573
telsoa01c577f2c2018-08-31 09:22:23 +01001574// Note: can move to CaffeParser when/if we optimise the text/string format
1575// to load on a layer by layer basis
1576void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001577{
telsoa01c577f2c2018-08-31 09:22:23 +01001578 // Caffe models sometimes have an implicit input layer.
1579 // In that case, add an explicit one.
telsoa014fcda012018-03-09 14:13:49 +00001580 if (netParameter.input_size() > 0)
1581 {
1582 LayerParameter* newLayer = netParameter.add_layer();
1583
1584 newLayer->set_type("Input");
1585 newLayer->set_name(netParameter.input(0));
1586 newLayer->add_top(netParameter.input(0));
1587
1588 InputParameter* inputParam = newLayer->mutable_input_param();
1589 BlobShape* shape = inputParam->add_shape();
1590
1591 int dim_size = netParameter.input_dim_size();
1592 for (int i = 0; i < dim_size; ++i)
1593 {
1594 shape->add_dim(netParameter.input_dim(i));
1595 }
1596 }
1597
telsoa01c577f2c2018-08-31 09:22:23 +01001598 // Replaces in-place layers with regular ones to make the rest of the parsing easier.
telsoa014fcda012018-03-09 14:13:49 +00001599 ResolveInPlaceLayers(netParameter);
1600
telsoa01c577f2c2018-08-31 09:22:23 +01001601 // Creates a lookup of Caffe layers by name.
telsoa014fcda012018-03-09 14:13:49 +00001602 for (int i = 0; i < netParameter.layer_size(); ++i)
1603 {
1604 const caffe::LayerParameter& layer = netParameter.layer(i);
1605 for (int i = 0; i < layer.top_size(); ++i)
1606 {
1607 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1608 }
1609 }
1610
telsoa01c577f2c2018-08-31 09:22:23 +01001611 // Finds the output layers the user requested.
telsoa014fcda012018-03-09 14:13:49 +00001612 std::vector<const caffe::LayerParameter*> targetLayers;
1613 for (const std::string& requestedOutputName : m_RequestedOutputs)
1614 {
1615 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1616 if (nodeIt == m_CaffeLayersByTopName.end())
1617 {
telsoa01c577f2c2018-08-31 09:22:23 +01001618 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001619 fmt::format("Couldn't find requested output layer '{}' in graph {}",
1620 requestedOutputName,
1621 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001622 }
1623 targetLayers.push_back(nodeIt->second);
1624 }
1625
telsoa01c577f2c2018-08-31 09:22:23 +01001626 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001627 std::vector<const caffe::LayerParameter*> sortedNodes;
1628 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1629 targetLayers,
1630 [this](const caffe::LayerParameter* node)
1631 {
1632 return GetInputs(*node);
1633 },
1634 sortedNodes))
1635 {
telsoa01c577f2c2018-08-31 09:22:23 +01001636 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001637 fmt::format("Cycle detected in graph. #nodes: {} {}",
1638 sortedNodes.size(),
1639 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001640 }
1641
telsoa01c577f2c2018-08-31 09:22:23 +01001642 // 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 +00001643 for (const caffe::LayerParameter* current : sortedNodes)
1644 {
1645 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1646 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1647 {
telsoa01c577f2c2018-08-31 09:22:23 +01001648 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001649 fmt::format("Unsupported layer type: '{}' for layer {} {}",
1650 current->type(),
1651 current->name(),
1652 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001653 }
1654 auto func = it->second;
1655 (this->*func)(*current);
1656 }
1657
telsoa01c577f2c2018-08-31 09:22:23 +01001658 // Adds ArmNN output layers connected to each requested output.
telsoa014fcda012018-03-09 14:13:49 +00001659 for (const std::string& requestedOutput : m_RequestedOutputs)
1660 {
1661 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1662
Matthew Sloyan589e3e82020-09-11 16:17:48 +01001663 const armnn::LayerBindingId outputId = armnn::numeric_cast<armnn::LayerBindingId>(
telsoa014fcda012018-03-09 14:13:49 +00001664 m_NetworkOutputsBindingInfo.size());
1665 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1666 outputSlot.Connect(outputLayer->GetInputSlot(0));
1667
1668 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1669 }
1670}
1671
telsoa01c577f2c2018-08-31 09:22:23 +01001672INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
telsoa014fcda012018-03-09 14:13:49 +00001673 const std::map<std::string, armnn::TensorShape>& inputShapes,
1674 const std::vector<std::string>& requestedOutputs)
1675{
1676 FILE* fd = fopen(graphFile, "r");
1677
1678 if (fd == nullptr)
1679 {
telsoa01c577f2c2018-08-31 09:22:23 +01001680 throw FileNotFoundException(
James Ward58dec6b2020-09-11 17:32:44 +01001681 fmt::format("Failed to open graph file: {} {}",
1682 graphFile,
1683 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001684 }
1685
telsoa01c577f2c2018-08-31 09:22:23 +01001686 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001687 NetParameter netParam;
1688 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1689 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1690 delete input;
1691 fclose(fd);
1692
1693 if (!success)
1694 {
telsoa01c577f2c2018-08-31 09:22:23 +01001695 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001696 fmt::format("Failed to parse graph file: {} {}",
1697 graphFile,
1698 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001699 }
1700
1701 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1702}
1703
telsoa01c577f2c2018-08-31 09:22:23 +01001704INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
telsoa014fcda012018-03-09 14:13:49 +00001705 const std::map<std::string, armnn::TensorShape>& inputShapes,
1706 const std::vector<std::string>& requestedOutputs)
1707{
telsoa01c577f2c2018-08-31 09:22:23 +01001708 // Parses the string into a message.
telsoa014fcda012018-03-09 14:13:49 +00001709 NetParameter netParam;
1710 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1711
1712 if (!success)
1713 {
telsoa01c577f2c2018-08-31 09:22:23 +01001714 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001715 fmt::format("Failed to parse graph string {}",
1716 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001717 }
1718
1719 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1720}
1721
1722INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1723 const std::map<std::string, armnn::TensorShape>& inputShapes,
1724 const std::vector<std::string>& requestedOutputs)
1725{
1726 FILE* fd = fopen(graphFile, "rb");
1727
1728 if (fd == nullptr)
1729 {
telsoa01c577f2c2018-08-31 09:22:23 +01001730 throw FileNotFoundException(
James Ward58dec6b2020-09-11 17:32:44 +01001731 fmt::format("Failed to open graph file at: {} {}",
1732 graphFile,
1733 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001734 }
1735
telsoa01c577f2c2018-08-31 09:22:23 +01001736 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001737 NetParameter netParam;
1738
1739 FileInputStream inStream(fileno(fd));
1740 CodedInputStream codedStream(&inStream);
Nikhil Raje5181532020-10-09 14:52:25 +01001741 codedStream.SetTotalBytesLimit(INT_MAX);
telsoa014fcda012018-03-09 14:13:49 +00001742 bool success = netParam.ParseFromCodedStream(&codedStream);
1743 fclose(fd);
1744
1745 if (!success)
1746 {
telsoa01c577f2c2018-08-31 09:22:23 +01001747 throw ParseException(
James Ward58dec6b2020-09-11 17:32:44 +01001748 fmt::format("Failed to parse protobuf file: {} {}",
1749 graphFile,
1750 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001751 }
1752
1753 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1754}
1755
telsoa01c577f2c2018-08-31 09:22:23 +01001756// Note: can move to CaffeParser when/if we optimise the text/string format
1757// to load on a layer by layer basis
1758INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
telsoa014fcda012018-03-09 14:13:49 +00001759 const std::map<std::string, armnn::TensorShape>& inputShapes,
1760 const std::vector<std::string>& requestedOutputs)
1761{
1762 m_NetworkInputsBindingInfo.clear();
1763 m_NetworkOutputsBindingInfo.clear();
1764
1765 m_Network = INetwork::Create();
1766
1767 m_InputShapes = inputShapes;
1768 if (requestedOutputs.size() == 0)
1769 {
1770 throw ParseException("requestedOutputs must have at least one entry");
1771 }
1772 m_RequestedOutputs = requestedOutputs;
1773
1774 try
1775 {
1776 LoadNetParam(netParam);
1777 }
1778 catch (const ParseException& e)
1779 {
1780 Cleanup();
1781 throw e;
1782 }
1783
1784 Cleanup();
1785
1786 return move(m_Network);
1787}
1788
telsoa01c577f2c2018-08-31 09:22:23 +01001789void CaffeParserBase::Cleanup() {
telsoa014fcda012018-03-09 14:13:49 +00001790 // cleanup, in case we reuse this parser
telsoa014fcda012018-03-09 14:13:49 +00001791 m_InputShapes.clear();
1792 m_RequestedOutputs.clear();
1793 m_ArmnnOutputSlotForCaffeTop.clear();
telsoa01c577f2c2018-08-31 09:22:23 +01001794 // NOTE: when we get the text/string format
1795 // optimised for memory then this data structure can
1796 // also move to the CaffeParser class
1797 m_CaffeLayersByTopName.clear();
telsoa014fcda012018-03-09 14:13:49 +00001798}
1799
1800}