IVGCVSW-2622 Add static quantization of 2DConvolution

Change-Id: If7985a54eba97f7c61413e0804879e4afbf65c4d
Signed-off-by: Jim Flynn <jim.flynn@arm.com>
diff --git a/src/armnn/QuantizerVisitor.cpp b/src/armnn/QuantizerVisitor.cpp
index 97a8bc1..c5e203e 100644
--- a/src/armnn/QuantizerVisitor.cpp
+++ b/src/armnn/QuantizerVisitor.cpp
@@ -153,4 +153,39 @@
     SetQuantizedInputConnections(layer, newLayer);
 }
 
+void QuantizerVisitor::VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                               const Convolution2dDescriptor& convolution2dDescriptor,
+                                               const ConstTensor& weights,
+                                               const char* name)
+{
+    std::vector<uint8_t> weightsBacking;
+    ConstTensor qWeights = CreateQuantizedConst(weights, weightsBacking);
+
+    IConnectableLayer* newLayer = m_QuantizedNetwork->AddConvolution2dLayer(convolution2dDescriptor,
+                                                                            qWeights,
+                                                                            name);
+    RecordLayer(layer, newLayer);
+    SetQuantizedInputConnections(layer, newLayer);
+}
+
+void QuantizerVisitor::VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                               const Convolution2dDescriptor& convolution2dDescriptor,
+                                               const ConstTensor& weights,
+                                               const ConstTensor& biases,
+                                               const char* name)
+{
+    std::vector<uint8_t> weightsBacking;
+    ConstTensor qWeights = CreateQuantizedConst(weights, weightsBacking);
+
+    std::vector<uint8_t> biasesBacking;
+    ConstTensor qBiases = CreateQuantizedConst(weights, biasesBacking);
+
+    IConnectableLayer* newLayer = m_QuantizedNetwork->AddConvolution2dLayer(convolution2dDescriptor,
+                                                                            qWeights,
+                                                                            qBiases,
+                                                                            name);
+    RecordLayer(layer, newLayer);
+    SetQuantizedInputConnections(layer, newLayer);
+}
+
 } //namespace armnn
diff --git a/src/armnn/QuantizerVisitor.hpp b/src/armnn/QuantizerVisitor.hpp
index 53b3edc..fbf9cfa 100644
--- a/src/armnn/QuantizerVisitor.hpp
+++ b/src/armnn/QuantizerVisitor.hpp
@@ -52,6 +52,17 @@
                                   const char *name = nullptr)  override;
 
     // Extract the quantized network
+    void VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                 const Convolution2dDescriptor& convolution2dDescriptor,
+                                 const ConstTensor& weights,
+                                 const char* name = nullptr) override;
+    void VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                 const Convolution2dDescriptor& convolution2dDescriptor,
+                                 const ConstTensor& weights,
+                                 const ConstTensor& biases,
+                                 const char* name = nullptr) override;
+
+    /// Extract the quantized network
     INetworkPtr RetrieveFinalNetwork() { return std::move(m_QuantizedNetwork); }
 
 private:
diff --git a/src/armnn/StaticRangeVisitor.cpp b/src/armnn/StaticRangeVisitor.cpp
index eac434e..fa95938 100644
--- a/src/armnn/StaticRangeVisitor.cpp
+++ b/src/armnn/StaticRangeVisitor.cpp
@@ -59,6 +59,27 @@
     SetRange(layer, 0, -15.0f, 15.0f);
 }
 
+void StaticRangeVisitor::VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                                 const Convolution2dDescriptor& convolution2dDescriptor,
+                                                 const ConstTensor& weights,
+                                                 const char* name)
+{
+    boost::ignore_unused(convolution2dDescriptor);
+    boost::ignore_unused(weights);
+    boost::ignore_unused(name);
+    SetRange(layer, 0, -15.0f, 15.0f);
+}
+
+void StaticRangeVisitor::VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                                 const Convolution2dDescriptor& convolution2dDescriptor,
+                                                 const ConstTensor& weights,
+                                                 const ConstTensor& biases,
+                                                 const char* name)
+{
+    boost::ignore_unused(biases);
+    VisitConvolution2dLayer(layer, convolution2dDescriptor, weights, name);
+}
+
 void StaticRangeVisitor::VisitActivationLayer(const IConnectableLayer* layer,
                                               const ActivationDescriptor& activationDescriptor,
                                               const char* name)
