IVGCVSW-2606 Produce quantized InputNetwork from simple FP32 InputNetwork

Change-Id: I2140a7af5961ddf8267fbb127202de3900ea79e3
Signed-off-by: Derek Lamberti <derek.lamberti@arm.com>
diff --git a/src/armnn/Layer.cpp b/src/armnn/Layer.cpp
index 85e1de0..50b28ad 100644
--- a/src/armnn/Layer.cpp
+++ b/src/armnn/Layer.cpp
@@ -106,6 +106,35 @@
     }
 }
 
+unsigned int OutputSlot::CalculateIndexOnOwner() const
+{
+    for (unsigned int i=0; i < GetOwningLayer().GetNumOutputSlots(); i++)
+    {
+        if (GetOwningLayer().GetOutputSlot(i) == (*this))
+        {
+            return i;
+        }
+    }
+    BOOST_ASSERT_MSG(false, "Did not find slot on owner.");
+    return 0; // Error
+}
+
+bool OutputSlot::operator==(const OutputSlot& other) const
+{
+    bool isSame = other.GetNumConnections() == GetNumConnections();
+    if (!isSame)
+    {
+        return false;
+    }
+
+    for (unsigned int i=0; i < GetNumConnections(); i++)
+    {
+        isSame &= other.GetConnection(i) == GetConnection(i);
+    }
+    return isSame;
+}
+
+
 void OutputSlot::ValidateConnectionIndex(unsigned int index) const
 {
     if (boost::numeric_cast<std::size_t>(index) >= m_Connections.size())
diff --git a/src/armnn/Layer.hpp b/src/armnn/Layer.hpp
index 51c6c09..c08c6b0 100644
--- a/src/armnn/Layer.hpp
+++ b/src/armnn/Layer.hpp
@@ -141,6 +141,10 @@
         return Disconnect(*boost::polymorphic_downcast<InputSlot*>(&slot));
     }
 
+    unsigned int CalculateIndexOnOwner() const;
+
+    bool operator==(const OutputSlot& other) const;
+
 private:
     void ValidateConnectionIndex(unsigned int index) const;
 
