aboutsummaryrefslogtreecommitdiff
path: root/src/armnnCaffeParser/CaffeParser.cpp
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/CaffeParser.cpp
parent4c7098bfeab1ffe1cdc77f6c15548d3e73274746 (diff)
downloadarmnn-c577f2c6a3b4ddb6ba87a882723c53a248afbeba.tar.gz
Release 18.08
Diffstat (limited to 'src/armnnCaffeParser/CaffeParser.cpp')
-rw-r--r--src/armnnCaffeParser/CaffeParser.cpp1311
1 files changed, 899 insertions, 412 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();
}
}
-
-