From a8d572dc48f47e66cd7abd6ad9b2d3a0f40ea94b Mon Sep 17 00:00:00 2001 From: Matteo Martincigh Date: Thu, 7 Feb 2019 17:51:09 +0000 Subject: IVGCVSW-2607 Implement Input range override mechanism * Added the OverrideInputRange method to the Quantizer API * Created OverrideInputRangeVisitor to implement the override mechanism * Moved the quantizer utility functions to the new NetworkQuantizerUtils files * Moved the map of quantization ranges out of the StaticRangeVisitor and into the NetworkQuantizer * Added unit tests * Code refactoring and cleanup Change-Id: I9c1d006c1b6a35fbc04584a832fbe489f8f9276d Signed-off-by: Matteo Martincigh --- CMakeLists.txt | 4 ++ include/armnn/INetworkQuantizer.hpp | 8 +-- src/armnn/NetworkQuantizer.cpp | 28 +++++---- src/armnn/NetworkQuantizer.hpp | 11 +++- src/armnn/NetworkQuantizerUtils.cpp | 61 +++++++++++++++++++ src/armnn/NetworkQuantizerUtils.hpp | 56 ++++++++++++++++++ src/armnn/OverrideInputRangeVisitor.cpp | 47 +++++++++++++++ src/armnn/OverrideInputRangeVisitor.hpp | 45 ++++++++++++++ src/armnn/QuantizerVisitor.cpp | 102 ++++---------------------------- src/armnn/QuantizerVisitor.hpp | 42 +++++++------ src/armnn/StaticRangeVisitor.cpp | 34 ++++++----- src/armnn/StaticRangeVisitor.hpp | 29 ++++----- src/armnn/test/QuantizerTest.cpp | 101 +++++++++++++++++++++++++++++-- 13 files changed, 410 insertions(+), 158 deletions(-) create mode 100644 src/armnn/NetworkQuantizerUtils.cpp create mode 100644 src/armnn/NetworkQuantizerUtils.hpp create mode 100644 src/armnn/OverrideInputRangeVisitor.cpp create mode 100644 src/armnn/OverrideInputRangeVisitor.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d43ef8d8b..e8d63b9a11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -307,12 +307,16 @@ list(APPEND armnn_sources src/armnn/Network.hpp src/armnn/NetworkQuantizer.cpp src/armnn/NetworkQuantizer.hpp + src/armnn/NetworkQuantizerUtils.cpp + src/armnn/NetworkQuantizerUtils.hpp src/armnn/NetworkUtils.cpp src/armnn/NetworkUtils.hpp src/armnn/Observable.cpp src/armnn/Observable.hpp src/armnn/Optimizer.cpp src/armnn/Optimizer.hpp + src/armnn/OverrideInputRangeVisitor.cpp + src/armnn/OverrideInputRangeVisitor.hpp src/armnn/Profiling.cpp src/armnn/ProfilingEvent.cpp src/armnn/ProfilingEvent.hpp diff --git a/include/armnn/INetworkQuantizer.hpp b/include/armnn/INetworkQuantizer.hpp index f3cc13ca35..5969fa4edf 100644 --- a/include/armnn/INetworkQuantizer.hpp +++ b/include/armnn/INetworkQuantizer.hpp @@ -7,9 +7,6 @@ #include -#include - - namespace armnn { @@ -23,11 +20,14 @@ public: static INetworkQuantizerPtr Create(INetwork* inputNetwork); ///< Create Quantizer object wrapped in unique_ptr static void Destroy(INetworkQuantizer* quantizer); ///< Destroy Quantizer object + /// Overrides the default quantization values for the input layer with the given id + virtual void OverrideInputRange(LayerBindingId layerId, float min, float max) = 0; + /// Extract final quantized network virtual INetworkPtr ExportNetwork() = 0; protected: - virtual ~INetworkQuantizer() {}; + virtual ~INetworkQuantizer() {} }; } //namespace armnn diff --git a/src/armnn/NetworkQuantizer.cpp b/src/armnn/NetworkQuantizer.cpp index f8e5ed23a7..ccbc501618 100644 --- a/src/armnn/NetworkQuantizer.cpp +++ b/src/armnn/NetworkQuantizer.cpp @@ -12,11 +12,12 @@ #include "Layer.hpp" #include "Network.hpp" #include "NetworkQuantizer.hpp" +#include "NetworkQuantizerUtils.hpp" #include "StaticRangeVisitor.hpp" #include "QuantizerVisitor.hpp" +#include "OverrideInputRangeVisitor.hpp" -#include #include #include @@ -38,26 +39,29 @@ void INetworkQuantizer::Destroy(INetworkQuantizer *quantizer) delete boost::polymorphic_downcast(quantizer); } +void NetworkQuantizer::OverrideInputRange(LayerBindingId layerId, float min, float max) +{ + const Graph& graph = boost::polymorphic_downcast(m_InputNetwork)->GetGraph(); + auto inputLayers = graph.GetInputLayers(); + + // Walk the input layers of the graph and override the quantization parameters of the one with the given id + OverrideInputRangeVisitor overrideInputRangeVisitor(m_GuidToRangesMap, layerId, MinMaxRange{min, max}); + VisitLayers(inputLayers, overrideInputRangeVisitor); +} + INetworkPtr NetworkQuantizer::ExportNetwork() { const Graph& graph = boost::polymorphic_downcast(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); + StaticRangeVisitor rangeVisitor(m_GuidToRangesMap); + VisitLayers(graph, rangeVisitor); // Step 2) Convert input InputNetwork to Quantized InputNetwork QuantizerVisitor quantizerVisitor(&rangeVisitor); - VisitLayers(quantizerVisitor); + VisitLayers(graph, quantizerVisitor); return quantizerVisitor.RetrieveFinalNetwork(); } -} //namespace armn \ No newline at end of file +} //namespace armn diff --git a/src/armnn/NetworkQuantizer.hpp b/src/armnn/NetworkQuantizer.hpp index 5543b3a444..2f7d36533c 100644 --- a/src/armnn/NetworkQuantizer.hpp +++ b/src/armnn/NetworkQuantizer.hpp @@ -9,6 +9,8 @@ #include #include +#include + namespace armnn { @@ -17,10 +19,17 @@ class NetworkQuantizer : public INetworkQuantizer public: NetworkQuantizer(INetwork* inputNetwork) : m_InputNetwork(inputNetwork) {} + void OverrideInputRange(LayerBindingId layerId, float min, float max) override; INetworkPtr ExportNetwork() override; private: + using MinMaxRange = std::pair; + using MinMaxRanges = std::vector; + INetwork* m_InputNetwork; + + /// Mapping from Guid to an array of ranges for outputs + std::unordered_map m_GuidToRangesMap; }; -} //namespace armnn \ No newline at end of file +} //namespace armnn diff --git a/src/armnn/NetworkQuantizerUtils.cpp b/src/armnn/NetworkQuantizerUtils.cpp new file mode 100644 index 0000000000..1bec63b58c --- /dev/null +++ b/src/armnn/NetworkQuantizerUtils.cpp @@ -0,0 +1,61 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "NetworkQuantizerUtils.hpp" + +#include +#include +#include + +namespace armnn +{ + +std::pair 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(std::round(offset)), static_cast(scale)); +} + +ConstTensor CreateQuantizedConst(const ConstTensor& tensor, std::vector& 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(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 armnn diff --git a/src/armnn/NetworkQuantizerUtils.hpp b/src/armnn/NetworkQuantizerUtils.hpp new file mode 100644 index 0000000000..458d21a974 --- /dev/null +++ b/src/armnn/NetworkQuantizerUtils.hpp @@ -0,0 +1,56 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include +#include +#include + +#include +#include + +#include + +namespace armnn +{ + +std::pair ComputeQAsymmParams(int numBits, double min, double max); + +template +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::max(); + float max = std::numeric_limits::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(src[i], scale, offset); + } +} + +ConstTensor CreateQuantizedConst(const ConstTensor& tensor, std::vector& backing); + +template +void VisitLayers(const LayerContainer& layerContainer, ILayerVisitor& visitor) +{ + for (auto layer : layerContainer) + { + layer->Accept(visitor); + } +} + +} // namespace armnn diff --git a/src/armnn/OverrideInputRangeVisitor.cpp b/src/armnn/OverrideInputRangeVisitor.cpp new file mode 100644 index 0000000000..4c70d3f4a6 --- /dev/null +++ b/src/armnn/OverrideInputRangeVisitor.cpp @@ -0,0 +1,47 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "OverrideInputRangeVisitor.hpp" +#include "NetworkQuantizerUtils.hpp" +#include "Layer.hpp" + +#include + +namespace armnn +{ + +OverrideInputRangeVisitor::OverrideInputRangeVisitor(std::unordered_map& guidToRangesMap, + LayerBindingId layerId, + const MinMaxRange& minMaxRange) + : m_GuidToRangesMap(guidToRangesMap) + , m_LayerId(layerId) + , m_MinMaxRange(minMaxRange) +{} + +void OverrideInputRangeVisitor::VisitInputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name) +{ + if (m_LayerId != id) + { + // Not the layer we are looking for + return; + } + + SetRange(layer); +} + +void OverrideInputRangeVisitor::SetRange(const IConnectableLayer* layer) +{ + BOOST_ASSERT(layer); + + auto& ranges = m_GuidToRangesMap[layer->GetGuid()]; + + if (ranges.size() < layer->GetNumOutputSlots()) + { + ranges.resize(layer->GetNumOutputSlots()); + } + ranges[0] = m_MinMaxRange; +} + +} // namespace armnn diff --git a/src/armnn/OverrideInputRangeVisitor.hpp b/src/armnn/OverrideInputRangeVisitor.hpp new file mode 100644 index 0000000000..a2da6c702e --- /dev/null +++ b/src/armnn/OverrideInputRangeVisitor.hpp @@ -0,0 +1,45 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "NetworkQuantizer.hpp" +#include "LayerVisitorBase.hpp" + +#include + +namespace armnn +{ + +/// Visitor object for overriding the input range of the quantized input layers in a network +class OverrideInputRangeVisitor : public LayerVisitorBase +{ +private: + using MinMaxRange = std::pair; + using MinMaxRanges = std::vector; + +public: + OverrideInputRangeVisitor(std::unordered_map& guidToRangesMap, + LayerBindingId layerId, + const MinMaxRange& minMaxRange); + ~OverrideInputRangeVisitor() = default; + + void VisitInputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name = nullptr) override; + +private: + /// Sets the range for the given input layer + void SetRange(const IConnectableLayer* layer); + + /// Mapping from a layer Guid to an array of ranges for outputs + std::unordered_map& m_GuidToRangesMap; + + /// The id of the input layer of which to override the input range + LayerBindingId m_LayerId; + + /// The new input range to be applied to the input layer + MinMaxRange m_MinMaxRange; +}; + +} // namespace armnn diff --git a/src/armnn/QuantizerVisitor.cpp b/src/armnn/QuantizerVisitor.cpp index 4e075149aa..1212716f97 100644 --- a/src/armnn/QuantizerVisitor.cpp +++ b/src/armnn/QuantizerVisitor.cpp @@ -6,92 +6,16 @@ #include "Network.hpp" #include "QuantizerVisitor.hpp" #include "StaticRangeVisitor.hpp" - -#include "armnn/TypesUtils.hpp" - -#include -#include -#include +#include "NetworkQuantizerUtils.hpp" namespace armnn { -namespace { - -std::pair 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(std::round(offset)), static_cast(scale)); -} - -template -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::max(); - float max = std::numeric_limits::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(src[i], scale, offset); - } -} - -ConstTensor CreateQuantizedConst(const ConstTensor& tensor, std::vector &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( 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) -: m_Ranges(ranges) -, m_QuantizedNetwork(INetwork::Create()) +QuantizerVisitor::QuantizerVisitor(const StaticRangeVisitor *staticRangeVisitor) + : m_StaticRangeVisitor(staticRangeVisitor) + , m_QuantizedNetwork(INetwork::Create()) { + BOOST_ASSERT(m_StaticRangeVisitor); } void QuantizerVisitor::SetQuantizedInputConnections(const IConnectableLayer *srcLayer, @@ -106,17 +30,17 @@ void QuantizerVisitor::SetQuantizedInputConnections(const IConnectableLayer *src unsigned int slotIdx = outputSlot->CalculateIndexOnOwner(); Layer& layerToFind = outputSlot->GetOwningLayer(); - auto found = m_OldToNewGuidMap.find(layerToFind.GetGuid()); - if (found != m_OldToNewGuidMap.end()) + auto found = m_OriginalToQuantizedGuidMap.find(layerToFind.GetGuid()); + if (found != m_OriginalToQuantizedGuidMap.end()) { // Connect the slots in the quantized model - IConnectableLayer* prevQuantizedLayer = m_GuidToLayerMap[found->second]; + IConnectableLayer* prevQuantizedLayer = m_QuantizedGuidToLayerMap[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 range = m_StaticRangeVisitor->GetRange(layerToFind.GetGuid(), i); auto qParams = ComputeQAsymmParams(8, range.first, range.second); // Set the quantization params @@ -128,7 +52,7 @@ void QuantizerVisitor::SetQuantizedInputConnections(const IConnectableLayer *src } else { - // error in graph traversal order + // Error in graph traversal order BOOST_ASSERT_MSG(false, "Error in graph traversal"); } } @@ -136,8 +60,8 @@ void QuantizerVisitor::SetQuantizedInputConnections(const IConnectableLayer *src void QuantizerVisitor::RecordLayer(const IConnectableLayer* srcLayer, IConnectableLayer* quantizedLayer) { - m_OldToNewGuidMap[srcLayer->GetGuid()] = quantizedLayer->GetGuid(); - m_GuidToLayerMap[quantizedLayer->GetGuid()] = quantizedLayer; + m_OriginalToQuantizedGuidMap[srcLayer->GetGuid()] = quantizedLayer->GetGuid(); + m_QuantizedGuidToLayerMap[quantizedLayer->GetGuid()] = quantizedLayer; } void QuantizerVisitor::VisitAdditionLayer(const IConnectableLayer *layer, const char *name) @@ -200,4 +124,4 @@ void QuantizerVisitor::VisitBatchNormalizationLayer(const IConnectableLayer *lay SetQuantizedInputConnections(layer, newLayer); } -} //namespace armnn \ No newline at end of file +} //namespace armnn diff --git a/src/armnn/QuantizerVisitor.hpp b/src/armnn/QuantizerVisitor.hpp index 0dc45822b4..dcaccd4ac7 100644 --- a/src/armnn/QuantizerVisitor.hpp +++ b/src/armnn/QuantizerVisitor.hpp @@ -6,31 +6,34 @@ #pragma once #include "LayerVisitorBase.hpp" +#include "StaticRangeVisitor.hpp" + #include #include +#include -#include +#include namespace armnn { -// Forward declarations +// Forward declaration class StaticRangeVisitor; /// Visitor object for quantizing layers in a network class QuantizerVisitor : public LayerVisitorBase { public: - QuantizerVisitor(StaticRangeVisitor* ranges); + QuantizerVisitor(const StaticRangeVisitor* staticRangeVisitor); ~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 VisitActivationLayer(const IConnectableLayer *layer, + /// 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 VisitActivationLayer(const IConnectableLayer* layer, const ActivationDescriptor& activationDescriptor, - const char *name = nullptr) override; - void VisitOutputLayer(const IConnectableLayer *layer, LayerBindingId id, const char *name = nullptr) override; + 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, @@ -39,22 +42,27 @@ public: const ConstTensor& gamma, const char* name = nullptr) override; - // Extract the quantized network + /// Extract the quantized network INetworkPtr RetrieveFinalNetwork() { return std::move(m_QuantizedNetwork); } -private: +private: /// Connects the layer to preceeding layers and sets the quantization parameters based on recorded ranges - void SetQuantizedInputConnections(const IConnectableLayer *srcLayer, IConnectableLayer *quantizedLayer); + void SetQuantizedInputConnections(const IConnectableLayer* srcLayer, IConnectableLayer* quantizedLayer); /// Record the guids so we can easily find the layers later void RecordLayer(const IConnectableLayer* srcLayer, IConnectableLayer* qLayer); + /// Reference to the static range visitor used to retrieve the quantization ranges + const StaticRangeVisitor* m_StaticRangeVisitor; + + /// Quantized version of the model we are building up + INetworkPtr m_QuantizedNetwork; - StaticRangeVisitor* m_Ranges; ///< Previously recorded min/max ranges per intermediate tensor - INetworkPtr m_QuantizedNetwork; ///< Quantized version of the model we are building up + /// Mapping from input network guids to quantized network guids + std::unordered_map m_OriginalToQuantizedGuidMap; - std::map m_OldToNewGuidMap; ///< Mapping from input network guids to quantized network guids - std::map m_GuidToLayerMap; ///< Mapping from guid to layer in quantized network + /// Mapping from guid to layer in quantized network + std::unordered_map m_QuantizedGuidToLayerMap; }; -} //namespace armnn \ No newline at end of file +} //namespace armnn diff --git a/src/armnn/StaticRangeVisitor.cpp b/src/armnn/StaticRangeVisitor.cpp index 1986e427f2..258d499279 100644 --- a/src/armnn/StaticRangeVisitor.cpp +++ b/src/armnn/StaticRangeVisitor.cpp @@ -12,6 +12,20 @@ namespace armnn { +StaticRangeVisitor::StaticRangeVisitor(std::unordered_map& guidToRangesMap) + : m_GuidToRangesMap(guidToRangesMap) +{} + +StaticRangeVisitor::MinMaxRange StaticRangeVisitor::GetRange(LayerGuid guid, unsigned int idx) const +{ + auto search = m_GuidToRangesMap.find(guid); + if (search == m_GuidToRangesMap.end()) + { + return DefaultRange(); + } + return search->second.at(idx); +} + void StaticRangeVisitor::SetRange(const IConnectableLayer* layer, unsigned int outputIdx, float min, float max) { auto& ranges = m_GuidToRangesMap[layer->GetGuid()]; @@ -23,20 +37,10 @@ void StaticRangeVisitor::SetRange(const IConnectableLayer* layer, unsigned int o 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) +void StaticRangeVisitor::VisitAdditionLayer(const IConnectableLayer* layer, const char* name) { SetRange(layer, 0, -20.f, 20.f); -}; +} void StaticRangeVisitor::VisitBatchNormalizationLayer(const IConnectableLayer* layer, const BatchNormalizationDescriptor& desc, @@ -55,9 +59,9 @@ void StaticRangeVisitor::VisitBatchNormalizationLayer(const IConnectableLayer* l SetRange(layer, 0, -15.0f, 15.0f); } -void StaticRangeVisitor::VisitActivationLayer(const IConnectableLayer *layer, +void StaticRangeVisitor::VisitActivationLayer(const IConnectableLayer* layer, const ActivationDescriptor& activationDescriptor, - const char *name) + const char* name) { switch (activationDescriptor.m_Function) { @@ -83,4 +87,4 @@ void StaticRangeVisitor::VisitActivationLayer(const IConnectableLayer *layer, } } -} //namespace armnn \ No newline at end of file +} //namespace armnn diff --git a/src/armnn/StaticRangeVisitor.hpp b/src/armnn/StaticRangeVisitor.hpp index ed02fb57dd..ea27947e7a 100644 --- a/src/armnn/StaticRangeVisitor.hpp +++ b/src/armnn/StaticRangeVisitor.hpp @@ -8,9 +8,9 @@ #include "LayerVisitorBase.hpp" #include +#include -#include -#include +#include namespace armnn { @@ -18,15 +18,16 @@ namespace armnn /// Visitor class to establish min/max ranges based on the type of the layer class StaticRangeVisitor : public LayerVisitorBase { +private: + using MinMaxRange = std::pair; + using MinMaxRanges = std::vector; + public: - StaticRangeVisitor() = default; + StaticRangeVisitor(std::unordered_map& guidToRangesMap); ~StaticRangeVisitor() = default; - using MinMaxRange = std::pair; - using MinMaxRanges = std::vector; - /// Functions to set the Range on a per-layer-type basis - void VisitAdditionLayer(const IConnectableLayer *layer, const char *name = nullptr) override; + void VisitAdditionLayer(const IConnectableLayer* layer, const char* name = nullptr) override; void VisitBatchNormalizationLayer(const IConnectableLayer* layer, const BatchNormalizationDescriptor& desc, const ConstTensor& mean, @@ -34,22 +35,22 @@ public: const ConstTensor& beta, const ConstTensor& gamma, const char* name = nullptr) override; - void VisitActivationLayer(const IConnectableLayer *layer, + void VisitActivationLayer(const IConnectableLayer* layer, const ActivationDescriptor& activationDescriptor, - const char *name = nullptr) override; + const char* name = nullptr) override; - /// Retreive the default range + /// Retrieve 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 + /// Retrieve 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 m_GuidToRangesMap; + /// Mapping from a layer Guid to an array of ranges for outputs + std::unordered_map& m_GuidToRangesMap; }; -} //namespace armnn \ No newline at end of file +} //namespace armnn diff --git a/src/armnn/test/QuantizerTest.cpp b/src/armnn/test/QuantizerTest.cpp index 6f9ad31cc0..7f782dc686 100644 --- a/src/armnn/test/QuantizerTest.cpp +++ b/src/armnn/test/QuantizerTest.cpp @@ -11,11 +11,19 @@ #include "../LayerVisitorBase.hpp" #include "../Network.hpp" #include "../Graph.hpp" +#include "../NetworkQuantizerUtils.hpp" +#include "../OverrideInputRangeVisitor.hpp" #include +#include + namespace armnn { +using MinMaxRange = std::pair; +using MinMaxRanges = std::vector; +using MinMaxRangeMap = std::unordered_map; + BOOST_AUTO_TEST_SUITE(Quantizer) class TestQuantization : public LayerVisitorBase @@ -44,12 +52,9 @@ public: void VisitLayersTopologically(const INetwork* inputNetwork, ILayerVisitor& visitor) { auto network = boost::polymorphic_downcast(inputNetwork); - auto graph = network->GetGraph().TopologicalSort(); - for (auto layer : graph) - { - layer->Accept(visitor); - } + + VisitLayers(graph, visitor); } BOOST_AUTO_TEST_CASE(QuantizeAddition) @@ -370,5 +375,89 @@ BOOST_AUTO_TEST_CASE(QuantizeBatchNorm) VisitLayersTopologically(quantizedNetwork.get(), validator); } +BOOST_AUTO_TEST_CASE(OverrideInputRangeEmptyNetwork) +{ + MinMaxRangeMap guidToRangesMap; // Empty map of ranges + MinMaxRange minMaxRange(-12.3f, 45.6f); // Range to use for the override + + Network network; // Empty network + auto inputLayers = network.GetGraph().GetInputLayers(); // Empty list of input layers + + OverrideInputRangeVisitor overrideInputRangeVisitor(guidToRangesMap, 0, minMaxRange); + VisitLayers(inputLayers, overrideInputRangeVisitor); + + BOOST_CHECK(guidToRangesMap.empty()); // Check that the map of ranges remained untouched +} + +BOOST_AUTO_TEST_CASE(OverrideInputRangeNoInputLayers) +{ + MinMaxRangeMap guidToRangesMap; // Empty map of ranges + MinMaxRange minMaxRange(-12.3f, 45.6f); // Range to use for the override + + Network network; + network.AddAdditionLayer(); // Network with no input layers + auto inputLayers = network.GetGraph().GetInputLayers(); // Empty list of input layers + + OverrideInputRangeVisitor overrideInputRangeVisitor(guidToRangesMap, 0, minMaxRange); + VisitLayers(inputLayers, overrideInputRangeVisitor); + + BOOST_CHECK(guidToRangesMap.empty()); // Check that the map of ranges remained untouched +} + +BOOST_AUTO_TEST_CASE(OverrideInputRangeInputLayers) +{ + MinMaxRangeMap guidToRangesMap; // Empty map of ranges + MinMaxRange minMaxRange(-12.3f, 45.6f); // Range to use for the override + + Network network; + + // Adding the layers + IConnectableLayer* input0 = network.AddInputLayer(0); + IConnectableLayer* input1 = network.AddInputLayer(1); + IConnectableLayer* addition = network.AddAdditionLayer(); + IConnectableLayer* output = network.AddOutputLayer(2); + + // Connecting the layer + input0->GetOutputSlot(0).Connect(addition->GetInputSlot(0)); + input1->GetOutputSlot(0).Connect(addition->GetInputSlot(1)); + addition->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + // Setting the TensorInfos + TensorShape shape{1U}; + TensorInfo info(shape, DataType::Float32); + input0->GetOutputSlot(0).SetTensorInfo(info); + input1->GetOutputSlot(0).SetTensorInfo(info); + addition->GetOutputSlot(0).SetTensorInfo(info); + + auto inputLayers = network.GetGraph().GetInputLayers(); // List of input layers + + // Trying to override the input range for the input layer with binding id 3 (does not exist in the network) + OverrideInputRangeVisitor overrideInputRangeVisitorLayer3(guidToRangesMap, 3, minMaxRange); + VisitLayers(inputLayers, overrideInputRangeVisitorLayer3); + + // Check that the map of ranges remained untouched + BOOST_CHECK(guidToRangesMap.empty()); + + // Override the input range for the input layer with binding id 1 + OverrideInputRangeVisitor overrideInputRangeVisitorLayer1(guidToRangesMap, 1, minMaxRange); + VisitLayers(inputLayers, overrideInputRangeVisitorLayer1); + + // Check that the map of ranges has been populated + BOOST_CHECK(!guidToRangesMap.empty()); + + // Check that an entry for the input layer with binding id 0 does not exist + BOOST_CHECK(guidToRangesMap.find(input0->GetGuid()) == guidToRangesMap.end()); + + // Check that an entry for the input layer with binding id 1 exists + BOOST_CHECK(guidToRangesMap.find(input1->GetGuid()) != guidToRangesMap.end()); + + // Check that at least a value has been added for the input layer with binding id 1 + BOOST_CHECK(!guidToRangesMap[input1->GetGuid()].empty()); + + // Check the the overridden values are what we intended to set + BOOST_CHECK(guidToRangesMap[input1->GetGuid()].at(0).first == minMaxRange.first); + BOOST_CHECK(guidToRangesMap[input1->GetGuid()].at(0).second == minMaxRange.second); +} + BOOST_AUTO_TEST_SUITE_END() -} //namespace armnn +} // namespace armnn -- cgit v1.2.1