IVGCVSW-2609 Quantize BatchNormalizationLayer

Change-Id: I7b847112a0322ffc8b88a0708d8439bfb97cfe2c
Signed-off-by: Derek Lamberti <derek.lamberti@arm.com>
diff --git a/src/armnn/QuantizerVisitor.cpp b/src/armnn/QuantizerVisitor.cpp
index fd08b2d..afe3713 100644
--- a/src/armnn/QuantizerVisitor.cpp
+++ b/src/armnn/QuantizerVisitor.cpp
@@ -7,6 +7,8 @@
 #include "QuantizerVisitor.hpp"
 #include "StaticRangeVisitor.hpp"
 
+#include "armnn/TypesUtils.hpp"
+
 #include <cmath>
 #include <stdint.h>
 #include <limits>
@@ -34,6 +36,56 @@
     return std::make_pair(static_cast<int>(std::round(offset)), static_cast<float>(scale));
 }
 
+template<typename srcType>
+void Quantize(const srcType* src, uint8_t* dst, size_t numElements, float &scale, int &offset)
+{
+    BOOST_ASSERT(src);
+    BOOST_ASSERT(dst);
+
+    float min = std::numeric_limits<srcType>::max();
+    float max = std::numeric_limits<srcType>::lowest();
+    for (size_t i = 0; i < numElements; ++i)
+    {
+        min = std::min(min, src[i]);
+        max = std::max(max, src[i]);
+    }
+
+    auto qParams = ComputeQAsymmParams(8, min, max);
+    offset = qParams.first;
+    scale = qParams.second;
+    for (size_t i = 0; i < numElements; ++i)
+    {
+        dst[i] = armnn::Quantize<uint8_t>(src[i], scale, offset);
+    }
+}
+
+ConstTensor CreateQuantizedConst(const ConstTensor& tensor, std::vector<uint8_t> &backing)
+{
+    float scale = 0.0f;
+    int offset = 0;
+    // Reserve the backing memory
+    backing.resize(tensor.GetInfo().GetNumElements());
+
+    DataType type = tensor.GetInfo().GetDataType();
+    switch(type)
+    {
+        case DataType::Float32:
+        {
+            Quantize(static_cast<const float*>( tensor.GetMemoryArea()),
+                     backing.data(),
+                     backing.size(),
+                     scale,
+                     offset);
+        }
+            break;
+        default:
+            BOOST_ASSERT_MSG(false, "Can't quantize unsupported data type");
+    }
+
+    TensorInfo qInfo(tensor.GetInfo().GetShape(), DataType::QuantisedAsymm8, scale, offset);
+    return ConstTensor(qInfo, backing);
+}
+
 } // namespace
 
 QuantizerVisitor::QuantizerVisitor(armnn::StaticRangeVisitor* ranges)
@@ -108,4 +160,35 @@
     SetQuantizedInputConnections(layer, newLayer);
 }
 
+void QuantizerVisitor::VisitBatchNormalizationLayer(const IConnectableLayer *layer,
+                                                    const BatchNormalizationDescriptor& desc,
+                                                    const ConstTensor& mean,
+                                                    const ConstTensor& variance,
+                                                    const ConstTensor& beta,
+                                                    const ConstTensor& gamma,
+                                                    const char *name)
+{
+    std::vector<uint8_t> meanBacking;
+    ConstTensor qMean = CreateQuantizedConst(mean, meanBacking);
+
+    std::vector<uint8_t> varianceBacking;
+    ConstTensor qVariance = CreateQuantizedConst(variance, varianceBacking);
+
+    std::vector<uint8_t> betaBacking;
+    ConstTensor qBeta = CreateQuantizedConst(beta, betaBacking);
+
+    std::vector<uint8_t> gammaBacking;
+    ConstTensor qGamma = CreateQuantizedConst(variance, gammaBacking);
+
+    IConnectableLayer* newLayer = m_QuantizedNetwork->AddBatchNormalizationLayer(desc,
+                                                                                 qMean,
+                                                                                 qVariance,
+                                                                                 qBeta,
+                                                                                 qGamma,
+                                                                                 name);
+
+    RecordLayer(layer, newLayer);
+    SetQuantizedInputConnections(layer, newLayer);
+}
+
 } //namespace armnn
\ No newline at end of file
diff --git a/src/armnn/QuantizerVisitor.hpp b/src/armnn/QuantizerVisitor.hpp
index 5ff457e..d6aee6b 100644
--- a/src/armnn/QuantizerVisitor.hpp
+++ b/src/armnn/QuantizerVisitor.hpp
@@ -28,6 +28,13 @@
     void VisitInputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name = nullptr) override;
     void VisitAdditionLayer(const IConnectableLayer *layer, const char *name = nullptr) override;
     void VisitOutputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name = nullptr)  override;
