aboutsummaryrefslogtreecommitdiff
path: root/src/armnnCaffeParser
diff options
context:
space:
mode:
authortelsoa01 <telmo.soares@arm.com>2018-08-31 09:22:23 +0100
committertelsoa01 <telmo.soares@arm.com>2018-08-31 09:22:23 +0100
commitc577f2c6a3b4ddb6ba87a882723c53a248afbeba (patch)
treebd7d4c148df27f8be6649d313efb24f536b7cf34 /src/armnnCaffeParser
parent4c7098bfeab1ffe1cdc77f6c15548d3e73274746 (diff)
downloadarmnn-c577f2c6a3b4ddb6ba87a882723c53a248afbeba.tar.gz
Release 18.08
Diffstat (limited to 'src/armnnCaffeParser')
-rw-r--r--src/armnnCaffeParser/CaffeParser.cpp1311
-rw-r--r--src/armnnCaffeParser/CaffeParser.hpp141
-rw-r--r--src/armnnCaffeParser/CaffeSupport.md5
-rw-r--r--src/armnnCaffeParser/RecordByRecordCaffeParser.cpp732
-rw-r--r--src/armnnCaffeParser/RecordByRecordCaffeParser.hpp53
-rw-r--r--src/armnnCaffeParser/test/TestAdd.cpp2
-rw-r--r--src/armnnCaffeParser/test/TestConcat.cpp2
-rw-r--r--src/armnnCaffeParser/test/TestConvolution.cpp133
-rw-r--r--src/armnnCaffeParser/test/TestDropout.cpp2
-rw-r--r--src/armnnCaffeParser/test/TestInPlace.cpp4
-rw-r--r--src/armnnCaffeParser/test/TestInputs.cpp22
-rw-r--r--src/armnnCaffeParser/test/TestMul.cpp2
-rw-r--r--src/armnnCaffeParser/test/TestMultiInputsOutputs.cpp2
-rw-r--r--src/armnnCaffeParser/test/TestPooling2d.cpp2
-rw-r--r--src/armnnCaffeParser/test/TestSplit.cpp2
15 files changed, 1930 insertions, 485 deletions
diff --git a/src/armnnCaffeParser/CaffeParser.cpp b/src/armnnCaffeParser/CaffeParser.cpp
index 254a819db4..35f458989f 100644
--- a/src/armnnCaffeParser/CaffeParser.cpp
+++ b/src/armnnCaffeParser/CaffeParser.cpp
@@ -3,6 +3,7 @@
// See LICENSE file in the project root for full license information.
//
#include "CaffeParser.hpp"
+#include "RecordByRecordCaffeParser.hpp"
#include "armnn/Descriptors.hpp"
#include "armnn/INetwork.hpp"
@@ -10,6 +11,7 @@
#include "armnn/Exceptions.hpp"
#include "GraphTopologicalSort.hpp"
+#include "VerificationHelpers.hpp"
#include <boost/numeric/conversion/cast.hpp>
#include <boost/assert.hpp>
@@ -43,7 +45,8 @@
/// This contains a flat list of Caffe 'layers' (e.g. convolution, pooling etc.).
/// Each layer has inputs (called "bottoms") and outputs (called "tops"). Data flows from bottom to top.
/// The bottoms of a layer refer to the tops of other layers, not their names.
-/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't need any other changes).
+/// The names of layers seem to be arbitrary (you could rename a layer and the network wouldn't
+/// need any other changes).
///
/// Some layers (e.g. Relu) can be configured so that their top and bottom are both the same. This is called an
/// "in-place" layer and is a Caffe runtime feature used to reduce memory usage by modifying tensors in-place.
@@ -58,63 +61,65 @@ using namespace caffe;
using namespace std;
using namespace google::protobuf::io;
-const std::map<std::string, CaffeParser::OperationParsingFunction> CaffeParser::ms_CaffeLayerNameToParsingFunctions = {
- { "Input", &CaffeParser::ParseInputLayer },
- { "Convolution", &CaffeParser::ParseConvLayer },
- { "Pooling", &CaffeParser::ParsePoolingLayer },
- { "ReLU", &CaffeParser::ParseReluLayer },
- { "LRN", &CaffeParser::ParseLRNLayer },
- { "InnerProduct", &CaffeParser::ParseInnerProductLayer },
- { "Softmax", &CaffeParser::ParseSoftmaxLayer },
- { "Eltwise", &CaffeParser::ParseEltwiseLayer },
- { "Concat", &CaffeParser::ParseConcatLayer },
- { "BatchNorm", &CaffeParser::ParseBatchNormLayer },
- { "Scale", &CaffeParser::ParseScaleLayer },
- { "Split", &CaffeParser::ParseSplitLayer },
- { "Dropout", &CaffeParser::ParseDropoutLayer},
-};
-
-ICaffeParser* ICaffeParser::CreateRaw()
-{
- return new CaffeParser();
-}
-
-ICaffeParserPtr ICaffeParser::Create()
+namespace
{
- return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
-}
-void ICaffeParser::Destroy(ICaffeParser* parser)
+const float* GetArrayPtrFromBlob(const LayerParameter& layerParam, unsigned int blobIndex)
{
- delete parser;
-}
+ auto nBlobs = layerParam.blobs_size();
+ if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
+ {
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Expected data blob at index %1% in layer %2% not found. nBlobs=%2%. %4%") %
+ blobIndex %
+ layerParam.name() %
+ nBlobs %
+ CHECK_LOCATION().AsString()));
+ }
-CaffeParser::CaffeParser()
-: m_Network(nullptr, nullptr)
-{
+ const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
+ const float* arrayPtr = blob.data().data();
+ return arrayPtr;
}
void GetDataFromBlob(const LayerParameter& layerParam, vector<float>& outData, unsigned int blobIndex)
{
- if (blobIndex >= boost::numeric_cast<unsigned int>(layerParam.blobs_size()))
+ auto nBlobs = layerParam.blobs_size();
+ if (blobIndex >= boost::numeric_cast<unsigned int>(nBlobs))
{
- throw ParseException(boost::str(boost::format("Expected data blob at index %1% in layer %2% not found")
- % blobIndex % layerParam.name()));
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Expected data blob at index %1% in layer %2% not found. %3%") %
+ blobIndex %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(blobIndex));
- if (boost::numeric_cast<size_t>(blob.data_size()) != outData.size())
+ size_t blobSize = boost::numeric_cast<size_t>(blob.data_size());
+ if (blobSize != outData.size())
{
- throw ParseException(boost::str(boost::format(
- "Data blob at index %1% in layer %2% has an unexpected size. Expected %3% elements but got %4% elements")
- % blobIndex % layerParam.name() % outData.size() % blob.data_size()));
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Data blob at index %1% in layer %2% has an unexpected size. "
+ "Expected %3% elements but got %4% elements. %5%") %
+ blobIndex %
+ layerParam.name() %
+ outData.size() %
+ blobSize %
+ CHECK_LOCATION().AsString()));
}
- for (unsigned int i = 0; i < outData.size(); ++i)
+ int outSizeInt = boost::numeric_cast<int>(outData.size());
+ for (int i = 0; i < outSizeInt; ++i)
{
- outData[i] = blob.data(boost::numeric_cast<int>(i));
+ outData[static_cast<size_t>(i)] = blob.data(i);
}
}
@@ -136,39 +141,213 @@ void ValidateNumInputsOutputs(const caffe::LayerParameter& layerParameter,
int numInputsActual = layerParameter.bottom_size();
if (numInputs != boost::numeric_cast<unsigned int>(numInputsActual))
{
- throw ParseException("Loading layer: invalid number of inputs");
+ throw ParseException(
+ boost::str(
+ boost::format("Invalid number of inputs requested %1% for layer %2% "
+ "while only %3% present. %4%") %
+ numInputs %
+ layerParameter.name() %
+ numInputsActual %
+ CHECK_LOCATION().AsString()));
}
int numOutputsActual = layerParameter.top_size();
if (numOutputs != boost::numeric_cast<unsigned int>(numOutputsActual))
{
- throw ParseException("Loading layer: invalid number of outputs");
+ throw ParseException(
+ boost::str(
+ boost::format("Invalid number of outputs requested %1% for layer %2% "
+ "while only %3% present. %4%") %
+ numOutputs %
+ layerParameter.name() %
+ numOutputsActual %
+ CHECK_LOCATION().AsString()));
}
}
-BindingPointInfo CaffeParser::GetNetworkInputBindingInfo(const std::string& name) const
+template <typename ParamType, typename ExtractOptional, typename ExtractFallback, typename ValueType>
+ValueType GetOptionalWithFallback(const ParamType& param,
+ ExtractOptional extractOptional,
+ ExtractFallback extractFallback,
+ ValueType defaultValue)
+{
+ auto optValue = extractOptional(param, defaultValue);
+ if (optValue.first)
+ {
+ return optValue.second;
+ }
+ auto fallbackValue = extractFallback(param, defaultValue);
+ return fallbackValue.second;
+}
+
+#define GET_OPTIONAL_WITH_VECTOR_FALLBACK(PARAM, \
+ PARAM_TYPE, \
+ OPTIONAL_VALUE, \
+ FALLBACK_VECTOR, \
+ VALUE_TYPE, \
+ DEFAULT_VALUE) \
+ GetOptionalWithFallback( \
+ PARAM, \
+ [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
+ { \
+ if (param.has_##OPTIONAL_VALUE ()) \
+ { \
+ return std::make_pair(true, param.OPTIONAL_VALUE ()); \
+ } \
+ else \
+ { \
+ return std::make_pair(false, defaultValue); \
+ } \
+ }, \
+ [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
+ { \
+ if (param.FALLBACK_VECTOR##_size() > 0) \
+ { \
+ return std::make_pair(true, (param.FALLBACK_VECTOR ()).Get(0)); \
+ } \
+ else \
+ { \
+ return std::make_pair(false, defaultValue); \
+ } \
+ }, \
+ DEFAULT_VALUE)
+
+#define GET_OPTIONAL_WITH_FALLBACK(PARAM, \
+ PARAM_TYPE, \
+ OPTIONAL_VALUE, \
+ FALLBACK_VALUE, \
+ VALUE_TYPE, \
+ DEFAULT_VALUE) \
+ GetOptionalWithFallback( \
+ PARAM, \
+ [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
+ { \
+ if (param.has_##OPTIONAL_VALUE ()) \
+ { \
+ return std::make_pair(true, param.OPTIONAL_VALUE ()); \
+ } \
+ else \
+ { \
+ return std::make_pair(false, defaultValue); \
+ } \
+ }, \
+ [](const PARAM_TYPE & param, VALUE_TYPE defaultValue) \
+ { \
+ if (param.has_##FALLBACK_VALUE ()) \
+ { \
+ return std::make_pair(true, param.FALLBACK_VALUE ()); \
+ } \
+ else \
+ { \
+ return std::make_pair(false, defaultValue); \
+ } \
+ }, \
+ DEFAULT_VALUE)
+
+
+void ValidateEqualValuesInRange(unsigned int valueA,
+ const char* valueNameA,
+ unsigned int valueB,
+ const char* valueNameB,
+ unsigned int min,
+ unsigned int max,
+ const armnn::CheckLocation& location)
+{
+ if (!IsInRange(valueA, min, max) || !IsInRange(valueB, min, max) || (valueA != valueB))
+ {
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "%1%=%2% and %3%=%4% must be equal and within the valid range"
+ "of [%5%, %6%] %7%") %
+ valueNameA %
+ valueA %
+ valueNameB %
+ valueB %
+ min %
+ max %
+ location.AsString()));
+ }
+}
+
+#define VALIDATE_EQUAL_VALUES_IN_RANGE(A, B, MIN_RANGE, MAX_RANGE) \
+ ValidateEqualValuesInRange(A, #A, B, #B, MIN_RANGE, MAX_RANGE, CHECK_LOCATION())
+
+} // namespace <anonymous>
+
+const std::map<std::string, CaffeParserBase::OperationParsingFunction>
+ CaffeParserBase::ms_CaffeLayerNameToParsingFunctions = {
+ { "Input", &CaffeParserBase::ParseInputLayer },
+ { "Convolution", &CaffeParserBase::ParseConvLayer },
+ { "Pooling", &CaffeParserBase::ParsePoolingLayer },
+ { "ReLU", &CaffeParserBase::ParseReluLayer },
+ { "LRN", &CaffeParserBase::ParseLRNLayer },
+ { "InnerProduct", &CaffeParserBase::ParseInnerProductLayer },
+ { "Softmax", &CaffeParserBase::ParseSoftmaxLayer },
+ { "Eltwise", &CaffeParserBase::ParseEltwiseLayer },
+ { "Concat", &CaffeParserBase::ParseConcatLayer },
+ { "BatchNorm", &CaffeParserBase::ParseBatchNormLayer },
+ { "Scale", &CaffeParserBase::ParseScaleLayer },
+ { "Split", &CaffeParserBase::ParseSplitLayer },
+ { "Dropout", &CaffeParserBase::ParseDropoutLayer},
+};
+
+ICaffeParser* ICaffeParser::CreateRaw()
+{
+ return new RecordByRecordCaffeParser();
+}
+
+ICaffeParserPtr ICaffeParser::Create()
+{
+ return ICaffeParserPtr(CreateRaw(), &ICaffeParser::Destroy);
+}
+
+void ICaffeParser::Destroy(ICaffeParser* parser)
+{
+ delete parser;
+}
+
+CaffeParserBase::CaffeParserBase()
+ : m_Network(nullptr, nullptr)
+{
+
+}
+
+CaffeParser::CaffeParser()
+: CaffeParserBase()
+{
+
+}
+
+BindingPointInfo CaffeParserBase::GetNetworkInputBindingInfo(const std::string& name) const
{
return GetBindingInfo(name, "input", m_NetworkInputsBindingInfo);
}
-BindingPointInfo CaffeParser::GetNetworkOutputBindingInfo(const std::string& name) const
+BindingPointInfo CaffeParserBase::GetNetworkOutputBindingInfo(const std::string& name) const
{
return GetBindingInfo(name, "output", m_NetworkOutputsBindingInfo);
}
-std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParser::GetBindingInfo(const std::string& layerName,
+std::pair<armnn::LayerBindingId, armnn::TensorInfo> CaffeParserBase::GetBindingInfo(const std::string& layerName,
const char* bindingPointDesc,
const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo)
{
auto it = nameToBindingInfo.find(layerName);
if (it == nameToBindingInfo.end())
{
- throw InvalidArgumentException(boost::str(boost::format("Unknown %1% '%2%'") % bindingPointDesc % layerName));
+ throw InvalidArgumentException(
+ boost::str(
+ boost::format(
+ "Unknown binding %1% for layer '%2%'. %3%") %
+ bindingPointDesc %
+ layerName %
+ CHECK_LOCATION().AsString()));
}
return it->second;
}
-TensorInfo CaffeParser::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
+TensorInfo CaffeParserBase::BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const
{
std::vector<unsigned int> shape;
for (int j = 0; j < blobShape.dim_size(); ++j)
@@ -191,7 +370,9 @@ BlobShape TensorDescToBlobShape(const TensorInfo& desc)
return ret;
}
-vector<const LayerParameter*> CaffeParser::GetInputs(const LayerParameter& layerParam)
+// Note: can move to CaffeParser when/if we optimise the text/string format
+// to load on a layer by layer basis
+vector<const LayerParameter*> CaffeParserBase::GetInputs(const LayerParameter& layerParam)
{
std::vector<const caffe::LayerParameter*> ret;
ret.reserve(boost::numeric_cast<size_t>(layerParam.bottom_size()));
@@ -202,8 +383,13 @@ vector<const LayerParameter*> CaffeParser::GetInputs(const LayerParameter& layer
if (inputIt == m_CaffeLayersByTopName.end())
{
throw ParseException(
- "Can't find Caffe layer with top called '" + inputName + "', which is listed as an input of '" +
- layerParam.name() + "'");
+ boost::str(
+ boost::format(
+ "Can't find Caffe layer with top called '%1%', "
+ "which is listed as an input of '%2%'. %3%") %
+ inputName %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
ret.push_back(inputIt->second);
}
@@ -211,17 +397,18 @@ vector<const LayerParameter*> CaffeParser::GetInputs(const LayerParameter& layer
return ret;
}
-void CaffeParser::ParseInputLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseInputLayer(const LayerParameter& layerParam)
{
BOOST_ASSERT(layerParam.type() == "Input");
ValidateNumInputsOutputs(layerParam, 0, 1);
const InputParameter& param = layerParam.input_param();
- const armnn::LayerBindingId inputId = boost::numeric_cast<armnn::LayerBindingId>(m_NetworkInputsBindingInfo.size());
+ const armnn::LayerBindingId inputId = boost::numeric_cast<armnn::LayerBindingId>(
+ m_NetworkInputsBindingInfo.size());
armnn::IConnectableLayer* const inputLayer = m_Network->AddInputLayer(inputId, layerParam.name().c_str());
- // Decide on the tensor info for this input. This can be specified in the Caffe network but can also
+ // Decides the tensor info for this input. This can be specified in the Caffe network but can also
// be overriden by user input (m_inputShapes).
armnn::TensorInfo inputTensorInfo;
@@ -241,15 +428,23 @@ void CaffeParser::ParseInputLayer(const LayerParameter& layerParam)
|| originalShape->dim(2) != overrideShape[2]
|| originalShape->dim(3) != overrideShape[3]))
{
- throw ParseException("Parsed input shape for '" + layerParam.name() +
- "' is incompatible with the override provided");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Parsed input shape for '%1%' is incompatible with the override provided. %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
inputTensorInfo.SetShape(overrideShape);
}
else if (!originalShape)
{
- throw ParseException("No input descriptor given for '" + layerParam.name() +
- "' and no input shape found in caffe model");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "No input descriptor given for '%1%' and no input shape found in caffe model. %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
TrackInputBinding(inputLayer, inputId, inputTensorInfo);
@@ -257,191 +452,110 @@ void CaffeParser::ParseInputLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), inputLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseConvLayer(const LayerParameter& layerParam)
+void CaffeParserBase::AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
+ const armnn::Convolution2dDescriptor& desc,
+ unsigned int kernelW,
+ unsigned int kernelH)
{
BOOST_ASSERT(layerParam.type() == "Convolution");
ValidateNumInputsOutputs(layerParam, 1, 1);
- ConvolutionParameter convParam = layerParam.convolution_param();
+ ConvolutionParameter convParam = layerParam.convolution_param();
BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
+ const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
- unsigned int kernelH = 0;
- unsigned int kernelW = 0;
- if (convParam.has_kernel_h() && convParam.has_kernel_w())
- {
- kernelH = convParam.kernel_h();
- kernelW = convParam.kernel_w();
- }
- else if (convParam.kernel_size_size() > 0)
- {
- kernelH = (convParam.kernel_size()).Get(0);
- kernelW = (convParam.kernel_size()).Get(0);
- }
- else
- {
- throw ParseException("Loading Convolution Layer: Kernel Size defined Illegally");
- }
-
- if (!IsInRange(kernelH, 0, 11) || !IsInRange(kernelW, 0, 11) || (kernelH != kernelW))
- {
- throw ParseException("Loading Convolution Layer: Kernel has invalid size");
- }
-
- unsigned int strideH = 0;
- unsigned int strideW = 0;
-
- if (convParam.has_stride_h() && convParam.has_stride_w())
- {
- strideH = convParam.stride_h();
- strideW = convParam.stride_w();
- }
- else if (convParam.stride_size() > 0)
- {
- strideH = (convParam.stride()).Get(0);
- strideW = (convParam.stride()).Get(0);
- }
- else
- {
- // Caffe stride default is 1
- strideH = strideW = 1;
- }
-
- if (!IsInRange(strideH, 0, 11) || !IsInRange(strideW, 0, 11) || (strideH != strideW))
- {
- throw ParseException("Loading Convolution Layer: stride has invalid size");
- }
-
- unsigned int padH = 0;
- unsigned int padW = 0;
-
- if (convParam.has_pad_h() && convParam.has_pad_w())
- {
- padH = convParam.pad_h();
- padW = convParam.pad_w();
- }
- else if (convParam.pad_size() > 0)
- {
- padH = (convParam.pad()).Get(0);
- padW = (convParam.pad()).Get(0);
- }
- else
- {
- padH = 0;
- padW = 0;
- }
-
- if (!IsInRange(padH, 0, 11) || !IsInRange(padW, 0, 11) || (padH != padW))
- {
- throw ParseException("Loading Convolution Layer: pad has invalid size");
- }
+ // asusme these were already verified by the caller ParseConvLayer() function
+ BOOST_ASSERT(numGroups < inputShape.dim(1));
+ BOOST_ASSERT(numGroups > 1);
// Handle grouping
- const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
vector<string> convLayerNames(numGroups);
vector<armnn::IConnectableLayer*> convLayers(numGroups);
convLayerNames[0] = layerParam.name();
- armnn::IConnectableLayer* splitterLayer = nullptr;
- if (numGroups > 1)
- {
- // This convolution is to be applied to chunks of the input data so add a splitter layer
-
- // Redirect the convolution input to the splitter
- unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
- static_cast<unsigned int>(inputShape.dim(1)),
- static_cast<unsigned int>(inputShape.dim(2)),
- static_cast<unsigned int>(inputShape.dim(3))};
+ // This convolution is to be applied to chunks of the input data so add a splitter layer
- // Split dimension 1 of the splitter output shape and conv input shapes
- // according to the number of groups
- splitterDimSizes[1] /= numGroups;
- inputShape.set_dim(1, splitterDimSizes[1]);
+ // Redirect the convolution input to the splitter
+ unsigned int splitterDimSizes[4] = {static_cast<unsigned int>(inputShape.dim(0)),
+ static_cast<unsigned int>(inputShape.dim(1)),
+ static_cast<unsigned int>(inputShape.dim(2)),
+ static_cast<unsigned int>(inputShape.dim(3))};
- // This is used to describe how the input is to be split
- ViewsDescriptor splitterDesc(numGroups);
+ // Split dimension 1 of the splitter output shape and conv input shapes
+ // according to the number of groups
- // Create an output node for each group, giving each a unique name
- for (unsigned int g = 0; g < numGroups; ++g)
- {
- // Work out the names of the splitter layers child convolutions
- stringstream ss;
- ss << layerParam.name() << "_" << g;
- convLayerNames[g] = ss.str();
-
- splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
+ splitterDimSizes[1] /= numGroups;
+ inputShape.set_dim(1, splitterDimSizes[1]);
- // Set the size of the views.
- for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
- {
- splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
- }
- }
+ // This is used to describe how the input is to be split
+ ViewsDescriptor splitterDesc(numGroups);
- const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
+ // Create an output node for each group, giving each a unique name
+ for (unsigned int g = 0; g < numGroups; ++g)
+ {
+ // Work out the names of the splitter layers child convolutions
+ stringstream ss;
+ ss << layerParam.name() << "_" << g;
+ convLayerNames[g] = ss.str();
- // Add the splitter layer
- splitterLayer = m_Network->AddSplitterLayer(splitterDesc,
- splitterLayerName.c_str());
+ splitterDesc.SetViewOriginCoord(g, 1, splitterDimSizes[1] * g);
- inputConnection.Connect(splitterLayer->GetInputSlot(0));
- for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
+ // Set the size of the views.
+ for (unsigned int dimIdx=0; dimIdx < 4; dimIdx++)
{
- splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
+ splitterDesc.SetViewSize(g, dimIdx, splitterDimSizes[dimIdx]);
}
}
- // Ignored Caffe Parameters
- // * Dilation Size
- // * Weight Filler
- // * Bias Filler
- // * Engine
- // * Force nd_im2col
- // * Axis
-
- // Not Available ArmNN Interface Parameters
- // * Rounding policy;
+ const std::string splitterLayerName = std::string("splitter_") + layerParam.bottom(0);
+ armnn::IConnectableLayer* splitterLayer = m_Network->AddSplitterLayer(splitterDesc, splitterLayerName.c_str());
- Convolution2dDescriptor convolution2dDescriptor;
- convolution2dDescriptor.m_PadLeft = padW;
- convolution2dDescriptor.m_PadRight = padW;
- convolution2dDescriptor.m_PadTop = padH;
- convolution2dDescriptor.m_PadBottom = padH;
- convolution2dDescriptor.m_StrideX = strideW;
- convolution2dDescriptor.m_StrideY = strideH;
+ inputConnection.Connect(splitterLayer->GetInputSlot(0));
+ for (unsigned int i = 0; i < splitterLayer->GetNumOutputSlots(); i++)
+ {
+ splitterLayer->GetOutputSlot(i).SetTensorInfo(BlobShapeToTensorInfo(inputShape));
+ }
unsigned int numFilters = convParam.num_output();
- // Populate convolution output tensor descriptor dimensions
+ // Populates convolution output tensor descriptor dimensions.
BlobShape outputShape;
outputShape.add_dim(0);
outputShape.set_dim(0, inputShape.dim(0));
outputShape.add_dim(1);
- // Ensure that dimension 1 of the convolution output is split according to the number of groups.
+ // Ensures that dimension 1 of the convolution output is split according to the number of groups.
outputShape.set_dim(1, numFilters / numGroups);
outputShape.add_dim(2);
outputShape.set_dim(
- 2, (static_cast<int>(static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
- boost::numeric_cast<float>(strideH)) + 1));
+ 2, (static_cast<int>(
+ static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
+ static_cast<float>(desc.m_StrideY)) + 1));
outputShape.add_dim(3);
outputShape.set_dim(
- 3, (static_cast<int>(static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
- boost::numeric_cast<float>(strideW)) + 1));
+ 3, (static_cast<int>(
+ static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
+ static_cast<float>(desc.m_StrideX)) + 1));
// Load the weight data for ALL groups
- vector<float> weightData(boost::numeric_cast<size_t>(numGroups * inputShape.dim(1) * outputShape.dim(1) *
- kernelH * kernelW));
+ vector<float> weightData(boost::numeric_cast<size_t>(numGroups *
+ inputShape.dim(1) * // number of input channels
+ outputShape.dim(1) * // number of output channels
+ kernelH *
+ kernelW));
GetDataFromBlob(layerParam, weightData, 0);
const unsigned int weightDimSizes[4] = {
- static_cast<unsigned int>(outputShape.dim(1)), static_cast<unsigned int>(inputShape.dim(1)), kernelH, kernelW};
+ static_cast<unsigned int>(outputShape.dim(1)),
+ static_cast<unsigned int>(inputShape.dim(1)),
+ kernelH,
+ kernelW};
- // Bias data - This defaults to true in Caffe
TensorInfo biasInfo;
vector<float> biasData;
- convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
- if (convolution2dDescriptor.m_BiasEnabled)
+
+ if (desc.m_BiasEnabled)
{
biasData.resize(boost::numeric_cast<size_t>(numGroups * outputShape.dim(1)), 1.f);
GetDataFromBlob(layerParam, biasData, 1);
@@ -453,179 +567,408 @@ void CaffeParser::ParseConvLayer(const LayerParameter& layerParam)
const unsigned int numWeightsPerGroup = boost::numeric_cast<unsigned int>(weightData.size()) / numGroups;
const unsigned int numBiasesPerGroup = boost::numeric_cast<unsigned int>(biasData.size()) / numGroups;
- armnn::IConnectableLayer* returnLayer = nullptr;
-
for (unsigned int g = 0; g < numGroups; ++g)
{
- // set the slot index, group 0 should be connected to the 0th output of the splitter
- // group 1 should be connected to the 1st output of the splitter
+ // Sets the slot index, group 0 should be connected to the 0th output of the splitter
+ // group 1 should be connected to the 1st output of the splitter.
- // Pull out the weights for this group from that loaded from the model file earlier
+ // Pulls out the weights for this group from that loaded from the model file earlier.
ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32),
weightData.data() + numWeightsPerGroup * g);
IConnectableLayer* convLayer = nullptr;
- if (convolution2dDescriptor.m_BiasEnabled)
+ if (desc.m_BiasEnabled)
{
- // Pull out the biases for this group from that loaded from the model file earlier
+ // Pulls out the biases for this group from that loaded from the model file earlier.
ConstTensor biases(biasInfo, biasData.data() + numBiasesPerGroup * g);
- convLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
- weights, biases, convLayerNames[g].c_str());
+ convLayer =
+ m_Network->AddConvolution2dLayer(desc, weights, biases, convLayerNames[g].c_str());
}
else
{
- convLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor,
- weights, convLayerNames[g].c_str());
+ convLayer =
+ m_Network->AddConvolution2dLayer(desc, weights, convLayerNames[g].c_str());
}
convLayers[g] = convLayer;
// If we have more than one group then the input to the nth convolution the splitter layer's nth output,
// otherwise it's the regular input to this layer.
- armnn::IOutputSlot& splitterInputConnection = splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
+ armnn::IOutputSlot& splitterInputConnection =
+ splitterLayer ? splitterLayer->GetOutputSlot(g) : inputConnection;
splitterInputConnection.Connect(convLayer->GetInputSlot(0));
convLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
-
- returnLayer = convLayer;
}
- if (numGroups > 1)
- {
- // If the convolution was performed in chunks, add a layer to merge the results
-
- // The merge input shape matches that of the convolution output
- unsigned int mergeDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
- static_cast<unsigned int>(outputShape.dim(1)),
- static_cast<unsigned int>(outputShape.dim(2)),
- static_cast<unsigned int>(outputShape.dim(3))};
+ // If the convolution was performed in chunks, add a layer to merge the results
- // This is used to describe how the input is to be merged
- OriginsDescriptor mergeDesc(numGroups);
+ // The merge input shape matches that of the convolution output
+ unsigned int mergeDimSizes[4] = {static_cast<unsigned int>(outputShape.dim(0)),
+ static_cast<unsigned int>(outputShape.dim(1)),
+ static_cast<unsigned int>(outputShape.dim(2)),
+ static_cast<unsigned int>(outputShape.dim(3))};
- // Now create an input node for each group, using the name from
- // the output of the corresponding convolution
- for (unsigned int g = 0; g < numGroups; ++g)
- {
- mergeDesc.SetViewOriginCoord(g, 1, mergeDimSizes[1] * g);
- }
+ // This is used to describe how the input is to be merged
+ OriginsDescriptor mergeDesc(numGroups);
- // Make sure the output from the merge is the correct size to hold the data for all groups
- mergeDimSizes[1] *= numGroups;
- outputShape.set_dim(1, mergeDimSizes[1]);
-
- // The merge layer just assumes the name of the original convolution
- // layer so the following layer connection "just works"
- const string mergeOutputName = layerParam.name();
+ // Now create an input node for each group, using the name from
+ // the output of the corresponding convolution
+ for (unsigned int g = 0; g < numGroups; ++g)
+ {
+ mergeDesc.SetViewOriginCoord(g, 1, mergeDimSizes[1] * g);
+ }
- // Finally add the merge layer
- IConnectableLayer* layer = m_Network->AddMergerLayer(mergeDesc, mergeOutputName.c_str());
+ // Make sure the output from the merge is the correct size to hold the data for all groups
+ mergeDimSizes[1] *= numGroups;
+ outputShape.set_dim(1, mergeDimSizes[1]);
- for (unsigned int g = 0; g < numGroups; ++g)
- {
- convLayers[g]->GetOutputSlot(0).Connect(layer->GetInputSlot(g));
- }
- layer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, mergeDimSizes, DataType::Float32));
+ // Finally add the merge layer
+ IConnectableLayer* mergerLayer = m_Network->AddMergerLayer(mergeDesc, layerParam.name().c_str());
- returnLayer = layer;
+ if (!mergerLayer)
+ {
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Failed to create final merger layer for Split+Convolution+Merger. "
+ "Layer=%1% #groups=%2% #filters=%3% %4%") %
+ layerParam.name() %
+ numGroups %
+ numFilters %
+ CHECK_LOCATION().AsString()));
}
- if (!returnLayer)
+ for (unsigned int g = 0; g < numGroups; ++g)
{
- throw ParseException("Loading Convolution Layer: invalid return layer");
+ convLayers[g]->GetOutputSlot(0).Connect(mergerLayer->GetInputSlot(g));
}
-
- SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
+ mergerLayer->GetOutputSlot(0).SetTensorInfo(armnn::TensorInfo(4, mergeDimSizes, DataType::Float32));
+ SetArmnnOutputSlotForCaffeTop(layerParam.top(0), mergerLayer->GetOutputSlot(0));
}
-void CaffeParser::ParsePoolingLayer(const LayerParameter& layerParam)
+void CaffeParserBase::AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
+ const armnn::Convolution2dDescriptor& convDesc,
+ unsigned int kernelW,
+ unsigned int kernelH)
{
+ BOOST_ASSERT(layerParam.type() == "Convolution");
ValidateNumInputsOutputs(layerParam, 1, 1);
- PoolingParameter param = layerParam.pooling_param();
+ ConvolutionParameter convParam = layerParam.convolution_param();
+ BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
- const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
+ DepthwiseConvolution2dDescriptor desc;
+ desc.m_PadLeft = convDesc.m_PadLeft;
+ desc.m_PadRight = convDesc.m_PadRight;
+ desc.m_PadTop = convDesc.m_PadTop;
+ desc.m_PadBottom = convDesc.m_PadBottom;
+ desc.m_StrideX = convDesc.m_StrideX;
+ desc.m_StrideY = convDesc.m_StrideY;
+ desc.m_BiasEnabled = convDesc.m_BiasEnabled;
- // Kernel size
- unsigned int kernel_h = 0;
- unsigned int kernel_w = 0;
- if (param.has_kernel_h() && param.has_kernel_w())
- {
- kernel_h = param.kernel_h();
- kernel_w = param.kernel_w();
- }
- else if (param.kernel_size() > 0)
- {
- kernel_h = param.kernel_size();
- kernel_w = param.kernel_size();
- }
- else if (param.has_global_pooling())
+ unsigned int numFilters = convParam.num_output();
+
+ BlobShape outputShape;
+ outputShape.add_dim(0);
+ outputShape.set_dim(0, inputShape.dim(0));
+ outputShape.add_dim(1);
+ outputShape.set_dim(1, numFilters);
+ outputShape.add_dim(2);
+ outputShape.set_dim(
+ 2, (static_cast<int>(
+ static_cast<float>(inputShape.dim(2) + 2 * desc.m_PadBottom - kernelH) /
+ static_cast<float>(desc.m_StrideY)) + 1));
+ outputShape.add_dim(3);
+ outputShape.set_dim(
+ 3, (static_cast<int>(
+ static_cast<float>(inputShape.dim(3) + 2 * desc.m_PadRight - kernelW) /
+ static_cast<float>(desc.m_StrideX)) + 1));
+
+ // Load the weight data
+ size_t allWeightsSize = boost::numeric_cast<size_t>(inputShape.dim(1) * kernelH * kernelW);
+ vector<float> weightData(allWeightsSize);
+
+ GetDataFromBlob(layerParam, weightData, 0);
+
+ // depth multiplier will be 1 for the depthwise convolution
+ const unsigned int weightDimSizes[4] = {
+ static_cast<unsigned int>(1), // depth multiplier
+ static_cast<unsigned int>(inputShape.dim(1)), // #channels
+ kernelH,
+ kernelW};
+
+ armnn::IConnectableLayer* returnLayer = nullptr;
+ ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
+
+ if (desc.m_BiasEnabled)
{
- kernel_h = inputInfo.GetShape()[2];
- kernel_w = inputInfo.GetShape()[3];
+ TensorInfo biasInfo;
+ vector<float> biasData;
+
+ biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
+ GetDataFromBlob(layerParam, biasData, 1);
+
+ const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
+ biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
+
+ ConstTensor biases(biasInfo, biasData.data());
+ returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, biases, layerParam.name().c_str());
}
else
{
- throw ParseException("Loading Pooling Layer: Kernel Size defined Illegally");
+ returnLayer = m_Network->AddDepthwiseConvolution2dLayer(desc, weights, layerParam.name().c_str());
}
- if (!IsInRange(kernel_h, 0, 11) || !IsInRange(kernel_w, 0, 11) || (kernel_h != kernel_w))
+ if (!returnLayer)
{
- throw ParseException(boost::str(
- boost::format("Loading Pooling Layer: kernel has invalid size: %1% x %2%") % kernel_h % kernel_w));
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Failed to create depthwise convolution layer. "
+ "Layer=%1% #filters=%2% %3%") %
+ layerParam.name() %
+ numFilters %
+ CHECK_LOCATION().AsString()));
}
+ armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
+ inputConnection.Connect(returnLayer->GetInputSlot(0));
+ returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
+ SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
+}
- // Strides
- // Default to a valid value for the case of global pooling (where the strides don't have to be explicitly set)
- unsigned int stride_h = 1;
- unsigned int stride_w = 1;
- if (param.has_stride_h() && param.has_stride_w())
- {
- stride_h = param.stride_h();
- stride_w = param.stride_w();
- }
- else if (param.has_stride())
- {
- stride_h = param.stride();
- stride_w = param.stride();
- }
- else if (!param.has_global_pooling())
+void CaffeParserBase::ParseConvLayer(const LayerParameter& layerParam)
+{
+ // Ignored Caffe Parameters
+ // * Dilation Size
+ // * Weight Filler
+ // * Bias Filler
+ // * Engine
+ // * Force nd_im2col
+ // * Axis
+
+ // Not Available ArmNN Interface Parameters
+ // * Rounding policy;
+
+ BOOST_ASSERT(layerParam.type() == "Convolution");
+ ValidateNumInputsOutputs(layerParam, 1, 1);
+
+ ConvolutionParameter convParam = layerParam.convolution_param();
+ BlobShape inputShape = TensorDescToBlobShape(GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo());
+ const unsigned int numGroups = convParam.has_group() ? convParam.group() : 1;
+ unsigned int numFilters = convParam.num_output();
+
+ const auto notFound = std::numeric_limits<unsigned int>::max();
+
+ unsigned int kernelH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
+ kernel_h, kernel_size, unsigned int, notFound);
+ unsigned int kernelW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
+ kernel_w, kernel_size, unsigned int, notFound);
+
+ unsigned int strideH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
+ stride_h, stride, unsigned int, 1u);
+ unsigned int strideW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
+ stride_w, stride, unsigned int, 1u);
+
+ unsigned int padH = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
+ pad_h, pad, unsigned int, 0u);
+ unsigned int padW = GET_OPTIONAL_WITH_VECTOR_FALLBACK(convParam, ConvolutionParameter,
+ pad_w, pad, unsigned int, 0u);
+
+ VALIDATE_EQUAL_VALUES_IN_RANGE(kernelH, kernelW, 0, 11);
+ VALIDATE_EQUAL_VALUES_IN_RANGE(strideH, strideW, 0, 11);
+ VALIDATE_EQUAL_VALUES_IN_RANGE(padH, padW, 0, 11);
+
+ Convolution2dDescriptor convolution2dDescriptor;
+ convolution2dDescriptor.m_PadLeft = padW;
+ convolution2dDescriptor.m_PadRight = padW;
+ convolution2dDescriptor.m_PadTop = padH;
+ convolution2dDescriptor.m_PadBottom = padH;
+ convolution2dDescriptor.m_StrideX = strideW;
+ convolution2dDescriptor.m_StrideY = strideH;
+ convolution2dDescriptor.m_BiasEnabled = convParam.has_bias_term() ? convParam.bias_term() : true;
+
+ if (numGroups > numFilters)
{
- throw ParseException("Loading Pooling Layer: Stride Size defined Illegally");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Error parsing Convolution: %1%. "
+ "The 'group'=%2% parameter cannot be larger than the "
+ "number of filters supplied ='%3%'. %4%") %
+ layerParam.name() %
+ numGroups %
+ numFilters %
+ CHECK_LOCATION().AsString()));
}
- if (!IsInRange(stride_h, 0, 11) || !IsInRange(stride_w, 0, 11) || (stride_h != stride_w))
+ if (inputShape.dim_size() != 4)
{
- throw ParseException("Loading Pooling Layer: stride has invalid size");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Convolution input shape is expected to have 4 dimensions. "
+ "%1%'s input has only %2%. %3%") %
+ layerParam.name() %
+ inputShape.dim_size() %
+ CHECK_LOCATION().AsString()));
}
- // Padding
- unsigned int pad_h = 0;
- unsigned int pad_w = 0;
- if (param.has_pad_h() && param.has_pad_w())
+ if (numGroups > 1)
{
- pad_h = param.pad_h();
- pad_w = param.pad_w();
+ if (numGroups > inputShape.dim(1))
+ {
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Error parsing Convolution: %1%. "
+ "The 'group'=%2% parameter cannot be larger than the "
+ "channel of the input shape=%3% (in NCHW format). %4%") %
+ layerParam.name() %
+ numGroups %
+ inputShape.dim(1) %
+ CHECK_LOCATION().AsString()));
+ }
+ else if (numGroups == inputShape.dim(1))
+ {
+ // we use a depthwise convolution here, because the number of groups equals to the
+ // input channels
+ AddConvLayerWithDepthwiseConv(layerParam, convolution2dDescriptor, kernelW, kernelH);
+ return;
+ }
+ else
+ {
+ // we split the input by channels into channels/groups separate convolutions
+ // and merger the results afterwards
+ AddConvLayerWithSplits(layerParam, convolution2dDescriptor, kernelW, kernelH);
+ return;
+ }
}
- else if (param.has_pad())
+
+ // NOTE: at this point we only need to handle #group=1 case, all other cases should be
+ // handled by the AddConvLayer* helpers
+
+ // Populate convolution output tensor descriptor dimensions
+ BlobShape outputShape;
+ outputShape.add_dim(0);
+ outputShape.set_dim(0, inputShape.dim(0));
+ outputShape.add_dim(1);
+ outputShape.set_dim(1, numFilters);
+ outputShape.add_dim(2);
+ outputShape.set_dim(
+ 2, (static_cast<int>(
+ static_cast<float>(inputShape.dim(2) + 2 * padH - kernelH) /
+ static_cast<float>(strideH)) + 1));
+ outputShape.add_dim(3);
+ outputShape.set_dim(
+ 3, (static_cast<int>(
+ static_cast<float>(inputShape.dim(3) + 2 * padW - kernelW) /
+ static_cast<float>(strideW)) + 1));
+
+ // Load the weight data for ALL groups
+ vector<float> weightData(boost::numeric_cast<size_t>(inputShape.dim(1) *
+ outputShape.dim(1) *
+ kernelH *
+ kernelW));
+ GetDataFromBlob(layerParam, weightData, 0);
+
+ const unsigned int weightDimSizes[4] = {
+ static_cast<unsigned int>(outputShape.dim(1)), // output channels
+ static_cast<unsigned int>(inputShape.dim(1)), // input channels
+ kernelH,
+ kernelW};
+
+ armnn::IConnectableLayer* returnLayer = nullptr;
+
+ // Pull out the weights for this group from that loaded from the model file earlier
+ ConstTensor weights(TensorInfo(4, weightDimSizes, DataType::Float32), weightData.data());
+
+ if (convolution2dDescriptor.m_BiasEnabled)
{
- pad_h = param.pad();
- pad_w = param.pad();
+ TensorInfo biasInfo;
+ vector<float> biasData;
+
+ biasData.resize(boost::numeric_cast<size_t>(outputShape.dim(1)), 1.f);
+ GetDataFromBlob(layerParam, biasData, 1);
+
+ const unsigned int biasDimSizes[1] = {static_cast<unsigned int>(outputShape.dim(1))};
+ biasInfo = TensorInfo(1, biasDimSizes, DataType::Float32);
+
+ // Pull out the biases for this group from that loaded from the model file earlier
+ ConstTensor biases(biasInfo, biasData.data());
+
+ returnLayer =
+ m_Network->AddConvolution2dLayer(convolution2dDescriptor, weights, biases, layerParam.name().c_str());
}
else
{
- pad_h = 0;
- pad_w = 0;
+ returnLayer = m_Network->AddConvolution2dLayer(convolution2dDescriptor, weights, layerParam.name().c_str());
}
- if (!IsInRange(pad_h, 0, 11) || !IsInRange(pad_w, 0, 11) || (pad_h != pad_w))
+ armnn::IOutputSlot& inputConnection = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
+ inputConnection.Connect(returnLayer->GetInputSlot(0));
+ returnLayer->GetOutputSlot(0).SetTensorInfo(BlobShapeToTensorInfo(outputShape));
+
+ if (!returnLayer)
{
- throw ParseException("Loading Pooling Layer: pad has invalid size");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Failed to create Convolution layer. "
+ "Layer=%1% #groups=%2% #filters=%3% %4%") %
+ layerParam.name() %
+ numGroups %
+ numFilters %
+ CHECK_LOCATION().AsString()));
}
+ SetArmnnOutputSlotForCaffeTop(layerParam.top(0), returnLayer->GetOutputSlot(0));
+}
+
+void CaffeParserBase::ParsePoolingLayer(const LayerParameter& layerParam)
+{
// Ignored Caffe Parameters
// Stochastic Pooling
// Engine
+ ValidateNumInputsOutputs(layerParam, 1, 1);
+ PoolingParameter param = layerParam.pooling_param();
+ const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
+
+ const auto notFound = std::numeric_limits<unsigned int>::max();
+
+ unsigned int kernel_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
+ kernel_h, kernel_size, unsigned int, notFound);
+ unsigned int kernel_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
+ kernel_w, kernel_size, unsigned int, notFound);
+
+ if ((kernel_h == notFound || kernel_w == notFound) && param.has_global_pooling())
+ {
+ kernel_h = inputInfo.GetShape()[2];
+ kernel_w = inputInfo.GetShape()[3];
+ }
+
+ VALIDATE_EQUAL_VALUES_IN_RANGE(kernel_h, kernel_w, 0, 11);
+
+ unsigned int stride_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
+ stride_h, stride, unsigned int, notFound);
+ unsigned int stride_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
+ stride_h, stride, unsigned int, notFound);
+
+ if ((stride_h == notFound || stride_w == notFound) && param.has_global_pooling())
+ {
+ stride_h = 1;
+ stride_w = 1;
+ }
+
+ VALIDATE_EQUAL_VALUES_IN_RANGE(stride_h, stride_w, 0, 11);
+
+ unsigned int pad_h = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
+ pad_h, pad, unsigned int, 0u);
+ unsigned int pad_w = GET_OPTIONAL_WITH_FALLBACK(param, PoolingParameter,
+ pad_w, pad, unsigned int, 0u);
+
+ VALIDATE_EQUAL_VALUES_IN_RANGE(pad_h, pad_w, 0, 11);
+
// Populate Weight and Bias Filter Descriptor
Pooling2dDescriptor pooling2dDescriptor;
if (param.has_pool())
@@ -645,17 +988,33 @@ void CaffeParser::ParsePoolingLayer(const LayerParameter& layerParam)
}
case PoolingParameter_PoolMethod_STOCHASTIC:
{
- throw ParseException("Loading Pooling Layer: Stochastic Pooling Not Supported");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Pooling Layer: Stochastic Pooling Not Supported. Layer=%1% %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
default:
{
- throw ParseException("Loading Pooling Layer: Mode Not Supported");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Pooling Layer: unknown pooling method: %1% for layer: %2% %3%") %
+ p %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
}
}
else
{
- throw ParseException("Loading Pooling Layer: No Pooling Method Defined");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "No Pooling Method Defined for %1% %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
pooling2dDescriptor.m_PadLeft = pad_w;
@@ -673,7 +1032,6 @@ void CaffeParser::ParsePoolingLayer(const LayerParameter& layerParam)
armnn::IConnectableLayer* poolingLayer = m_Network->AddPooling2dLayer(pooling2dDescriptor,
layerParam.name().c_str());
-
TensorInfo outputInfo(
{ inputInfo.GetShape()[0],
inputInfo.GetShape()[1],
@@ -690,7 +1048,7 @@ void CaffeParser::ParsePoolingLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), poolingLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseReluLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseReluLayer(const LayerParameter& layerParam)
{
ValidateNumInputsOutputs(layerParam, 1, 1);
@@ -716,7 +1074,7 @@ void CaffeParser::ParseReluLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), activationLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseLRNLayer(const LayerParameter& layerParam)
{
ValidateNumInputsOutputs(layerParam, 1, 1);
@@ -724,9 +1082,9 @@ void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
- // Ignored BATCH NORMALIZATION Caffe Parameters
- // Ignored MVN Caffe Parameters
- // Ignored LRN Caffe Parameters
+ // Ignored BATCH NORMALIZATION Caffe Parameters.
+ // Ignored MVN Caffe Parameters.
+ // Ignored LRN Caffe Parameters.
// Engine
NormalizationDescriptor normalizationDescriptor;
@@ -746,12 +1104,20 @@ void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
break;
}
default:
- throw ParseException("Loading LRN Layer: Mode Not Supported");
+ {
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Unknown region %1% for LRN layer %2% %3%") %
+ n %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
+ }
}
}
else
{
- // Caffe defaults to normalization across channels
+ // Caffe defaults to normalization across channels.
normalizationDescriptor.m_NormChannelType = NormalizationAlgorithmChannel::Across;
}
@@ -762,7 +1128,12 @@ void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
}
else
{
- throw ParseException("Loading LRN Layer: Local_size not defined");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "local_size not defined for LRN layer %1% %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
if (param.has_alpha())
@@ -772,7 +1143,12 @@ void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
}
else
{
- throw ParseException("Loading LRN Layer: Alpha not defined");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Alpha not defined for LRN layer %1% %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
if (param.has_beta())
{
@@ -780,14 +1156,22 @@ void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
}
else
{
- throw ParseException("Loading LRN Layer: Beta not defined");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Beta not defined for LRN layer %1% %2%") %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
+
if (param.has_k())
{
normalizationDescriptor.m_K = param.k();
}
else
+ {
normalizationDescriptor.m_K = 1;
+ }
IConnectableLayer* const normLayer = m_Network->AddNormalizationLayer(normalizationDescriptor,
layerParam.name().c_str());
@@ -797,7 +1181,7 @@ void CaffeParser::ParseLRNLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), normLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseInnerProductLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseInnerProductLayer(const LayerParameter& layerParam)
{
InnerProductParameter param = layerParam.inner_product_param();
@@ -805,7 +1189,7 @@ void CaffeParser::ParseInnerProductLayer(const LayerParameter& layerParam)
unsigned int outputSize = param.num_output();
- // Ignored Caffe Parameters
+ // Ignored Caffe Parameters:
// Weight Filler
// Bias Filler
// Engine
@@ -815,12 +1199,12 @@ void CaffeParser::ParseInnerProductLayer(const LayerParameter& layerParam)
if (param.has_transpose())
{
- // If true assume transposed weights
+ // If true, assumes transposed weights.
tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = param.transpose();
}
else
{
- // caffe defaults to transposed
+ // Caffe defaults to transposed.
tensorFullyConnectedDescriptor.m_TransposeWeightMatrix = true;
}
@@ -829,32 +1213,28 @@ void CaffeParser::ParseInnerProductLayer(const LayerParameter& layerParam)
TensorInfo weightInfo;
TensorInfo biasInfo;
- // allow implicit flattening of extra dimensions
+ // Allows implicit flattening of extra dimensions.
unsigned int inputSize = inputInfo.GetShape()[1];
for (unsigned int i = 2; i < inputInfo.GetNumDimensions(); ++i)
{
inputSize *= inputInfo.GetShape()[i];
}
- vector<float> weightData(inputSize * outputSize);
-
- GetDataFromBlob(layerParam, weightData, 0);
+ const float* weightDataPtr = GetArrayPtrFromBlob(layerParam, 0);
const unsigned int swTD[2] = { outputSize, inputSize };
- ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightData);
+ ConstTensor weights(TensorInfo(2, swTD, DataType::Float32), weightDataPtr);
tensorFullyConnectedDescriptor.m_BiasEnabled = true;
- // Todo: check whether bias enabled
+ // Todo: check whether bias enabled.
armnn::IConnectableLayer* fullyConnectedLayer = nullptr;
if (tensorFullyConnectedDescriptor.m_BiasEnabled)
{
// BIAS VALUE
- vector<float> biasData(outputSize);
-
- GetDataFromBlob(layerParam, biasData, 1);
+ const float* biasDataPtr = GetArrayPtrFromBlob(layerParam, 1);
const unsigned int sbTD[1] = { outputSize };
- ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasData);
+ ConstTensor biases(TensorInfo(1, sbTD, DataType::Float32), biasDataPtr);
fullyConnectedLayer = m_Network->AddFullyConnectedLayer(tensorFullyConnectedDescriptor, weights, biases,
layerParam.name().c_str());
@@ -871,7 +1251,7 @@ void CaffeParser::ParseInnerProductLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), fullyConnectedLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseSoftmaxLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseSoftmaxLayer(const LayerParameter& layerParam)
{
ValidateNumInputsOutputs(layerParam, 1, 1);
@@ -879,7 +1259,7 @@ void CaffeParser::ParseSoftmaxLayer(const LayerParameter& layerParam)
const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
- // Ignored Caffe Parameters
+ // Ignored Caffe Parameters:
// axis
// Engine
@@ -892,16 +1272,16 @@ void CaffeParser::ParseSoftmaxLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), softmaxLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseEltwiseLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseEltwiseLayer(const LayerParameter& layerParam)
{
ValidateNumInputsOutputs(layerParam, 2, 1);
const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
- // Ignored Caffe Parameters
+ // Ignored Caffe Parameters:
// coeff
- EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // default to sum as per caffe
+ EltwiseParameter_EltwiseOp operation = EltwiseParameter_EltwiseOp_SUM; // Defaults to sum as per caffe.
if (layerParam.has_eltwise_param() && layerParam.eltwise_param().has_operation())
{
@@ -923,7 +1303,13 @@ void CaffeParser::ParseEltwiseLayer(const LayerParameter& layerParam)
}
default:
{
- throw ParseException("Unsupported operation in Eltwise layer");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Unsupported operation %1% in Eltwise layer %2% %3%") %
+ operation %
+ layerParam.name() %
+ CHECK_LOCATION().AsString()));
}
}
@@ -933,14 +1319,15 @@ void CaffeParser::ParseEltwiseLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), newLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseConcatLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseConcatLayer(const LayerParameter& layerParam)
{
unsigned int numInputs = static_cast<unsigned int>(layerParam.bottom_size());
- // we assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3)
+ // We assume concat happens along the channel dimension, which is 1 in (0, 1, 2, 3).
unsigned int concatDim = 1;
unsigned int numOfDims = 4;
- OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);// we only consider 4-D tensor here
+ // we only consider 4-D tensor here
+ OriginsDescriptor concatDescriptor(static_cast<uint32_t>(numInputs), numOfDims);
std::vector<unsigned int>mergeDimSizes(numOfDims, 0u);
unsigned int mergeDim = 0;
@@ -948,10 +1335,18 @@ void CaffeParser::ParseConcatLayer(const LayerParameter& layerParam)
{
const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(
layerParam.bottom(boost::numeric_cast<int>(viewIndex))).GetTensorInfo();
- // Check whether the dimensions of the input tensors are actually 4
+ // Checks whether the dimensions of the input tensors are actually 4.
if (inputInfo.GetNumDimensions()!=4)
{
- throw ParseException("The number of dimensions for input tensors of the concatenation op should be 4.");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "The number of dimensions for input tensors of "
+ "the concatenation op should be 4. Inputs of %1% has "
+ "%2% dimensions. %3%") %
+ layerParam.name() %
+ inputInfo.GetNumDimensions() %
+ CHECK_LOCATION().AsString()));
}
mergeDimSizes[0] = inputInfo.GetShape()[0];
@@ -974,7 +1369,7 @@ void CaffeParser::ParseConcatLayer(const LayerParameter& layerParam)
}
mergeDimSizes[concatDim] = mergeDim;
- armnn::IConnectableLayer *concatlayer = m_Network->AddMergerLayer(concatDescriptor, layerParam.name().c_str());
+ armnn::IConnectableLayer* concatlayer = m_Network->AddMergerLayer(concatDescriptor, layerParam.name().c_str());
for (unsigned int i = 0; i < numInputs; ++i)
{
armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(boost::numeric_cast<int>(i)));
@@ -985,7 +1380,7 @@ void CaffeParser::ParseConcatLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), concatlayer->GetOutputSlot(0));
}
-void CaffeParser::ParseBatchNormLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseBatchNormLayer(const LayerParameter& layerParam)
{
ValidateNumInputsOutputs(layerParam, 1, 1);
@@ -1000,9 +1395,14 @@ void CaffeParser::ParseBatchNormLayer(const LayerParameter& layerParam)
{
if (!param.use_global_stats())
{
- throw ParseException(boost::str(boost::format("Error parsing Batch Norm layer '%1%': "
- "Parameter 'use_global_stats' is set to false, which is unsupported (value used for training).")
- % name));
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Error parsing Batch Norm layer '%1%': "
+ "Parameter 'use_global_stats' is set to false, which is "
+ "unsupported (value used for training). %2%") %
+ name %
+ CHECK_LOCATION().AsString()));
}
}
@@ -1018,7 +1418,7 @@ void CaffeParser::ParseBatchNormLayer(const LayerParameter& layerParam)
vector<float> varianceData(channels);
GetDataFromBlob(layerParam, varianceData, 1);
- // read moving average factor and apply scaling (if required)
+ // Reads moving average factor and applies scaling (if required).
const BlobProto& blob = layerParam.blobs(boost::numeric_cast<int>(2));
const float movingAverageFactor = blob.data(boost::numeric_cast<int>(0));
if(movingAverageFactor != 0.0f)
@@ -1030,7 +1430,7 @@ void CaffeParser::ParseBatchNormLayer(const LayerParameter& layerParam)
std::transform(meanData.begin(), meanData.end(), meanData.begin(), scaleFunction);
}
- // identity scale operation
+ // Identifies scale operation.
vector<float> betaData(channels, 0.0f);
vector<float> gammaData(channels, 1.0f);
@@ -1046,9 +1446,9 @@ void CaffeParser::ParseBatchNormLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseScaleLayer(const LayerParameter& layerParam)
+void CaffeParserBase::ParseScaleLayer(const LayerParameter& layerParam)
{
- // current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance
+ // Current unoptimal solution: add a batchnormalization layer with 0 mean and 1 variance.
ValidateNumInputsOutputs(layerParam, 1, 1);
const TensorInfo& inputInfo = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)).GetTensorInfo();
@@ -1059,14 +1459,21 @@ void CaffeParser::ParseScaleLayer(const LayerParameter& layerParam)
if (param.axis() != 1)
{
// Would have to use something other than BatchNormalizationLayer in this case
- throw ParseException("Loading Scale Layer: Only axis 1 supported currently");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Loading Scale Layer: Only axis 1 is supported currently. "
+ "Layer=%1% Axis=%2% %3%") %
+ layerParam.name() %
+ param.axis() %
+ CHECK_LOCATION().AsString()));
}
unsigned int channels = inputInfo.GetShape()[1];
unsigned int shape[] = {channels};
BatchNormalizationDescriptor desc;
- desc.m_Eps = 0.0f; // don't need epsilon if variance is 1
+ desc.m_Eps = 0.0f; // Don't need epsilon if variance is 1.
vector<float> meanData(channels, 0.0f);
vector<float> varianceData(channels, 1.0f);
vector<float> betaData(channels, 0.0f);
@@ -1091,12 +1498,19 @@ void CaffeParser::ParseScaleLayer(const LayerParameter& layerParam)
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), batchNormLayer->GetOutputSlot(0));
}
-void CaffeParser::ParseSplitLayer(const caffe::LayerParameter& layerParam)
+void CaffeParserBase::ParseSplitLayer(const caffe::LayerParameter& layerParam)
{
- // Used in caffe to duplicate memory - not necessary in armnn
+ // Used in caffe to duplicate memory - not necessary in armnn.
if (layerParam.bottom_size() != 1)
{
- throw ParseException("Split layer '" + layerParam.name() + "' should have exactly 1 bottom");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Split layer '%1%' should have exactly 1 bottom. "
+ "#bottoms=%2% %3%") %
+ layerParam.name() %
+ layerParam.bottom_size() %
+ CHECK_LOCATION().AsString()));
}
armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0));
for (int i = 0; i < layerParam.top_size(); i++)
@@ -1105,31 +1519,39 @@ void CaffeParser::ParseSplitLayer(const caffe::LayerParameter& layerParam)
}
}
-void CaffeParser::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
+void CaffeParserBase::ParseDropoutLayer(const caffe::LayerParameter& layerParam)
{
- // Ignored for inference so patch the single input to its single output
+ // Ignored for inference, so patch the single input to its single output.
if (layerParam.bottom_size() != 1 || layerParam.top_size() != 1)
{
- throw ParseException("Dropout layer '" + layerParam.name() + "' should have exactly 1 bottom and 1 top");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Dropout layer '%1%' should have exactly 1 bottom and 1 top. "
+ "#bottoms=%2% #tops=%3% %4%") %
+ layerParam.name() %
+ layerParam.bottom_size() %
+ layerParam.top_size() %
+ CHECK_LOCATION().AsString()));
}
SetArmnnOutputSlotForCaffeTop(layerParam.top(0), GetArmnnOutputSlotForCaffeTop(layerParam.bottom(0)));
}
-void CaffeParser::TrackInputBinding(armnn::IConnectableLayer* layer,
+void CaffeParserBase::TrackInputBinding(armnn::IConnectableLayer* layer,
armnn::LayerBindingId id,
const armnn::TensorInfo& tensorInfo)
{
return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkInputsBindingInfo);
}
-void CaffeParser::TrackOutputBinding(armnn::IConnectableLayer* layer,
+void CaffeParserBase::TrackOutputBinding(armnn::IConnectableLayer* layer,
armnn::LayerBindingId id,
const armnn::TensorInfo& tensorInfo)
{
return TrackBindingPoint(layer, id, tensorInfo, layer->GetName(), m_NetworkOutputsBindingInfo);
}
-void CaffeParser::TrackBindingPoint(armnn::IConnectableLayer* layer,
+void CaffeParserBase::TrackBindingPoint(armnn::IConnectableLayer* layer,
armnn::LayerBindingId id,
const armnn::TensorInfo& tensorInfo,
const char* bindingPointDesc,
@@ -1143,12 +1565,17 @@ void CaffeParser::TrackBindingPoint(armnn::IConnectableLayer* layer,
}
else
{
- throw ParseException(boost::str(
- boost::format("Id %1% used by more than one %2% layer") % id % bindingPointDesc));
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Id %1% used by more than one %2% layer %3%") %
+ id %
+ bindingPointDesc %
+ CHECK_LOCATION().AsString()));
}
}
-armnn::IOutputSlot& CaffeParser::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
+armnn::IOutputSlot& CaffeParserBase::GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const
{
auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
if (it != m_ArmnnOutputSlotForCaffeTop.end())
@@ -1157,12 +1584,17 @@ armnn::IOutputSlot& CaffeParser::GetArmnnOutputSlotForCaffeTop(const std::string
}
else
{
- throw ParseException(boost::str(boost::format(
- "Could not find armnn output slot for Caffe top '%1%'") % caffeTopName));
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Could not find armnn output slot for Caffe top '%1%' %2%") %
+ caffeTopName %
+ CHECK_LOCATION().AsString()));
}
}
-void CaffeParser::SetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
+void CaffeParserBase::SetArmnnOutputSlotForCaffeTop(
+ const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot)
{
auto it = m_ArmnnOutputSlotForCaffeTop.find(caffeTopName);
if (it == m_ArmnnOutputSlotForCaffeTop.end())
@@ -1171,31 +1603,39 @@ void CaffeParser::SetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName,
}
else
{
- throw ParseException("Attempting to add duplicate entry for Caffe top '" + caffeTopName + "'");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Attempting to add duplicate entry for Caffe top '%1%' %2%") %
+ caffeTopName %
+ CHECK_LOCATION().AsString()));
}
}
-void CaffeParser::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
+// Note: can move to CaffeParser when/if we optimise the text/string format
+// to load on a layer by layer basis
+void CaffeParserBase::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
{
- // Find layers with the same top
+ // Finds layers with the same top.
std::map<std::string, std::vector<caffe::LayerParameter*>> layersByTop;
for (int layerIdx = 0; layerIdx < netParameter.layer_size(); ++layerIdx)
{
caffe::LayerParameter& layer = *netParameter.mutable_layer(layerIdx);
+ std::string name = layer.name();
for (int i = 0; i < layer.top_size(); ++i)
{
layersByTop[layer.top(i)].push_back(&layer);
}
}
- // For each set of layers with the same top, resolve them to a linear chain rather than in-place layers.
+ // For each set of layers with the same top, resolves them to a linear chain rather than in-place layers.
// Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
for (auto layersWithSameTopIt : layersByTop)
{
const std::string& top = layersWithSameTopIt.first;
const std::vector<caffe::LayerParameter*>& layersWithSameTop = layersWithSameTopIt.second;
- // Chain the layers together in the order that they are listed in the prototxt (hopefully this is correct).
+ // Chains the layers together in the order that they are listed in the prototxt (hopefully this is correct).
// Note that the last layer will not have its top modified so that other layers will continue to reference it.
for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
{
@@ -1203,25 +1643,41 @@ void CaffeParser::ResolveInPlaceLayers(caffe::NetParameter& netParameter)
caffe::LayerParameter& layer2 = *layersWithSameTop[layerIdx+1];
if (layer1.top_size() != 1)
{
- throw ParseException("Node '" + layer1.name() + "' is an in-place layer but "
- "doesn't have exactly one top.");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Node '%1%' is an in-place layer but doesn't have exactly one "
+ "top. It has %2% instead. %3%") %
+ layer1.name() %
+ layer1.top_size() %
+ CHECK_LOCATION().AsString()));
}
std::string newTop = layer1.name() + "_top";
layer1.set_top(0, newTop);
if (layer2.bottom_size() != 1 || layer2.bottom(0) != top)
{
- throw ParseException("Node '" + layer2.name() + "' is an in-place layer but "
- " doesn't have exactly one bottom, or it doesn't match its top.");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Node '%1%' is an in-place layer but "
+ "doesn't have exactly one bottom, or it doesn't match its top. "
+ "#bottoms=%2%, first bottom is %3%, top is %4% %5%") %
+ layer2.name() %
+ layer2.bottom(0) %
+ top %
+ CHECK_LOCATION().AsString()));
}
layer2.set_bottom(0, newTop);
}
}
}
-void CaffeParser::LoadNetParam(NetParameter& netParameter)
+// Note: can move to CaffeParser when/if we optimise the text/string format
+// to load on a layer by layer basis
+void CaffeParserBase::LoadNetParam(NetParameter& netParameter)
{
- // caffe models sometimes have an implicit input layer.
- // in that case, add an explicit one
+ // Caffe models sometimes have an implicit input layer.
+ // In that case, add an explicit one.
if (netParameter.input_size() > 0)
{
LayerParameter* newLayer = netParameter.add_layer();
@@ -1240,10 +1696,10 @@ void CaffeParser::LoadNetParam(NetParameter& netParameter)
}
}
- // Replace in-place layers with regular ones to make the rest of the parsing easier.
+ // Replaces in-place layers with regular ones to make the rest of the parsing easier.
ResolveInPlaceLayers(netParameter);
- // Create a lookup of Caffe layers by name
+ // Creates a lookup of Caffe layers by name.
for (int i = 0; i < netParameter.layer_size(); ++i)
{
const caffe::LayerParameter& layer = netParameter.layer(i);
@@ -1253,19 +1709,24 @@ void CaffeParser::LoadNetParam(NetParameter& netParameter)
}
}
- // Find the output layers the user requested
+ // Finds the output layers the user requested.
std::vector<const caffe::LayerParameter*> targetLayers;
for (const std::string& requestedOutputName : m_RequestedOutputs)
{
auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
if (nodeIt == m_CaffeLayersByTopName.end())
{
- throw ParseException("Couldn't find requested output layer '" + requestedOutputName + "' in graph");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Couldn't find requested output layer '%1%' in graph %2%") %
+ requestedOutputName %
+ CHECK_LOCATION().AsString()));
}
targetLayers.push_back(nodeIt->second);
}
- // Sort them into a linear ordering such that all inputs of a node are before the node itself
+ // Sorts them into a linear ordering such that all inputs of a node are before the node itself.
std::vector<const caffe::LayerParameter*> sortedNodes;
if (!armnnUtils::GraphTopologicalSort<const caffe::LayerParameter*>(
targetLayers,
@@ -1275,22 +1736,32 @@ void CaffeParser::LoadNetParam(NetParameter& netParameter)
},
sortedNodes))
{
- throw ParseException("Cycle detected in graph");
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Cycle detected in graph. #nodes: %1% %2%") %
+ sortedNodes.size() %
+ CHECK_LOCATION().AsString()));
}
- // Parse each node in order, knowing that all inputs of a node will be processed before the node itself
+ // Parses each node in order, knowing that all inputs of a node will be processed before the node itself.
for (const caffe::LayerParameter* current : sortedNodes)
{
auto it = ms_CaffeLayerNameToParsingFunctions.find(current->type());
if (it == ms_CaffeLayerNameToParsingFunctions.end())
{
- throw ParseException("Unsupported layer type '" + current->type() + "'");
+ throw ParseException(
+ boost::str(
+ boost::format("Unsupported layer type: '%1%' for layer %2% %3%") %
+ current->type() %
+ current->name() %
+ CHECK_LOCATION().AsString()));
}
auto func = it->second;
(this->*func)(*current);
}
- // Add ArmNN output layers connected to each requested output
+ // Adds ArmNN output layers connected to each requested output.
for (const std::string& requestedOutput : m_RequestedOutputs)
{
armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
@@ -1304,7 +1775,7 @@ void CaffeParser::LoadNetParam(NetParameter& netParameter)
}
}
-INetworkPtr CaffeParser::CreateNetworkFromTextFile(const char* graphFile,
+INetworkPtr CaffeParserBase::CreateNetworkFromTextFile(const char* graphFile,
const std::map<std::string, armnn::TensorShape>& inputShapes,
const std::vector<std::string>& requestedOutputs)
{
@@ -1312,12 +1783,15 @@ INetworkPtr CaffeParser::CreateNetworkFromTextFile(const char* graphFile,
if (fd == nullptr)
{
- std::stringstream error;
- error << "Graph file " << graphFile << " failed to open";
- throw FileNotFoundException(error.str());
+ throw FileNotFoundException(
+ boost::str(
+ boost::format(
+ "Failed to open graph file: %1% %2%") %
+ graphFile %
+ CHECK_LOCATION().AsString()));
}
- // Parse the file into a message
+ // Parses the file into a message.
NetParameter netParam;
auto input = new google::protobuf::io::FileInputStream(fileno(fd));
bool success = google::protobuf::TextFormat::Parse(input, &netParam);
@@ -1326,27 +1800,32 @@ INetworkPtr CaffeParser::CreateNetworkFromTextFile(const char* graphFile,
if (!success)
{
- std::stringstream error;
- error << "Failed to parse graph file";
- throw ParseException(error.str());
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Failed to parse graph file: %1% %2%") %
+ graphFile %
+ CHECK_LOCATION().AsString()));
}
return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
}
-INetworkPtr CaffeParser::CreateNetworkFromString(const char* protoText,
+INetworkPtr CaffeParserBase::CreateNetworkFromString(const char* protoText,
const std::map<std::string, armnn::TensorShape>& inputShapes,
const std::vector<std::string>& requestedOutputs)
{
- // Parse the string into a message
+ // Parses the string into a message.
NetParameter netParam;
bool success = google::protobuf::TextFormat::ParseFromString(protoText, &netParam);
if (!success)
{
- std::stringstream error;
- error << "Failed to parse graph string";
- throw ParseException(error.str());
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Failed to parse graph string %1%") %
+ CHECK_LOCATION().AsString()));
}
return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
@@ -1360,12 +1839,15 @@ INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
if (fd == nullptr)
{
- std::stringstream error;
- error << "Graph file " << graphFile << " failed to open";
- throw FileNotFoundException(error.str());
+ throw FileNotFoundException(
+ boost::str(
+ boost::format(
+ "Failed to open graph file at: %1% %2%") %
+ graphFile %
+ CHECK_LOCATION().AsString()));
}
- // Parse the file into a message
+ // Parses the file into a message.
NetParameter netParam;
FileInputStream inStream(fileno(fd));
@@ -1376,15 +1858,20 @@ INetworkPtr CaffeParser::CreateNetworkFromBinaryFile(const char* graphFile,
if (!success)
{
- std::stringstream error;
- error << "Failed to parse protobuf file" << graphFile;
- throw ParseException(error.str());
+ throw ParseException(
+ boost::str(
+ boost::format(
+ "Failed to parse protobuf file: %1% %2%") %
+ graphFile %
+ CHECK_LOCATION().AsString()));
}
return CreateNetworkFromNetParameter(netParam, inputShapes, requestedOutputs);
}
-INetworkPtr CaffeParser::CreateNetworkFromNetParameter(NetParameter& netParam,
+// Note: can move to CaffeParser when/if we optimise the text/string format
+// to load on a layer by layer basis
+INetworkPtr CaffeParserBase::CreateNetworkFromNetParameter(NetParameter& netParam,
const std::map<std::string, armnn::TensorShape>& inputShapes,
const std::vector<std::string>& requestedOutputs)
{
@@ -1415,15 +1902,15 @@ INetworkPtr CaffeParser::CreateNetworkFromNetParameter(NetParameter& netParam,
return move(m_Network);
}
-void CaffeParser::Cleanup()
-{
+void CaffeParserBase::Cleanup() {
// cleanup, in case we reuse this parser
- m_CaffeLayersByTopName.clear();
m_InputShapes.clear();
m_RequestedOutputs.clear();
m_ArmnnOutputSlotForCaffeTop.clear();
+ // NOTE: when we get the text/string format
+ // optimised for memory then this data structure can
+ // also move to the CaffeParser class
+ m_CaffeLayersByTopName.clear();
}
}
-
-
diff --git a/src/armnnCaffeParser/CaffeParser.hpp b/src/armnnCaffeParser/CaffeParser.hpp
index 0b31e187dd..51867b6ace 100644
--- a/src/armnnCaffeParser/CaffeParser.hpp
+++ b/src/armnnCaffeParser/CaffeParser.hpp
@@ -25,60 +25,34 @@ namespace armnnCaffeParser
using BindingPointInfo = std::pair<armnn::LayerBindingId, armnn::TensorInfo>;
-class CaffeParser : public ICaffeParser
+class CaffeParserBase: public ICaffeParser
{
public:
+
+ // Because we haven't looked at reducing the memory usage when loading from Text/String
+ // have to retain these functions here for the moment.
/// Create the network from a protobuf text file on disk
virtual armnn::INetworkPtr CreateNetworkFromTextFile(
const char* graphFile,
const std::map<std::string, armnn::TensorShape>& inputShapes,
const std::vector<std::string>& requestedOutputs) override;
- /// Create the network from a protobuf binary file on disk
- virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(
- const char* graphFile,
- const std::map<std::string, armnn::TensorShape>& inputShapes,
- const std::vector<std::string>& requestedOutputs) override;
- /// Create the network directly from protobuf text in a string. Useful for debugging/testing
+ /// Creates the network directly from protobuf text in a string. Useful for debugging/testing.
virtual armnn::INetworkPtr CreateNetworkFromString(
const char* protoText,
const std::map<std::string, armnn::TensorShape>& inputShapes,
const std::vector<std::string>& requestedOutputs) override;
- /// Retrieve binding info (layer id and tensor info) for the network input identified by the given layer name
+ /// Retrieves binding info (layer id and tensor info) for the network input identified by the given layer name.
virtual BindingPointInfo GetNetworkInputBindingInfo(const std::string& name) const override;
- /// Retrieve binding info (layer id and tensor info) for the network output identified by the given layer name
+ /// Retrieves binding info (layer id and tensor info) for the network output identified by the given layer name.
virtual BindingPointInfo GetNetworkOutputBindingInfo(const std::string& name) const override;
-public:
- CaffeParser();
-
-private:
- static std::pair<armnn::LayerBindingId, armnn::TensorInfo> GetBindingInfo(const std::string& layerName,
- const char* bindingPointDesc,
- const std::unordered_map<std::string, BindingPointInfo>& bindingInfos);
-
- /// Parses a NetParameter loaded into memory from one of the other CreateNetwork*
- armnn::INetworkPtr CreateNetworkFromNetParameter(
- caffe::NetParameter& netParam,
- const std::map<std::string, armnn::TensorShape>& inputShapes,
- const std::vector<std::string>& requestedOutputs);
-
- /// does the actual conversion from caffe::NetParameter to armnn::INetwork
- void LoadNetParam(caffe::NetParameter& netParameter);
-
- /// Find the Caffe layers listed as inputs (bottoms) for a given layer.
- std::vector<const caffe::LayerParameter*> GetInputs(const caffe::LayerParameter& layerParam);
-
- /// Modifies the Caffe network to replace "in-place" layers (whose top() and bottom() are both the same)
- /// with regular layers. This simplifies further parsing.
- void ResolveInPlaceLayers(caffe::NetParameter& netParameter);
-
- /// Converts Caffe's protobuf tensor shape format to ArmNN's
- armnn::TensorInfo BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const;
+ CaffeParserBase();
+protected:
/// Adds an armnn layer to m_Network given a Caffe LayerParameter of the correct type
/// and is responsible for recording any newly created IOutputSlots using SetArmnnOutputSlotForCaffeTop().
/// @{
@@ -97,46 +71,105 @@ private:
void ParseDropoutLayer(const caffe::LayerParameter& layerParam);
/// @}
+ /// ParseConv may use these helpers depending on the group parameter
+ /// @{
+ void AddConvLayerWithSplits(const caffe::LayerParameter& layerParam,
+ const armnn::Convolution2dDescriptor & desc,
+ unsigned int kernelW,
+ unsigned int kernelH);
+ void AddConvLayerWithDepthwiseConv(const caffe::LayerParameter& layerParam,
+ const armnn::Convolution2dDescriptor & desc,
+ unsigned int kernelW,
+ unsigned int kernelH);
+ /// @}
+
+ /// Converts Caffe's protobuf tensor shape format to ArmNN's
+ armnn::TensorInfo BlobShapeToTensorInfo(const caffe::BlobShape& blobShape) const;
+
void TrackInputBinding(armnn::IConnectableLayer* layer,
- armnn::LayerBindingId id,
- const armnn::TensorInfo& tensorInfo);
+ armnn::LayerBindingId id,
+ const armnn::TensorInfo& tensorInfo);
+
+ static void TrackBindingPoint(armnn::IConnectableLayer* layer, armnn::LayerBindingId id,
+ const armnn::TensorInfo& tensorInfo,
+ const char* bindingPointDesc,
+ std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo);
void TrackOutputBinding(armnn::IConnectableLayer* layer,
- armnn::LayerBindingId id,
- const armnn::TensorInfo& tensorInfo);
+ armnn::LayerBindingId id,
+ const armnn::TensorInfo& tensorInfo);
- static void TrackBindingPoint(armnn::IConnectableLayer* layer, armnn::LayerBindingId id,
- const armnn::TensorInfo& tensorInfo,
- const char* bindingPointDesc,
- std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo);
+
+ void SetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot);
/// Retrieves the Armnn IOutputSlot representing the given Caffe top.
/// Throws if it cannot be found (e.g. not parsed yet).
armnn::IOutputSlot& GetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName) const;
- void SetArmnnOutputSlotForCaffeTop(const std::string& caffeTopName, armnn::IOutputSlot& armnnOutputSlot);
+ static std::pair<armnn::LayerBindingId, armnn::TensorInfo> GetBindingInfo(
+ const std::string& layerName,
+ const char* bindingPointDesc,
+ const std::unordered_map<std::string, BindingPointInfo>& bindingInfos);
+
void Cleanup();
- armnn::INetworkPtr m_Network;
+ using OperationParsingFunction = void(CaffeParserBase::*)(const caffe::LayerParameter& layerParam);
- std::map<std::string, const caffe::LayerParameter*> m_CaffeLayersByTopName;
+ /// Maps Caffe layer names to parsing member functions.
+ static const std::map<std::string, OperationParsingFunction> ms_CaffeLayerNameToParsingFunctions;
- using OperationParsingFunction = void(CaffeParser::*)(const caffe::LayerParameter& layerParam);
+ /// maps input layer names to their corresponding ids and tensor infos
+ std::unordered_map<std::string, BindingPointInfo> m_NetworkInputsBindingInfo;
- /// map of Caffe layer names to parsing member functions
- static const std::map<std::string, OperationParsingFunction> ms_CaffeLayerNameToParsingFunctions;
+ /// maps output layer names to their corresponding ids and tensor infos
+ std::unordered_map<std::string, BindingPointInfo> m_NetworkOutputsBindingInfo;
+
+ armnn::INetworkPtr m_Network;
std::map<std::string, armnn::TensorShape> m_InputShapes;
- std::vector<std::string> m_RequestedOutputs;
/// As we add armnn layers we store the armnn IOutputSlot which corresponds to the Caffe tops.
std::unordered_map<std::string, armnn::IOutputSlot*> m_ArmnnOutputSlotForCaffeTop;
- /// maps input layer names to their corresponding ids and tensor infos
- std::unordered_map<std::string, BindingPointInfo> m_NetworkInputsBindingInfo;
+ std::vector<std::string> m_RequestedOutputs;
+
+
+ // Stuff which has gone to base class simply because we haven't done any
+ // memory optimisation on the text/string format. If we move this to a layer
+ // by layer parse as well these can move to the CaffeParser class.
+ std::map<std::string, const caffe::LayerParameter*> m_CaffeLayersByTopName;
+
+ /// Parses a NetParameter loaded into memory from one of the other CreateNetwork*
+ armnn::INetworkPtr CreateNetworkFromNetParameter(
+ caffe::NetParameter& netParam,
+ const std::map<std::string, armnn::TensorShape>& inputShapes,
+ const std::vector<std::string>& requestedOutputs);
+
+ /// does the actual conversion from caffe::NetParameter to armnn::INetwork
+ void LoadNetParam(caffe::NetParameter& netParameter);
+
+ /// Find the Caffe layers listed as inputs (bottoms) for a given layer.
+ std::vector<const caffe::LayerParameter*> GetInputs(const caffe::LayerParameter& layerParam);
+
+ /// Modifies the Caffe network to replace "in-place" layers (whose top() and bottom() are both the same)
+ /// with regular layers. This simplifies further parsing.
+ void ResolveInPlaceLayers(caffe::NetParameter& netParameter);
+
+};
+
+class CaffeParser : public CaffeParserBase
+{
+public:
+
+ /// Create the network from a protobuf binary file on disk
+ virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(
+ const char* graphFile,
+ const std::map<std::string, armnn::TensorShape>& inputShapes,
+ const std::vector<std::string>& requestedOutputs) override;
+
+public:
+ CaffeParser();
- /// maps output layer names to their corresponding ids and tensor infos
- std::unordered_map<std::string, BindingPointInfo> m_NetworkOutputsBindingInfo;
};
} \ No newline at end of file
diff --git a/src/armnnCaffeParser/CaffeSupport.md b/src/armnnCaffeParser/CaffeSupport.md
index b5229ebf04..9e4f1fa993 100644
--- a/src/armnnCaffeParser/CaffeSupport.md
+++ b/src/armnnCaffeParser/CaffeSupport.md
@@ -17,6 +17,11 @@ The Arm NN SDK supports the following machine learning layers for Caffe networks
- BatchNorm, in inference mode.
- Convolution, excluding the Dilation Size, Weight Filler, Bias Filler, Engine, Force nd_im2col, and Axis parameters.
+
+ Caffe doesn't support depthwise convolution, the equivalent layer is implemented through the notion of groups. ArmNN supports groups this way:
+ - when group=1, it is a normal conv2d
+ - when group=#input_channels, we can replace it by a depthwise convolution
+ - when group>1 && group<#input_channels, we need to split the input into the given number of groups, apply a separate convolution and then merge the results
- Concat, along the channel dimension only.
- Dropout, in inference mode.
- Eltwise, excluding the coeff parameter.
diff --git a/src/armnnCaffeParser/RecordByRecordCaffeParser.cpp b/src/armnnCaffeParser/RecordByRecordCaffeParser.cpp
new file mode 100644
index 0000000000..60747f3bce
--- /dev/null
+++ b/src/armnnCaffeParser/RecordByRecordCaffeParser.cpp
@@ -0,0 +1,732 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// See LICENSE file in the project root for full license information.
+//
+
+#include "RecordByRecordCaffeParser.hpp"
+
+#include "armnn/Exceptions.hpp"
+#include "armnn/Utils.hpp"
+
+
+#include "GraphTopologicalSort.hpp"
+
+#include <boost/numeric/conversion/cast.hpp>
+
+// Caffe
+#include <google/protobuf/wire_format.h>
+
+
+//#include <stdio.h>
+#include <limits.h>
+#include <sstream>
+//#include <iostream>
+#include <fstream>
+
+namespace armnnCaffeParser
+{
+// class which holds information on the absolute position in the stream
+// of the data and the length of the data record.
+class VarLenDataInfo
+{
+public:
+ VarLenDataInfo(std::streamoff positionOfData, size_t sizeOfData) :
+ m_PositionOfData(positionOfData), m_SizeOfData(sizeOfData) {}
+
+ VarLenDataInfo(const VarLenDataInfo& x) :
+ m_PositionOfData(x.PositionOfData()), m_SizeOfData (x.SizeOfData()) {}
+
+ VarLenDataInfo& operator=(const VarLenDataInfo& x)
+ {
+ // handle self assignment
+ if (this == &x) {
+ return *this;
+ }
+ m_PositionOfData = x.PositionOfData(); m_SizeOfData = x.SizeOfData(); return *this;
+ }
+
+ std::streamoff PositionOfData() const {return m_PositionOfData;}
+ size_t SizeOfData() const {return m_SizeOfData;}
+
+private:
+ std::streamoff m_PositionOfData;
+ size_t m_SizeOfData;
+
+};
+
+// class which holds enough information on a LayerParameter in the Caffe protobuf
+// format to allow it to be resolved for in place layering and sorted topologically
+// prior to the entire record being parsed into memory.
+//
+// NOTE: function naming follows that of the protobuf classes these proxies are standing in for
+class LayerParameterInfo : public VarLenDataInfo
+{
+public:
+ static const std::string INPUT;
+ LayerParameterInfo(const VarLenDataInfo& varLenDataInfo) :
+ VarLenDataInfo(varLenDataInfo.PositionOfData(), varLenDataInfo.SizeOfData()),
+ m_newTops(false), m_newBottoms(false) {}
+
+ LayerParameterInfo(std::streamoff positionOfData, size_t sizeOfData) :
+ VarLenDataInfo(positionOfData, sizeOfData), m_newTops(false), m_newBottoms(false) {}
+
+ LayerParameterInfo(const LayerParameterInfo& x) :
+ VarLenDataInfo(x.PositionOfData(), x.SizeOfData()),
+ m_name(x.m_name),
+ m_type(x.m_type),
+ m_tops(x.m_tops),
+ m_bottoms(x.m_bottoms),
+ m_newTops(x.m_newTops),
+ m_newBottoms(x.m_newBottoms) {}
+
+ LayerParameterInfo& operator=(const LayerParameterInfo& x)
+ {
+ if (this == &x) {
+ return *this;
+ }
+ VarLenDataInfo::operator=(x);
+ m_name = x.m_name;
+ m_type = x.m_type;
+ m_tops = x.m_tops;
+ m_bottoms = x.m_bottoms;
+ m_newTops = x.m_newTops;
+ m_newBottoms = x.m_newBottoms;
+ return *this;
+ }
+
+ const std::string name() const {return m_name;}
+ void set_name(const std::unique_ptr<char[]>& theName, size_t length)
+ {
+ m_name = std::string(theName.get(), length);
+ }
+ void set_name(const std::string& theName) {m_name = theName;}
+
+ const std::string type() const {return m_type;}
+ void set_type(const std::unique_ptr<char[]>& theType, size_t length)
+ {
+ m_type = std::string(theType.get(), length);
+ }
+ void set_type(const std::string& theType) {m_type = theType;}
+
+ void add_top(const std::unique_ptr<char[]>& top, size_t length)
+ {
+ std::string topName(top.get(), length);
+ m_tops.push_back(topName);
+ }
+ void add_top(const std::string& topName)
+ {
+ m_tops.push_back(topName);
+ }
+ const std::string top(unsigned long i) const {return m_tops[i];}
+ unsigned long top_size() const {return m_tops.size();}
+ void set_top(unsigned long i, const std::string& newName) {m_tops[i] = newName; m_newTops = true;}
+ bool new_tops() const {return m_newTops;}
+
+ void add_bottom(const std::unique_ptr<char[]>& bottom, size_t length)
+ {
+ std::string bottomName(bottom.get(), length);
+ m_bottoms.push_back(bottomName);
+ }
+ unsigned long bottom_size() const {return m_bottoms.size();}
+ const std::string bottom(unsigned long i) const {return m_bottoms[i];}
+ void set_bottom(unsigned long i, const std::string& newName) {m_bottoms[i] = newName; m_newBottoms = true;}
+ bool new_bottoms() const {return m_newBottoms;}
+
+ // if the position and size of the data is zero and the type is "Input" then this is an 'Implicit Input Layer'
+ // and needs to be handled differently from ordinary layers.
+ bool isImplicitInputLayer() const
+ {
+ if ((PositionOfData() == 0) && (SizeOfData() == 0) && INPUT.compare(type()) == 0)
+ {return true;} else {return false;}
+ }
+
+private:
+ std::string m_name;
+ std::string m_type;
+ std::vector<std::string> m_tops;
+ std::vector<std::string> m_bottoms;
+ // mark the layers whose topology was changed
+ // by the ResolveInPlaceLayers method.
+ bool m_newTops;
+ bool m_newBottoms;
+};
+
+// class which holds the field type (wire type) and field id (id from the .proto schema)
+// read from the protobuf messages as per the binary encoding described in
+// https://developers.google.com/protocol-buffers/docs/encoding
+//
+// NOTE: function naming follows that of the protobuf classes these proxies are standing in for
+class ProtobufFieldInfo
+{
+public:
+ ProtobufFieldInfo(int field_type, int field_id) :
+ m_eof(false), m_field_type(field_type), m_field_id(field_id) {}
+ ProtobufFieldInfo() : m_eof(true), m_field_type(0), m_field_id(0) {}
+
+ bool eof() {return m_eof;}
+ int field_type() {return m_field_type;}
+ int field_id() {return m_field_id;}
+
+private:
+ bool m_eof;
+ int m_field_type;
+ int m_field_id;
+};
+
+
+// There are some NetParameter level data which are required
+// to correctly processes some Caffe models. Specifically those which
+// have 'implicit' input layers. Also it is nice to have the name of the model.
+//
+// NOTE: function naming follows that of the protobuf classes these proxies are standing in for
+class NetParameterInfo
+{
+public:
+ const std::string name() const {return m_name;}
+ void set_name(const std::unique_ptr<char[]>& theName, size_t length)
+ {
+ m_name = std::string(theName.get(), length);
+ }
+
+ void add_input(const std::unique_ptr<char[]>& input, size_t length)
+ {
+ std::string inputName(input.get(), length);
+ m_inputs.push_back(inputName);
+ }
+ const std::string input(unsigned long i) const {return m_inputs[i];}
+ unsigned long input_size() const {return m_inputs.size();}
+
+ void add_input_dimension(int input_dimension) {
+ m_input_dimensions.push_back(input_dimension);
+ }
+ int input_dimension(unsigned long i) const {return m_input_dimensions[i];}
+ unsigned long input_dimensions_size() const {return m_input_dimensions.size();}
+
+ void add_blob_shape(caffe::BlobShape shape) {
+ m_blob_shapes.push_back(shape);
+ }
+ const caffe::BlobShape blob_shape(unsigned long i) const {return m_blob_shapes[i];}
+ unsigned long blob_shapes_size() const {return m_blob_shapes.size();}
+
+private:
+ std::string m_name;
+ std::vector<std::string> m_inputs;
+ std::vector<int> m_input_dimensions;
+ std::vector<caffe::BlobShape> m_blob_shapes;
+
+};
+
+}; // namespace armnnCaffeParser
+
+using namespace armnnCaffeParser;
+
+// Initialise the class const
+const std::string LayerParameterInfo::INPUT = "Input";
+
+namespace
+{
+
+ProtobufFieldInfo readFieldInfo(std::ifstream& ifs)
+{
+ unsigned char first_byte = static_cast<unsigned char>(ifs.get());
+ if (!ifs.good())
+ {
+ ProtobufFieldInfo eof;
+ return eof;
+ }
+ int field_type = first_byte&7;
+ int field_id = first_byte>>3;
+ if ((field_id & 16) == 16)
+ {
+ unsigned char second_byte = static_cast<unsigned char>(ifs.get());
+ if (!ifs.good())
+ {
+ ProtobufFieldInfo eof;
+ return eof;
+ }
+ field_id = (field_id-16) + ((second_byte&127)<<4);
+ }
+ ProtobufFieldInfo fieldInfo(field_type, field_id);
+ return fieldInfo;
+}
+
+const static int MAX_NUM_BYTES = 5;
+
+int ReadBase128(std::ifstream& ifs)
+{
+ int result = 0;
+ unsigned int shift_by = 0;
+ int bytesRead = 0;
+ while (true)
+ {
+ unsigned char a_byte = static_cast<unsigned char>(ifs.get());
+ ++bytesRead;
+ if (bytesRead > MAX_NUM_BYTES)
+ {
+ throw armnn::ParseException(
+ "ReadBase128 exceeded the maximum number of bytes expected for an integer representation");
+ }
+ result += (a_byte & 127) << shift_by;
+ shift_by += 7;
+ if ((a_byte & 128) != 128)
+ {
+ break;
+ }
+ }
+ return result;
+}
+
+
+std::unique_ptr<char[]> AllocateBuffer(std::ifstream& ifs, VarLenDataInfo& dataInfo)
+{
+ std::unique_ptr<char[]> ptr(new char[dataInfo.SizeOfData()]);
+ ifs.clear();
+ ifs.seekg(dataInfo.PositionOfData(), std::ios_base::beg);
+ ifs.read(ptr.get(), boost::numeric_cast<std::streamsize>(dataInfo.SizeOfData()));
+ return ptr;
+}
+
+VarLenDataInfo CreateVarLenDataInfo(std::streamoff bufferStart, std::streamoff endOfLayer) {
+ std::streamoff sizeOfLayer = endOfLayer - bufferStart;
+ if (sizeOfLayer < 0)
+ {
+ std::stringstream ss;
+ ss << "error when determining buffer size, negative value [" << sizeOfLayer << "]";
+ throw armnn::ParseException(ss.str());
+ }
+ // NOTE: as some of the data being read in will be translated into strings (names of layers etc)
+ // the maximum size we can deal with is the upper size limit of a string i.e. size_t
+ // on the platform in which I am currently compiling std::streamoff is signed long int and
+ // size_t is unsigned long int so there is no way this error condition can fire but this stuff
+ // is supposed to be portable so the check remains in place
+ if (boost::numeric_cast<size_t>(sizeOfLayer) > SIZE_MAX) {
+ std::stringstream ss;
+ ss << "layer is greater than " << SIZE_MAX << " in size cannot process. layer size = [" << sizeOfLayer << "]";
+ throw armnn::ParseException(ss.str());
+ }
+ LayerParameterInfo info(bufferStart, boost::numeric_cast<size_t>(sizeOfLayer));
+ return info;
+}
+
+void ReadTopologicalInfoForLayerParameter(LayerParameterInfo& layerInfo, std::ifstream& ifs)
+{
+ // position the file pointer to the start of the layer data
+ ifs.clear();
+ ifs.seekg(layerInfo.PositionOfData(), std::ios_base::beg);
+ std::streamoff endOfLayer = layerInfo.PositionOfData() +
+ boost::numeric_cast<std::streamoff>(layerInfo.SizeOfData());
+ while(true)
+ {
+ // check to see if we have reached the end of the record
+ std::streamoff currentPosition = ifs.tellg();
+ if (currentPosition >= endOfLayer) {
+ return;
+ }
+ // read the information for the next field.
+ ProtobufFieldInfo fieldInfo = readFieldInfo(ifs);
+ if (fieldInfo.eof())
+ {
+ return;
+ // TODO: figure out whether this is an error condition or not...
+ //throw armnn::ParseException("failed to read field from LayerParameter data");
+ }
+ // process the field
+ switch (fieldInfo.field_type())
+ {
+ case 0:
+ {
+ ReadBase128(ifs);
+ break;
+ }
+ case 2:
+ {
+ int size = ReadBase128(ifs);
+ std::streamoff posStartOfData = ifs.tellg();
+ VarLenDataInfo dataInfo(posStartOfData, boost::numeric_cast<size_t>(size));
+ //optional string name = 1; // the layer name
+ //optional string type = 2; // the layer type
+ //repeated string bottom = 3; // the name of each bottom blob
+ //repeated string top = 4; // the name of each top blob
+ if (fieldInfo.field_id() == 1)
+ {
+ // read and set the name of the layer
+ auto layerName = AllocateBuffer(ifs, dataInfo);
+ layerInfo.set_name(layerName, dataInfo.SizeOfData());
+ }
+ else if (fieldInfo.field_id() == 2)
+ {
+ // read and set the type of the layer
+ auto layerType = AllocateBuffer(ifs, dataInfo);
+ layerInfo.set_type(layerType, dataInfo.SizeOfData());
+ }
+ else if (fieldInfo.field_id() == 3)
+ {
+ // read and add a bottom to the layer
+ auto bottom = AllocateBuffer(ifs, dataInfo);
+ layerInfo.add_bottom(bottom, dataInfo.SizeOfData());
+ }
+ else if (fieldInfo.field_id() == 4)
+ {
+ // read and add a top to the layer
+ auto top = AllocateBuffer(ifs, dataInfo);
+ layerInfo.add_top(top, dataInfo.SizeOfData());
+ }
+ else
+ {
+ ifs.seekg(size, std::ios_base::cur);
+ if (!ifs.good())
+ {
+ // TODO: error out?
+ return;
+ }
+ }
+ break;
+ }
+ case 1:
+ {
+ // 64 bit
+ // advance by eight bytes
+ ifs.seekg(8, std::ios_base::cur);
+ if (!ifs.good())
+ {
+ // TODO: error out?
+ return;
+ }
+ break;
+ }
+ case 5:
+ {
+ // 32 bit
+ // advance by four bytes
+ ifs.seekg(4, std::ios_base::cur);
+ if (!ifs.good())
+ {
+ // TODO: error out?
+ return;
+ }
+ break;
+ }
+ default:
+ {
+ throw armnn::ParseException("Encounted an unknown field type");
+ break;
+ }
+ }
+ }
+}
+
+void ResolveInPlaceLayers(std::vector<LayerParameterInfo>& layerInfo)
+{
+ std::map<std::string, std::vector<LayerParameterInfo*>> layersByTop;
+ for (auto& info : layerInfo)
+ {
+ for (unsigned long i = 0; i < info.top_size(); ++i)
+ {
+ layersByTop[info.top(i)].push_back(&info);
+ }
+ }
+ // For each set of layers with the same top, resolve them to a linear chain rather than in-place layers.
+ // Note that for 'regular' layers, there will be a single layer in each group and so this will be a no-op.
+ for (auto& layersWithSameTopIterator : layersByTop)
+ {
+ const std::string& top = layersWithSameTopIterator.first;
+ const std::vector<LayerParameterInfo*> layersWithSameTop = layersWithSameTopIterator.second;
+
+ // Chain the layers together in the order that they are listed in the prototxt (hopefully this is correct).
+ // Note that the last layer will not have its top modified so that other layers will continue to reference it.
+ for (unsigned int layerIdx = 0; layerIdx < layersWithSameTop.size() - 1; ++layerIdx)
+ {
+ LayerParameterInfo* layer1 = layersWithSameTop[layerIdx];
+ LayerParameterInfo* layer2 = layersWithSameTop[layerIdx + 1];
+ if (layer1->top_size() != 1)
+ {
+ throw armnn::ParseException("Node '" + layer1->name() + "' is an in-place layer but "
+ "doesn't have exactly one top.");
+ }
+ std::string newTop = layer1->name() + "_top";
+ layer1->set_top(0, newTop);
+ if (layer2->bottom_size() != 1 || layer2->bottom(0) != top)
+ {
+ throw armnn::ParseException("Node '" + layer2->name() + "' is an in-place layer but "
+ " doesn't have exactly one bottom, or it doesn't match its top.");
+ }
+ layer2->set_bottom(0, newTop);
+
+ }
+ }
+}
+
+} // anonymous namespace, can't be seen outside this source file
+
+RecordByRecordCaffeParser::RecordByRecordCaffeParser() : CaffeParserBase()
+{}
+
+armnn::INetworkPtr RecordByRecordCaffeParser::CreateNetworkFromBinaryFile(
+ const char* graphFile,
+ const std::map<std::string, armnn::TensorShape>& inputShapes,
+ const std::vector<std::string>& requestedOutputs)
+{
+
+ m_InputShapes = inputShapes;
+ if (requestedOutputs.size() == 0)
+ {
+ throw armnn::ParseException("requestedOutputs must have at least one entry");
+ }
+ m_RequestedOutputs = requestedOutputs;
+
+ //FILE * fp = fopen(graphFile, "rb");
+ std::ifstream ifs(graphFile, std::ifstream::in|std::ifstream::binary);
+ std::vector<LayerParameterInfo> layerInfo;
+ NetParameterInfo netParameterInfo;
+ while(true)
+ {
+ ProtobufFieldInfo fieldInfo = readFieldInfo(ifs);
+ if (fieldInfo.eof())
+ {
+ break;
+ }
+ switch(fieldInfo.field_type())
+ {
+ case 0:
+ {
+ ReadBase128(ifs);
+ break;
+ }
+ case 2:
+ {
+ // The values of interest from the caffe.proto schema are:
+ // optional string name = 1; // consider giving the network a name
+ // DEPRECATED. See InputParameter. The input blobs to the network.
+ // repeated string input = 3;
+ // DEPRECATED. See InputParameter. The shape of the input blobs.
+ // repeated BlobShape input_shape = 8;
+
+ // 4D input dimensions -- deprecated. Use "input_shape" instead.
+ // If specified, for each input blob there should be four
+ // values specifying the num, channels, height and width of the input blob.
+ // Thus, there should be a total of (4 * #input) numbers.
+ // repeated int32 input_dim = 4;
+
+ // The layers that make up the net. Each of their configurations, including
+ // connectivity and behavior, is specified as a LayerParameter.
+ // repeated LayerParameter layer = 100; // ID 100 so layers are printed last.
+
+ // The first four will (if present) be read into the NetParameterInfo
+ // the LayerParameters will be read into the LayerParameterInfo vector.
+
+ int size = ReadBase128(ifs);
+ std::streamoff posStartOfData = ifs.tellg();
+ ifs.seekg(size, std::ios_base::cur);
+ if(!ifs.good())
+ {
+ throw armnn::ParseException("failed to seek ahead in binary caffe file");
+ }
+ std::streamoff endOfLayer = ifs.tellg();
+ if (fieldInfo.field_id() == 1)
+ {
+ VarLenDataInfo dataInfo = CreateVarLenDataInfo(posStartOfData, endOfLayer);
+ auto graphName = AllocateBuffer(ifs, dataInfo);
+ netParameterInfo.set_name(graphName, dataInfo.SizeOfData());
+ }
+ if (fieldInfo.field_id() == 3)
+ {
+ VarLenDataInfo dataInfo = CreateVarLenDataInfo(posStartOfData, endOfLayer);
+ auto inputName = AllocateBuffer(ifs, dataInfo);
+ netParameterInfo.add_input(inputName, dataInfo.SizeOfData());
+ }
+ if (fieldInfo.field_id() == 8)
+ {
+ VarLenDataInfo dataInfo = CreateVarLenDataInfo(posStartOfData, endOfLayer);
+ auto inputShape = AllocateBuffer(ifs, dataInfo);
+ caffe::BlobShape blobShape;
+ bool bRet = blobShape.ParseFromArray(inputShape.get(), static_cast<int>(dataInfo.SizeOfData()));
+ if (!bRet)
+ {
+ throw armnn::ParseException("Failed to parse input shape");
+ }
+ netParameterInfo.add_blob_shape(blobShape);
+ }
+ if (fieldInfo.field_id() == 4)
+ {
+ int input_dim = ReadBase128(ifs);
+ netParameterInfo.add_input_dimension(input_dim);
+ }
+ if (fieldInfo.field_id() == 100)
+ {
+ LayerParameterInfo info(CreateVarLenDataInfo(posStartOfData, endOfLayer));
+ ReadTopologicalInfoForLayerParameter(info, ifs);
+ layerInfo.push_back(info);
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ }
+ std::vector<const LayerParameterInfo*> sortedNodes;
+ ProcessLayers(netParameterInfo, layerInfo, m_RequestedOutputs, sortedNodes);
+ armnn::INetworkPtr networkPtr = LoadLayers(ifs, sortedNodes, netParameterInfo);
+ return networkPtr;
+
+}
+
+void RecordByRecordCaffeParser::ProcessLayers(
+ const NetParameterInfo& netParameterInfo,
+ std::vector<LayerParameterInfo>& layerInfo,
+ const std::vector<std::string>& m_RequestedOutputs,
+ std::vector<const LayerParameterInfo*>& sortedNodes)
+{
+ // if there is an implicit input layer add it to the layerInfo list
+ if (netParameterInfo.input_size() > 0)
+ {
+ LayerParameterInfo implicitInputLayer(0, 0);
+ implicitInputLayer.set_type(LayerParameterInfo::INPUT);
+ implicitInputLayer.set_name(netParameterInfo.input(0));
+ implicitInputLayer.add_top(netParameterInfo.input(0));
+ layerInfo.push_back(implicitInputLayer);
+ }
+ ::ResolveInPlaceLayers(layerInfo);
+
+ for (LayerParameterInfo& info : layerInfo)
+ {
+ for (unsigned long i = 0; i < info.top_size(); ++i)
+ {
+ m_CaffeLayersByTopName[info.top(i)] = &info;
+ }
+ }
+
+ // Find the output layers the user requested
+ std::vector<const LayerParameterInfo*> targetLayers;
+ for (const std::string& requestedOutputName : m_RequestedOutputs)
+ {
+ auto nodeIt = m_CaffeLayersByTopName.find(requestedOutputName);
+ if (nodeIt == m_CaffeLayersByTopName.end())
+ {
+ throw armnn::ParseException(
+ "Couldn't find requested output layer '" + requestedOutputName + "' in graph");
+ }
+ targetLayers.push_back(nodeIt->second);
+ }
+
+ // Sort them into a linear ordering such that all inputs of a node are before the node itself
+ if (!armnnUtils::GraphTopologicalSort<const LayerParameterInfo*>(
+ targetLayers,
+ [this](const LayerParameterInfo* node)
+ {
+ return GetInputs(*node);
+ },
+ sortedNodes))
+ {
+ throw armnn::ParseException("Cycle detected in graph");
+ }
+}
+
+
+std::vector<const LayerParameterInfo*> RecordByRecordCaffeParser::GetInputs(
+ const LayerParameterInfo& layerParam)
+{
+ std::vector<const LayerParameterInfo*> ret;
+ ret.reserve(layerParam.bottom_size());
+ for (unsigned long j = 0; j < layerParam.bottom_size(); ++j)
+ {
+ std::string inputName = layerParam.bottom(j);
+ auto inputIt = m_CaffeLayersByTopName.find(inputName);
+ if (inputIt == m_CaffeLayersByTopName.end())
+ {
+ throw armnn::ParseException(
+ "Can't find Caffe layer with top called '" + inputName + "', which is listed as an input of '" +
+ layerParam.name() + "'");
+ }
+ ret.push_back(inputIt->second);
+ }
+
+ return ret;
+}
+
+armnn::INetworkPtr RecordByRecordCaffeParser::LoadLayers(std::ifstream& ifs,
+ std::vector<const LayerParameterInfo *>& sortedNodes,
+ const NetParameterInfo& netParameterInfo)
+{
+
+ m_NetworkInputsBindingInfo.clear();
+ m_NetworkOutputsBindingInfo.clear();
+
+ m_Network = armnn::INetwork::Create();
+
+ for (auto info : sortedNodes)
+ {
+ caffe::LayerParameter layer;
+ if (info->isImplicitInputLayer())
+ {
+ // create the matching Layer Parameter programatically from the data in the
+ // net parameter info which has been passed in...
+ layer.set_type(LayerParameterInfo::INPUT);
+ layer.set_name(netParameterInfo.input(0));
+ layer.add_top(netParameterInfo.input(0));
+
+ caffe::InputParameter* inputParam = layer.mutable_input_param();
+ caffe::BlobShape* shape = inputParam->add_shape();
+
+ long unsigned int dim_size = netParameterInfo.input_dimensions_size();
+ for (long unsigned int i = 0; i < dim_size; ++i)
+ {
+ shape->add_dim(netParameterInfo.input_dimension(i));
+ }
+ }
+ else
+ {
+ char *buffer = new char[info->SizeOfData()];
+ ifs.clear();
+ ifs.seekg(info->PositionOfData(), std::ios_base::beg);
+ ifs.read(buffer, boost::numeric_cast<std::streamsize>(info->SizeOfData()));
+ bool bRet = layer.ParseFromArray(buffer, static_cast<int>(info->SizeOfData()));
+ delete[] buffer;
+ if (!bRet)
+ {
+ throw armnn::ParseException("Failed to parse layer [" + info->name() + "]");
+ }
+ }
+
+ if (info->new_tops())
+ {
+ //update the tops
+ layer.set_top(0, info->top(0));
+ }
+ if (info->new_bottoms())
+ {
+ //update the bottoms
+ layer.set_bottom(0, info->bottom(0));
+ }
+
+ auto it = ms_CaffeLayerNameToParsingFunctions.find(layer.type());
+ if (it == ms_CaffeLayerNameToParsingFunctions.end())
+ {
+ throw armnn::ParseException("Unsupported layer type '" + layer.type() + "'");
+ }
+ auto func = it->second;
+ (this->*func)(layer);
+ }
+ ifs.close();
+
+ // Add ArmNN output layers connected to each requested output
+ for (const std::string& requestedOutput : m_RequestedOutputs)
+ {
+ armnn::IOutputSlot& outputSlot = GetArmnnOutputSlotForCaffeTop(requestedOutput);
+
+ const armnn::LayerBindingId outputId = boost::numeric_cast<armnn::LayerBindingId>(
+ m_NetworkOutputsBindingInfo.size());
+ armnn::IConnectableLayer* const outputLayer = m_Network->AddOutputLayer(outputId, requestedOutput.c_str());
+ outputSlot.Connect(outputLayer->GetInputSlot(0));
+
+ TrackOutputBinding(outputLayer, outputId, outputLayer->GetInputSlot(0).GetConnection()->GetTensorInfo());
+ }
+
+ Cleanup();
+
+ return move(m_Network);
+}
+
+
+
diff --git a/src/armnnCaffeParser/RecordByRecordCaffeParser.hpp b/src/armnnCaffeParser/RecordByRecordCaffeParser.hpp
new file mode 100644
index 0000000000..f0855b4ecb
--- /dev/null
+++ b/src/armnnCaffeParser/RecordByRecordCaffeParser.hpp
@@ -0,0 +1,53 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// See LICENSE file in the project root for full license information.
+//
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include <iostream>
+
+#include "caffe/proto/caffe.pb.h"
+
+#include "CaffeParser.hpp"
+
+
+
+namespace armnnCaffeParser
+{
+
+class NetParameterInfo;
+class LayerParameterInfo;
+
+
+class RecordByRecordCaffeParser : public CaffeParserBase
+{
+public:
+
+ /// Create the network from a protobuf binary file on disk
+ virtual armnn::INetworkPtr CreateNetworkFromBinaryFile(
+ const char* graphFile,
+ const std::map<std::string, armnn::TensorShape>& inputShapes,
+ const std::vector<std::string>& requestedOutputs) override;
+
+ RecordByRecordCaffeParser();
+
+private:
+ void ProcessLayers(const NetParameterInfo& netParameterInfo,
+ std::vector<LayerParameterInfo>& layerInfo,
+ const std::vector<std::string>& m_RequestedOutputs,
+ std::vector<const LayerParameterInfo*>& sortedNodes);
+ armnn::INetworkPtr LoadLayers(std::ifstream& ifs,
+ std::vector<const LayerParameterInfo *>& sortedNodes,
+ const NetParameterInfo& netParameterInfo);
+ std::vector<const LayerParameterInfo*> GetInputs(
+ const LayerParameterInfo& layerParam);
+
+ std::map<std::string, const LayerParameterInfo*> m_CaffeLayersByTopName;
+ std::vector<std::string> m_RequestedOutputs;
+};
+
+} // namespace armnnCaffeParser
+
diff --git a/src/armnnCaffeParser/test/TestAdd.cpp b/src/armnnCaffeParser/test/TestAdd.cpp
index 7d91924638..1ee593739e 100644
--- a/src/armnnCaffeParser/test/TestAdd.cpp
+++ b/src/armnnCaffeParser/test/TestAdd.cpp
@@ -8,7 +8,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct AddFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct AddFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
AddFixture()
{
diff --git a/src/armnnCaffeParser/test/TestConcat.cpp b/src/armnnCaffeParser/test/TestConcat.cpp
index 441c28c837..52ef4aff4b 100644
--- a/src/armnnCaffeParser/test/TestConcat.cpp
+++ b/src/armnnCaffeParser/test/TestConcat.cpp
@@ -8,7 +8,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct ConcatFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct ConcatFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
ConcatFixture()
{
diff --git a/src/armnnCaffeParser/test/TestConvolution.cpp b/src/armnnCaffeParser/test/TestConvolution.cpp
new file mode 100644
index 0000000000..4e3af3ca85
--- /dev/null
+++ b/src/armnnCaffeParser/test/TestConvolution.cpp
@@ -0,0 +1,133 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// See LICENSE file in the project root for full license information.
+//
+#include <boost/test/unit_test.hpp>
+#include "armnnCaffeParser/ICaffeParser.hpp"
+#include "ParserPrototxtFixture.hpp"
+#include <sstream>
+#include <initializer_list>
+
+namespace
+{
+
+template <typename T>
+std::string TaggedSequence(const std::string & tag, const std::initializer_list<T> & data)
+{
+ bool first = true;
+ std::stringstream ss;
+ for (auto && d : data)
+ {
+ if (!first)
+ {
+ ss << " , ";
+ }
+ else
+ {
+ first = false;
+ }
+ ss << " " << tag << " : " << d << " ";
+ }
+ return ss.str();
+}
+
+template <typename T>
+std::string TaggedSequence(const std::string & tag, T data, unsigned int n)
+{
+ std::stringstream ss;
+ for (unsigned int i=0; i<n; ++i)
+ {
+ if (i>0)
+ {
+ ss << " , ";
+ }
+ ss << " " << tag << " : " << data << " ";
+ }
+ return ss.str();
+}
+
+} // namespace <anonymous>
+
+BOOST_AUTO_TEST_SUITE(CaffeParser)
+
+struct ConvolutionFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+{
+ ConvolutionFixture(const std::initializer_list<unsigned int> & inputDims,
+ const std::initializer_list<float> & filterData,
+ unsigned int kernelSize,
+ unsigned int numOutput=1,
+ unsigned int group=1)
+ {
+ m_Prototext = R"(
+ name: "ConvolutionTest"
+ layer {
+ name: "input1"
+ type: "Input"
+ top: "input1"
+ input_param { shape: { )" + TaggedSequence("dim", inputDims) + R"( } }
+ }
+ layer {
+ name: "conv1"
+ type: "Convolution"
+ bottom: "input1"
+ top: "conv1"
+ blobs: { )" + TaggedSequence("data", filterData) + R"( }
+ blobs: { )" + TaggedSequence("data", 0, numOutput) + R"( }
+ convolution_param {
+ num_output: )" + std::to_string(numOutput) + R"(
+ pad: 0
+ kernel_size: )" + std::to_string(kernelSize) + R"(
+ stride: 1
+ group: )" + std::to_string(group) + R"(
+ }
+ }
+ )";
+ SetupSingleInputSingleOutput("input1", "conv1");
+ }
+};
+
+struct SimpleConvolutionFixture : public ConvolutionFixture
+{
+ SimpleConvolutionFixture()
+ : ConvolutionFixture( {1, 1, 2, 2}, {1.0f, 1.0f, 1.0f, 1.0f}, 2)
+ {
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(SimpleConvolution, SimpleConvolutionFixture)
+{
+ RunTest<4>({ 1, 3, 5, 7 }, { 16 });
+}
+
+struct GroupConvolutionFixture : public ConvolutionFixture
+{
+ GroupConvolutionFixture()
+ : ConvolutionFixture(
+ {1, 2, 2, 2},
+ {
+ 1.0f, 1.0f, 1.0f, 1.0f, // filter for channel #0
+ 2.0f, 2.0f, 2.0f, 2.0f // filter for channel #1
+ },
+ 2, // kernel size is 2x2
+ 2, // number of output channels is 2
+ 2) // number of groups (separate filters)
+ {
+ }
+};
+
+BOOST_FIXTURE_TEST_CASE(GroupConvolution, GroupConvolutionFixture)
+{
+ RunTest<4>(
+ {
+ 1, 2, 3, 4, // input channel #0
+ 5, 6, 7, 8, // input channel #1
+ },
+ {
+ 10, // convolution result for channel #0 applying filter #0
+ 52 // same for channel #1 and filter #1
+ }
+ );
+}
+
+
+BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file
diff --git a/src/armnnCaffeParser/test/TestDropout.cpp b/src/armnnCaffeParser/test/TestDropout.cpp
index 16f2c2728c..66135251e0 100644
--- a/src/armnnCaffeParser/test/TestDropout.cpp
+++ b/src/armnnCaffeParser/test/TestDropout.cpp
@@ -9,7 +9,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct DropoutFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct DropoutFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
DropoutFixture()
{
diff --git a/src/armnnCaffeParser/test/TestInPlace.cpp b/src/armnnCaffeParser/test/TestInPlace.cpp
index 3954baa75b..623f01c8f1 100644
--- a/src/armnnCaffeParser/test/TestInPlace.cpp
+++ b/src/armnnCaffeParser/test/TestInPlace.cpp
@@ -9,7 +9,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
// The pooling layer should take its input from the relu, not the add directly.
-struct InPlaceFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct InPlaceFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
InPlaceFixture()
{
@@ -59,7 +59,7 @@ BOOST_FIXTURE_TEST_CASE(ParseInPlace, InPlaceFixture)
// The requested output of the network is a layer which has an activation attached.
// The output of the network should therefore actually be the activation layer.
-struct InPlaceOutputFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct InPlaceOutputFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
InPlaceOutputFixture()
{
diff --git a/src/armnnCaffeParser/test/TestInputs.cpp b/src/armnnCaffeParser/test/TestInputs.cpp
index f0e2343a33..b8458de5a7 100644
--- a/src/armnnCaffeParser/test/TestInputs.cpp
+++ b/src/armnnCaffeParser/test/TestInputs.cpp
@@ -35,16 +35,18 @@ BOOST_AUTO_TEST_CASE(InputShapes)
std::string implicitInputNoShape = "name: \"Minimal\"\n"
"input: \"data\" \n";
- armnn::IRuntimePtr runtime(armnn::IRuntime::Create(armnn::Compute::CpuRef));
+ armnn::IRuntime::CreationOptions options;
+ armnn::IRuntimePtr runtime(armnn::IRuntime::Create(options));
armnnCaffeParser::ICaffeParserPtr parser(armnnCaffeParser::ICaffeParser::Create());
armnn::INetworkPtr network(nullptr, nullptr);
armnn::NetworkId netId;
// Check everything works normally
+ std::vector<armnn::Compute> backends = {armnn::Compute::CpuRef};
{
network = parser->CreateNetworkFromString(explicitInput.c_str(), {}, { "data" });
BOOST_TEST(network.get());
- runtime->LoadNetwork(netId, Optimize(*network, runtime->GetDeviceSpec()));
+ runtime->LoadNetwork(netId, Optimize(*network, backends, runtime->GetDeviceSpec()));
armnnCaffeParser::BindingPointInfo inputBindingInfo = parser->GetNetworkInputBindingInfo("data");
armnn::TensorInfo inputTensorInfo = inputBindingInfo.second;
@@ -56,11 +58,11 @@ BOOST_AUTO_TEST_CASE(InputShapes)
BOOST_TEST(inputTensorInfo.GetShape()[3] == 4);
}
- // Check everything works with implicit input
+ // Checks everything works with implicit input.
{
network = parser->CreateNetworkFromString(implicitInput.c_str(), {}, { "data" });
BOOST_TEST(network.get());
- runtime->LoadNetwork(netId, Optimize(*network, runtime->GetDeviceSpec()));
+ runtime->LoadNetwork(netId, Optimize(*network, backends, runtime->GetDeviceSpec()));
armnnCaffeParser::BindingPointInfo inputBindingInfo = parser->GetNetworkInputBindingInfo("data");
armnn::TensorInfo inputTensorInfo = inputBindingInfo.second;
@@ -72,11 +74,11 @@ BOOST_AUTO_TEST_CASE(InputShapes)
BOOST_TEST(inputTensorInfo.GetShape()[3] == 4);
}
- // Check everything works with implicit and passing shape
+ // Checks everything works with implicit and passing shape.
{
network = parser->CreateNetworkFromString(implicitInput.c_str(), { {"data", { 2, 2, 3, 4 } } }, { "data" });
BOOST_TEST(network.get());
- runtime->LoadNetwork(netId, Optimize(*network, runtime->GetDeviceSpec()));
+ runtime->LoadNetwork(netId, Optimize(*network, backends, runtime->GetDeviceSpec()));
armnnCaffeParser::BindingPointInfo inputBindingInfo = parser->GetNetworkInputBindingInfo("data");
armnn::TensorInfo inputTensorInfo = inputBindingInfo.second;
@@ -88,11 +90,11 @@ BOOST_AUTO_TEST_CASE(InputShapes)
BOOST_TEST(inputTensorInfo.GetShape()[3] == 4);
}
- // Check everything works with implicit (no shape) and passing shape
+ // Checks everything works with implicit (no shape) and passing shape.
{
network = parser->CreateNetworkFromString(implicitInputNoShape.c_str(), {{"data", {2, 2, 3, 4} }}, { "data" });
BOOST_TEST(network.get());
- runtime->LoadNetwork(netId, Optimize(*network, runtime->GetDeviceSpec()));
+ runtime->LoadNetwork(netId, Optimize(*network, backends, runtime->GetDeviceSpec()));
armnnCaffeParser::BindingPointInfo inputBindingInfo = parser->GetNetworkInputBindingInfo("data");
armnn::TensorInfo inputTensorInfo = inputBindingInfo.second;
@@ -104,13 +106,13 @@ BOOST_AUTO_TEST_CASE(InputShapes)
BOOST_TEST(inputTensorInfo.GetShape()[3] == 4);
}
- // Check exception on incompatible shapes
+ // Checks exception on incompatible shapes.
{
BOOST_CHECK_THROW(parser->CreateNetworkFromString(implicitInput.c_str(), {{"data",{ 2, 2, 3, 2 }}}, {"data"}),
armnn::ParseException);
}
- // Check exception when no shape available
+ // Checks exception when no shape available.
{
BOOST_CHECK_THROW(parser->CreateNetworkFromString(implicitInputNoShape.c_str(), {}, { "data" }),
armnn::ParseException);
diff --git a/src/armnnCaffeParser/test/TestMul.cpp b/src/armnnCaffeParser/test/TestMul.cpp
index b53318e81e..dc72780a47 100644
--- a/src/armnnCaffeParser/test/TestMul.cpp
+++ b/src/armnnCaffeParser/test/TestMul.cpp
@@ -8,7 +8,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct MulFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct MulFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
MulFixture()
{
diff --git a/src/armnnCaffeParser/test/TestMultiInputsOutputs.cpp b/src/armnnCaffeParser/test/TestMultiInputsOutputs.cpp
index cd87246bee..ebda3ce1b8 100644
--- a/src/armnnCaffeParser/test/TestMultiInputsOutputs.cpp
+++ b/src/armnnCaffeParser/test/TestMultiInputsOutputs.cpp
@@ -8,7 +8,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct MultiInputsOutputsFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct MultiInputsOutputsFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
MultiInputsOutputsFixture()
{
diff --git a/src/armnnCaffeParser/test/TestPooling2d.cpp b/src/armnnCaffeParser/test/TestPooling2d.cpp
index 25cd124648..b48693129c 100644
--- a/src/armnnCaffeParser/test/TestPooling2d.cpp
+++ b/src/armnnCaffeParser/test/TestPooling2d.cpp
@@ -8,7 +8,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct GlobalPoolingFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct GlobalPoolingFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
GlobalPoolingFixture()
{
diff --git a/src/armnnCaffeParser/test/TestSplit.cpp b/src/armnnCaffeParser/test/TestSplit.cpp
index c2f29fb4f3..a84d7ec70a 100644
--- a/src/armnnCaffeParser/test/TestSplit.cpp
+++ b/src/armnnCaffeParser/test/TestSplit.cpp
@@ -8,7 +8,7 @@
BOOST_AUTO_TEST_SUITE(CaffeParser)
-struct SplitFixture : public ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
+struct SplitFixture : public armnnUtils::ParserPrototxtFixture<armnnCaffeParser::ICaffeParser>
{
SplitFixture()
{