blob: 90579e67fd3299e700ddb5adad83ecc6f77932d3 [file] [log] [blame]
telsoa014fcda012018-03-09 14:13:49 +00001//
2// 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
16#include <boost/numeric/conversion/cast.hpp>
17#include <boost/assert.hpp>
18#include <boost/format.hpp>
19#include <boost/log/trivial.hpp>
20
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>
32#include <google/protobuf/wire_format_lite_inl.h>
33#include <google/protobuf/descriptor.h>
34#include <google/protobuf/generated_message_reflection.h>
35#include <google/protobuf/reflection_ops.h>
36#include <google/protobuf/wire_format.h>
37
38#include <cmath>
39#include <sstream>
40#include <queue>
41#include <fcntl.h>
42
43/// Caffe networks are loaded from protobuf files (binary or text) using the protobuf library and the generated
44/// code from caffe.pb.h. This gives us a caffe::NetParameter which is an in-memory version of the file.
45/// This contains a flat list of Caffe 'layers' (e.g. convolution, pooling etc.).
46/// Each layer has inputs (called "bottoms") and outputs (called "tops"). Data flows from bottom to top.
47/// The bottoms of a layer refer to the tops of other layers, not their names.
telsoa01c577f2c2018-08-31 09:22:23 +010048/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't
49/// need any other changes).
telsoa014fcda012018-03-09 14:13:49 +000050///
51/// Some layers (e.g. Relu) can be configured so that their top and bottom are both the same. This is called an
52/// "in-place" layer and is a Caffe runtime feature used to reduce memory usage by modifying tensors in-place.
53/// This isn't relevant to the parser and so we preprocess these layers to convert them to regular layers, to result
54/// in a consistent graph structure.
55
56namespace armnnCaffeParser
57{
58
59using namespace armnn;
60using namespace caffe;
61using namespace std;
62using namespace google::protobuf::io;
63
telsoa01c577f2c2018-08-31 09:22:23 +010064namespace
telsoa014fcda012018-03-09 14:13:49 +000065{
66
telsoa01c577f2c2018-08-31 09:22:23 +010067const float* GetArrayPtrFromBlob(const LayerParameter& layerParam, unsigned int blobIndex)
telsoa014fcda012018-03-09 14:13:49 +000068{
telsoa01c577f2c2018-08-31 09:22:23 +010069 auto nBlobs = layerParam.blobs_size();
70 if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000071 {
telsoa01c577f2c2018-08-31 09:22:23 +010072 throw ParseException(
73 boost::str(
74 boost::format(
75 "Expected data blob at index %1% in layer %2% not found. nBlobs=%2%. %4%") %
76 blobIndex %
77 layerParam.name() %
78 nBlobs %
79 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +000080 }
81
82 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
83
telsoa01c577f2c2018-08-31 09:22:23 +010084 const float* arrayPtr = blob.data().data();
85 return arrayPtr;
86}
87
88void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
89{
90 auto nBlobs = layerParam.blobs_size();
91 if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
telsoa014fcda012018-03-09 14:13:49 +000092 {
telsoa01c577f2c2018-08-31 09:22:23 +010093 throw ParseException(
94 boost::str(
95 boost::format(
96 "Expected data blob at index %1% in layer %2% not found. %3%") %
97 blobIndex %
98 layerParam.name() %
99 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000100 }
101
telsoa01c577f2c2018-08-31 09:22:23 +0100102 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
103
104 size_t blobSize = boost::numeric_cast<size_t>(blob.data_size());
105 if (blobSize != outData.size())
telsoa014fcda012018-03-09 14:13:49 +0000106 {
telsoa01c577f2c2018-08-31 09:22:23 +0100107 throw ParseException(
108 boost::str(
109 boost::format(
110 "Data blob at index %1% in layer %2% has an unexpected size. "
111 "Expected %3% elements but got %4% elements. %5%") %
112 blobIndex %
113 layerParam.name() %
114 outData.size() %
115 blobSize %
116 CHECK_LOCATION().AsString()));
117 }
118
119 int outSizeInt = boost::numeric_cast<int>(outData.size());
120 for (int i = 0; i < outSizeInt; ++i)
121 {
122 outData[static_cast<size_t>(i)] = blob.data(i);
telsoa014fcda012018-03-09 14:13:49 +0000123 }
124}
125
telsoa014fcda012018-03-09 14:13:49 +0000126template <typename T>
127size_t SizeOfVectorData(const vector<T>& vec)
128{
129 return vec.size() * sizeof(T);
130}
131
132void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
133 unsigned int numInputs,
134 unsigned int numOutputs)
135{
136 int numInputsActual = layerParameter.bottom_size();
137 if (numInputs != boost::numeric_cast<unsigned int>(numInputsActual))
138 {
telsoa01c577f2c2018-08-31 09:22:23 +0100139 throw ParseException(
140 boost::str(
141 boost::format("Invalid number of inputs requested %1% for layer %2% "
142 "while only %3% present. %4%") %
143 numInputs %
144 layerParameter.name() %
145 numInputsActual %
146 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000147 }
148
149 int numOutputsActual = layerParameter.top_size();
150 if (numOutputs != boost::numeric_cast<unsigned int>(numOutputsActual))
151 {
telsoa01c577f2c2018-08-31 09:22:23 +0100152 throw ParseException(
153 boost::str(
154 boost::format("Invalid number of outputs requested %1% for layer %2% "
155 "while only %3% present. %4%") %
156 numOutputs %
157 layerParameter.name() %
158 numOutputsActual %
159 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000160 }
161}
162
telsoa01c577f2c2018-08-31 09:22:23 +0100163template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
164ValueType GetOptionalWithFallback(const ParamType& param,
165 ExtractOptional extractOptional,
166 ExtractFallback extractFallback,
167 ValueType defaultValue)
168{
169 auto optValue = extractOptional(param, defaultValue);
170 if (optValue.first)
171 {
172 return optValue.second;
173 }
174 auto fallbackValue = extractFallback(param, defaultValue);
175 return fallbackValue.second;
176}
177
178#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
179 PARAM_TYPE, \
180 OPTIONAL_VALUE, \
181 FALLBACK_VECTOR, \
182 VALUE_TYPE, \
183 DEFAULT_VALUE) \
184 GetOptionalWithFallback( \
185 PARAM, \
186 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
187 { \
188 if (param.has_##OPTIONAL_VALUE ()) \
189 { \
190 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
191 } \
192 else \
193 { \
194 return std::make_pair(false, defaultValue); \
195 } \
196 }, \
197 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
198 { \
199 if (param.FALLBACK_VECTOR##_size() > 0) \
200 { \
201 return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
202 } \
203 else \
204 { \
205 return std::make_pair(false, defaultValue); \
206 } \
207 }, \
208 DEFAULT_VALUE)
209
210#define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
211 PARAM_TYPE, \
212 OPTIONAL_VALUE, \
213 FALLBACK_VALUE, \
214 VALUE_TYPE, \
215 DEFAULT_VALUE) \
216 GetOptionalWithFallback( \
217 PARAM, \
218 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
219 { \
220 if (param.has_##OPTIONAL_VALUE ()) \
221 { \
222 return std::make_pair(true, param.OPTIONAL_VALUE ()); \
223 } \
224 else \
225 { \
226 return std::make_pair(false, defaultValue); \
227 } \
228 }, \
229 [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
230 { \
231 if (param.has_##FALLBACK_VALUE ()) \
232 { \
233 return std::make_pair(true, param.FALLBACK_VALUE ()); \
234 } \
235 else \
236 { \
237 return std::make_pair(false, defaultValue); \
238 } \
239 }, \
240 DEFAULT_VALUE)
241
telsoa01c577f2c2018-08-31 09:22:23 +0100242} // namespace <anonymous>
243
244const std::map<std::string, CaffeParserBase::OperationParsingFunction>
245 CaffeParserBase::ms_CaffeLayerNameToParsingFunctions = {
246 { "Input", &CaffeParserBase::ParseInputLayer },
247 { "Convolution", &CaffeParserBase::ParseConvLayer },
248 { "Pooling", &CaffeParserBase::ParsePoolingLayer },
249 { "ReLU", &CaffeParserBase::ParseReluLayer },
250 { "LRN", &CaffeParserBase::ParseLRNLayer },
251 { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
252 { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
253 { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
254 { "Concat", &CaffeParserBase::ParseConcatLayer },
255 { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
256 { "Scale", &CaffeParserBase::ParseScaleLayer },
257 { "Split", &CaffeParserBase::ParseSplitLayer },
258 { "Dropout", &CaffeParserBase::ParseDropoutLayer},
259};
260
261ICaffeParser* ICaffeParser::CreateRaw()
262{
263 return new RecordByRecordCaffeParser();
264}
265
266ICaffeParserPtr ICaffeParser::Create()
267{
268 return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
269}
270
271void ICaffeParser::Destroy(ICaffeParser* parser)
272{
273 delete parser;
274}
275
276CaffeParserBase::CaffeParserBase()
277 : m_Network(nullptr, nullptr)
278{
279
280}
281
282CaffeParser::CaffeParser()
283: CaffeParserBase()
284{
285
286}
287
288BindingPointInfo CaffeParserBase::GetNetworkInputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000289{
290 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
291}
292
telsoa01c577f2c2018-08-31 09:22:23 +0100293BindingPointInfo CaffeParserBase::GetNetworkOutputBindingInfo(const std::string& name) const
telsoa014fcda012018-03-09 14:13:49 +0000294{
295 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
296}
297
telsoa01c577f2c2018-08-31 09:22:23 +0100298std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
telsoa014fcda012018-03-09 14:13:49 +0000299 const char* bindingPointDesc,
300 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
301{
302 auto it = nameToBindingInfo.find(layerName);
303 if (it == nameToBindingInfo.end())
304 {
telsoa01c577f2c2018-08-31 09:22:23 +0100305 throw InvalidArgumentException(
306 boost::str(
307 boost::format(
308 "Unknown binding %1% for layer '%2%'. %3%") %
309 bindingPointDesc %
310 layerName %
311 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000312 }
313 return it->second;
314}
315
telsoa01c577f2c2018-08-31 09:22:23 +0100316TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
telsoa014fcda012018-03-09 14:13:49 +0000317{
318 std::vector<unsigned int> shape;
319 for (int j = 0; j < blobShape.dim_size(); ++j)
320 {
321 shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
322 }
323
324 return TensorInfo(boost::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
325}
326
327BlobShape TensorDescToBlobShape(const TensorInfo& desc)
328{
329 BlobShape ret;
330 for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
331 {
332 ret.add_dim(i);
333 ret.set_dim(boost::numeric_cast<int>(i), desc.GetShape()[i]);
334 }
335
336 return ret;
337}
338
telsoa01c577f2c2018-08-31 09:22:23 +0100339// Note: can move to CaffeParser when/if we optimise the text/string format
340// to load on a layer by layer basis
341vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000342{
343 std::vector<const caffe::LayerParameter*> ret;
344 ret.reserve(boost::numeric_cast<size_t>(layerParam.bottom_size()));
345 for (int j = 0; j < layerParam.bottom_size(); ++j)
346 {
347 std::string inputName = layerParam.bottom(j);
348 auto inputIt = m_CaffeLayersByTopName.find(inputName);
349 if (inputIt == m_CaffeLayersByTopName.end())
350 {
351 throw ParseException(
telsoa01c577f2c2018-08-31 09:22:23 +0100352 boost::str(
353 boost::format(
354 "Can't find Caffe layer with top called '%1%', "
355 "which is listed as an input of '%2%'. %3%") %
356 inputName %
357 layerParam.name() %
358 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000359 }
360 ret.push_back(inputIt->second);
361 }
362
363 return ret;
364}
365
telsoa01c577f2c2018-08-31 09:22:23 +0100366void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000367{
368 BOOST_ASSERT(layerParam.type() == "Input");
369 ValidateNumInputsOutputs(layerParam, 0, 1);
370
371 const InputParameter& param = layerParam.input_param();
372
telsoa01c577f2c2018-08-31 09:22:23 +0100373 const armnn::LayerBindingId inputId = boost::numeric_cast<armnn::LayerBindingId>(
374 m_NetworkInputsBindingInfo.size());
telsoa014fcda012018-03-09 14:13:49 +0000375 armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
376
telsoa01c577f2c2018-08-31 09:22:23 +0100377 // Decides the tensor info for this input. This can be specified in the Caffe network but can also
telsoa014fcda012018-03-09 14:13:49 +0000378 // be overriden by user input (m_inputShapes).
379 armnn::TensorInfo inputTensorInfo;
380
381 const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
382 &param.shape(0) : nullptr;
383 if (originalShape)
384 {
385 inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
386 }
387
388 auto overrideIt = m_InputShapes.find(layerParam.name());
389 if (overrideIt != m_InputShapes.end())
390 {
391 const TensorShape& overrideShape = overrideIt->second;
392 if (originalShape &&
393 ( originalShape->dim(1) != overrideShape[1]
394 || originalShape->dim(2) != overrideShape[2]
395 || originalShape->dim(3) != overrideShape[3]))
396 {
telsoa01c577f2c2018-08-31 09:22:23 +0100397 throw ParseException(
398 boost::str(
399 boost::format(
400 "Parsed input shape for '%1%' is incompatible with the override provided. %2%") %
401 layerParam.name() %
402 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000403 }
404 inputTensorInfo.SetShape(overrideShape);
405 }
406 else if (!originalShape)
407 {
telsoa01c577f2c2018-08-31 09:22:23 +0100408 throw ParseException(
409 boost::str(
410 boost::format(
411 "No input descriptor given for '%1%' and no input shape found in caffe model. %2%") %
412 layerParam.name() %
413 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000414 }
415
416 TrackInputBinding(inputLayer, inputId, inputTensorInfo);
417 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
418 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
419}
420
telsoa01c577f2c2018-08-31 09:22:23 +0100421void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
422 const armnn::Convolution2dDescriptor& desc,
423 unsigned int kernelW,
424 unsigned int kernelH)
telsoa014fcda012018-03-09 14:13:49 +0000425{
426 BOOST_ASSERT(layerParam.type() == "Convolution");
427 ValidateNumInputsOutputs(layerParam, 1, 1);
428
telsoa01c577f2c2018-08-31 09:22:23 +0100429 ConvolutionParameter convParam = layerParam.convolution_param();
telsoa014fcda012018-03-09 14:13:49 +0000430 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa01c577f2c2018-08-31 09:22:23 +0100431 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
telsoa014fcda012018-03-09 14:13:49 +0000432
telsoa01c577f2c2018-08-31 09:22:23 +0100433 // asusme these were already verified by the caller ParseConvLayer() function
434 BOOST_ASSERT(numGroups < inputShape.dim(1));
435 BOOST_ASSERT(numGroups > 1);
telsoa014fcda012018-03-09 14:13:49 +0000436
437 // Handle grouping
telsoa014fcda012018-03-09 14:13:49 +0000438 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
439
440 vector<string> convLayerNames(numGroups);
441 vector<armnn::IConnectableLayer*> convLayers(numGroups);
442 convLayerNames[0] = layerParam.name();
443
telsoa01c577f2c2018-08-31 09:22:23 +0100444 // This convolution is to be applied to chunks of the input data so add a splitter layer
445
446 // Redirect the convolution input to the splitter
447 unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
448 static_cast<unsigned int>(inputShape.dim(1)),
449 static_cast<unsigned int>(inputShape.dim(2)),
450 static_cast<unsigned int>(inputShape.dim(3))};
451
452 // Split dimension 1 of the splitter output shape and conv input shapes
453 // according to the number of groups
454
455 splitterDimSizes[1] /= numGroups;
456 inputShape.set_dim(1, splitterDimSizes[1]);
457
458 // This is used to describe how the input is to be split
459 ViewsDescriptor splitterDesc(numGroups);
460
461 // Create an output node for each group, giving each a unique name
462 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000463 {
telsoa01c577f2c2018-08-31 09:22:23 +0100464 // Work out the names of the splitter layers child convolutions
465 stringstream ss;
466 ss << layerParam.name() << "_" << g;
467 convLayerNames[g] = ss.str();
telsoa014fcda012018-03-09 14:13:49 +0000468
telsoa01c577f2c2018-08-31 09:22:23 +0100469 splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
telsoa014fcda012018-03-09 14:13:49 +0000470
telsoa01c577f2c2018-08-31 09:22:23 +0100471 // Set the size of the views.
472 for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
telsoa014fcda012018-03-09 14:13:49 +0000473 {
telsoa01c577f2c2018-08-31 09:22:23 +0100474 splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
telsoa014fcda012018-03-09 14:13:49 +0000475 }
476 }
477
telsoa01c577f2c2018-08-31 09:22:23 +0100478 const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
479 armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
telsoa014fcda012018-03-09 14:13:49 +0000480
telsoa01c577f2c2018-08-31 09:22:23 +0100481 inputConnection.Connect(splitterLayer->GetInputSlot(0));
482 for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
483 {
484 splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
485 }
telsoa014fcda012018-03-09 14:13:49 +0000486
487 unsigned int numFilters = convParam.num_output();
488
telsoa01c577f2c2018-08-31 09:22:23 +0100489 // Populates convolution output tensor descriptor dimensions.
telsoa014fcda012018-03-09 14:13:49 +0000490 BlobShape outputShape;
491 outputShape.add_dim(0);
492 outputShape.set_dim(0, inputShape.dim(0));
493 outputShape.add_dim(1);
telsoa01c577f2c2018-08-31 09:22:23 +0100494 // Ensures that dimension 1 of the convolution output is split according to the number of groups.
telsoa014fcda012018-03-09 14:13:49 +0000495 outputShape.set_dim(1, numFilters / numGroups);
496 outputShape.add_dim(2);
497 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100498 2, (static_cast<int>(
499 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
500 static_cast<float>(desc.m_StrideY)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000501 outputShape.add_dim(3);
502 outputShape.set_dim(
telsoa01c577f2c2018-08-31 09:22:23 +0100503 3, (static_cast<int>(
504 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
505 static_cast<float>(desc.m_StrideX)) + 1));
telsoa014fcda012018-03-09 14:13:49 +0000506
507 // Load the weight data for ALL groups
telsoa01c577f2c2018-08-31 09:22:23 +0100508 vector<float> weightData(boost::numeric_cast<size_t>(numGroups *
509 inputShape.dim(1) * // number of input channels
510 outputShape.dim(1) * // number of output channels
511 kernelH *
512 kernelW));
telsoa014fcda012018-03-09 14:13:49 +0000513 GetDataFromBlob(layerParam, weightData, 0);
514
515 const unsigned int weightDimSizes[4] = {
telsoa01c577f2c2018-08-31 09:22:23 +0100516 static_cast<unsigned int>(outputShape.dim(1)),
517 static_cast<unsigned int>(inputShape.dim(1)),
518 kernelH,
519 kernelW};
telsoa014fcda012018-03-09 14:13:49 +0000520
telsoa014fcda012018-03-09 14:13:49 +0000521 TensorInfo biasInfo;
522 vector<float> biasData;
telsoa01c577f2c2018-08-31 09:22:23 +0100523
524 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000525 {
526 biasData.resize(boost::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
527 GetDataFromBlob(layerParam, biasData, 1);
528
529 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
530 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
531 }
532
533 const unsigned int numWeightsPerGroup = boost::numeric_cast<unsigned int>(weightData.size()) / numGroups;
534 const unsigned int numBiasesPerGroup = boost::numeric_cast<unsigned int>(biasData.size()) / numGroups;
535
telsoa014fcda012018-03-09 14:13:49 +0000536 for (unsigned int g = 0; g < numGroups; ++g)
537 {
telsoa01c577f2c2018-08-31 09:22:23 +0100538 // Sets the slot index, group 0 should be connected to the 0th output of the splitter
539 // group 1 should be connected to the 1st output of the splitter.
telsoa014fcda012018-03-09 14:13:49 +0000540
telsoa01c577f2c2018-08-31 09:22:23 +0100541 // Pulls out the weights for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000542 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
543 weightData.data() + numWeightsPerGroup * g);
544
545 IConnectableLayer* convLayer = nullptr;
telsoa01c577f2c2018-08-31 09:22:23 +0100546 if (desc.m_BiasEnabled)
telsoa014fcda012018-03-09 14:13:49 +0000547 {
telsoa01c577f2c2018-08-31 09:22:23 +0100548 // Pulls out the biases for this group from that loaded from the model file earlier.
telsoa014fcda012018-03-09 14:13:49 +0000549 ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
550
telsoa01c577f2c2018-08-31 09:22:23 +0100551 convLayer =
552 m_Network->AddConvolution2dLayer(desc, weights, biases, convLayerNames[g].c_str());
telsoa014fcda012018-03-09 14:13:49 +0000553 }
554 else
555 {
telsoa01c577f2c2018-08-31 09:22:23 +0100556 convLayer =
557 m_Network->AddConvolution2dLayer(desc, weights, convLayerNames[g].c_str());
telsoa014fcda012018-03-09 14:13:49 +0000558 }
559 convLayers[g] = convLayer;
560
561 // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
562 // otherwise it's the regular input to this layer.
telsoa01c577f2c2018-08-31 09:22:23 +0100563 armnn::IOutputSlot& splitterInputConnection =
564 splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
telsoa014fcda012018-03-09 14:13:49 +0000565 splitterInputConnection.Connect(convLayer->GetInputSlot(0));
566 convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
telsoa014fcda012018-03-09 14:13:49 +0000567 }
568
telsoa01c577f2c2018-08-31 09:22:23 +0100569 // If the convolution was performed in chunks, add a layer to merge the results
570
571 // The merge input shape matches that of the convolution output
572 unsigned int mergeDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
573 static_cast<unsigned int>(outputShape.dim(1)),
574 static_cast<unsigned int>(outputShape.dim(2)),
575 static_cast<unsigned int>(outputShape.dim(3))};
576
577 // This is used to describe how the input is to be merged
578 OriginsDescriptor mergeDesc(numGroups);
579
580 // Now create an input node for each group, using the name from
581 // the output of the corresponding convolution
582 for (unsigned int g = 0; g < numGroups; ++g)
telsoa014fcda012018-03-09 14:13:49 +0000583 {
telsoa01c577f2c2018-08-31 09:22:23 +0100584 mergeDesc.SetViewOriginCoord(g, 1, mergeDimSizes[1] * g);
585 }
telsoa014fcda012018-03-09 14:13:49 +0000586
telsoa01c577f2c2018-08-31 09:22:23 +0100587 // Make sure the output from the merge is the correct size to hold the data for all groups
588 mergeDimSizes[1] *= numGroups;
589 outputShape.set_dim(1, mergeDimSizes[1]);
telsoa014fcda012018-03-09 14:13:49 +0000590
telsoa01c577f2c2018-08-31 09:22:23 +0100591 // Finally add the merge layer
Jim Flynn906f9462019-05-10 13:55:21 +0100592 IConnectableLayer* mergerLayer = m_Network->AddConcatLayer(mergeDesc, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000593
telsoa01c577f2c2018-08-31 09:22:23 +0100594 if (!mergerLayer)
595 {
596 throw ParseException(
597 boost::str(
598 boost::format(
599 "Failed to create final merger layer for Split+Convolution+Merger. "
600 "Layer=%1% #groups=%2% #filters=%3% %4%") %
601 layerParam.name() %
602 numGroups %
603 numFilters %
604 CHECK_LOCATION().AsString()));
605 }
telsoa014fcda012018-03-09 14:13:49 +0000606
telsoa01c577f2c2018-08-31 09:22:23 +0100607 for (unsigned int g = 0; g < numGroups; ++g)
608 {
609 convLayers[g]->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(g));
610 }
611 mergerLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, mergeDimSizes, DataType::Float32));
612 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), mergerLayer->GetOutputSlot(0));
613}
telsoa014fcda012018-03-09 14:13:49 +0000614
telsoa01c577f2c2018-08-31 09:22:23 +0100615void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
616 const armnn::Convolution2dDescriptor& convDesc,
617 unsigned int kernelW,
618 unsigned int kernelH)
619{
620 BOOST_ASSERT(layerParam.type() == "Convolution");
621 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000622
telsoa01c577f2c2018-08-31 09:22:23 +0100623 ConvolutionParameter convParam = layerParam.convolution_param();
624 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
telsoa014fcda012018-03-09 14:13:49 +0000625
telsoa01c577f2c2018-08-31 09:22:23 +0100626 DepthwiseConvolution2dDescriptor desc;
627 desc.m_PadLeft = convDesc.m_PadLeft;
628 desc.m_PadRight = convDesc.m_PadRight;
629 desc.m_PadTop = convDesc.m_PadTop;
630 desc.m_PadBottom = convDesc.m_PadBottom;
631 desc.m_StrideX = convDesc.m_StrideX;
632 desc.m_StrideY = convDesc.m_StrideY;
633 desc.m_BiasEnabled = convDesc.m_BiasEnabled;
telsoa014fcda012018-03-09 14:13:49 +0000634
telsoa01c577f2c2018-08-31 09:22:23 +0100635 unsigned int numFilters = convParam.num_output();
636
637 BlobShape outputShape;
638 outputShape.add_dim(0);
639 outputShape.set_dim(0, inputShape.dim(0));
640 outputShape.add_dim(1);
641 outputShape.set_dim(1, numFilters);
642 outputShape.add_dim(2);
643 outputShape.set_dim(
644 2, (static_cast<int>(
645 static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
646 static_cast<float>(desc.m_StrideY)) + 1));
647 outputShape.add_dim(3);
648 outputShape.set_dim(
649 3, (static_cast<int>(
650 static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
651 static_cast<float>(desc.m_StrideX)) + 1));
652
653 // Load the weight data
654 size_t allWeightsSize = boost::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
655 vector<float> weightData(allWeightsSize);
656
657 GetDataFromBlob(layerParam, weightData, 0);
658
659 // depth multiplier will be 1 for the depthwise convolution
660 const unsigned int weightDimSizes[4] = {
661 static_cast<unsigned int>(1), // depth multiplier
662 static_cast<unsigned int>(inputShape.dim(1)), // #channels
663 kernelH,
664 kernelW};
665
666 armnn::IConnectableLayer* returnLayer = nullptr;
667 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
668
669 if (desc.m_BiasEnabled)
670 {
671 TensorInfo biasInfo;
672 vector<float> biasData;
673
674 biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
675 GetDataFromBlob(layerParam, biasData, 1);
676
677 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
678 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
679
680 ConstTensor biases(biasInfo, biasData.data());
681 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, biases, layerParam.name().c_str());
682 }
683 else
684 {
685 returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +0000686 }
687
surmeh013537c2c2018-05-18 16:31:43 +0100688 if (!returnLayer)
689 {
telsoa01c577f2c2018-08-31 09:22:23 +0100690 throw ParseException(
691 boost::str(
692 boost::format(
693 "Failed to create depthwise convolution layer. "
694 "Layer=%1% #filters=%2% %3%") %
695 layerParam.name() %
696 numFilters %
697 CHECK_LOCATION().AsString()));
698 }
699 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
700 inputConnection.Connect(returnLayer->GetInputSlot(0));
701 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
702 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
703}
704
705void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
706{
707 // Ignored Caffe Parameters
708 // * Dilation Size
709 // * Weight Filler
710 // * Bias Filler
711 // * Engine
712 // * Force nd_im2col
713 // * Axis
714
715 // Not Available ArmNN Interface Parameters
716 // * Rounding policy;
717
718 BOOST_ASSERT(layerParam.type() == "Convolution");
719 ValidateNumInputsOutputs(layerParam, 1, 1);
720
721 ConvolutionParameter convParam = layerParam.convolution_param();
722 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
723 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
724 unsigned int numFilters = convParam.num_output();
725
726 const auto notFound = std::numeric_limits<unsigned int>::max();
727
728 unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
729 kernel_h, kernel_size, unsigned int, notFound);
730 unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
731 kernel_w, kernel_size, unsigned int, notFound);
732
733 unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
734 stride_h, stride, unsigned int, 1u);
735 unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
736 stride_w, stride, unsigned int, 1u);
737
738 unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
739 pad_h, pad, unsigned int, 0u);
740 unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
741 pad_w, pad, unsigned int, 0u);
742
telsoa01c577f2c2018-08-31 09:22:23 +0100743 Convolution2dDescriptor convolution2dDescriptor;
744 convolution2dDescriptor.m_PadLeft = padW;
745 convolution2dDescriptor.m_PadRight = padW;
746 convolution2dDescriptor.m_PadTop = padH;
747 convolution2dDescriptor.m_PadBottom = padH;
748 convolution2dDescriptor.m_StrideX = strideW;
749 convolution2dDescriptor.m_StrideY = strideH;
750 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
751
752 if (numGroups > numFilters)
753 {
754 throw ParseException(
755 boost::str(
756 boost::format(
757 "Error parsing Convolution: %1%. "
758 "The 'group'=%2% parameter cannot be larger than the "
759 "number of filters supplied ='%3%'. %4%") %
760 layerParam.name() %
761 numGroups %
762 numFilters %
763 CHECK_LOCATION().AsString()));
764 }
765
766 if (inputShape.dim_size() != 4)
767 {
768 throw ParseException(
769 boost::str(
770 boost::format(
771 "Convolution input shape is expected to have 4 dimensions. "
772 "%1%'s input has only %2%. %3%") %
773 layerParam.name() %
774 inputShape.dim_size() %
775 CHECK_LOCATION().AsString()));
776 }
777
778 if (numGroups > 1)
779 {
780 if (numGroups > inputShape.dim(1))
781 {
782 throw ParseException(
783 boost::str(
784 boost::format(
785 "Error parsing Convolution: %1%. "
786 "The 'group'=%2% parameter cannot be larger than the "
787 "channel of the input shape=%3% (in NCHW format). %4%") %
788 layerParam.name() %
789 numGroups %
790 inputShape.dim(1) %
791 CHECK_LOCATION().AsString()));
792 }
793 else if (numGroups == inputShape.dim(1))
794 {
795 // we use a depthwise convolution here, because the number of groups equals to the
796 // input channels
797 AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
798 return;
799 }
800 else
801 {
802 // we split the input by channels into channels/groups separate convolutions
803 // and merger the results afterwards
804 AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
805 return;
806 }
807 }
808
809 // NOTE: at this point we only need to handle #group=1 case, all other cases should be
810 // handled by the AddConvLayer* helpers
811
812 // Populate convolution output tensor descriptor dimensions
813 BlobShape outputShape;
814 outputShape.add_dim(0);
815 outputShape.set_dim(0, inputShape.dim(0));
816 outputShape.add_dim(1);
817 outputShape.set_dim(1, numFilters);
818 outputShape.add_dim(2);
819 outputShape.set_dim(
820 2, (static_cast<int>(
821 static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
822 static_cast<float>(strideH)) + 1));
823 outputShape.add_dim(3);
824 outputShape.set_dim(
825 3, (static_cast<int>(
826 static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
827 static_cast<float>(strideW)) + 1));
828
829 // Load the weight data for ALL groups
830 vector<float> weightData(boost::numeric_cast<size_t>(inputShape.dim(1) *
831 outputShape.dim(1) *
832 kernelH *
833 kernelW));
834 GetDataFromBlob(layerParam, weightData, 0);
835
836 const unsigned int weightDimSizes[4] = {
837 static_cast<unsigned int>(outputShape.dim(1)), // output channels
838 static_cast<unsigned int>(inputShape.dim(1)), // input channels
839 kernelH,
840 kernelW};
841
842 armnn::IConnectableLayer* returnLayer = nullptr;
843
844 // Pull out the weights for this group from that loaded from the model file earlier
845 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
846
847 if (convolution2dDescriptor.m_BiasEnabled)
848 {
849 TensorInfo biasInfo;
850 vector<float> biasData;
851
852 biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
853 GetDataFromBlob(layerParam, biasData, 1);
854
855 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
856 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
857
858 // Pull out the biases for this group from that loaded from the model file earlier
859 ConstTensor biases(biasInfo, biasData.data());
860
861 returnLayer =
862 m_Network->AddConvolution2dLayer(convolution2dDescriptor, weights, biases, layerParam.name().c_str());
863 }
864 else
865 {
866 returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor, weights, layerParam.name().c_str());
867 }
868
869 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
870 inputConnection.Connect(returnLayer->GetInputSlot(0));
871 returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
872
873 if (!returnLayer)
874 {
875 throw ParseException(
876 boost::str(
877 boost::format(
878 "Failed to create Convolution layer. "
879 "Layer=%1% #groups=%2% #filters=%3% %4%") %
880 layerParam.name() %
881 numGroups %
882 numFilters %
883 CHECK_LOCATION().AsString()));
surmeh013537c2c2018-05-18 16:31:43 +0100884 }
885
telsoa014fcda012018-03-09 14:13:49 +0000886 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
887}
888
telsoa01c577f2c2018-08-31 09:22:23 +0100889void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +0000890{
telsoa01c577f2c2018-08-31 09:22:23 +0100891 // Ignored Caffe Parameters
892 // Stochastic Pooling
893 // Engine
894
telsoa014fcda012018-03-09 14:13:49 +0000895 ValidateNumInputsOutputs(layerParam, 1, 1);
telsoa014fcda012018-03-09 14:13:49 +0000896 PoolingParameter param = layerParam.pooling_param();
telsoa014fcda012018-03-09 14:13:49 +0000897 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
898
telsoa01c577f2c2018-08-31 09:22:23 +0100899 const auto notFound = std::numeric_limits<unsigned int>::max();
900
901 unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
902 kernel_h, kernel_size, unsigned int, notFound);
903 unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
904 kernel_w, kernel_size, unsigned int, notFound);
905
906 if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000907 {
908 kernel_h = inputInfo.GetShape()[2];
909 kernel_w = inputInfo.GetShape()[3];
910 }
telsoa01c577f2c2018-08-31 09:22:23 +0100911
telsoa01c577f2c2018-08-31 09:22:23 +0100912 unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
913 stride_h, stride, unsigned int, notFound);
914 unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
915 stride_h, stride, unsigned int, notFound);
916
917 if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
telsoa014fcda012018-03-09 14:13:49 +0000918 {
telsoa01c577f2c2018-08-31 09:22:23 +0100919 stride_h = 1;
920 stride_w = 1;
telsoa014fcda012018-03-09 14:13:49 +0000921 }
922
telsoa01c577f2c2018-08-31 09:22:23 +0100923 unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
924 pad_h, pad, unsigned int, 0u);
925 unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
926 pad_w, pad, unsigned int, 0u);
telsoa014fcda012018-03-09 14:13:49 +0000927
telsoa014fcda012018-03-09 14:13:49 +0000928 // Populate Weight and Bias Filter Descriptor
929 Pooling2dDescriptor pooling2dDescriptor;
930 if (param.has_pool())
931 {
932 PoolingParameter_PoolMethod p = param.pool();
933 switch (p)
934 {
935 case PoolingParameter_PoolMethod_MAX:
936 {
937 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
938 break;
939 }
940 case PoolingParameter_PoolMethod_AVE:
941 {
942 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
943 break;
944 }
945 case PoolingParameter_PoolMethod_STOCHASTIC:
946 {
telsoa01c577f2c2018-08-31 09:22:23 +0100947 throw ParseException(
948 boost::str(
949 boost::format(
950 "Pooling Layer: Stochastic Pooling Not Supported. Layer=%1% %2%") %
951 layerParam.name() %
952 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000953 }
954 default:
955 {
telsoa01c577f2c2018-08-31 09:22:23 +0100956 throw ParseException(
957 boost::str(
958 boost::format(
959 "Pooling Layer: unknown pooling method: %1% for layer: %2% %3%") %
960 p %
961 layerParam.name() %
962 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000963 }
964 }
965 }
966 else
967 {
telsoa01c577f2c2018-08-31 09:22:23 +0100968 throw ParseException(
969 boost::str(
970 boost::format(
971 "No Pooling Method Defined for %1% %2%") %
972 layerParam.name() %
973 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +0000974 }
975
976 pooling2dDescriptor.m_PadLeft = pad_w;
977 pooling2dDescriptor.m_PadRight = pad_w;
978 pooling2dDescriptor.m_PadTop = pad_h;
979 pooling2dDescriptor.m_PadBottom = pad_h;
980 pooling2dDescriptor.m_StrideX = stride_w;
981 pooling2dDescriptor.m_StrideY = stride_h;
982 pooling2dDescriptor.m_PoolWidth = kernel_w;
983 pooling2dDescriptor.m_PoolHeight = kernel_h;
984
985 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
986 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
987
988 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
989 layerParam.name().c_str());
990
telsoa014fcda012018-03-09 14:13:49 +0000991 TensorInfo outputInfo(
992 { inputInfo.GetShape()[0],
993 inputInfo.GetShape()[1],
994 static_cast<unsigned int>(ceil(
995 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
996 boost::numeric_cast<float>(stride_h))) + 1,
997 static_cast<unsigned int>(ceil(
998 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
999 boost::numeric_cast<float>(stride_w))) + 1 },
1000 DataType::Float32);
1001
1002 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
1003 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1004 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
1005}
1006
telsoa01c577f2c2018-08-31 09:22:23 +01001007void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001008{
1009 ValidateNumInputsOutputs(layerParam, 1, 1);
1010
1011 const string& name = layerParam.name();
1012 const ReLUParameter& param = layerParam.relu_param();
1013
1014 ActivationDescriptor activationDescriptor;
1015 const float negativeSlope = param.negative_slope();
1016 if (negativeSlope == 0.0f)
1017 {
1018 activationDescriptor.m_Function = ActivationFunction::ReLu;
1019 }
1020 else
1021 {
1022 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
1023 activationDescriptor.m_A = negativeSlope;
1024 }
1025
1026 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1027 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
1028 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
1029 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1030 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
1031}
1032
telsoa01c577f2c2018-08-31 09:22:23 +01001033void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001034{
1035 ValidateNumInputsOutputs(layerParam, 1, 1);
1036
1037 LRNParameter param = layerParam.lrn_param();
1038
1039 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1040
telsoa01c577f2c2018-08-31 09:22:23 +01001041 // Ignored BATCH NORMALIZATION Caffe Parameters.
1042 // Ignored MVN Caffe Parameters.
1043 // Ignored LRN Caffe Parameters.
telsoa014fcda012018-03-09 14:13:49 +00001044 // Engine
1045
1046 NormalizationDescriptor normalizationDescriptor;
1047 if (param.has_norm_region())
1048 {
1049 LRNParameter_NormRegion n = param.norm_region();
1050 switch (n)
1051 {
1052 case LRNParameter_NormRegion_ACROSS_CHANNELS:
1053 {
1054 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1055 break;
1056 }
1057 case LRNParameter_NormRegion_WITHIN_CHANNEL:
1058 {
1059 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
1060 break;
1061 }
1062 default:
telsoa01c577f2c2018-08-31 09:22:23 +01001063 {
1064 throw ParseException(
1065 boost::str(
1066 boost::format(
1067 "Unknown region %1% for LRN layer %2% %3%") %
1068 n %
1069 layerParam.name() %
1070 CHECK_LOCATION().AsString()));
1071 }
telsoa014fcda012018-03-09 14:13:49 +00001072 }
1073 }
1074 else
1075 {
telsoa01c577f2c2018-08-31 09:22:23 +01001076 // Caffe defaults to normalization across channels.
telsoa014fcda012018-03-09 14:13:49 +00001077 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
1078 }
1079
1080 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
1081 if (param.has_local_size())
1082 {
1083 normalizationDescriptor.m_NormSize = param.local_size();
1084 }
1085 else
1086 {
telsoa01c577f2c2018-08-31 09:22:23 +01001087 throw ParseException(
1088 boost::str(
1089 boost::format(
1090 "local_size not defined for LRN layer %1% %2%") %
1091 layerParam.name() %
1092 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001093 }
1094
1095 if (param.has_alpha())
1096 {
1097 normalizationDescriptor.m_Alpha = param.alpha();
1098 normalizationDescriptor.m_Alpha /= boost::numeric_cast<float>(param.local_size());
1099 }
1100 else
1101 {
telsoa01c577f2c2018-08-31 09:22:23 +01001102 throw ParseException(
1103 boost::str(
1104 boost::format(
1105 "Alpha not defined for LRN layer %1% %2%") %
1106 layerParam.name() %
1107 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001108 }
1109 if (param.has_beta())
1110 {
1111 normalizationDescriptor.m_Beta = param.beta();
1112 }
1113 else
1114 {
telsoa01c577f2c2018-08-31 09:22:23 +01001115 throw ParseException(
1116 boost::str(
1117 boost::format(
1118 "Beta not defined for LRN layer %1% %2%") %
1119 layerParam.name() %
1120 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001121 }
telsoa01c577f2c2018-08-31 09:22:23 +01001122
telsoa014fcda012018-03-09 14:13:49 +00001123 if (param.has_k())
1124 {
1125 normalizationDescriptor.m_K = param.k();
1126 }
1127 else
telsoa01c577f2c2018-08-31 09:22:23 +01001128 {
telsoa014fcda012018-03-09 14:13:49 +00001129 normalizationDescriptor.m_K = 1;
telsoa01c577f2c2018-08-31 09:22:23 +01001130 }
telsoa014fcda012018-03-09 14:13:49 +00001131
1132 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
1133 layerParam.name().c_str());
1134 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
1135 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1136
1137 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
1138}
1139
telsoa01c577f2c2018-08-31 09:22:23 +01001140void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001141{
1142 InnerProductParameter param = layerParam.inner_product_param();
1143
1144 ValidateNumInputsOutputs(layerParam, 1, 1);
1145
1146 unsigned int outputSize = param.num_output();
1147
telsoa01c577f2c2018-08-31 09:22:23 +01001148 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001149 // Weight Filler
1150 // Bias Filler
1151 // Engine
1152 // Axis
1153
1154 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
1155
1156 if (param.has_transpose())
1157 {
telsoa01c577f2c2018-08-31 09:22:23 +01001158 // If true, assumes transposed weights.
telsoa014fcda012018-03-09 14:13:49 +00001159 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
1160 }
1161 else
1162 {
telsoa01c577f2c2018-08-31 09:22:23 +01001163 // Caffe defaults to transposed.
telsoa014fcda012018-03-09 14:13:49 +00001164 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
1165 }
1166
1167 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1168
1169 TensorInfo weightInfo;
1170 TensorInfo biasInfo;
1171
telsoa01c577f2c2018-08-31 09:22:23 +01001172 // Allows implicit flattening of extra dimensions.
telsoa014fcda012018-03-09 14:13:49 +00001173 unsigned int inputSize = inputInfo.GetShape()[1];
1174 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
1175 {
1176 inputSize *= inputInfo.GetShape()[i];
1177 }
1178
telsoa01c577f2c2018-08-31 09:22:23 +01001179 const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
telsoa014fcda012018-03-09 14:13:49 +00001180 const unsigned int swTD[2] = { outputSize, inputSize };
telsoa01c577f2c2018-08-31 09:22:23 +01001181 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001182
1183 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
telsoa01c577f2c2018-08-31 09:22:23 +01001184 // Todo: check whether bias enabled.
telsoa014fcda012018-03-09 14:13:49 +00001185 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
1186 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
1187 {
1188 // BIAS VALUE
telsoa01c577f2c2018-08-31 09:22:23 +01001189 const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
telsoa014fcda012018-03-09 14:13:49 +00001190
1191 const unsigned int sbTD[1] = { outputSize };
1192
telsoa01c577f2c2018-08-31 09:22:23 +01001193 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
telsoa014fcda012018-03-09 14:13:49 +00001194
1195 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor, weights, biases,
1196 layerParam.name().c_str());
1197 }
1198 else
1199 {
1200 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor, weights,
1201 layerParam.name().c_str());
1202 }
1203
1204 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
1205 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
1206 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
1207 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
1208}
1209
telsoa01c577f2c2018-08-31 09:22:23 +01001210void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001211{
1212 ValidateNumInputsOutputs(layerParam, 1, 1);
1213
1214 SoftmaxParameter param = layerParam.softmax_param();
1215
1216 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1217
telsoa01c577f2c2018-08-31 09:22:23 +01001218 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001219 // axis
1220 // Engine
1221
1222 armnn::SoftmaxDescriptor softmaxDescriptor;
1223 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
1224 softmaxDescriptor,
1225 layerParam.name().c_str());
1226 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
1227 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1228 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
1229}
1230
telsoa01c577f2c2018-08-31 09:22:23 +01001231void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001232{
1233 ValidateNumInputsOutputs(layerParam, 2, 1);
1234
1235 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1236
telsoa01c577f2c2018-08-31 09:22:23 +01001237 // Ignored Caffe Parameters:
telsoa014fcda012018-03-09 14:13:49 +00001238 // coeff
1239
telsoa01c577f2c2018-08-31 09:22:23 +01001240 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
telsoa014fcda012018-03-09 14:13:49 +00001241
1242 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
1243 {
1244 operation = layerParam.eltwise_param().operation();
1245 }
1246
1247 armnn::IConnectableLayer* newLayer = nullptr;
1248 switch (operation)
1249 {
1250 case EltwiseParameter_EltwiseOp_SUM:
1251 {
1252 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
1253 break;
1254 }
1255 case EltwiseParameter_EltwiseOp_PROD:
1256 {
1257 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
1258 break;
1259 }
1260 default:
1261 {
telsoa01c577f2c2018-08-31 09:22:23 +01001262 throw ParseException(
1263 boost::str(
1264 boost::format(
1265 "Unsupported operation %1% in Eltwise layer %2% %3%") %
1266 operation %
1267 layerParam.name() %
1268 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001269 }
1270 }
1271
1272 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
1273 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
1274 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1275 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
1276}
1277
telsoa01c577f2c2018-08-31 09:22:23 +01001278void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001279{
1280 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
telsoa01c577f2c2018-08-31 09:22:23 +01001281 // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
telsoa014fcda012018-03-09 14:13:49 +00001282 unsigned int concatDim = 1;
1283 unsigned int numOfDims = 4;
1284
telsoa01c577f2c2018-08-31 09:22:23 +01001285 // we only consider 4-D tensor here
1286 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
telsoa014fcda012018-03-09 14:13:49 +00001287 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
1288
1289 unsigned int mergeDim = 0;
1290 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
1291 {
1292 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
1293 layerParam.bottom(boost::numeric_cast<int>(viewIndex))).GetTensorInfo();
telsoa01c577f2c2018-08-31 09:22:23 +01001294 // Checks whether the dimensions of the input tensors are actually 4.
telsoa014fcda012018-03-09 14:13:49 +00001295 if (inputInfo.GetNumDimensions()!=4)
1296 {
telsoa01c577f2c2018-08-31 09:22:23 +01001297 throw ParseException(
1298 boost::str(
1299 boost::format(
1300 "The number of dimensions for input tensors of "
1301 "the concatenation op should be 4. Inputs of %1% has "
1302 "%2% dimensions. %3%") %
1303 layerParam.name() %
1304 inputInfo.GetNumDimensions() %
1305 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001306 }
1307
1308 mergeDimSizes[0] = inputInfo.GetShape()[0];
1309 mergeDimSizes[1] = inputInfo.GetShape()[1];
1310 mergeDimSizes[2] = inputInfo.GetShape()[2];
1311 mergeDimSizes[3] = inputInfo.GetShape()[3];
1312
1313 for (unsigned int j = 0; j < concatDim; ++j)
1314 {
1315 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1316 }
1317
1318 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
1319 mergeDim += mergeDimSizes[concatDim];
1320
1321 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
1322 {
1323 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
1324 }
1325 }
1326 mergeDimSizes[concatDim] = mergeDim;
1327
Jim Flynn906f9462019-05-10 13:55:21 +01001328 armnn::IConnectableLayer* concatlayer = m_Network->AddConcatLayer(concatDescriptor, layerParam.name().c_str());
telsoa014fcda012018-03-09 14:13:49 +00001329 for (unsigned int i = 0; i < numInputs; ++i)
1330 {
1331 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(boost::numeric_cast<int>(i)));
1332 outputSlot.Connect(concatlayer->GetInputSlot(i));
1333 }
1334
1335 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
1336 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
1337}
1338
telsoa01c577f2c2018-08-31 09:22:23 +01001339void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001340{
1341 ValidateNumInputsOutputs(layerParam, 1, 1);
1342
1343 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1344
1345 string name = layerParam.name();
1346
1347 BatchNormParameter param = layerParam.batch_norm_param();
1348 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
1349 // when the network is in the testing phase).
1350 if (param.has_use_global_stats())
1351 {
1352 if (!param.use_global_stats())
1353 {
telsoa01c577f2c2018-08-31 09:22:23 +01001354 throw ParseException(
1355 boost::str(
1356 boost::format(
1357 "Error parsing Batch Norm layer '%1%': "
1358 "Parameter 'use_global_stats' is set to false, which is "
1359 "unsupported (value used for training). %2%") %
1360 name %
1361 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001362 }
1363 }
1364
1365 BatchNormalizationDescriptor desc;
1366 desc.m_Eps = param.eps();
1367
1368 unsigned int channels = inputInfo.GetShape()[1];
1369 unsigned int shape[] = {channels};
1370
1371 vector<float> meanData(channels);
1372 GetDataFromBlob(layerParam, meanData, 0);
1373
1374 vector<float> varianceData(channels);
1375 GetDataFromBlob(layerParam, varianceData, 1);
1376
telsoa01c577f2c2018-08-31 09:22:23 +01001377 // Reads moving average factor and applies scaling (if required).
surmeh013537c2c2018-05-18 16:31:43 +01001378 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(2));
1379 const float movingAverageFactor = blob.data(boost::numeric_cast<int>(0));
1380 if(movingAverageFactor != 0.0f)
1381 {
1382 const float scaleFactor = 1.0f / movingAverageFactor;
1383 auto scaleFunction = [scaleFactor](float f) -> float { return f * scaleFactor; };
1384
1385 std::transform(varianceData.begin(), varianceData.end(), varianceData.begin(), scaleFunction);
1386 std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
1387 }
1388
telsoa01c577f2c2018-08-31 09:22:23 +01001389 // Identifies scale operation.
telsoa014fcda012018-03-09 14:13:49 +00001390 vector<float> betaData(channels, 0.0f);
1391 vector<float> gammaData(channels, 1.0f);
1392
1393 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1394 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1395 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1396 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1397
1398 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1399 mean, variance, beta, gamma, name.c_str());
1400 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1401 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1402 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1403}
1404
telsoa01c577f2c2018-08-31 09:22:23 +01001405void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001406{
telsoa01c577f2c2018-08-31 09:22:23 +01001407 // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
telsoa014fcda012018-03-09 14:13:49 +00001408 ValidateNumInputsOutputs(layerParam, 1, 1);
1409
1410 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1411
1412 string name = layerParam.name();
1413
1414 ScaleParameter param = layerParam.scale_param();
1415 if (param.axis() != 1)
1416 {
1417 // Would have to use something other than BatchNormalizationLayer in this case
telsoa01c577f2c2018-08-31 09:22:23 +01001418 throw ParseException(
1419 boost::str(
1420 boost::format(
1421 "Loading Scale Layer: Only axis 1 is supported currently. "
1422 "Layer=%1% Axis=%2% %3%") %
1423 layerParam.name() %
1424 param.axis() %
1425 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001426 }
1427
1428 unsigned int channels = inputInfo.GetShape()[1];
1429 unsigned int shape[] = {channels};
1430
1431 BatchNormalizationDescriptor desc;
telsoa01c577f2c2018-08-31 09:22:23 +01001432 desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
telsoa014fcda012018-03-09 14:13:49 +00001433 vector<float> meanData(channels, 0.0f);
1434 vector<float> varianceData(channels, 1.0f);
1435 vector<float> betaData(channels, 0.0f);
1436 vector<float> gammaData(channels);
1437
1438 GetDataFromBlob(layerParam, gammaData, 0);
1439
1440 if(param.has_bias_term())
1441 {
1442 GetDataFromBlob(layerParam, betaData, 1);
1443 }
1444
1445 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1446 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1447 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1448 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1449
1450 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1451 mean, variance, beta, gamma, name.c_str());
1452 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1453 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1454 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1455}
1456
telsoa01c577f2c2018-08-31 09:22:23 +01001457void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001458{
telsoa01c577f2c2018-08-31 09:22:23 +01001459 // Used in caffe to duplicate memory - not necessary in armnn.
telsoa014fcda012018-03-09 14:13:49 +00001460 if (layerParam.bottom_size() != 1)
1461 {
telsoa01c577f2c2018-08-31 09:22:23 +01001462 throw ParseException(
1463 boost::str(
1464 boost::format(
1465 "Split layer '%1%' should have exactly 1 bottom. "
1466 "#bottoms=%2% %3%") %
1467 layerParam.name() %
1468 layerParam.bottom_size() %
1469 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001470 }
1471 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1472 for (int i = 0; i < layerParam.top_size(); i++)
1473 {
1474 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1475 }
1476}
1477
telsoa01c577f2c2018-08-31 09:22:23 +01001478void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
telsoa014fcda012018-03-09 14:13:49 +00001479{
telsoa01c577f2c2018-08-31 09:22:23 +01001480 // Ignored for inference, so patch the single input to its single output.
telsoa014fcda012018-03-09 14:13:49 +00001481 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1482 {
telsoa01c577f2c2018-08-31 09:22:23 +01001483 throw ParseException(
1484 boost::str(
1485 boost::format(
1486 "Dropout layer '%1%' should have exactly 1 bottom and 1 top. "
1487 "#bottoms=%2% #tops=%3% %4%") %
1488 layerParam.name() %
1489 layerParam.bottom_size() %
1490 layerParam.top_size() %
1491 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001492 }
1493 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1494}
1495
telsoa01c577f2c2018-08-31 09:22:23 +01001496void CaffeParserBase::TrackInputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001497 armnn::LayerBindingId id,
1498 const armnn::TensorInfo& tensorInfo)
1499{
1500 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1501}
1502
telsoa01c577f2c2018-08-31 09:22:23 +01001503void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001504 armnn::LayerBindingId id,
1505 const armnn::TensorInfo& tensorInfo)
1506{
1507 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1508}
1509
telsoa01c577f2c2018-08-31 09:22:23 +01001510void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
telsoa014fcda012018-03-09 14:13:49 +00001511 armnn::LayerBindingId id,
1512 const armnn::TensorInfo& tensorInfo,
1513 const char* bindingPointDesc,
1514 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1515{
1516 const std::string layerName = layer->GetName();
1517 auto it = nameToBindingInfo.find(layerName);
1518 if (it == nameToBindingInfo.end())
1519 {
1520 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1521 }
1522 else
1523 {
telsoa01c577f2c2018-08-31 09:22:23 +01001524 throw ParseException(
1525 boost::str(
1526 boost::format(
1527 "Id %1% used by more than one %2% layer %3%") %
1528 id %
1529 bindingPointDesc %
1530 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001531 }
1532}
1533
telsoa01c577f2c2018-08-31 09:22:23 +01001534armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
telsoa014fcda012018-03-09 14:13:49 +00001535{
1536 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1537 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1538 {
1539 return *it->second;
1540 }
1541 else
1542 {
telsoa01c577f2c2018-08-31 09:22:23 +01001543 throw ParseException(
1544 boost::str(
1545 boost::format(
1546 "Could not find armnn output slot for Caffe top '%1%' %2%") %
1547 caffeTopName %
1548 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001549 }
1550}
1551
telsoa01c577f2c2018-08-31 09:22:23 +01001552void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
1553 const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
telsoa014fcda012018-03-09 14:13:49 +00001554{
1555 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1556 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1557 {
1558 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1559 }
1560 else
1561 {
telsoa01c577f2c2018-08-31 09:22:23 +01001562 throw ParseException(
1563 boost::str(
1564 boost::format(
1565 "Attempting to add duplicate entry for Caffe top '%1%' %2%") %
1566 caffeTopName %
1567 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001568 }
1569}
1570
telsoa01c577f2c2018-08-31 09:22:23 +01001571// Note: can move to CaffeParser when/if we optimise the text/string format
1572// to load on a layer by layer basis
1573void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001574{
telsoa01c577f2c2018-08-31 09:22:23 +01001575 // Finds layers with the same top.
telsoa014fcda012018-03-09 14:13:49 +00001576 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1577 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1578 {
1579 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
telsoa01c577f2c2018-08-31 09:22:23 +01001580 std::string name = layer.name();
telsoa014fcda012018-03-09 14:13:49 +00001581 for (int i = 0; i < layer.top_size(); ++i)
1582 {
1583 layersByTop[layer.top(i)].push_back(&layer);
1584 }
1585 }
1586
telsoa01c577f2c2018-08-31 09:22:23 +01001587 // For each set of layers with the same top, resolves them to a linear chain rather than in-place layers.
telsoa014fcda012018-03-09 14:13:49 +00001588 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1589 for (auto layersWithSameTopIt : layersByTop)
1590 {
1591 const std::string& top = layersWithSameTopIt.first;
1592 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1593
telsoa01c577f2c2018-08-31 09:22:23 +01001594 // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
telsoa014fcda012018-03-09 14:13:49 +00001595 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1596 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1597 {
1598 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1599 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1600 if (layer1.top_size() != 1)
1601 {
telsoa01c577f2c2018-08-31 09:22:23 +01001602 throw ParseException(
1603 boost::str(
1604 boost::format(
1605 "Node '%1%' is an in-place layer but doesn't have exactly one "
1606 "top. It has %2% instead. %3%") %
1607 layer1.name() %
1608 layer1.top_size() %
1609 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001610 }
1611 std::string newTop = layer1.name() + "_top";
1612 layer1.set_top(0, newTop);
1613 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1614 {
telsoa01c577f2c2018-08-31 09:22:23 +01001615 throw ParseException(
1616 boost::str(
1617 boost::format(
1618 "Node '%1%' is an in-place layer but "
1619 "doesn't have exactly one bottom, or it doesn't match its top. "
1620 "#bottoms=%2%, first bottom is %3%, top is %4% %5%") %
1621 layer2.name() %
1622 layer2.bottom(0) %
1623 top %
1624 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001625 }
1626 layer2.set_bottom(0, newTop);
1627 }
1628 }
1629}
1630
telsoa01c577f2c2018-08-31 09:22:23 +01001631// Note: can move to CaffeParser when/if we optimise the text/string format
1632// to load on a layer by layer basis
1633void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
telsoa014fcda012018-03-09 14:13:49 +00001634{
telsoa01c577f2c2018-08-31 09:22:23 +01001635 // Caffe models sometimes have an implicit input layer.
1636 // In that case, add an explicit one.
telsoa014fcda012018-03-09 14:13:49 +00001637 if (netParameter.input_size() > 0)
1638 {
1639 LayerParameter* newLayer = netParameter.add_layer();
1640
1641 newLayer->set_type("Input");
1642 newLayer->set_name(netParameter.input(0));
1643 newLayer->add_top(netParameter.input(0));
1644
1645 InputParameter* inputParam = newLayer->mutable_input_param();
1646 BlobShape* shape = inputParam->add_shape();
1647
1648 int dim_size = netParameter.input_dim_size();
1649 for (int i = 0; i < dim_size; ++i)
1650 {
1651 shape->add_dim(netParameter.input_dim(i));
1652 }
1653 }
1654
telsoa01c577f2c2018-08-31 09:22:23 +01001655 // Replaces in-place layers with regular ones to make the rest of the parsing easier.
telsoa014fcda012018-03-09 14:13:49 +00001656 ResolveInPlaceLayers(netParameter);
1657
telsoa01c577f2c2018-08-31 09:22:23 +01001658 // Creates a lookup of Caffe layers by name.
telsoa014fcda012018-03-09 14:13:49 +00001659 for (int i = 0; i < netParameter.layer_size(); ++i)
1660 {
1661 const caffe::LayerParameter& layer = netParameter.layer(i);
1662 for (int i = 0; i < layer.top_size(); ++i)
1663 {
1664 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1665 }
1666 }
1667
telsoa01c577f2c2018-08-31 09:22:23 +01001668 // Finds the output layers the user requested.
telsoa014fcda012018-03-09 14:13:49 +00001669 std::vector<const caffe::LayerParameter*> targetLayers;
1670 for (const std::string& requestedOutputName : m_RequestedOutputs)
1671 {
1672 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1673 if (nodeIt == m_CaffeLayersByTopName.end())
1674 {
telsoa01c577f2c2018-08-31 09:22:23 +01001675 throw ParseException(
1676 boost::str(
1677 boost::format(
1678 "Couldn't find requested output layer '%1%' in graph %2%") %
1679 requestedOutputName %
1680 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001681 }
1682 targetLayers.push_back(nodeIt->second);
1683 }
1684
telsoa01c577f2c2018-08-31 09:22:23 +01001685 // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001686 std::vector<const caffe::LayerParameter*> sortedNodes;
1687 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1688 targetLayers,
1689 [this](const caffe::LayerParameter* node)
1690 {
1691 return GetInputs(*node);
1692 },
1693 sortedNodes))
1694 {
telsoa01c577f2c2018-08-31 09:22:23 +01001695 throw ParseException(
1696 boost::str(
1697 boost::format(
1698 "Cycle detected in graph. #nodes: %1% %2%") %
1699 sortedNodes.size() %
1700 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001701 }
1702
telsoa01c577f2c2018-08-31 09:22:23 +01001703 // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
telsoa014fcda012018-03-09 14:13:49 +00001704 for (const caffe::LayerParameter* current : sortedNodes)
1705 {
1706 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1707 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1708 {
telsoa01c577f2c2018-08-31 09:22:23 +01001709 throw ParseException(
1710 boost::str(
1711 boost::format("Unsupported layer type: '%1%' for layer %2% %3%") %
1712 current->type() %
1713 current->name() %
1714 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001715 }
1716 auto func = it->second;
1717 (this->*func)(*current);
1718 }
1719
telsoa01c577f2c2018-08-31 09:22:23 +01001720 // Adds ArmNN output layers connected to each requested output.
telsoa014fcda012018-03-09 14:13:49 +00001721 for (const std::string& requestedOutput : m_RequestedOutputs)
1722 {
1723 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1724
1725 const armnn::LayerBindingId outputId = boost::numeric_cast<armnn::LayerBindingId>(
1726 m_NetworkOutputsBindingInfo.size());
1727 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1728 outputSlot.Connect(outputLayer->GetInputSlot(0));
1729
1730 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1731 }
1732}
1733
telsoa01c577f2c2018-08-31 09:22:23 +01001734INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
telsoa014fcda012018-03-09 14:13:49 +00001735 const std::map<std::string, armnn::TensorShape>& inputShapes,
1736 const std::vector<std::string>& requestedOutputs)
1737{
1738 FILE* fd = fopen(graphFile, "r");
1739
1740 if (fd == nullptr)
1741 {
telsoa01c577f2c2018-08-31 09:22:23 +01001742 throw FileNotFoundException(
1743 boost::str(
1744 boost::format(
1745 "Failed to open graph file: %1% %2%") %
1746 graphFile %
1747 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001748 }
1749
telsoa01c577f2c2018-08-31 09:22:23 +01001750 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001751 NetParameter netParam;
1752 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1753 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1754 delete input;
1755 fclose(fd);
1756
1757 if (!success)
1758 {
telsoa01c577f2c2018-08-31 09:22:23 +01001759 throw ParseException(
1760 boost::str(
1761 boost::format(
1762 "Failed to parse graph file: %1% %2%") %
1763 graphFile %
1764 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001765 }
1766
1767 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1768}
1769
telsoa01c577f2c2018-08-31 09:22:23 +01001770INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
telsoa014fcda012018-03-09 14:13:49 +00001771 const std::map<std::string, armnn::TensorShape>& inputShapes,
1772 const std::vector<std::string>& requestedOutputs)
1773{
telsoa01c577f2c2018-08-31 09:22:23 +01001774 // Parses the string into a message.
telsoa014fcda012018-03-09 14:13:49 +00001775 NetParameter netParam;
1776 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1777
1778 if (!success)
1779 {
telsoa01c577f2c2018-08-31 09:22:23 +01001780 throw ParseException(
1781 boost::str(
1782 boost::format(
1783 "Failed to parse graph string %1%") %
1784 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001785 }
1786
1787 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1788}
1789
1790INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1791 const std::map<std::string, armnn::TensorShape>& inputShapes,
1792 const std::vector<std::string>& requestedOutputs)
1793{
1794 FILE* fd = fopen(graphFile, "rb");
1795
1796 if (fd == nullptr)
1797 {
telsoa01c577f2c2018-08-31 09:22:23 +01001798 throw FileNotFoundException(
1799 boost::str(
1800 boost::format(
1801 "Failed to open graph file at: %1% %2%") %
1802 graphFile %
1803 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001804 }
1805
telsoa01c577f2c2018-08-31 09:22:23 +01001806 // Parses the file into a message.
telsoa014fcda012018-03-09 14:13:49 +00001807 NetParameter netParam;
1808
1809 FileInputStream inStream(fileno(fd));
1810 CodedInputStream codedStream(&inStream);
1811 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
1812 bool success = netParam.ParseFromCodedStream(&codedStream);
1813 fclose(fd);
1814
1815 if (!success)
1816 {
telsoa01c577f2c2018-08-31 09:22:23 +01001817 throw ParseException(
1818 boost::str(
1819 boost::format(
1820 "Failed to parse protobuf file: %1% %2%") %
1821 graphFile %
1822 CHECK_LOCATION().AsString()));
telsoa014fcda012018-03-09 14:13:49 +00001823 }
1824
1825 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1826}
1827
telsoa01c577f2c2018-08-31 09:22:23 +01001828// Note: can move to CaffeParser when/if we optimise the text/string format
1829// to load on a layer by layer basis
1830INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
telsoa014fcda012018-03-09 14:13:49 +00001831 const std::map<std::string, armnn::TensorShape>& inputShapes,
1832 const std::vector<std::string>& requestedOutputs)
1833{
1834 m_NetworkInputsBindingInfo.clear();
1835 m_NetworkOutputsBindingInfo.clear();
1836
1837 m_Network = INetwork::Create();
1838
1839 m_InputShapes = inputShapes;
1840 if (requestedOutputs.size() == 0)
1841 {
1842 throw ParseException("requestedOutputs must have at least one entry");
1843 }
1844 m_RequestedOutputs = requestedOutputs;
1845
1846 try
1847 {
1848 LoadNetParam(netParam);
1849 }
1850 catch (const ParseException& e)
1851 {
1852 Cleanup();
1853 throw e;
1854 }
1855
1856 Cleanup();
1857
1858 return move(m_Network);
1859}
1860
telsoa01c577f2c2018-08-31 09:22:23 +01001861void CaffeParserBase::Cleanup() {
telsoa014fcda012018-03-09 14:13:49 +00001862 // cleanup, in case we reuse this parser
telsoa014fcda012018-03-09 14:13:49 +00001863 m_InputShapes.clear();
1864 m_RequestedOutputs.clear();
1865 m_ArmnnOutputSlotForCaffeTop.clear();
telsoa01c577f2c2018-08-31 09:22:23 +01001866 // NOTE: when we get the text/string format
1867 // optimised for memory then this data structure can
1868 // also move to the CaffeParser class
1869 m_CaffeLayersByTopName.clear();
telsoa014fcda012018-03-09 14:13:49 +00001870}
1871
1872}