diff options
author | telsoa01 <telmo.soares@arm.com> | 2018-08-31 09:22:23 +0100 |
---|---|---|
committer | telsoa01 <telmo.soares@arm.com> | 2018-08-31 09:22:23 +0100 |
commit | c577f2c6a3b4ddb6ba87a882723c53a248afbeba (patch) | |
tree | bd7d4c148df27f8be6649d313efb24f536b7cf34 /src/armnnCaffeParser/CaffeParser.cpp | |
parent | 4c7098bfeab1ffe1cdc77f6c15548d3e73274746 (diff) | |
download | armnn-c577f2c6a3b4ddb6ba87a882723c53a248afbeba.tar.gz |
Release 18.08
Diffstat (limited to 'src/armnnCaffeParser/CaffeParser.cpp')
-rw-r--r-- | src/armnnCaffeParser/CaffeParser.cpp | 1311 |
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(); } } - - |