From 1a84fe3b92aaab61d2f3b865d347459e16eb43d4 Mon Sep 17 00:00:00 2001 From: Sadik Armagan Date: Fri, 27 Mar 2020 15:56:57 +0000 Subject: IVGCVSW-4555 ArmnnConverter (Serializer) does not support per-axis quantization params * TensorInfo can have multiple scales and quantization dimension. Signed-off-by: Sadik Armagan Change-Id: I0ff02e3766996b6a9da6dc4e92d366bc9505c77d --- src/armnnDeserializer/Deserializer.cpp | 24 +++- src/armnnSerializer/ArmnnSchema.fbs | 7 +- src/armnnSerializer/Serializer.cpp | 47 ++++--- src/armnnSerializer/Serializer.hpp | 3 + src/armnnSerializer/SerializerUtils.cpp | 3 + src/armnnSerializer/test/SerializerTests.cpp | 194 +++++++++++++++++++++++++++ 6 files changed, 253 insertions(+), 25 deletions(-) (limited to 'src') diff --git a/src/armnnDeserializer/Deserializer.cpp b/src/armnnDeserializer/Deserializer.cpp index bc6fbf0194..58232a2763 100644 --- a/src/armnnDeserializer/Deserializer.cpp +++ b/src/armnnDeserializer/Deserializer.cpp @@ -508,6 +508,9 @@ armnn::TensorInfo ToTensorInfo(Deserializer::TensorRawPtr tensorPtr) case DataType_QAsymmS8: type = armnn::DataType::QAsymmS8; break; + case DataType_QSymmS8: + type = armnn::DataType::QSymmS8; + break; case DataType_QuantisedAsymm8: case DataType_QAsymmU8: type = armnn::DataType::QAsymmU8; @@ -539,13 +542,30 @@ armnn::TensorInfo ToTensorInfo(Deserializer::TensorRawPtr tensorPtr) location.AsString())); } } - float quantizationScale = tensorPtr->quantizationScale(); - int32_t quantizationOffset = tensorPtr->quantizationOffset(); + auto dimensions = tensorPtr->dimensions(); unsigned int size = dimensions->size(); std::vector outputDims(dimensions->begin(), dimensions->begin() + size); + auto quantizationScales = tensorPtr->quantizationScales(); + + if (quantizationScales) + { + unsigned int quantizationScalesSize = quantizationScales->size(); + std::vector scales(quantizationScales->begin(), quantizationScales->begin() + quantizationScalesSize); + unsigned int quantizationDim = tensorPtr->quantizationDim(); + armnn::TensorInfo result(size, + outputDims.data(), + type, + scales, + quantizationDim); + return result; + } + + float quantizationScale = tensorPtr->quantizationScale(); + int32_t quantizationOffset = tensorPtr->quantizationOffset(); + // two statements (on purpose) for easier debugging: armnn::TensorInfo result(size, outputDims.data(), diff --git a/src/armnnSerializer/ArmnnSchema.fbs b/src/armnnSerializer/ArmnnSchema.fbs index ca3db5d542..ff79f6cffe 100644 --- a/src/armnnSerializer/ArmnnSchema.fbs +++ b/src/armnnSerializer/ArmnnSchema.fbs @@ -38,7 +38,8 @@ enum DataType : byte { QuantisedSymm16 = 5, // deprecated QAsymmU8 = 6, QSymmS16 = 7, - QAsymmS8 = 8 + QAsymmS8 = 8, + QSymmS8 = 9 } enum DataLayout : byte { @@ -54,8 +55,10 @@ enum ResizeMethod: byte { table TensorInfo { dimensions:[uint]; dataType:DataType; - quantizationScale:float = 1.0; + quantizationScale:float = 1.0; // @deprecated Use quantizationScales instead quantizationOffset:int = 0; + quantizationScales:[float]; + quantizationDim:uint; } struct Connection { diff --git a/src/armnnSerializer/Serializer.cpp b/src/armnnSerializer/Serializer.cpp index 37ab326a28..cb7a5c456e 100644 --- a/src/armnnSerializer/Serializer.cpp +++ b/src/armnnSerializer/Serializer.cpp @@ -1420,25 +1420,43 @@ flatbuffers::Offset> SerializerVisitor::CreateDataVector( return fbVector; } -flatbuffers::Offset - SerializerVisitor::CreateConstTensorInfo(const armnn::ConstTensor& constTensor) +flatbuffers::Offset SerializerVisitor::CreateTensorInfo(const armnn::TensorInfo& tensorInfo) { - armnn::TensorInfo tensorInfo = constTensor.GetInfo(); - // Get the dimensions std::vector shape; - for(unsigned int dim = 0; dim < tensorInfo.GetShape().GetNumDimensions(); ++dim) { shape.push_back(tensorInfo.GetShape()[dim]); } + if (tensorInfo.HasPerAxisQuantization()) + { + // Create FlatBuffer TensorInfo + auto flatBufferTensorInfo = + serializer::CreateTensorInfo(m_flatBufferBuilder, + m_flatBufferBuilder.CreateVector(shape), + GetFlatBufferDataType(tensorInfo.GetDataType()), + tensorInfo.GetQuantizationScales()[0], + tensorInfo.GetQuantizationOffset(), + m_flatBufferBuilder.CreateVector(tensorInfo.GetQuantizationScales()), + tensorInfo.GetQuantizationDim().value()); + return flatBufferTensorInfo; + } + // Create FlatBuffer TensorInfo auto flatBufferTensorInfo = serializer::CreateTensorInfo(m_flatBufferBuilder, m_flatBufferBuilder.CreateVector(shape), GetFlatBufferDataType(tensorInfo.GetDataType()), tensorInfo.GetQuantizationScale(), tensorInfo.GetQuantizationOffset()); + return flatBufferTensorInfo; +} + +flatbuffers::Offset + SerializerVisitor::CreateConstTensorInfo(const armnn::ConstTensor& constTensor) +{ + armnn::TensorInfo tensorInfo = constTensor.GetInfo(); + flatbuffers::Offset fbPayload; switch (tensorInfo.GetDataType()) @@ -1471,6 +1489,7 @@ flatbuffers::Offset fbPayload = flatBuffersData.o; break; } + case armnn::DataType::QSymmS8: case armnn::DataType::QAsymmU8: case armnn::DataType::Boolean: default: @@ -1484,7 +1503,7 @@ flatbuffers::Offset } flatbuffers::Offset flatBufferConstTensor = serializer::CreateConstTensor( m_flatBufferBuilder, - flatBufferTensorInfo, + CreateTensorInfo(tensorInfo), GetFlatBufferConstTensorData(tensorInfo.GetDataType()), fbPayload); return flatBufferConstTensor; @@ -1533,24 +1552,10 @@ std::vector> const IOutputSlot& outputSlot = layer->GetOutputSlot(slotIndex); const armnn::TensorInfo& tensorInfo = outputSlot.GetTensorInfo(); - // Get the dimensions - std::vector shape; - for(unsigned int dim = 0; dim < tensorInfo.GetShape().GetNumDimensions(); ++dim) - { - shape.push_back(tensorInfo.GetShape()[dim]); - } - - // Create FlatBuffer TensorInfo - auto flatBufferTensorInfo = serializer::CreateTensorInfo(m_flatBufferBuilder, - m_flatBufferBuilder.CreateVector(shape), - GetFlatBufferDataType(tensorInfo.GetDataType()), - tensorInfo.GetQuantizationScale(), - tensorInfo.GetQuantizationOffset()); - // Create FlatBuffer Outputslot outputSlots.push_back(serializer::CreateOutputSlot(m_flatBufferBuilder, slotIndex, - flatBufferTensorInfo)); + CreateTensorInfo(tensorInfo))); } return outputSlots; } diff --git a/src/armnnSerializer/Serializer.hpp b/src/armnnSerializer/Serializer.hpp index 806caefc22..6dd655827d 100644 --- a/src/armnnSerializer/Serializer.hpp +++ b/src/armnnSerializer/Serializer.hpp @@ -293,6 +293,9 @@ private: flatbuffers::Offset CreateConstTensorInfo( const armnn::ConstTensor& constTensor); + /// Creates the serializer TensorInfo for the armnn TensorInfo. + flatbuffers::Offset CreateTensorInfo(const armnn::TensorInfo& tensorInfo); + template flatbuffers::Offset> CreateDataVector(const void* memory, unsigned int size); diff --git a/src/armnnSerializer/SerializerUtils.cpp b/src/armnnSerializer/SerializerUtils.cpp index c1847715a0..5566abf7e4 100644 --- a/src/armnnSerializer/SerializerUtils.cpp +++ b/src/armnnSerializer/SerializerUtils.cpp @@ -39,6 +39,7 @@ armnnSerializer::ConstTensorData GetFlatBufferConstTensorData(armnn::DataType da case armnn::DataType::QSymmS16: return armnnSerializer::ConstTensorData::ConstTensorData_ShortData; case armnn::DataType::QAsymmU8: + case armnn::DataType::QSymmS8: case armnn::DataType::Boolean: return armnnSerializer::ConstTensorData::ConstTensorData_ByteData; default: @@ -62,6 +63,8 @@ armnnSerializer::DataType GetFlatBufferDataType(armnn::DataType dataType) return armnnSerializer::DataType::DataType_QAsymmS8; case armnn::DataType::QAsymmU8: return armnnSerializer::DataType::DataType_QAsymmU8; + case armnn::DataType::QSymmS8: + return armnnSerializer::DataType::DataType_QSymmS8; case armnn::DataType::Boolean: return armnnSerializer::DataType::DataType_Boolean; default: diff --git a/src/armnnSerializer/test/SerializerTests.cpp b/src/armnnSerializer/test/SerializerTests.cpp index 8c9c92b634..db89430439 100644 --- a/src/armnnSerializer/test/SerializerTests.cpp +++ b/src/armnnSerializer/test/SerializerTests.cpp @@ -214,6 +214,10 @@ void CompareConstTensor(const armnn::ConstTensor& tensor1, const armnn::ConstTen CompareConstTensorData( tensor1.GetMemoryArea(), tensor2.GetMemoryArea(), tensor1.GetNumElements()); break; + case armnn::DataType::QSymmS8: + CompareConstTensorData( + tensor1.GetMemoryArea(), tensor2.GetMemoryArea(), tensor1.GetNumElements()); + break; case armnn::DataType::Signed32: CompareConstTensorData( tensor1.GetMemoryArea(), tensor2.GetMemoryArea(), tensor1.GetNumElements()); @@ -621,6 +625,100 @@ BOOST_AUTO_TEST_CASE(SerializeConvolution2d) deserializedNetwork->Accept(verifier); } +BOOST_AUTO_TEST_CASE(SerializeConvolution2dWithPerAxisParams) +{ + using Descriptor = armnn::Convolution2dDescriptor; + class Convolution2dLayerVerifier : public LayerVerifierBaseWithDescriptor + { + public: + Convolution2dLayerVerifier(const std::string& layerName, + const std::vector& inputInfos, + const std::vector& outputInfos, + const Descriptor& descriptor, + const armnn::ConstTensor& weights, + const armnn::Optional& biases) + : LayerVerifierBaseWithDescriptor(layerName, inputInfos, outputInfos, descriptor) + , m_Weights(weights) + , m_Biases(biases) {} + + void VisitConvolution2dLayer(const armnn::IConnectableLayer* layer, + const Descriptor& descriptor, + const armnn::ConstTensor& weights, + const armnn::Optional& biases, + const char* name) override + { + VerifyNameAndConnections(layer, name); + VerifyDescriptor(descriptor); + + // check weights + CompareConstTensor(weights, m_Weights); + + // check biases + BOOST_CHECK(biases.has_value() == descriptor.m_BiasEnabled); + BOOST_CHECK(biases.has_value() == m_Biases.has_value()); + + if (biases.has_value() && m_Biases.has_value()) + { + CompareConstTensor(biases.value(), m_Biases.value()); + } + } + + private: + armnn::ConstTensor m_Weights; + armnn::Optional m_Biases; + }; + + using namespace armnn; + + const std::string layerName("convolution2dWithPerAxis"); + const TensorInfo inputInfo ({ 1, 3, 1, 2 }, DataType::QAsymmU8, 0.55f, 128); + const TensorInfo outputInfo({ 1, 3, 1, 3 }, DataType::QAsymmU8, 0.75f, 128); + + const std::vector quantScales{ 0.75f, 0.65f, 0.85f }; + constexpr unsigned int quantDimension = 0; + + const TensorInfo kernelInfo({ 3, 1, 1, 2 }, DataType::QSymmS8, quantScales, quantDimension); + + const std::vector biasQuantScales{ 0.25f, 0.50f, 0.75f }; + const TensorInfo biasInfo({ 3 }, DataType::Signed32, biasQuantScales, quantDimension); + + std::vector kernelData = GenerateRandomData(kernelInfo.GetNumElements()); + armnn::ConstTensor weights(kernelInfo, kernelData); + std::vector biasData = GenerateRandomData(biasInfo.GetNumElements()); + armnn::ConstTensor biases(biasInfo, biasData); + + Convolution2dDescriptor descriptor; + descriptor.m_StrideX = 1; + descriptor.m_StrideY = 1; + descriptor.m_PadLeft = 0; + descriptor.m_PadRight = 0; + descriptor.m_PadTop = 0; + descriptor.m_PadBottom = 0; + descriptor.m_BiasEnabled = true; + descriptor.m_DataLayout = armnn::DataLayout::NHWC; + + armnn::INetworkPtr network = armnn::INetwork::Create(); + armnn::IConnectableLayer* const inputLayer = network->AddInputLayer(0); + armnn::IConnectableLayer* const convLayer = + network->AddConvolution2dLayer(descriptor, + weights, + armnn::Optional(biases), + layerName.c_str()); + armnn::IConnectableLayer* const outputLayer = network->AddOutputLayer(0); + + inputLayer->GetOutputSlot(0).Connect(convLayer->GetInputSlot(0)); + convLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); + + inputLayer->GetOutputSlot(0).SetTensorInfo(inputInfo); + convLayer->GetOutputSlot(0).SetTensorInfo(outputInfo); + + armnn::INetworkPtr deserializedNetwork = DeserializeNetwork(SerializeNetwork(*network)); + BOOST_CHECK(deserializedNetwork); + + Convolution2dLayerVerifier verifier(layerName, {inputInfo}, {outputInfo}, descriptor, weights, biases); + deserializedNetwork->Accept(verifier); +} + BOOST_AUTO_TEST_CASE(SerializeDepthToSpace) { DECLARE_LAYER_VERIFIER_CLASS_WITH_DESCRIPTOR(DepthToSpace) @@ -742,6 +840,102 @@ BOOST_AUTO_TEST_CASE(SerializeDepthwiseConvolution2d) deserializedNetwork->Accept(verifier); } +BOOST_AUTO_TEST_CASE(SerializeDepthwiseConvolution2dWithPerAxisParams) +{ + using Descriptor = armnn::DepthwiseConvolution2dDescriptor; + class DepthwiseConvolution2dLayerVerifier : public LayerVerifierBaseWithDescriptor + { + public: + DepthwiseConvolution2dLayerVerifier(const std::string& layerName, + const std::vector& inputInfos, + const std::vector& outputInfos, + const Descriptor& descriptor, + const armnn::ConstTensor& weights, + const armnn::Optional& biases) : + LayerVerifierBaseWithDescriptor(layerName, inputInfos, outputInfos, descriptor), + m_Weights(weights), + m_Biases(biases) {} + + void VisitDepthwiseConvolution2dLayer(const armnn::IConnectableLayer* layer, + const Descriptor& descriptor, + const armnn::ConstTensor& weights, + const armnn::Optional& biases, + const char* name) override + { + VerifyNameAndConnections(layer, name); + VerifyDescriptor(descriptor); + + // check weights + CompareConstTensor(weights, m_Weights); + + // check biases + BOOST_CHECK(biases.has_value() == descriptor.m_BiasEnabled); + BOOST_CHECK(biases.has_value() == m_Biases.has_value()); + + if (biases.has_value() && m_Biases.has_value()) + { + CompareConstTensor(biases.value(), m_Biases.value()); + } + } + + private: + armnn::ConstTensor m_Weights; + armnn::Optional m_Biases; + }; + + using namespace armnn; + + const std::string layerName("depwiseConvolution2dWithPerAxis"); + const TensorInfo inputInfo ({ 1, 3, 3, 2 }, DataType::QAsymmU8, 0.55f, 128); + const TensorInfo outputInfo({ 1, 2, 2, 4 }, DataType::QAsymmU8, 0.75f, 128); + + const std::vector quantScales{ 0.75f, 0.80f, 0.90f, 0.95f }; + const unsigned int quantDimension = 0; + TensorInfo kernelInfo({ 2, 2, 2, 2 }, DataType::QSymmS8, quantScales, quantDimension); + + const std::vector biasQuantScales{ 0.25f, 0.35f, 0.45f, 0.55f }; + constexpr unsigned int biasQuantDimension = 0; + TensorInfo biasInfo({ 4 }, DataType::Signed32, biasQuantScales, biasQuantDimension); + + std::vector kernelData = GenerateRandomData(kernelInfo.GetNumElements()); + armnn::ConstTensor weights(kernelInfo, kernelData); + std::vector biasData = GenerateRandomData(biasInfo.GetNumElements()); + armnn::ConstTensor biases(biasInfo, biasData); + + DepthwiseConvolution2dDescriptor descriptor; + descriptor.m_StrideX = 1; + descriptor.m_StrideY = 1; + descriptor.m_PadLeft = 0; + descriptor.m_PadRight = 0; + descriptor.m_PadTop = 0; + descriptor.m_PadBottom = 0; + descriptor.m_DilationX = 1; + descriptor.m_DilationY = 1; + descriptor.m_BiasEnabled = true; + descriptor.m_DataLayout = armnn::DataLayout::NHWC; + + armnn::INetworkPtr network = armnn::INetwork::Create(); + armnn::IConnectableLayer* const inputLayer = network->AddInputLayer(0); + armnn::IConnectableLayer* const depthwiseConvLayer = + network->AddDepthwiseConvolution2dLayer(descriptor, + weights, + armnn::Optional(biases), + layerName.c_str()); + armnn::IConnectableLayer* const outputLayer = network->AddOutputLayer(0); + + inputLayer->GetOutputSlot(0).Connect(depthwiseConvLayer->GetInputSlot(0)); + depthwiseConvLayer->GetOutputSlot(0).Connect(outputLayer->GetInputSlot(0)); + + inputLayer->GetOutputSlot(0).SetTensorInfo(inputInfo); + depthwiseConvLayer->GetOutputSlot(0).SetTensorInfo(outputInfo); + + armnn::INetworkPtr deserializedNetwork = DeserializeNetwork(SerializeNetwork(*network)); + BOOST_CHECK(deserializedNetwork); + + DepthwiseConvolution2dLayerVerifier verifier(layerName, {inputInfo}, {outputInfo}, descriptor, weights, biases); + deserializedNetwork->Accept(verifier); +} + BOOST_AUTO_TEST_CASE(SerializeDequantize) { DECLARE_LAYER_VERIFIER_CLASS(Dequantize) -- cgit v1.2.1