blob: e12badc3a01e4bff994202acfd1f81e641568a9d [file] [log] [blame]
telsoa014fcda012018-03-09 14:13:49 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// See LICENSE file in the project root for full license information.
4//
5#include "CaffeParser.hpp"
6
7#include "armnn/Descriptors.hpp"
8#include "armnn/INetwork.hpp"
9#include "armnn/Utils.hpp"
10#include "armnn/Exceptions.hpp"
11
12#include "GraphTopologicalSort.hpp"
13
14#include <boost/numeric/conversion/cast.hpp>
15#include <boost/assert.hpp>
16#include <boost/format.hpp>
17#include <boost/log/trivial.hpp>
18
19// Caffe
20#include "caffe/proto/caffe.pb.h"
21
22// ProtoBuf
23#include <google/protobuf/io/coded_stream.h>
24#include <google/protobuf/io/zero_copy_stream.h>
25#include <google/protobuf/io/zero_copy_stream_impl.h>
26#include <google/protobuf/text_format.h>
27#include <google/protobuf/stubs/common.h>
28#include <google/protobuf/stubs/once.h>
29#include <google/protobuf/io/coded_stream.h>
30#include <google/protobuf/wire_format_lite_inl.h>
31#include <google/protobuf/descriptor.h>
32#include <google/protobuf/generated_message_reflection.h>
33#include <google/protobuf/reflection_ops.h>
34#include <google/protobuf/wire_format.h>
35
36#include <cmath>
37#include <sstream>
38#include <queue>
39#include <fcntl.h>
40
41/// Caffe networks are loaded from protobuf files (binary or text) using the protobuf library and the generated
42/// code from caffe.pb.h. This gives us a caffe::NetParameter which is an in-memory version of the file.
43/// This contains a flat list of Caffe 'layers' (e.g. convolution, pooling etc.).
44/// Each layer has inputs (called "bottoms") and outputs (called "tops"). Data flows from bottom to top.
45/// The bottoms of a layer refer to the tops of other layers, not their names.
46/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't need any other changes).
47///
48/// Some layers (e.g. Relu) can be configured so that their top and bottom are both the same. This is called an
49/// "in-place" layer and is a Caffe runtime feature used to reduce memory usage by modifying tensors in-place.
50/// This isn't relevant to the parser and so we preprocess these layers to convert them to regular layers, to result
51/// in a consistent graph structure.
52
53namespace armnnCaffeParser
54{
55
56using namespace armnn;
57using namespace caffe;
58using namespace std;
59using namespace google::protobuf::io;
60
61const std::map<std::string, CaffeParser::OperationParsingFunction> CaffeParser::ms_CaffeLayerNameToParsingFunctions = {
62 { "Input", &CaffeParser::ParseInputLayer },
63 { "Convolution", &CaffeParser::ParseConvLayer },
64 { "Pooling", &CaffeParser::ParsePoolingLayer },
65 { "ReLU", &CaffeParser::ParseReluLayer },
66 { "LRN", &CaffeParser::ParseLRNLayer },
67 { "InnerProduct", &CaffeParser::ParseInnerProductLayer },
68 { "Softmax", &CaffeParser::ParseSoftmaxLayer },
69 { "Eltwise", &CaffeParser::ParseEltwiseLayer },
70 { "Concat", &CaffeParser::ParseConcatLayer },
71 { "BatchNorm", &CaffeParser::ParseBatchNormLayer },
72 { "Scale", &CaffeParser::ParseScaleLayer },
73 { "Split", &CaffeParser::ParseSplitLayer },
74 { "Dropout", &CaffeParser::ParseDropoutLayer},
75};
76
77ICaffeParser* ICaffeParser::CreateRaw()
78{
79 return new CaffeParser();
80}
81
82ICaffeParserPtr ICaffeParser::Create()
83{
84 return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
85}
86
87void ICaffeParser::Destroy(ICaffeParser* parser)
88{
89 delete parser;
90}
91
92CaffeParser::CaffeParser()
93: m_Network(nullptr, nullptr)
94{
95
96}
97
98void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
99{
100 if (blobIndex >= boost::numeric_cast<unsigned int>(layerParam.blobs_size()))
101 {
102 throw ParseException(boost::str(boost::format("Expected data blob at index %1% in layer %2% not found")
103 % blobIndex % layerParam.name()));
104 }
105
106 const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
107
108 if (boost::numeric_cast<size_t>(blob.data_size()) != outData.size())
109 {
110 throw ParseException(boost::str(boost::format(
111 "Data blob at index %1% in layer %2% has an unexpected size. Expected %3% elements but got %4% elements")
112 % blobIndex % layerParam.name() % outData.size() % blob.data_size()));
113 }
114
115 for (unsigned int i = 0; i < outData.size(); ++i)
116 {
117 outData[i] = blob.data(boost::numeric_cast<int>(i));
118 }
119}
120
121bool IsInRange(unsigned int value, unsigned int min, unsigned int max)
122{
123 return (value >= min && value <= max) ? true : false;
124}
125
126template <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 {
139 throw ParseException("Loading layer: invalid number of inputs");
140 }
141
142 int numOutputsActual = layerParameter.top_size();
143 if (numOutputs != boost::numeric_cast<unsigned int>(numOutputsActual))
144 {
145 throw ParseException("Loading layer: invalid number of outputs");
146 }
147}
148
149BindingPointInfo CaffeParser::GetNetworkInputBindingInfo(const std::string& name) const
150{
151 return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
152}
153
154BindingPointInfo CaffeParser::GetNetworkOutputBindingInfo(const std::string& name) const
155{
156 return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
157}
158
159std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParser::GetBindingInfo(const std::string& layerName,
160 const char* bindingPointDesc,
161 const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
162{
163 auto it = nameToBindingInfo.find(layerName);
164 if (it == nameToBindingInfo.end())
165 {
166 throw InvalidArgumentException(boost::str(boost::format("Unknown %1% '%2%'") % bindingPointDesc % layerName));
167 }
168 return it->second;
169}
170
171TensorInfo CaffeParser::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
172{
173 std::vector<unsigned int> shape;
174 for (int j = 0; j < blobShape.dim_size(); ++j)
175 {
176 shape.push_back(static_cast<unsigned int>(blobShape.dim(j)));
177 }
178
179 return TensorInfo(boost::numeric_cast<unsigned int>(shape.size()), shape.data(), DataType::Float32);
180}
181
182BlobShape TensorDescToBlobShape(const TensorInfo& desc)
183{
184 BlobShape ret;
185 for (unsigned int i = 0; i < desc.GetNumDimensions(); ++i)
186 {
187 ret.add_dim(i);
188 ret.set_dim(boost::numeric_cast<int>(i), desc.GetShape()[i]);
189 }
190
191 return ret;
192}
193
194vector<const LayerParameter*> CaffeParser::GetInputs(const LayerParameter& layerParam)
195{
196 std::vector<const caffe::LayerParameter*> ret;
197 ret.reserve(boost::numeric_cast<size_t>(layerParam.bottom_size()));
198 for (int j = 0; j < layerParam.bottom_size(); ++j)
199 {
200 std::string inputName = layerParam.bottom(j);
201 auto inputIt = m_CaffeLayersByTopName.find(inputName);
202 if (inputIt == m_CaffeLayersByTopName.end())
203 {
204 throw ParseException(
205 "Can't find Caffe layer with top called '" + inputName + "', which is listed as an input of '" +
206 layerParam.name() + "'");
207 }
208 ret.push_back(inputIt->second);
209 }
210
211 return ret;
212}
213
214void CaffeParser::ParseInputLayer(const LayerParameter& layerParam)
215{
216 BOOST_ASSERT(layerParam.type() == "Input");
217 ValidateNumInputsOutputs(layerParam, 0, 1);
218
219 const InputParameter& param = layerParam.input_param();
220
221 const armnn::LayerBindingId inputId = boost::numeric_cast<armnn::LayerBindingId>(m_NetworkInputsBindingInfo.size());
222 armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
223
224 // Decide on the tensor info for this input. This can be specified in the Caffe network but can also
225 // be overriden by user input (m_inputShapes).
226 armnn::TensorInfo inputTensorInfo;
227
228 const BlobShape* originalShape = param.shape_size() > 0 && param.shape(0).dim_size() > 0 ?
229 &param.shape(0) : nullptr;
230 if (originalShape)
231 {
232 inputTensorInfo = BlobShapeToTensorInfo(*originalShape);
233 }
234
235 auto overrideIt = m_InputShapes.find(layerParam.name());
236 if (overrideIt != m_InputShapes.end())
237 {
238 const TensorShape& overrideShape = overrideIt->second;
239 if (originalShape &&
240 ( originalShape->dim(1) != overrideShape[1]
241 || originalShape->dim(2) != overrideShape[2]
242 || originalShape->dim(3) != overrideShape[3]))
243 {
244 throw ParseException("Parsed input shape for '" + layerParam.name() +
245 "' is incompatible with the override provided");
246 }
247 inputTensorInfo.SetShape(overrideShape);
248 }
249 else if (!originalShape)
250 {
251 throw ParseException("No input descriptor given for '" + layerParam.name() +
252 "' and no input shape found in caffe model");
253 }
254
255 TrackInputBinding(inputLayer, inputId, inputTensorInfo);
256 inputLayer->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
257 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
258}
259
260void CaffeParser::ParseConvLayer(const LayerParameter& layerParam)
261{
262 BOOST_ASSERT(layerParam.type() == "Convolution");
263 ValidateNumInputsOutputs(layerParam, 1, 1);
264
265 ConvolutionParameter convParam = layerParam.convolution_param();
266 BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
267
268 unsigned int kernelH = 0;
269 unsigned int kernelW = 0;
270 if (convParam.has_kernel_h() && convParam.has_kernel_w())
271 {
272 kernelH = convParam.kernel_h();
273 kernelW = convParam.kernel_w();
274 }
275 else if (convParam.kernel_size_size() > 0)
276 {
277 kernelH = (convParam.kernel_size()).Get(0);
278 kernelW = (convParam.kernel_size()).Get(0);
279 }
280 else
281 {
282 throw ParseException("Loading Convolution Layer: Kernel Size defined Illegally");
283 }
284
285 if (!IsInRange(kernelH, 0, 11) || !IsInRange(kernelW, 0, 11) || (kernelH != kernelW))
286 {
287 throw ParseException("Loading Convolution Layer: Kernel has invalid size");
288 }
289
290 unsigned int strideH = 0;
291 unsigned int strideW = 0;
292
293 if (convParam.has_stride_h() && convParam.has_stride_w())
294 {
295 strideH = convParam.stride_h();
296 strideW = convParam.stride_w();
297 }
298 else if (convParam.stride_size() > 0)
299 {
300 strideH = (convParam.stride()).Get(0);
301 strideW = (convParam.stride()).Get(0);
302 }
303 else
304 {
305 // Caffe stride default is 1
306 strideH = strideW = 1;
307 }
308
309 if (!IsInRange(strideH, 0, 11) || !IsInRange(strideW, 0, 11) || (strideH != strideW))
310 {
311 throw ParseException("Loading Convolution Layer: stride has invalid size");
312 }
313
314 unsigned int padH = 0;
315 unsigned int padW = 0;
316
317 if (convParam.has_pad_h() && convParam.has_pad_w())
318 {
319 padH = convParam.pad_h();
320 padW = convParam.pad_w();
321 }
322 else if (convParam.pad_size() > 0)
323 {
324 padH = (convParam.pad()).Get(0);
325 padW = (convParam.pad()).Get(0);
326 }
327 else
328 {
329 padH = 0;
330 padW = 0;
331 }
332
333 if (!IsInRange(padH, 0, 11) || !IsInRange(padW, 0, 11) || (padH != padW))
334 {
335 throw ParseException("Loading Convolution Layer: pad has invalid size");
336 }
337
338 // Handle grouping
339 const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
340 armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
341
342 vector<string> convLayerNames(numGroups);
343 vector<armnn::IConnectableLayer*> convLayers(numGroups);
344 convLayerNames[0] = layerParam.name();
345
346 armnn::IConnectableLayer* splitterLayer = nullptr;
347 if (numGroups > 1)
348 {
349 // This convolution is to be applied to chunks of the input data so add a splitter layer
350
351 // Redirect the convolution input to the splitter
352 unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
353 static_cast<unsigned int>(inputShape.dim(1)),
354 static_cast<unsigned int>(inputShape.dim(2)),
355 static_cast<unsigned int>(inputShape.dim(3))};
356
357 // Split dimension 1 of the splitter output shape and conv input shapes
358 // according to the number of groups
359 splitterDimSizes[1] /= numGroups;
360 inputShape.set_dim(1, splitterDimSizes[1]);
361
362 // This is used to describe how the input is to be split
363 ViewsDescriptor splitterDesc(numGroups);
364
365 // Create an output node for each group, giving each a unique name
366 for (unsigned int g = 0; g < numGroups; ++g)
367 {
368 // Work out the names of the splitter layers child convolutions
369 stringstream ss;
370 ss << layerParam.name() << "_" << g;
371 convLayerNames[g] = ss.str();
372
373 splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
374
375 // Set the size of the views.
376 for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
377 {
378 splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
379 }
380 }
381
382 const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
383
384 // Add the splitter layer
385 splitterLayer = m_Network->AddSplitterLayer(splitterDesc,
386 splitterLayerName.c_str());
387
388 inputConnection.Connect(splitterLayer->GetInputSlot(0));
389 for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
390 {
391 splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
392 }
393 }
394
395 // Ignored Caffe Parameters
396 // * Dilation Size
397 // * Weight Filler
398 // * Bias Filler
399 // * Engine
400 // * Force nd_im2col
401 // * Axis
402
403 // Not Available ArmNN Interface Parameters
404 // * Rounding policy;
405
406 Convolution2dDescriptor convolution2dDescriptor;
407 convolution2dDescriptor.m_PadLeft = padW;
408 convolution2dDescriptor.m_PadRight = padW;
409 convolution2dDescriptor.m_PadTop = padH;
410 convolution2dDescriptor.m_PadBottom = padH;
411 convolution2dDescriptor.m_StrideX = strideW;
412 convolution2dDescriptor.m_StrideY = strideH;
413
414 unsigned int numFilters = convParam.num_output();
415
416 // Populate convolution output tensor descriptor dimensions
417 BlobShape outputShape;
418 outputShape.add_dim(0);
419 outputShape.set_dim(0, inputShape.dim(0));
420 outputShape.add_dim(1);
421 // Ensure that dimension 1 of the convolution output is split according to the number of groups.
422 outputShape.set_dim(1, numFilters / numGroups);
423 outputShape.add_dim(2);
424 outputShape.set_dim(
425 2, (static_cast<int>(static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
426 boost::numeric_cast<float>(strideH)) + 1));
427 outputShape.add_dim(3);
428 outputShape.set_dim(
429 3, (static_cast<int>(static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
430 boost::numeric_cast<float>(strideW)) + 1));
431
432 // Load the weight data for ALL groups
433 vector<float> weightData(boost::numeric_cast<size_t>(numGroups * inputShape.dim(1) * outputShape.dim(1) *
434 kernelH * kernelW));
435 GetDataFromBlob(layerParam, weightData, 0);
436
437 const unsigned int weightDimSizes[4] = {
438 static_cast<unsigned int>(outputShape.dim(1)), static_cast<unsigned int>(inputShape.dim(1)), kernelH, kernelW};
439
440 // Bias data - This defaults to true in Caffe
441 TensorInfo biasInfo;
442 vector<float> biasData;
443 convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
444 if (convolution2dDescriptor.m_BiasEnabled)
445 {
446 biasData.resize(boost::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
447 GetDataFromBlob(layerParam, biasData, 1);
448
449 const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
450 biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
451 }
452
453 const unsigned int numWeightsPerGroup = boost::numeric_cast<unsigned int>(weightData.size()) / numGroups;
454 const unsigned int numBiasesPerGroup = boost::numeric_cast<unsigned int>(biasData.size()) / numGroups;
455
456 armnn::IConnectableLayer* returnLayer = nullptr;
457
458 for (unsigned int g = 0; g < numGroups; ++g)
459 {
460 // set the slot index, group 0 should be connected to the 0th output of the splitter
461 // group 1 should be connected to the 1st output of the splitter
462
463 // Pull out the weights for this group from that loaded from the model file earlier
464 ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
465 weightData.data() + numWeightsPerGroup * g);
466
467 IConnectableLayer* convLayer = nullptr;
468 if (convolution2dDescriptor.m_BiasEnabled)
469 {
470 // Pull out the biases for this group from that loaded from the model file earlier
471 ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
472
473 convLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
474 weights, biases, convLayerNames[g].c_str());
475 }
476 else
477 {
478 convLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
479 weights, convLayerNames[g].c_str());
480 }
481 convLayers[g] = convLayer;
482
483 // If we have more than one group then the input to the nth convolution the splitter layer's nth output,
484 // otherwise it's the regular input to this layer.
485 armnn::IOutputSlot& splitterInputConnection = splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
486 splitterInputConnection.Connect(convLayer->GetInputSlot(0));
487 convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
488
489 returnLayer = convLayer;
490 }
491
492 if (numGroups > 1)
493 {
494 // If the convolution was performed in chunks, add a layer to merge the results
495
496 // The merge input shape matches that of the convolution output
497 unsigned int mergeDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
498 static_cast<unsigned int>(outputShape.dim(1)),
499 static_cast<unsigned int>(outputShape.dim(2)),
500 static_cast<unsigned int>(outputShape.dim(3))};
501
502 // This is used to describe how the input is to be merged
503 OriginsDescriptor mergeDesc(numGroups);
504
505 // Now create an input node for each group, using the name from
506 // the output of the corresponding convolution
507 for (unsigned int g = 0; g < numGroups; ++g)
508 {
509 mergeDesc.SetViewOriginCoord(g, 1, mergeDimSizes[1] * g);
510 }
511
512 // Make sure the output from the merge is the correct size to hold the data for all groups
513 mergeDimSizes[1] *= numGroups;
514 outputShape.set_dim(1, mergeDimSizes[1]);
515
516 // The merge layer just assumes the name of the original convolution
517 // layer so the following layer connection "just works"
518 const string mergeOutputName = layerParam.name();
519
520 // Finally add the merge layer
521 IConnectableLayer* layer = m_Network->AddMergerLayer(mergeDesc, mergeOutputName.c_str());
522
523 for (unsigned int g = 0; g < numGroups; ++g)
524 {
525 convLayers[g]->GetOutputSlot(0).Connect(layer->GetInputSlot(g));
526 }
527 layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, mergeDimSizes, DataType::Float32));
528
529 returnLayer = layer;
530 }
531
532 BOOST_ASSERT(returnLayer);
533 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
534}
535
536void CaffeParser::ParsePoolingLayer(const LayerParameter& layerParam)
537{
538 ValidateNumInputsOutputs(layerParam, 1, 1);
539
540 PoolingParameter param = layerParam.pooling_param();
541
542 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
543
544 // Kernel size
545 unsigned int kernel_h = 0;
546 unsigned int kernel_w = 0;
547 if (param.has_kernel_h() && param.has_kernel_w())
548 {
549 kernel_h = param.kernel_h();
550 kernel_w = param.kernel_w();
551 }
552 else if (param.kernel_size() > 0)
553 {
554 kernel_h = param.kernel_size();
555 kernel_w = param.kernel_size();
556 }
557 else if (param.has_global_pooling())
558 {
559 kernel_h = inputInfo.GetShape()[2];
560 kernel_w = inputInfo.GetShape()[3];
561 }
562 else
563 {
564 throw ParseException("Loading Pooling Layer: Kernel Size defined Illegally");
565 }
566
567 if (!IsInRange(kernel_h, 0, 11) || !IsInRange(kernel_w, 0, 11) || (kernel_h != kernel_w))
568 {
569 throw ParseException(boost::str(
570 boost::format("Loading Pooling Layer: kernel has invalid size: %1% x %2%") % kernel_h % kernel_w));
571 }
572
573 // Strides
574 // Default to a valid value for the case of global pooling (where the strides don't have to be explicitly set)
575 unsigned int stride_h = 1;
576 unsigned int stride_w = 1;
577 if (param.has_stride_h() && param.has_stride_w())
578 {
579 stride_h = param.stride_h();
580 stride_w = param.stride_w();
581 }
582 else if (param.has_stride())
583 {
584 stride_h = param.stride();
585 stride_w = param.stride();
586 }
587 else if (!param.has_global_pooling())
588 {
589 throw ParseException("Loading Pooling Layer: Stride Size defined Illegally");
590 }
591
592 if (!IsInRange(stride_h, 0, 11) || !IsInRange(stride_w, 0, 11) || (stride_h != stride_w))
593 {
594 throw ParseException("Loading Pooling Layer: stride has invalid size");
595 }
596
597 // Padding
598 unsigned int pad_h = 0;
599 unsigned int pad_w = 0;
600 if (param.has_pad_h() && param.has_pad_w())
601 {
602 pad_h = param.pad_h();
603 pad_w = param.pad_w();
604 }
605 else if (param.has_pad())
606 {
607 pad_h = param.pad();
608 pad_w = param.pad();
609 }
610 else
611 {
612 pad_h = 0;
613 pad_w = 0;
614 }
615
616 if (!IsInRange(pad_h, 0, 11) || !IsInRange(pad_w, 0, 11) || (pad_h != pad_w))
617 {
618 throw ParseException("Loading Pooling Layer: pad has invalid size");
619 }
620
621 // Ignored Caffe Parameters
622 // Stochastic Pooling
623 // Engine
624
625 // Populate Weight and Bias Filter Descriptor
626 Pooling2dDescriptor pooling2dDescriptor;
627 if (param.has_pool())
628 {
629 PoolingParameter_PoolMethod p = param.pool();
630 switch (p)
631 {
632 case PoolingParameter_PoolMethod_MAX:
633 {
634 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Max;
635 break;
636 }
637 case PoolingParameter_PoolMethod_AVE:
638 {
639 pooling2dDescriptor.m_PoolType = PoolingAlgorithm::Average;
640 break;
641 }
642 case PoolingParameter_PoolMethod_STOCHASTIC:
643 {
644 throw ParseException("Loading Pooling Layer: Stochastic Pooling Not Supported");
645 }
646 default:
647 {
648 throw ParseException("Loading Pooling Layer: Mode Not Supported");
649 }
650 }
651 }
652 else
653 {
654 throw ParseException("Loading Pooling Layer: No Pooling Method Defined");
655 }
656
657 pooling2dDescriptor.m_PadLeft = pad_w;
658 pooling2dDescriptor.m_PadRight = pad_w;
659 pooling2dDescriptor.m_PadTop = pad_h;
660 pooling2dDescriptor.m_PadBottom = pad_h;
661 pooling2dDescriptor.m_StrideX = stride_w;
662 pooling2dDescriptor.m_StrideY = stride_h;
663 pooling2dDescriptor.m_PoolWidth = kernel_w;
664 pooling2dDescriptor.m_PoolHeight = kernel_h;
665
666 pooling2dDescriptor.m_OutputShapeRounding = OutputShapeRounding::Ceiling;
667 pooling2dDescriptor.m_PaddingMethod = PaddingMethod::IgnoreValue;
668
669 armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
670 layerParam.name().c_str());
671
672
673 TensorInfo outputInfo(
674 { inputInfo.GetShape()[0],
675 inputInfo.GetShape()[1],
676 static_cast<unsigned int>(ceil(
677 static_cast<float>(inputInfo.GetShape()[2] + 2 * pad_h - kernel_h) /
678 boost::numeric_cast<float>(stride_h))) + 1,
679 static_cast<unsigned int>(ceil(
680 static_cast<float>(inputInfo.GetShape()[3] + 2 * pad_w - kernel_w) /
681 boost::numeric_cast<float>(stride_w))) + 1 },
682 DataType::Float32);
683
684 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(poolingLayer->GetInputSlot(0));
685 poolingLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
686 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
687}
688
689void CaffeParser::ParseReluLayer(const LayerParameter& layerParam)
690{
691 ValidateNumInputsOutputs(layerParam, 1, 1);
692
693 const string& name = layerParam.name();
694 const ReLUParameter& param = layerParam.relu_param();
695
696 ActivationDescriptor activationDescriptor;
697 const float negativeSlope = param.negative_slope();
698 if (negativeSlope == 0.0f)
699 {
700 activationDescriptor.m_Function = ActivationFunction::ReLu;
701 }
702 else
703 {
704 activationDescriptor.m_Function = ActivationFunction::LeakyReLu;
705 activationDescriptor.m_A = negativeSlope;
706 }
707
708 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
709 IConnectableLayer* const activationLayer = m_Network->AddActivationLayer(activationDescriptor, name.c_str());
710 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(activationLayer->GetInputSlot(0));
711 activationLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
712 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
713}
714
715void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
716{
717 ValidateNumInputsOutputs(layerParam, 1, 1);
718
719 LRNParameter param = layerParam.lrn_param();
720
721 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
722
723 // Ignored BATCH NORMALIZATION Caffe Parameters
724 // Ignored MVN Caffe Parameters
725 // Ignored LRN Caffe Parameters
726 // Engine
727
728 NormalizationDescriptor normalizationDescriptor;
729 if (param.has_norm_region())
730 {
731 LRNParameter_NormRegion n = param.norm_region();
732 switch (n)
733 {
734 case LRNParameter_NormRegion_ACROSS_CHANNELS:
735 {
736 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
737 break;
738 }
739 case LRNParameter_NormRegion_WITHIN_CHANNEL:
740 {
741 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Within;
742 break;
743 }
744 default:
745 throw ParseException("Loading LRN Layer: Mode Not Supported");
746 }
747 }
748 else
749 {
750 // Caffe defaults to normalization across channels
751 normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
752 }
753
754 normalizationDescriptor.m_NormMethodType = NormalizationAlgorithmMethod::LocalBrightness;
755 if (param.has_local_size())
756 {
757 normalizationDescriptor.m_NormSize = param.local_size();
758 }
759 else
760 {
761 throw ParseException("Loading LRN Layer: Local_size not defined");
762 }
763
764 if (param.has_alpha())
765 {
766 normalizationDescriptor.m_Alpha = param.alpha();
767 normalizationDescriptor.m_Alpha /= boost::numeric_cast<float>(param.local_size());
768 }
769 else
770 {
771 throw ParseException("Loading LRN Layer: Alpha not defined");
772 }
773 if (param.has_beta())
774 {
775 normalizationDescriptor.m_Beta = param.beta();
776 }
777 else
778 {
779 throw ParseException("Loading LRN Layer: Beta not defined");
780 }
781 if (param.has_k())
782 {
783 normalizationDescriptor.m_K = param.k();
784 }
785 else
786 normalizationDescriptor.m_K = 1;
787
788 IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
789 layerParam.name().c_str());
790 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(normLayer->GetInputSlot(0));
791 normLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
792
793 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
794}
795
796void CaffeParser::ParseInnerProductLayer(const LayerParameter& layerParam)
797{
798 InnerProductParameter param = layerParam.inner_product_param();
799
800 ValidateNumInputsOutputs(layerParam, 1, 1);
801
802 unsigned int outputSize = param.num_output();
803
804 // Ignored Caffe Parameters
805 // Weight Filler
806 // Bias Filler
807 // Engine
808 // Axis
809
810 FullyConnectedDescriptor tensorFullyConnectedDescriptor;
811
812 if (param.has_transpose())
813 {
814 // If true assume transposed weights
815 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
816 }
817 else
818 {
819 // caffe defaults to transposed
820 tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
821 }
822
823 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
824
825 TensorInfo weightInfo;
826 TensorInfo biasInfo;
827
828 // allow implicit flattening of extra dimensions
829 unsigned int inputSize = inputInfo.GetShape()[1];
830 for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
831 {
832 inputSize *= inputInfo.GetShape()[i];
833 }
834
835 vector<float> weightData(inputSize * outputSize);
836
837 GetDataFromBlob(layerParam, weightData, 0);
838 const unsigned int swTD[2] = { outputSize, inputSize };
839 ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightData);
840
841 tensorFullyConnectedDescriptor.m_BiasEnabled = true;
842 // Todo: check whether bias enabled
843 armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
844 if (tensorFullyConnectedDescriptor.m_BiasEnabled)
845 {
846 // BIAS VALUE
847 vector<float> biasData(outputSize);
848
849 GetDataFromBlob(layerParam, biasData, 1);
850
851 const unsigned int sbTD[1] = { outputSize };
852
853 ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasData);
854
855 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor, weights, biases,
856 layerParam.name().c_str());
857 }
858 else
859 {
860 fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor, weights,
861 layerParam.name().c_str());
862 }
863
864 TensorInfo outputInfo({ inputInfo.GetShape()[0], outputSize }, DataType::Float32);
865 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(fullyConnectedLayer->GetInputSlot(0));
866 fullyConnectedLayer->GetOutputSlot(0).SetTensorInfo(outputInfo);
867 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
868}
869
870void CaffeParser::ParseSoftmaxLayer(const LayerParameter& layerParam)
871{
872 ValidateNumInputsOutputs(layerParam, 1, 1);
873
874 SoftmaxParameter param = layerParam.softmax_param();
875
876 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
877
878 // Ignored Caffe Parameters
879 // axis
880 // Engine
881
882 armnn::SoftmaxDescriptor softmaxDescriptor;
883 armnn::IConnectableLayer* const softmaxLayer = m_Network->AddSoftmaxLayer(
884 softmaxDescriptor,
885 layerParam.name().c_str());
886 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(softmaxLayer->GetInputSlot(0));
887 softmaxLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
888 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
889}
890
891void CaffeParser::ParseEltwiseLayer(const LayerParameter& layerParam)
892{
893 ValidateNumInputsOutputs(layerParam, 2, 1);
894
895 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
896
897 // Ignored Caffe Parameters
898 // coeff
899
900 EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // default to sum as per caffe
901
902 if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
903 {
904 operation = layerParam.eltwise_param().operation();
905 }
906
907 armnn::IConnectableLayer* newLayer = nullptr;
908 switch (operation)
909 {
910 case EltwiseParameter_EltwiseOp_SUM:
911 {
912 newLayer = m_Network->AddAdditionLayer(layerParam.name().c_str());
913 break;
914 }
915 case EltwiseParameter_EltwiseOp_PROD:
916 {
917 newLayer = m_Network->AddMultiplicationLayer(layerParam.name().c_str());
918 break;
919 }
920 default:
921 {
922 throw ParseException("Unsupported operation in Eltwise layer");
923 }
924 }
925
926 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(newLayer->GetInputSlot(0));
927 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(1)).Connect(newLayer->GetInputSlot(1));
928 newLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
929 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
930}
931
932void CaffeParser::ParseConcatLayer(const LayerParameter& layerParam)
933{
934 unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
935 // we assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3)
936 unsigned int concatDim = 1;
937 unsigned int numOfDims = 4;
938
939 OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);// we only consider 4-D tensor here
940 std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
941
942 unsigned int mergeDim = 0;
943 for (unsigned int viewIndex = 0; viewIndex < numInputs; ++viewIndex)
944 {
945 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
946 layerParam.bottom(boost::numeric_cast<int>(viewIndex))).GetTensorInfo();
947 // Check whether the dimensions of the input tensors are actually 4
948 if (inputInfo.GetNumDimensions()!=4)
949 {
950 throw ParseException("The number of dimensions for input tensors of the concatenation op should be 4.");
951 }
952
953 mergeDimSizes[0] = inputInfo.GetShape()[0];
954 mergeDimSizes[1] = inputInfo.GetShape()[1];
955 mergeDimSizes[2] = inputInfo.GetShape()[2];
956 mergeDimSizes[3] = inputInfo.GetShape()[3];
957
958 for (unsigned int j = 0; j < concatDim; ++j)
959 {
960 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
961 }
962
963 concatDescriptor.SetViewOriginCoord(viewIndex, concatDim, mergeDim);
964 mergeDim += mergeDimSizes[concatDim];
965
966 for (unsigned int j = concatDim+1; j < numOfDims; ++j)
967 {
968 concatDescriptor.SetViewOriginCoord(viewIndex, j, 0);
969 }
970 }
971 mergeDimSizes[concatDim] = mergeDim;
972
973 armnn::IConnectableLayer *concatlayer = m_Network->AddMergerLayer(concatDescriptor, layerParam.name().c_str());
974 for (unsigned int i = 0; i < numInputs; ++i)
975 {
976 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(boost::numeric_cast<int>(i)));
977 outputSlot.Connect(concatlayer->GetInputSlot(i));
978 }
979
980 concatlayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(numOfDims, mergeDimSizes.data(), DataType::Float32));
981 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
982}
983
984void CaffeParser::ParseBatchNormLayer(const LayerParameter& layerParam)
985{
986 ValidateNumInputsOutputs(layerParam, 1, 1);
987
988 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
989
990 string name = layerParam.name();
991
992 BatchNormParameter param = layerParam.batch_norm_param();
993 // If use_global_stats is not explicitly set in the model, assume it to be true (its default value
994 // when the network is in the testing phase).
995 if (param.has_use_global_stats())
996 {
997 if (!param.use_global_stats())
998 {
999 throw ParseException(boost::str(boost::format("Error parsing Batch Norm layer '%1%': "
1000 "Parameter 'use_global_stats' is set to false, which is unsupported (value used for training).")
1001 % name));
1002 }
1003 }
1004
1005 BatchNormalizationDescriptor desc;
1006 desc.m_Eps = param.eps();
1007
1008 unsigned int channels = inputInfo.GetShape()[1];
1009 unsigned int shape[] = {channels};
1010
1011 vector<float> meanData(channels);
1012 GetDataFromBlob(layerParam, meanData, 0);
1013
1014 vector<float> varianceData(channels);
1015 GetDataFromBlob(layerParam, varianceData, 1);
1016
1017 // identity scale operation
1018 vector<float> betaData(channels, 0.0f);
1019 vector<float> gammaData(channels, 1.0f);
1020
1021 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1022 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1023 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1024 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1025
1026 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1027 mean, variance, beta, gamma, name.c_str());
1028 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1029 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1030 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1031}
1032
1033void CaffeParser::ParseScaleLayer(const LayerParameter& layerParam)
1034{
1035 // current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance
1036 ValidateNumInputsOutputs(layerParam, 1, 1);
1037
1038 const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
1039
1040 string name = layerParam.name();
1041
1042 ScaleParameter param = layerParam.scale_param();
1043 if (param.axis() != 1)
1044 {
1045 // Would have to use something other than BatchNormalizationLayer in this case
1046 throw ParseException("Loading Scale Layer: Only axis 1 supported currently");
1047 }
1048
1049 unsigned int channels = inputInfo.GetShape()[1];
1050 unsigned int shape[] = {channels};
1051
1052 BatchNormalizationDescriptor desc;
1053 desc.m_Eps = 0.0f; // don't need epsilon if variance is 1
1054 vector<float> meanData(channels, 0.0f);
1055 vector<float> varianceData(channels, 1.0f);
1056 vector<float> betaData(channels, 0.0f);
1057 vector<float> gammaData(channels);
1058
1059 GetDataFromBlob(layerParam, gammaData, 0);
1060
1061 if(param.has_bias_term())
1062 {
1063 GetDataFromBlob(layerParam, betaData, 1);
1064 }
1065
1066 ConstTensor mean(TensorInfo(1, shape, armnn::DataType::Float32), meanData);
1067 ConstTensor variance(TensorInfo(1, shape, armnn::DataType::Float32), varianceData);
1068 ConstTensor beta(TensorInfo(1, shape, armnn::DataType::Float32), betaData);
1069 ConstTensor gamma(TensorInfo(1, shape, armnn::DataType::Float32), gammaData);
1070
1071 armnn::IConnectableLayer* const batchNormLayer = m_Network->AddBatchNormalizationLayer(desc,
1072 mean, variance, beta, gamma, name.c_str());
1073 GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).Connect(batchNormLayer->GetInputSlot(0));
1074 batchNormLayer->GetOutputSlot(0).SetTensorInfo(inputInfo);
1075 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
1076}
1077
1078void CaffeParser::ParseSplitLayer(const caffe::LayerParameter& layerParam)
1079{
1080 // Used in caffe to duplicate memory - not necessary in armnn
1081 if (layerParam.bottom_size() != 1)
1082 {
1083 throw ParseException("Split layer '" + layerParam.name() + "' should have exactly 1 bottom");
1084 }
1085 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
1086 for (int i = 0; i < layerParam.top_size(); i++)
1087 {
1088 SetArmnnOutputSlotForCaffeTop(layerParam.top(i), outputSlot);
1089 }
1090}
1091
1092void CaffeParser::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
1093{
1094 // Ignored for inference so patch the single input to its single output
1095 if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
1096 {
1097 throw ParseException("Dropout layer '" + layerParam.name() + "' should have exactly 1 bottom and 1 top");
1098 }
1099 SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
1100}
1101
1102void CaffeParser::TrackInputBinding(armnn::IConnectableLayer* layer,
1103 armnn::LayerBindingId id,
1104 const armnn::TensorInfo& tensorInfo)
1105{
1106 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
1107}
1108
1109void CaffeParser::TrackOutputBinding(armnn::IConnectableLayer* layer,
1110 armnn::LayerBindingId id,
1111 const armnn::TensorInfo& tensorInfo)
1112{
1113 return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
1114}
1115
1116void CaffeParser::TrackBindingPoint(armnn::IConnectableLayer* layer,
1117 armnn::LayerBindingId id,
1118 const armnn::TensorInfo& tensorInfo,
1119 const char* bindingPointDesc,
1120 std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
1121{
1122 const std::string layerName = layer->GetName();
1123 auto it = nameToBindingInfo.find(layerName);
1124 if (it == nameToBindingInfo.end())
1125 {
1126 nameToBindingInfo[layerName] = std::make_pair(id, tensorInfo);
1127 }
1128 else
1129 {
1130 throw ParseException(boost::str(
1131 boost::format("Id %1% used by more than one %2% layer") % id % bindingPointDesc));
1132 }
1133}
1134
1135armnn::IOutputSlot& CaffeParser::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
1136{
1137 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1138 if (it != m_ArmnnOutputSlotForCaffeTop.end())
1139 {
1140 return *it->second;
1141 }
1142 else
1143 {
1144 throw ParseException(boost::str(boost::format(
1145 "Could not find armnn output slot for Caffe top '%1%'") % caffeTopName));
1146 }
1147}
1148
1149void CaffeParser::SetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
1150{
1151 auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
1152 if (it == m_ArmnnOutputSlotForCaffeTop.end())
1153 {
1154 m_ArmnnOutputSlotForCaffeTop[caffeTopName] = &armnnOutputSlot;
1155 }
1156 else
1157 {
1158 throw ParseException("Attempting to add duplicate entry for Caffe top '" + caffeTopName + "'");
1159 }
1160}
1161
1162void CaffeParser::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
1163{
1164 // Find layers with the same top
1165 std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
1166 for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
1167 {
1168 caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
1169 for (int i = 0; i < layer.top_size(); ++i)
1170 {
1171 layersByTop[layer.top(i)].push_back(&layer);
1172 }
1173 }
1174
1175 // For each set of layers with the same top, resolve them to a linear chain rather than in-place layers.
1176 // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
1177 for (auto layersWithSameTopIt : layersByTop)
1178 {
1179 const std::string& top = layersWithSameTopIt.first;
1180 const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
1181
1182 // Chain the layers together in the order that they are listed in the prototxt (hopefully this is correct).
1183 // Note that the last layer will not have its top modified so that other layers will continue to reference it.
1184 for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
1185 {
1186 caffe::LayerParameter& layer1 = *layersWithSameTop[layerIdx];
1187 caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
1188 if (layer1.top_size() != 1)
1189 {
1190 throw ParseException("Node '" + layer1.name() + "' is an in-place layer but "
1191 "doesn't have exactly one top.");
1192 }
1193 std::string newTop = layer1.name() + "_top";
1194 layer1.set_top(0, newTop);
1195 if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
1196 {
1197 throw ParseException("Node '" + layer2.name() + "' is an in-place layer but "
1198 " doesn't have exactly one bottom, or it doesn't match its top.");
1199 }
1200 layer2.set_bottom(0, newTop);
1201 }
1202 }
1203}
1204
1205void CaffeParser::LoadNetParam(NetParameter& netParameter)
1206{
1207 // caffe models sometimes have an implicit input layer.
1208 // in that case, add an explicit one
1209 if (netParameter.input_size() > 0)
1210 {
1211 LayerParameter* newLayer = netParameter.add_layer();
1212
1213 newLayer->set_type("Input");
1214 newLayer->set_name(netParameter.input(0));
1215 newLayer->add_top(netParameter.input(0));
1216
1217 InputParameter* inputParam = newLayer->mutable_input_param();
1218 BlobShape* shape = inputParam->add_shape();
1219
1220 int dim_size = netParameter.input_dim_size();
1221 for (int i = 0; i < dim_size; ++i)
1222 {
1223 shape->add_dim(netParameter.input_dim(i));
1224 }
1225 }
1226
1227 // Replace in-place layers with regular ones to make the rest of the parsing easier.
1228 ResolveInPlaceLayers(netParameter);
1229
1230 // Create a lookup of Caffe layers by name
1231 for (int i = 0; i < netParameter.layer_size(); ++i)
1232 {
1233 const caffe::LayerParameter& layer = netParameter.layer(i);
1234 for (int i = 0; i < layer.top_size(); ++i)
1235 {
1236 m_CaffeLayersByTopName[layer.top(i)] = &layer;
1237 }
1238 }
1239
1240 // Find the output layers the user requested
1241 std::vector<const caffe::LayerParameter*> targetLayers;
1242 for (const std::string& requestedOutputName : m_RequestedOutputs)
1243 {
1244 auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
1245 if (nodeIt == m_CaffeLayersByTopName.end())
1246 {
1247 throw ParseException("Couldn't find requested output layer '" + requestedOutputName + "' in graph");
1248 }
1249 targetLayers.push_back(nodeIt->second);
1250 }
1251
1252 // Sort them into a linear ordering such that all inputs of a node are before the node itself
1253 std::vector<const caffe::LayerParameter*> sortedNodes;
1254 if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
1255 targetLayers,
1256 [this](const caffe::LayerParameter* node)
1257 {
1258 return GetInputs(*node);
1259 },
1260 sortedNodes))
1261 {
1262 throw ParseException("Cycle detected in graph");
1263 }
1264
1265 // Parse each node in order, knowing that all inputs of a node will be processed before the node itself
1266 for (const caffe::LayerParameter* current : sortedNodes)
1267 {
1268 auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
1269 if (it == ms_CaffeLayerNameToParsingFunctions.end())
1270 {
1271 throw ParseException("Unsupported layer type '" + current->type() + "'");
1272 }
1273 auto func = it->second;
1274 (this->*func)(*current);
1275 }
1276
1277 // Add ArmNN output layers connected to each requested output
1278 for (const std::string& requestedOutput : m_RequestedOutputs)
1279 {
1280 armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
1281
1282 const armnn::LayerBindingId outputId = boost::numeric_cast<armnn::LayerBindingId>(
1283 m_NetworkOutputsBindingInfo.size());
1284 armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
1285 outputSlot.Connect(outputLayer->GetInputSlot(0));
1286
1287 TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
1288 }
1289}
1290
1291INetworkPtr CaffeParser::CreateNetworkFromTextFile(const char* graphFile,
1292 const std::map<std::string, armnn::TensorShape>& inputShapes,
1293 const std::vector<std::string>& requestedOutputs)
1294{
1295 FILE* fd = fopen(graphFile, "r");
1296
1297 if (fd == nullptr)
1298 {
1299 std::stringstream error;
1300 error << "Graph file " << graphFile << " failed to open";
1301 throw FileNotFoundException(error.str());
1302 }
1303
1304 // Parse the file into a message
1305 NetParameter netParam;
1306 auto input = new google::protobuf::io::FileInputStream(fileno(fd));
1307 bool success = google::protobuf::TextFormat::Parse(input, &netParam);
1308 delete input;
1309 fclose(fd);
1310
1311 if (!success)
1312 {
1313 std::stringstream error;
1314 error << "Failed to parse graph file";
1315 throw ParseException(error.str());
1316 }
1317
1318 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1319}
1320
1321INetworkPtr CaffeParser::CreateNetworkFromString(const char* protoText,
1322 const std::map<std::string, armnn::TensorShape>& inputShapes,
1323 const std::vector<std::string>& requestedOutputs)
1324{
1325 // Parse the string into a message
1326 NetParameter netParam;
1327 bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
1328
1329 if (!success)
1330 {
1331 std::stringstream error;
1332 error << "Failed to parse graph string";
1333 throw ParseException(error.str());
1334 }
1335
1336 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1337}
1338
1339INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
1340 const std::map<std::string, armnn::TensorShape>& inputShapes,
1341 const std::vector<std::string>& requestedOutputs)
1342{
1343 FILE* fd = fopen(graphFile, "rb");
1344
1345 if (fd == nullptr)
1346 {
1347 std::stringstream error;
1348 error << "Graph file " << graphFile << " failed to open";
1349 throw FileNotFoundException(error.str());
1350 }
1351
1352 // Parse the file into a message
1353 NetParameter netParam;
1354
1355 FileInputStream inStream(fileno(fd));
1356 CodedInputStream codedStream(&inStream);
1357 codedStream.SetTotalBytesLimit(INT_MAX, INT_MAX);
1358 bool success = netParam.ParseFromCodedStream(&codedStream);
1359 fclose(fd);
1360
1361 if (!success)
1362 {
1363 std::stringstream error;
1364 error << "Failed to parse protobuf file" << graphFile;
1365 throw ParseException(error.str());
1366 }
1367
1368 return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
1369}
1370
1371INetworkPtr CaffeParser::CreateNetworkFromNetParameter(NetParameter& netParam,
1372 const std::map<std::string, armnn::TensorShape>& inputShapes,
1373 const std::vector<std::string>& requestedOutputs)
1374{
1375 m_NetworkInputsBindingInfo.clear();
1376 m_NetworkOutputsBindingInfo.clear();
1377
1378 m_Network = INetwork::Create();
1379
1380 m_InputShapes = inputShapes;
1381 if (requestedOutputs.size() == 0)
1382 {
1383 throw ParseException("requestedOutputs must have at least one entry");
1384 }
1385 m_RequestedOutputs = requestedOutputs;
1386
1387 try
1388 {
1389 LoadNetParam(netParam);
1390 }
1391 catch (const ParseException& e)
1392 {
1393 Cleanup();
1394 throw e;
1395 }
1396
1397 Cleanup();
1398
1399 return move(m_Network);
1400}
1401
1402void CaffeParser::Cleanup()
1403{
1404 // cleanup, in case we reuse this parser
1405 m_CaffeLayersByTopName.clear();
1406 m_InputShapes.clear();
1407 m_RequestedOutputs.clear();
1408 m_ArmnnOutputSlotForCaffeTop.clear();
1409}
1410
1411}
1412
1413