+    void VisitBatchNormalizationLayer(const IConnectableLayer* layer,
+                                      const BatchNormalizationDescriptor& desc,
+                                      const ConstTensor& mean,
+                                      const ConstTensor& variance,
+                                      const ConstTensor& beta,
+                                      const ConstTensor& gamma,
+                                      const char* name = nullptr) override;
 
     // Extract the quantized network
     INetworkPtr RetrieveFinalNetwork() { return std::move(m_QuantizedNetwork); }
diff --git a/src/armnn/StaticRangeVisitor.cpp b/src/armnn/StaticRangeVisitor.cpp
index 8e90ba8..cc8c26e 100644
--- a/src/armnn/StaticRangeVisitor.cpp
+++ b/src/armnn/StaticRangeVisitor.cpp
@@ -5,6 +5,7 @@
 
 #include "StaticRangeVisitor.hpp"
 
+#include <boost/core/ignore_unused.hpp>
 
 namespace armnn
 {
@@ -35,4 +36,21 @@
     SetRange(layer, 0, -20.f, 20.f);
 };
 
+void StaticRangeVisitor::VisitBatchNormalizationLayer(const IConnectableLayer* layer,
+                                                      const BatchNormalizationDescriptor& desc,
+                                                      const ConstTensor& mean,
+                                                      const ConstTensor& variance,
+                                                      const ConstTensor& beta,
+                                                      const ConstTensor& gamma,
+                                                      const char* name)
+{
+    boost::ignore_unused(desc);
+    boost::ignore_unused(mean);
+    boost::ignore_unused(variance);
+    boost::ignore_unused(beta);
+    boost::ignore_unused(gamma);
+    boost::ignore_unused(name);
+    SetRange(layer, 0, -15.0f, 15.0f);
+}
+
 } //namespace armnn
\ No newline at end of file
diff --git a/src/armnn/StaticRangeVisitor.hpp b/src/armnn/StaticRangeVisitor.hpp
index af59dac..4276a17 100644
--- a/src/armnn/StaticRangeVisitor.hpp
+++ b/src/armnn/StaticRangeVisitor.hpp
@@ -27,6 +27,13 @@
 
     /// Functions to set the Range on a per-layer-type basis
     void VisitAdditionLayer(const IConnectableLayer *layer, const char *name = nullptr) override;
+    void VisitBatchNormalizationLayer(const IConnectableLayer* layer,
+                                      const BatchNormalizationDescriptor& desc,
+                                      const ConstTensor& mean,
+                                      const ConstTensor& variance,
+                                      const ConstTensor& beta,
+                                      const ConstTensor& gamma,
+                                      const char* name = nullptr) override;
 
     /// Retreive the default range
     MinMaxRange DefaultRange() const { return std::make_pair(-15.0f, 15.0f); }
diff --git a/src/armnn/layers/BatchNormalizationLayer.cpp b/src/armnn/layers/BatchNormalizationLayer.cpp
index 8513205..2212f47 100644
--- a/src/armnn/layers/BatchNormalizationLayer.cpp
+++ b/src/armnn/layers/BatchNormalizationLayer.cpp
@@ -71,10 +71,10 @@
 
 void BatchNormalizationLayer::Accept(ILayerVisitor& visitor) const
 {
-    ConstTensor meanTensor(m_Mean->GetTensorInfo(), m_Mean->GetTensor<void*>()) ;
-    ConstTensor varianceTensor(m_Variance->GetTensorInfo(), m_Variance->GetTensor<void*>()) ;
-    ConstTensor betaTensor(m_Beta->GetTensorInfo(), m_Beta->GetTensor<void*>()) ;
-    ConstTensor gammaTensor(m_Gamma->GetTensorInfo(), m_Gamma->GetTensor<void*>()) ;
+    ConstTensor meanTensor(m_Mean->GetTensorInfo(), m_Mean->Map(true));
+    ConstTensor varianceTensor(m_Variance->GetTensorInfo(), m_Variance->Map(true));
+    ConstTensor betaTensor(m_Beta->GetTensorInfo(), m_Beta->Map(true));
+    ConstTensor gammaTensor(m_Gamma->GetTensorInfo(), m_Gamma->Map(true));
     visitor.VisitBatchNormalizationLayer(this, GetParameters(), meanTensor, varianceTensor, betaTensor, gammaTensor);
 }
 