diff --git a/src/armnn/StaticRangeVisitor.hpp b/src/armnn/StaticRangeVisitor.hpp
index 94a6ea0..81a0f4a 100644
--- a/src/armnn/StaticRangeVisitor.hpp
+++ b/src/armnn/StaticRangeVisitor.hpp
@@ -35,6 +35,15 @@
                                       const ConstTensor& beta,
                                       const ConstTensor& gamma,
                                       const char* name = nullptr) override;
+    void VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                 const Convolution2dDescriptor& convolution2dDescriptor,
+                                 const ConstTensor& weights,
+                                 const char* name = nullptr) override;
+    void VisitConvolution2dLayer(const IConnectableLayer* layer,
+                                 const Convolution2dDescriptor& convolution2dDescriptor,
+                                 const ConstTensor& weights,
+                                 const ConstTensor& biases,
+                                 const char* name = nullptr) override;
     void VisitActivationLayer(const IConnectableLayer* layer,
                               const ActivationDescriptor& activationDescriptor,
                               const char* name = nullptr) override;
diff --git a/src/armnn/test/QuantizerTest.cpp b/src/armnn/test/QuantizerTest.cpp
index dd90368..a960c6b 100644
--- a/src/armnn/test/QuantizerTest.cpp
+++ b/src/armnn/test/QuantizerTest.cpp
@@ -566,5 +566,92 @@
     ValidateFullyConnectedLayer(true);
 }
 
+class TestConv2dQuantization : public TestQuantization
+{
+public:
+    virtual void VisitConvolution2dLayer(const IConnectableLayer *layer,
+                                         const Convolution2dDescriptor &convolution2dDescriptor,
+                                         const ConstTensor &weights,
+                                         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 weights const
+        BOOST_TEST((weights.GetInfo().GetDataType() == DataType::QuantisedAsymm8));
+        BOOST_CHECK_CLOSE(weights.GetInfo().GetQuantizationScale(), 3.0f / 255.0f, 0.000001f);
+        BOOST_TEST((weights.GetInfo().GetQuantizationOffset() == 85));
+    }
+
+    virtual void VisitConvolution2dLayer(const IConnectableLayer *layer,
+                                         const Convolution2dDescriptor &convolution2dDescriptor,
+                                         const ConstTensor &weights,
+                                         const ConstTensor &biases,
+                                         const char *name = nullptr)
+    {
+        VisitConvolution2dLayer(layer, convolution2dDescriptor, weights, name);
+
+        // test biases const
+        BOOST_TEST((biases.GetInfo().GetDataType() == DataType::QuantisedAsymm8));
+        BOOST_CHECK_CLOSE(biases.GetInfo().GetQuantizationScale(), 3.0f / 255.0f, 0.000001f);
+        BOOST_TEST((biases.GetInfo().GetQuantizationOffset() == 85));
+    }
+};
+
+void TestQuantizeConvolution2d(bool useBiases)
+{
+    auto network = INetwork::Create();
+
+    TensorShape shape{3U};
+    TensorInfo info(shape, DataType::Float32);
+
+    std::vector<float> weightsData{-1.0f, 1.5f, 2.0f};
+    ConstTensor weights(info, weightsData);
+
+    Convolution2dDescriptor descriptor;
+    descriptor.m_BiasEnabled = useBiases;
+
+    // Add the layers
+    IConnectableLayer* input0 = network->AddInputLayer(0);
+    IConnectableLayer* conv2d;
+    if (useBiases)
+    {
+        std::vector<float> biasesData{-1.0f, 1.5f, 2.0f};
+        ConstTensor biases(info, biasesData);
+        conv2d = network->AddConvolution2dLayer(descriptor, weights, biases);
+    }
+    else
+    {
+        conv2d = network->AddConvolution2dLayer(descriptor, weights);
+    }
+    IConnectableLayer* output = network->AddOutputLayer(1);
+
+    // Establish connections
+    input0->GetOutputSlot(0).Connect(conv2d->GetInputSlot(0));
+    conv2d->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    //Set TensorInfo
+    input0->GetOutputSlot(0).SetTensorInfo(info);
+    conv2d->GetOutputSlot(0).SetTensorInfo(info);
+
+    auto quantizedNetwork = INetworkQuantizer::Create(network.get())->ExportNetwork();
+    TestConv2dQuantization validator;
+    VisitLayersTopologically(quantizedNetwork.get(), validator);
+}
+
+BOOST_AUTO_TEST_CASE(QuantizeConvolution2d)
+{
+    TestQuantizeConvolution2d(false);
+}
+
+BOOST_AUTO_TEST_CASE(QuantizeConvolution2dWithBiases)
+{
+    TestQuantizeConvolution2d(true);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
 } // namespace armnn