diff --git a/src/armnn/LayerVisitorBase.hpp b/src/armnn/LayerVisitorBase.hpp
new file mode 100644
index 0000000..037a5a7
--- /dev/null
+++ b/src/armnn/LayerVisitorBase.hpp
@@ -0,0 +1,179 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <armnn/ILayerVisitor.hpp>
+
+namespace armnn
+{
+
+// Visitor base class with empty implementations.
+class LayerVisitorBase : public ILayerVisitor
+{
+protected:
+    LayerVisitorBase() {}
+    virtual ~LayerVisitorBase() {}
+
+public:
+    virtual void VisitInputLayer(const IConnectableLayer*,
+                                 LayerBindingId,
+                                 const char*) {}
+
+    virtual void VisitConvolution2dLayer(const IConnectableLayer*,
+                                         const Convolution2dDescriptor&,
+                                         const ConstTensor&,
+                                         const char*) {}
+
+    virtual void VisitConvolution2dLayer(const IConnectableLayer*,
+                                         const Convolution2dDescriptor&,
+                                         const ConstTensor&,
+                                         const ConstTensor&,
+                                         const char*) {}
+
+    virtual void VisitDepthwiseConvolution2dLayer(const IConnectableLayer*,
+                                                  const DepthwiseConvolution2dDescriptor&,
+                                                  const ConstTensor& ,
+                                                  const char*) {}
+
+    virtual void VisitDepthwiseConvolution2dLayer(const IConnectableLayer*,
+                                                  const DepthwiseConvolution2dDescriptor&,
+                                                  const ConstTensor&,
+                                                  const ConstTensor&,
+                                                  const char*) {}
+
+    virtual void VisitDetectionPostProcessLayer(const IConnectableLayer*,
+                                                const DetectionPostProcessDescriptor&,
+                                                const char*) {}
+
+    virtual void VisitFullyConnectedLayer(const IConnectableLayer*,
+                                          const FullyConnectedDescriptor&,
+                                          const ConstTensor&,
+                                          const char*) {}
+
+    virtual void VisitFullyConnectedLayer(const IConnectableLayer*,
+                                          const FullyConnectedDescriptor&,
+                                          const ConstTensor&,
+                                          const ConstTensor&,
+                                          const char*) {}
+
+    virtual void VisitPermuteLayer(const IConnectableLayer*,
+                                   const PermuteDescriptor&,
+                                   const char*) {}
+
+    virtual void VisitBatchToSpaceNdLayer(const IConnectableLayer*,
+                                          const BatchToSpaceNdDescriptor&,
+                                          const char*) {}
+
+    virtual void VisitPooling2dLayer(const IConnectableLayer*,
+                                     const Pooling2dDescriptor&,
+                                     const char*) {}
+
+    virtual void VisitActivationLayer(const IConnectableLayer*,
+                                      const ActivationDescriptor&,
+                                      const char*) {}
+
+    virtual void VisitNormalizationLayer(const IConnectableLayer*,
+                                         const NormalizationDescriptor&,
+                                         const char*) {}
+
+    virtual void VisitSoftmaxLayer(const IConnectableLayer*,
+                                   const SoftmaxDescriptor&,
+                                   const char*) {}
+
+    virtual void VisitSplitterLayer(const IConnectableLayer*,
+                                    const ViewsDescriptor&,
+                                    const char*) {}
+
+    virtual void VisitMergerLayer(const IConnectableLayer*,
+                                  const OriginsDescriptor&,
+                                  const char*) {}
+
+    virtual void VisitAdditionLayer(const IConnectableLayer*,
+                                    const char*) {}
+
+    virtual void VisitMultiplicationLayer(const IConnectableLayer*,
+                                          const char*) {}
+
+    virtual void VisitBatchNormalizationLayer(const IConnectableLayer*,
+                                              const BatchNormalizationDescriptor&,
+                                              const ConstTensor&,
+                                              const ConstTensor&,
+                                              const ConstTensor&,
+                                              const ConstTensor&,
+                                              const char*) {}
+
+    virtual void VisitResizeBilinearLayer(const IConnectableLayer*,
+                                          const ResizeBilinearDescriptor&,
+                                          const char*) {}
+
+    virtual void VisitL2NormalizationLayer(const IConnectableLayer*,
+                                           const L2NormalizationDescriptor&,
+                                           const char*) {}
+
+    virtual void VisitConstantLayer(const IConnectableLayer*,
+                                    const ConstTensor&,
+                                    const char*) {}
+
+    virtual void VisitReshapeLayer(const IConnectableLayer*,
+                                   const ReshapeDescriptor&,
+                                   const char*) {}
+
+    virtual void VisitSpaceToBatchNdLayer(const IConnectableLayer*,
+                                          const SpaceToBatchNdDescriptor&,
+                                          const char*) {}
+
+    virtual void VisitFloorLayer(const IConnectableLayer*,
+                                 const char*) {}
+
+    virtual void VisitOutputLayer(const IConnectableLayer*,
+                                  LayerBindingId id,
+                                  const char*) {}
+    
+    virtual void VisitLstmLayer(const IConnectableLayer*,
+                                const LstmDescriptor&,
+                                const LstmInputParams&,
+                                const char*) {}
+    
+    virtual void VisitDivisionLayer(const IConnectableLayer*,
+                                    const char*) {}
+    
+    virtual void VisitSubtractionLayer(const IConnectableLayer*,
+                                       const char*) {}
+    
+    virtual void VisitMaximumLayer(const IConnectableLayer*,
+                                   const char*) {}
+    
+    virtual void VisitMeanLayer(const IConnectableLayer*,
+                                const MeanDescriptor&,
+                                const char*) {}
+    
+    virtual void VisitPadLayer(const IConnectableLayer*,
+                               const PadDescriptor&,
+                               const char*) {}
+    
+    virtual void VisitStridedSliceLayer(const IConnectableLayer*,
+                                        const StridedSliceDescriptor&,
+                                        const char*) {}
+    
+    virtual void VisitMinimumLayer(const IConnectableLayer*,
+                                   const char*) {}
+    
+    virtual void VisitGreaterLayer(const IConnectableLayer*,
+                                   const char*) {}
+    
+    virtual void VisitEqualLayer(const IConnectableLayer*,
+                                 const char*) {}
+    
+    virtual void VisitRsqrtLayer(const IConnectableLayer*,
+                                 const char*) {}
+    
+    virtual void VisitGatherLayer(const IConnectableLayer*,
+                                  const char*) {}
+    
+};
+
+} //namespace armnn
+
diff --git a/src/armnn/NetworkQuantizer.cpp b/src/armnn/NetworkQuantizer.cpp
new file mode 100644
index 0000000..f8e5ed2
--- /dev/null
+++ b/src/armnn/NetworkQuantizer.cpp
@@ -0,0 +1,63 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <armnn/ILayerVisitor.hpp>
+#include <armnn/INetwork.hpp>
+#include <armnn/Tensor.hpp>
+#include <armnn/Types.hpp>
+
+#include "Graph.hpp"
+#include "Layer.hpp"
+#include "Network.hpp"
+#include "NetworkQuantizer.hpp"
+
+#include "StaticRangeVisitor.hpp"
+#include "QuantizerVisitor.hpp"
+
+#include <map>
+#include <vector>
+#include <cmath>
+
+namespace armnn
+{
+
+INetworkQuantizer* INetworkQuantizer::CreateRaw(INetwork *inputNetwork)
+{
+    return new NetworkQuantizer(inputNetwork);
+}
+
+INetworkQuantizerPtr INetworkQuantizer::Create(INetwork* inputNetwork)
+{
+    return INetworkQuantizerPtr(CreateRaw(inputNetwork), &INetworkQuantizer::Destroy);
+}
+
+void INetworkQuantizer::Destroy(INetworkQuantizer *quantizer)
+{
+    delete boost::polymorphic_downcast<NetworkQuantizer*>(quantizer);
+}
+
+INetworkPtr NetworkQuantizer::ExportNetwork()
+{
+    const Graph& graph = boost::polymorphic_downcast<const Network*>(m_InputNetwork)->GetGraph().TopologicalSort();
+    auto VisitLayers = [&graph](ILayerVisitor& visitor)
+        {
+            for (auto layer : graph)
+            {
+                layer->Accept(visitor);
+            }
+        };
+
+    // Step 1) Walk the graph and register min/max values for intermediate tensors
+    StaticRangeVisitor rangeVisitor;
+    VisitLayers(rangeVisitor);
+
+    // Step 2) Convert input InputNetwork to Quantized InputNetwork
+    QuantizerVisitor quantizerVisitor(&rangeVisitor);
+    VisitLayers(quantizerVisitor);
+
+    return quantizerVisitor.RetrieveFinalNetwork();
+}
+
+} //namespace armn
\ No newline at end of file
diff --git a/src/armnn/NetworkQuantizer.hpp b/src/armnn/NetworkQuantizer.hpp
new file mode 100644
index 0000000..5543b3a
--- /dev/null
+++ b/src/armnn/NetworkQuantizer.hpp
@@ -0,0 +1,26 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <armnn/INetwork.hpp>
+#include <armnn/INetworkQuantizer.hpp>
+#include <armnn/Types.hpp>
+
+namespace armnn
+{
+
+class NetworkQuantizer : public INetworkQuantizer
+{
+public:
+    NetworkQuantizer(INetwork* inputNetwork) : m_InputNetwork(inputNetwork) {}
+
+    INetworkPtr ExportNetwork() override;
+
+private:
+    INetwork* m_InputNetwork;
+};
+
+} //namespace armnn
\ No newline at end of file
diff --git a/src/armnn/QuantizerVisitor.cpp b/src/armnn/QuantizerVisitor.cpp
new file mode 100644
index 0000000..7608d0a
--- /dev/null
+++ b/src/armnn/QuantizerVisitor.cpp
@@ -0,0 +1,111 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "Network.hpp"
+#include "QuantizerVisitor.hpp"
+#include "StaticRangeVisitor.hpp"
+
+#include <cmath>
+#include <stdint.h>
+#include <limits>
+
+namespace armnn
+{
+
+namespace {
+
+std::pair<int, float> 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<int>(std::round(offset)), static_cast<float>(scale));
+}
+
+} // namespace
+
+QuantizerVisitor::QuantizerVisitor(armnn::StaticRangeVisitor* ranges)
+: m_Ranges(ranges)
+, m_QuantizedNetwork(INetwork::Create())
+{
+}
+
+void QuantizerVisitor::SetQuantizedInputConnections(const IConnectableLayer *srcLayer,
+                                                    IConnectableLayer *quantizedLayer)
+{
+    m_OldToNewGuidMap[srcLayer->GetGuid()] = quantizedLayer->GetGuid();
+
+    for (unsigned int i=0; i < srcLayer->GetNumInputSlots(); i++)
+    {
+        const IInputSlot& srcInputSlot = srcLayer->GetInputSlot(i);
+        const InputSlot* inputSlot = boost::polymorphic_downcast<const InputSlot*>(&srcInputSlot);
+        const OutputSlot* outputSlot = inputSlot->GetConnectedOutputSlot();
+
+        unsigned int slotIdx = outputSlot->CalculateIndexOnOwner();
+        Layer& layerToFind = outputSlot->GetOwningLayer();
+
+        auto found = m_OldToNewGuidMap.find(layerToFind.GetGuid());
+        if (found != m_OldToNewGuidMap.end())
+        {
+            // Connect the slots in the quantized model
+            IConnectableLayer* prevQuantizedLayer = m_GuidToLayerMap[found->second];
+            IInputSlot& newInputSlot = quantizedLayer->GetInputSlot(i);
+            IOutputSlot& newOutputSlot = prevQuantizedLayer->GetOutputSlot(slotIdx);
+            newOutputSlot.Connect(newInputSlot);
+
+            // Fetch the min/max ranges that were computed earlier
+            auto range = m_Ranges->GetRange(layerToFind.GetGuid(), i);
+            auto qParams = ComputeQAsymmParams(8, range.first, range.second);
+
+            // Set the quantization params
+            TensorInfo info(newOutputSlot.GetTensorInfo());
+            info.SetDataType(DataType::QuantisedAsymm8);
+            info.SetQuantizationOffset(qParams.first);
+            info.SetQuantizationScale(qParams.second);
+        }
+        else
+        {
+            // error in graph traversal order
+            BOOST_ASSERT_MSG(false, "Error in graph traversal");
+        }
+    }
+}
+
+void QuantizerVisitor::RecordLayer(IConnectableLayer* layer)
+{
+    m_GuidToLayerMap[layer->GetGuid()] = layer;
+}
+
+void QuantizerVisitor::VisitAdditionLayer(const IConnectableLayer *layer, const char *name)
+{
+    IConnectableLayer* newLayer = m_QuantizedNetwork->AddAdditionLayer(name);
+    RecordLayer(newLayer);
+    SetQuantizedInputConnections(layer, newLayer);
+}
+
+void QuantizerVisitor::VisitInputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name)
+{
+    IConnectableLayer* newLayer = m_QuantizedNetwork->AddInputLayer(id, name);
+    RecordLayer(newLayer);
+}
+
+void QuantizerVisitor::VisitOutputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name)
+{
+    IConnectableLayer* newLayer = m_QuantizedNetwork->AddOutputLayer(id, name);
+    RecordLayer(newLayer);
+    SetQuantizedInputConnections(layer, newLayer);
+}
+
+} //namespace armnn
\ No newline at end of file
diff --git a/src/armnn/QuantizerVisitor.hpp b/src/armnn/QuantizerVisitor.hpp
new file mode 100644
index 0000000..bf017d7
--- /dev/null
+++ b/src/armnn/QuantizerVisitor.hpp
@@ -0,0 +1,50 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "LayerVisitorBase.hpp"
+#include <armnn/INetwork.hpp>
+#include <armnn/Types.hpp>
+
+#include <map>
+
+namespace armnn
+{
+
+// Forward declarations
+class StaticRangeVisitor;
+
+/// Visitor object for quantizing layers in a network
+class QuantizerVisitor : public LayerVisitorBase
+{
+public:
+    QuantizerVisitor(StaticRangeVisitor* ranges);
+    ~QuantizerVisitor() = default;
+
+    // Functions to quantize the individual layers, overridden from ILayerVisitor
+    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;
+
+    // Extract the quantized network
+    INetworkPtr RetrieveFinalNetwork() { return std::move(m_QuantizedNetwork); }
+private:
+
+    /// Connects the layer to preceeding layers and sets the quantization parameters based on recorded ranges
+    void SetQuantizedInputConnections(const IConnectableLayer *srcLayer, IConnectableLayer *quantizedLayer);
+
+    /// Record the guid so we can easily find it later
+    void RecordLayer(IConnectableLayer* layer);
+
+
+    StaticRangeVisitor* m_Ranges;           ///< Previously recorded min/max ranges per intermediate tensor
+    INetworkPtr m_QuantizedNetwork;         ///< Quantized version of the model we are building up
+
+    std::map<LayerGuid, LayerGuid> m_OldToNewGuidMap;  ///< Mapping from input network guids to quantized network guids
+    std::map<LayerGuid, IConnectableLayer*> m_GuidToLayerMap; ///< Mapping from guid to layer in quantized network
+};
+
+} //namespace armnn
\ No newline at end of file
diff --git a/src/armnn/StaticRangeVisitor.cpp b/src/armnn/StaticRangeVisitor.cpp
new file mode 100644
index 0000000..8e90ba8
--- /dev/null
+++ b/src/armnn/StaticRangeVisitor.cpp
@@ -0,0 +1,38 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "StaticRangeVisitor.hpp"
+
+
+namespace armnn
+{
+
+void StaticRangeVisitor::SetRange(const IConnectableLayer* layer, unsigned int outputIdx, float min, float max)
+{
+    auto& ranges = m_GuidToRangesMap[layer->GetGuid()];
+
+    if (ranges.size() < layer->GetNumOutputSlots())
+    {
+        ranges.resize(layer->GetNumOutputSlots());
+    }
+    ranges[outputIdx] = std::make_pair(min, max);
+}
+
+StaticRangeVisitor::MinMaxRange StaticRangeVisitor::GetRange(LayerGuid guid, unsigned int idx) const
+{
+    auto found = m_GuidToRangesMap.find(guid);
+    if (found != m_GuidToRangesMap.end())
+    {
+        return found->second.at(idx);
+    }
+    return DefaultRange();
+}
+
+void StaticRangeVisitor::VisitAdditionLayer(const IConnectableLayer *layer, const char *name)
+{
+    SetRange(layer, 0, -20.f, 20.f);
+};
+
+} //namespace armnn
\ No newline at end of file
diff --git a/src/armnn/StaticRangeVisitor.hpp b/src/armnn/StaticRangeVisitor.hpp
new file mode 100644
index 0000000..38f0088
--- /dev/null
+++ b/src/armnn/StaticRangeVisitor.hpp
@@ -0,0 +1,45 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "LayerVisitorBase.hpp"
+
+#include <armnn/INetwork.hpp>
+
+#include <map>
+#include <vector>
+
+namespace armnn
+{
+
+/// Visitor class to establish min/max ranges based on the type of the layer
+class StaticRangeVisitor : public LayerVisitorBase
+{
+public:
+    StaticRangeVisitor() = default;
+    ~StaticRangeVisitor() = default;
+
+    using MinMaxRange = std::pair<float, float>;
+    using MinMaxRanges = std::vector<MinMaxRange>;
+
+    /// Functions to set the Range on a per-layer-type basis
+    void VisitAdditionLayer(const IConnectableLayer *layer, const char *name = nullptr) override;
+
+    /// Retreive the default range
+    MinMaxRange DefaultRange() const { return std::make_pair(-15.0f, 15.0f); }
+
+    /// Retreive the Range for a particular output slot on a particular layer
+    MinMaxRange GetRange(LayerGuid guid, unsigned int idx) const;
+
+private:
+    /// Set the range for an output slot on a layer
+    void SetRange(const IConnectableLayer* layer, unsigned int outputIdx, float min, float max);
+
+    /// Mapping from Guid to an array of ranges for outputs
+    std::map<LayerGuid, MinMaxRanges> m_GuidToRangesMap;
+};
+
+} //namespace armnn
\ No newline at end of file