diff --git a/src/armnn/test/QuantizerTest.cpp b/src/armnn/test/QuantizerTest.cpp
index 56b1497..fbafbd8 100644
--- a/src/armnn/test/QuantizerTest.cpp
+++ b/src/armnn/test/QuantizerTest.cpp
@@ -92,5 +92,88 @@
     VisitLayersTopologically(quantizedNetwork.get(), validator);
 }
 
+BOOST_AUTO_TEST_CASE(QuantizeBatchNorm)
+{
+
+    class TestQuantization : public LayerVisitorBase<VisitorThrowingPolicy>
+    {
+    public:
+        virtual void VisitBatchNormalizationLayer(const IConnectableLayer* layer,
+                                                  const BatchNormalizationDescriptor& desc,
+                                                  const ConstTensor& mean,
+                                                  const ConstTensor& variance,
+                                                  const ConstTensor& beta,
+                                                  const ConstTensor& gamma,
+                                                  const char* name = nullptr)
+        {
+            TensorInfo info = layer->GetOutputSlot(0).GetTensorInfo();
+
+            BOOST_TEST((info.GetDataType() == DataType::QuantisedAsymm8));
+
+            BOOST_TEST((info.GetQuantizationOffset() == 128));
+
+            // Based off current static value [-15.0f, 15.0f]
+            BOOST_CHECK_CLOSE(info.GetQuantizationScale(), 30.0f/255.0f, 0.000001f );
+
+            //Test constants
+            BOOST_TEST((mean.GetInfo().GetDataType() == DataType::QuantisedAsymm8));
+            BOOST_TEST((variance.GetInfo().GetDataType() == DataType::QuantisedAsymm8));
+            BOOST_TEST((beta.GetInfo().GetDataType() == DataType::QuantisedAsymm8));
+            BOOST_TEST((gamma.GetInfo().GetDataType() == DataType::QuantisedAsymm8));
+
+            BOOST_CHECK_CLOSE(mean.GetInfo().GetQuantizationScale(), 3.0f/255.0f, 0.000001f);
+            BOOST_CHECK_CLOSE(variance.GetInfo().GetQuantizationScale(), 3.0f/255.0f, 0.000001f);
+            BOOST_CHECK_CLOSE(beta.GetInfo().GetQuantizationScale(), 3.0f/255.0f, 0.000001f);
+            BOOST_CHECK_CLOSE(gamma.GetInfo().GetQuantizationScale(), 3.0f/255.0f, 0.000001f);
+
+            BOOST_TEST((mean.GetInfo().GetQuantizationOffset() == 85));
+        }
+
+        virtual void VisitInputLayer(const IConnectableLayer* layer,
+                                     LayerBindingId id,
+                                     const char* name = nullptr)
+        {}
+
+        virtual void VisitOutputLayer(const IConnectableLayer* layer,
+                                      LayerBindingId id,
+                                      const char* name = nullptr)
+        {}
+    };
+
+    auto network = INetwork::Create();
+
+    TensorShape shape{3U};
+    TensorInfo info(shape, DataType::Float32);
+
+    std::vector<float> meanData{-1.0f, 1.5f, 2.0f};
+    std::vector<float> varData{-1.0f, 1.5f, 2.0f};
+    std::vector<float> betaData{-1.0f, 1.5f, 2.0f};
+    std::vector<float> gammaData{-1.0f, 1.5f, 2.0f};
+
+    ConstTensor mean(info, meanData);
+    ConstTensor var(info, varData);
+    ConstTensor beta(info, betaData);
+    ConstTensor gamma(info, gammaData);
+
+    BatchNormalizationDescriptor desc;
+
+    // Add the layers
+    IConnectableLayer* input0 = network->AddInputLayer(0);
+    IConnectableLayer* batchNorm = network->AddBatchNormalizationLayer(desc, mean, var, beta, gamma);
+    IConnectableLayer* output = network->AddOutputLayer(1);
+
+    // Establish connections
+    input0->GetOutputSlot(0).Connect(batchNorm->GetInputSlot(0));
+    batchNorm->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    //Set TensorInfo
+    input0->GetOutputSlot(0).SetTensorInfo(info);
+    batchNorm->GetOutputSlot(0).SetTensorInfo(info);
+
+    auto quantizedNetwork = INetworkQuantizer::Create(network.get())->ExportNetwork();
+    TestQuantization validator;
+    VisitLayersTopologically(quantizedNetwork.get(), validator);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 } //namespace armnn