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