IVGCVSW-2858 Add support for QSymm16 quantization
authorNattapat Chaimanowong <nattapat.chaimanowong@arm.com>
Wed, 20 Mar 2019 11:51:14 +0000 (11:51 +0000)
committernattapat.chaimanowong <nattapat.chaimanowong@arm.com>
Wed, 20 Mar 2019 14:49:03 +0000 (14:49 +0000)
Change-Id: Ia7c305c30c39ec0e9db447a461479be17fde250c
Signed-off-by: Nattapat Chaimanowong <nattapat.chaimanowong@arm.com>
CMakeLists.txt
include/armnn/INetworkQuantizer.hpp
src/armnn/NetworkQuantizationScheme.hpp [new file with mode: 0644]
src/armnn/NetworkQuantizer.cpp
src/armnn/NetworkQuantizer.hpp
src/armnn/NetworkQuantizerUtils.cpp
src/armnn/NetworkQuantizerUtils.hpp
src/armnn/QuantizerVisitor.cpp
src/armnn/QuantizerVisitor.hpp
src/armnn/test/QuantizerTest.cpp

index 99f00d8..fe1c1a7 100644 (file)
@@ -297,6 +297,7 @@ list(APPEND armnn_sources
     src/armnn/LoadedNetwork.hpp
     src/armnn/Network.cpp
     src/armnn/Network.hpp
+    src/armnn/NetworkQuantizationScheme.hpp
     src/armnn/NetworkQuantizer.cpp
     src/armnn/NetworkQuantizer.hpp
     src/armnn/NetworkQuantizerUtils.cpp
index 5969fa4..54c1c88 100644 (file)
@@ -6,19 +6,33 @@
 #pragma once
 
 #include <armnn/INetwork.hpp>
+#include <armnn/Types.hpp>
 
 namespace armnn
 {
 
+struct QuantizerOptions
+{
+    QuantizerOptions() : m_ActivationFormat(DataType::QuantisedAsymm8) {}
+    QuantizerOptions(DataType activationFormat) : m_ActivationFormat(activationFormat) {}
+
+    DataType m_ActivationFormat;
+};
+
 using INetworkQuantizerPtr = std::unique_ptr<class INetworkQuantizer, void(*)(INetworkQuantizer* quantizer)>;
 
 /// Quantizer class Quantizes a float32 InputNetwork
 class INetworkQuantizer
 {
 public:
-    static INetworkQuantizer* CreateRaw(INetwork* inputNetwork); ///< Create Quantizer object and return raw pointer
-    static INetworkQuantizerPtr Create(INetwork* inputNetwork);  ///< Create Quantizer object wrapped in unique_ptr
-    static void Destroy(INetworkQuantizer* quantizer);           ///< Destroy Quantizer object
+    /// Create Quantizer object and return raw pointer
+    static INetworkQuantizer* CreateRaw(INetwork* inputNetwork, const QuantizerOptions& options = QuantizerOptions());
+
+    /// Create Quantizer object wrapped in unique_ptr
+    static INetworkQuantizerPtr Create(INetwork* inputNetwork, const QuantizerOptions& options = QuantizerOptions());
+
+    /// Destroy Quantizer object
+    static void Destroy(INetworkQuantizer* quantizer);
 
     /// Overrides the default quantization values for the input layer with the given id
     virtual void OverrideInputRange(LayerBindingId layerId, float min, float max) = 0;
diff --git a/src/armnn/NetworkQuantizationScheme.hpp b/src/armnn/NetworkQuantizationScheme.hpp
new file mode 100644 (file)
index 0000000..065205d
--- /dev/null
@@ -0,0 +1,80 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <armnn/Types.hpp>
+
+#include <cmath>
+#include <algorithm>
+
+namespace armnn
+{
+
+using OffsetScalePair = std::pair<float, int>;
+
+struct IQuantizationScheme
+{
+    virtual OffsetScalePair ComputeScheme(double min, double max) const = 0;
+
+    virtual int NumBits() const = 0;
+
+    virtual DataType GetDataType() const = 0;
+
+    virtual ~IQuantizationScheme() {}
+};
+
+struct QAsymm8QuantizationScheme : IQuantizationScheme
+{
+    OffsetScalePair ComputeScheme(double min, double max) const override
+    {
+        if (min >= max)
+        {
+            throw InvalidArgumentException("min >= max will result in invalid quantization.");
+        }
+
+        double highest = (1 << NumBits()) - 1;
+
+        min = std::min(0.0, min); // min <= 0.0
+        max = std::max(0.0, max); // max >= 0.0
+
+        // Assumes quantization range [0-highest]
+        double scale = (max-min) / highest;
+        double offset = -min / scale;
+
+        // Clamp offset [0-highest]
+        offset = std::max(0.0, std::min(highest, offset));
+
+        return std::make_pair(static_cast<float>(scale), static_cast<int>(std::round(offset)));
+    }
+
+    int NumBits() const override { return 8; }
+
+    DataType GetDataType() const override { return DataType::QuantisedAsymm8; }
+};
+
+struct QSymm16QuantizationScheme : IQuantizationScheme
+{
+    OffsetScalePair ComputeScheme(double min, double max) const override
+    {
+        if (min >= max)
+        {
+            throw InvalidArgumentException("min >= max will result in invalid quantization.");
+        }
+
+        double highest = (1 << (NumBits()-1)) - 1; // (numbits-1) accounts for the sign bit
+
+        double extent = std::max(std::abs(min), std::abs(max));
+        double scale = extent / highest;
+
+        return std::make_pair(static_cast<float>(scale), 0);
+    }
+
+    int NumBits() const override { return 16; }
+
+    DataType GetDataType() const override { return DataType::QuantisedSymm16; }
+};
+
+} // namespace armnn
index bf5c9ef..f577aea 100644 (file)
 namespace armnn
 {
 
-INetworkQuantizer* INetworkQuantizer::CreateRaw(INetwork* inputNetwork)
+INetworkQuantizer* INetworkQuantizer::CreateRaw(INetwork* inputNetwork, const QuantizerOptions& options)
 {
-    return new NetworkQuantizer(inputNetwork);
+    return new NetworkQuantizer(inputNetwork, options);
 }
 
-INetworkQuantizerPtr INetworkQuantizer::Create(INetwork* inputNetwork)
+INetworkQuantizerPtr INetworkQuantizer::Create(INetwork* inputNetwork, const QuantizerOptions& options)
 {
-    return INetworkQuantizerPtr(CreateRaw(inputNetwork), &INetworkQuantizer::Destroy);
+    return INetworkQuantizerPtr(CreateRaw(inputNetwork, options), &INetworkQuantizer::Destroy);
 }
 
 void INetworkQuantizer::Destroy(INetworkQuantizer *quantizer)
@@ -58,7 +58,20 @@ INetworkPtr NetworkQuantizer::ExportNetwork()
     VisitLayers(graph, rangeVisitor);
 
     // Step 2) Convert input InputNetwork to Quantized InputNetwork
-    QuantizerVisitor quantizerVisitor(m_Ranges);
+    std::unique_ptr<IQuantizationScheme> quantizationScheme;
+    switch (m_Options.m_ActivationFormat)
+    {
+        case DataType::QuantisedAsymm8:
+            quantizationScheme = std::make_unique<QAsymm8QuantizationScheme>();
+            break;
+        case DataType::QuantisedSymm16:
+            quantizationScheme = std::make_unique<QSymm16QuantizationScheme>();
+            break;
+        default:
+            throw InvalidArgumentException("Unsupported quantization target");
+    }
+
+    QuantizerVisitor quantizerVisitor(m_Ranges, quantizationScheme.get());
     VisitLayers(graph, quantizerVisitor);
 
     return quantizerVisitor.RetrieveFinalNetwork();
index 5b87851..5e93f70 100644 (file)
@@ -17,7 +17,8 @@ namespace armnn
 class NetworkQuantizer : public INetworkQuantizer
 {
 public:
-    NetworkQuantizer(INetwork* inputNetwork) : m_InputNetwork(inputNetwork) {}
+    NetworkQuantizer(INetwork* inputNetwork, const QuantizerOptions& options)
+    : m_InputNetwork(inputNetwork), m_Options(options) {}
 
     void OverrideInputRange(LayerBindingId layerId, float min, float max) override;
     INetworkPtr ExportNetwork() override;
@@ -28,6 +29,9 @@ private:
 
     /// Mapping from Guid to an array of ranges for outputs
     RangeTracker m_Ranges;
+
+    /// Options for the NetworkQuantizer
+    QuantizerOptions m_Options;
 };
 
 } //namespace armnn
index 551760f..a6f9ebd 100644 (file)
 namespace armnn
 {
 
-std::pair<float, int> ComputeQAsymmParams(int numBits, double min, double max)
-{
-    BOOST_ASSERT_MSG(min < max, "min >= max will result in invalid quantization.");
-    double highest = (1 << numBits) - 1;
-
-    min = std::min(0.0, min); // min <= 0.0
-    max = std::max(0.0, max); // max >= 0.0
-
-    // Assumes quantization range [0-highest]
-    double scale = (max-min) / highest;
-    double offset = -min / scale;
-
-    // Clamp offset [0-highest]
-    offset = std::max(0.0, std::min(highest, offset));
-
-    return std::make_pair(static_cast<float>(scale), static_cast<int>(std::round(offset)));
-}
-
 ConstTensor CreateQuantizedConst(const ConstTensor& tensor, std::vector<uint8_t>& backing)
 {
     float scale = 0.0f;
@@ -43,11 +25,11 @@ ConstTensor CreateQuantizedConst(const ConstTensor& tensor, std::vector<uint8_t>
     {
         case DataType::Float32:
         {
-            Quantize(static_cast<const float*>(tensor.GetMemoryArea()),
-                     backing.data(),
-                     backing.size(),
-                     scale,
-                     offset);
+            QuantizeConstant(static_cast<const float*>(tensor.GetMemoryArea()),
+                             backing.data(),
+                             backing.size(),
+                             scale,
+                             offset);
         }
             break;
         default:
index c23517e..26f67f9 100644 (file)
@@ -5,6 +5,8 @@
 
 #pragma once
 
+#include "NetworkQuantizationScheme.hpp"
+
 #include <armnn/Tensor.hpp>
 #include <armnn/TypesUtils.hpp>
 #include <armnn/ILayerVisitor.hpp>
 namespace armnn
 {
 
-std::pair<float, int> ComputeQAsymmParams(int numBits, double min, double max);
-
 template<typename srcType>
-void Quantize(const srcType* src, uint8_t* dst, size_t numElements, float& scale, int& offset)
+void QuantizeConstant(const srcType* src, uint8_t* dst, size_t numElements, float& scale, int& offset)
 {
     BOOST_ASSERT(src);
     BOOST_ASSERT(dst);
@@ -33,9 +33,11 @@ void Quantize(const srcType* src, uint8_t* dst, size_t numElements, float& scale
         max = std::max(max, src[i]);
     }
 
-    auto qParams = ComputeQAsymmParams(8, min, max);
+    QAsymm8QuantizationScheme quantizationScheme;
+    OffsetScalePair qParams = quantizationScheme.ComputeScheme(min, max);
     scale = qParams.first;
     offset = qParams.second;
+
     for (size_t i = 0; i < numElements; ++i)
     {
         dst[i] = armnn::Quantize<uint8_t>(src[i], scale, offset);
index 110594c..95f7c50 100644 (file)
 namespace armnn
 {
 
-QuantizerVisitor::QuantizerVisitor(const RangeTracker& rangeTracker)
+QuantizerVisitor::QuantizerVisitor(const RangeTracker& rangeTracker, const IQuantizationScheme* quantizationScheme)
     : m_Ranges(rangeTracker)
     , m_QuantizedNetwork(INetwork::Create())
+    , m_QuantizationScheme(quantizationScheme)
 {
 }
 
@@ -45,11 +46,11 @@ void QuantizerVisitor::SetQuantizedInputConnections(const IConnectableLayer* src
 
         // Fetch the min/max ranges that were computed earlier
         auto range = m_Ranges.GetRange(layerToFind.GetGuid(), slotIdx);
-        auto qParams = ComputeQAsymmParams(8, range.first, range.second);
+        OffsetScalePair qParams = m_QuantizationScheme->ComputeScheme(range.first, range.second);
 
         // Set the quantization params
         TensorInfo info(newOutputSlot.GetTensorInfo());
-        info.SetDataType(DataType::QuantisedAsymm8);
+        info.SetDataType(m_QuantizationScheme->GetDataType());
         info.SetQuantizationOffset(qParams.second);
         info.SetQuantizationScale(qParams.first);
         newOutputSlot.SetTensorInfo(info);
index 2682663..1751229 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "armnn/LayerVisitorBase.hpp"
 #include "StaticRangeVisitor.hpp"
+#include "NetworkQuantizationScheme.hpp"
 
 #include <armnn/INetwork.hpp>
 #include <armnn/Types.hpp>
@@ -24,7 +25,7 @@ class StaticRangeVisitor;
 class QuantizerVisitor : public LayerVisitorBase<VisitorNoThrowPolicy>
 {
 public:
-    QuantizerVisitor(const RangeTracker& rangeTracker);
+    QuantizerVisitor(const RangeTracker& rangeTracker, const IQuantizationScheme* quantizationScheme);
     ~QuantizerVisitor() = default;
 
     /// Functions to quantize the individual layers, overridden from ILayerVisitor
@@ -129,6 +130,8 @@ private:
 
     /// Mapping from guid to layer in quantized network
     std::unordered_map<LayerGuid, IConnectableLayer*> m_QuantizedGuidToLayerMap;
+
+    const IQuantizationScheme* m_QuantizationScheme;
 };
 
 } //namespace armnn
index f7723bd..eead9b7 100644 (file)
@@ -9,8 +9,9 @@
 #include <armnn/Types.hpp>
 
 #include "armnn/LayerVisitorBase.hpp"
-#include "../Network.hpp"
 #include "../Graph.hpp"
+#include "../Network.hpp"
+#include "../NetworkQuantizationScheme.hpp"
 #include "../NetworkQuantizerUtils.hpp"
 #include "../OverrideInputRangeVisitor.hpp"
 #include "../RangeTracker.hpp"
@@ -997,7 +998,9 @@ BOOST_AUTO_TEST_CASE(QuantizeMerger)
                                       const OriginsDescriptor& mergerDescriptor,
                                       const char* name = nullptr)
         {
-            std::pair<float, int> expectedValues = ComputeQAsymmParams(8, m_Min, m_Max);
+
+            QAsymm8QuantizationScheme quantizationScheme;
+            OffsetScalePair expectedValues = quantizationScheme.ComputeScheme(m_Min, m_Max);
 
             TensorInfo info = layer->GetOutputSlot(0).GetTensorInfo();