diff options
Diffstat (limited to 'src/armnn/layers')
42 files changed, 866 insertions, 201 deletions
diff --git a/src/armnn/layers/ActivationLayer.cpp b/src/armnn/layers/ActivationLayer.cpp index 2371eaa97c..ad1e4a9eba 100644 --- a/src/armnn/layers/ActivationLayer.cpp +++ b/src/armnn/layers/ActivationLayer.cpp @@ -30,12 +30,16 @@ ActivationLayer* ActivationLayer::Clone(Graph& graph) const void ActivationLayer::ValidateTensorShapesFromInputs() { - auto& info = GetInputSlot(0).GetConnection()->GetTensorInfo(); + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); ConditionalThrowIfNotEqual<LayerValidationException>( "ActivationLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - info.GetShape()); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/AdditionLayer.cpp b/src/armnn/layers/AdditionLayer.cpp index 85d12eabcb..ab73a918db 100644 --- a/src/armnn/layers/AdditionLayer.cpp +++ b/src/armnn/layers/AdditionLayer.cpp @@ -28,41 +28,51 @@ AdditionLayer* AdditionLayer::Clone(Graph& graph) const return CloneBase<AdditionLayer>(graph, GetName()); } -void AdditionLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> AdditionLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - auto& input0 = GetInputSlot(0).GetConnection()->GetTensorInfo(); - auto& input1 = GetInputSlot(1).GetConnection()->GetTensorInfo(); + BOOST_ASSERT(inputShapes.size() == 2); + auto& input0 = inputShapes[0]; + auto& input1 = inputShapes[1]; - // Get the max of the inputs + // Get the max of the inputs. BOOST_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions()); unsigned int numDims = input0.GetNumDimensions(); std::vector<unsigned int> dims(numDims); - // validate inputs are broadcast compatible -#if !NDEBUG for (unsigned int i = 0; i < numDims; i++) { - unsigned int dim0 = input0.GetShape()[i]; - unsigned int dim1 = input1.GetShape()[i]; + unsigned int dim0 = input0[i]; + unsigned int dim1 = input1[i]; + + // Validates inputs are broadcast compatible. +#if !NDEBUG if (dim0 != dim1) { BOOST_ASSERT_MSG(dim0 == 1 || dim1 == 1, "Dimensions should either match or one should be of size 1."); } - } #endif - for (unsigned int i = 0; i < numDims; i++) - { - unsigned int dim0 = input0.GetShape()[i]; - unsigned int dim1 = input1.GetShape()[i]; dims[i] = std::max(dim0, dim1); } - TensorShape outShape(numDims, dims.data()); + return std::vector<TensorShape>({ TensorShape(numDims, dims.data()) }); +} + +void AdditionLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(2, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ + GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(), + GetInputSlot(1).GetConnection()->GetTensorInfo().GetShape() + }); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( "AdditionLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/AdditionLayer.hpp b/src/armnn/layers/AdditionLayer.hpp index c48c027763..37f0b5c259 100644 --- a/src/armnn/layers/AdditionLayer.hpp +++ b/src/armnn/layers/AdditionLayer.hpp @@ -19,6 +19,8 @@ public: void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; + protected: AdditionLayer(const char* name); ~AdditionLayer() = default; diff --git a/src/armnn/layers/BatchNormalizationLayer.cpp b/src/armnn/layers/BatchNormalizationLayer.cpp index ebb8954ea7..0bf81ebec9 100644 --- a/src/armnn/layers/BatchNormalizationLayer.cpp +++ b/src/armnn/layers/BatchNormalizationLayer.cpp @@ -21,12 +21,19 @@ BatchNormalizationLayer::BatchNormalizationLayer(const armnn::BatchNormalization std::unique_ptr<IWorkload> BatchNormalizationLayer::CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const { + // on this level constant data should not be released.. + BOOST_ASSERT_MSG(m_Mean != nullptr, "BatchNormalizationLayer: Mean data should not be null."); + BOOST_ASSERT_MSG(m_Variance != nullptr, "BatchNormalizationLayer: Variance data should not be null."); + BOOST_ASSERT_MSG(m_Beta != nullptr, "BatchNormalizationLayer: Beta data should not be null."); + BOOST_ASSERT_MSG(m_Gamma != nullptr, "BatchNormalizationLayer: Gamma data should not be null."); + BatchNormalizationQueueDescriptor descriptor; descriptor.m_Mean = m_Mean.get(); descriptor.m_Variance = m_Variance.get(); descriptor.m_Beta = m_Beta.get(); descriptor.m_Gamma = m_Gamma.get(); + return factory.CreateBatchNormalization(descriptor, PrepInfoAndDesc(descriptor, graph)); } @@ -44,17 +51,22 @@ BatchNormalizationLayer* BatchNormalizationLayer::Clone(Graph& graph) const void BatchNormalizationLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "BatchNormalizationLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "BatchNormalizationLayer: TensorInfo must be set on connected OutputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); - auto& info = GetInputSlot(0).GetConnection()->GetTensorInfo(); + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); ConditionalThrowIfNotEqual<LayerValidationException>( "BatchNormalizationLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - info.GetShape()); + inferredShapes[0]); + +} + +Layer::ConstantTensors BatchNormalizationLayer::GetConstantTensorsByRef() +{ + return {m_Mean, m_Variance, m_Beta, m_Gamma}; } } // namespace armnn diff --git a/src/armnn/layers/BatchNormalizationLayer.hpp b/src/armnn/layers/BatchNormalizationLayer.hpp index d8082e5e98..9a1b5bccc8 100644 --- a/src/armnn/layers/BatchNormalizationLayer.hpp +++ b/src/armnn/layers/BatchNormalizationLayer.hpp @@ -29,6 +29,8 @@ public: protected: BatchNormalizationLayer(const BatchNormalizationDescriptor& param, const char* name); ~BatchNormalizationLayer() = default; + + ConstantTensors GetConstantTensorsByRef() override; }; } // namespace diff --git a/src/armnn/layers/ConstantLayer.cpp b/src/armnn/layers/ConstantLayer.cpp index 937d38a31d..2abc595605 100644 --- a/src/armnn/layers/ConstantLayer.cpp +++ b/src/armnn/layers/ConstantLayer.cpp @@ -13,9 +13,8 @@ namespace armnn { -ConstantLayer::ConstantLayer(const std::shared_ptr<ScopedCpuTensorHandle>& input, const char* name) +ConstantLayer::ConstantLayer(const char* name) : Layer(0, 1, LayerType::Constant, name) - , m_LayerOutput(input) { } @@ -29,13 +28,22 @@ std::unique_ptr<IWorkload> ConstantLayer::CreateWorkload(const Graph& graph, ConstantLayer* ConstantLayer::Clone(Graph& graph) const { - // Cloned layers share the same layer output object - return CloneBase<ConstantLayer>(graph, m_LayerOutput, GetName()); + // Cloned layers share the same layer output object. + auto layer = CloneBase<ConstantLayer>(graph, GetName()); + + layer->m_LayerOutput = m_LayerOutput ? std::make_unique<ScopedCpuTensorHandle>(*m_LayerOutput) : nullptr; + + return std::move(layer); +} + +std::vector<TensorShape> ConstantLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const +{ + return std::vector<TensorShape>({ m_LayerOutput->GetTensorInfo().GetShape() }); } void ConstantLayer::ValidateTensorShapesFromInputs() { - // get the output shape from the value of the constant layer + // Get the output shape from the value of the constant layer. TensorShape const& outShape = m_LayerOutput->GetTensorInfo().GetShape(); ConditionalThrowIfNotEqual<LayerValidationException>( "ConstantLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", diff --git a/src/armnn/layers/ConstantLayer.hpp b/src/armnn/layers/ConstantLayer.hpp index e8e8d2298c..f215832eae 100644 --- a/src/armnn/layers/ConstantLayer.hpp +++ b/src/armnn/layers/ConstantLayer.hpp @@ -21,12 +21,18 @@ public: void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; + + // Free up the constant source data + void ReleaseConstantData() override {}; + + std::unique_ptr<ScopedCpuTensorHandle> m_LayerOutput; protected: - ConstantLayer(const std::shared_ptr<ScopedCpuTensorHandle>& input, const char* name); + ConstantLayer(const char* name); ~ConstantLayer() = default; -private: - std::shared_ptr<ScopedCpuTensorHandle> m_LayerOutput; + ConstantTensors GetConstantTensorsByRef() override { return {m_LayerOutput}; } + }; } // namespace diff --git a/src/armnn/layers/ConvertFp16ToFp32Layer.cpp b/src/armnn/layers/ConvertFp16ToFp32Layer.cpp new file mode 100644 index 0000000000..80d981c267 --- /dev/null +++ b/src/armnn/layers/ConvertFp16ToFp32Layer.cpp @@ -0,0 +1,48 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "ConvertFp16ToFp32Layer.hpp" +#include "LayerCloneBase.hpp" + +#include <armnn/TypesUtils.hpp> + +#include <backends/WorkloadData.hpp> +#include <backends/WorkloadFactory.hpp> + +namespace armnn +{ + +ConvertFp16ToFp32Layer::ConvertFp16ToFp32Layer(const char* name) + : Layer(1, 1, LayerType::ConvertFp16ToFp32, name) +{ +} + +std::unique_ptr<IWorkload> ConvertFp16ToFp32Layer::CreateWorkload(const Graph& graph, + const IWorkloadFactory& factory) const +{ + ConvertFp16ToFp32QueueDescriptor descriptor; + return factory.CreateConvertFp16ToFp32(descriptor, PrepInfoAndDesc(descriptor, graph)); +} + +ConvertFp16ToFp32Layer* ConvertFp16ToFp32Layer::Clone(Graph& graph) const +{ + return CloneBase<ConvertFp16ToFp32Layer>(graph, GetName()); +} + +void ConvertFp16ToFp32Layer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); + + ConditionalThrowIfNotEqual<LayerValidationException>( + "ConvertFp16ToFp32Layer: TensorShape set on OutputSlot[0] does not match the inferred shape.", + GetOutputSlot(0).GetTensorInfo().GetShape(), + inferredShapes[0]); +} + +} // namespace armnn diff --git a/src/armnn/layers/ConvertFp16ToFp32Layer.hpp b/src/armnn/layers/ConvertFp16ToFp32Layer.hpp new file mode 100644 index 0000000000..94f1fb8925 --- /dev/null +++ b/src/armnn/layers/ConvertFp16ToFp32Layer.hpp @@ -0,0 +1,28 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#pragma once + +#include <Layer.hpp> + +namespace armnn +{ + +class ConvertFp16ToFp32Layer : public Layer +{ +public: + virtual std::unique_ptr<IWorkload> CreateWorkload(const Graph& graph, + const IWorkloadFactory& factory) const override; + + ConvertFp16ToFp32Layer* Clone(Graph& graph) const override; + + void ValidateTensorShapesFromInputs() override; + +protected: + ConvertFp16ToFp32Layer(const char* name); + ~ConvertFp16ToFp32Layer() = default; +}; + +} // namespace diff --git a/src/armnn/layers/ConvertFp32ToFp16Layer.cpp b/src/armnn/layers/ConvertFp32ToFp16Layer.cpp new file mode 100644 index 0000000000..70d6b668f8 --- /dev/null +++ b/src/armnn/layers/ConvertFp32ToFp16Layer.cpp @@ -0,0 +1,47 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#include "ConvertFp32ToFp16Layer.hpp" + +#include "LayerCloneBase.hpp" + +#include <armnn/TypesUtils.hpp> +#include <backends/WorkloadData.hpp> +#include <backends/WorkloadFactory.hpp> + +namespace armnn +{ + +ConvertFp32ToFp16Layer::ConvertFp32ToFp16Layer(const char* name) + : Layer(1, 1, LayerType::ConvertFp32ToFp16, name) +{ +} + +std::unique_ptr<IWorkload> ConvertFp32ToFp16Layer::CreateWorkload(const Graph& graph, + const IWorkloadFactory& factory) const +{ + ConvertFp32ToFp16QueueDescriptor descriptor; + return factory.CreateConvertFp32ToFp16(descriptor, PrepInfoAndDesc(descriptor, graph)); +} + +ConvertFp32ToFp16Layer* ConvertFp32ToFp16Layer::Clone(Graph& graph) const +{ + return CloneBase<ConvertFp32ToFp16Layer>(graph, GetName()); +} + +void ConvertFp32ToFp16Layer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); + + ConditionalThrowIfNotEqual<LayerValidationException>( + "ConvertFp32ToFp16Layer: TensorShape set on OutputSlot[0] does not match the inferred shape.", + GetOutputSlot(0).GetTensorInfo().GetShape(), + inferredShapes[0]); +} + +} // namespace armnn diff --git a/src/armnn/layers/ConvertFp32ToFp16Layer.hpp b/src/armnn/layers/ConvertFp32ToFp16Layer.hpp new file mode 100644 index 0000000000..5c3883021d --- /dev/null +++ b/src/armnn/layers/ConvertFp32ToFp16Layer.hpp @@ -0,0 +1,27 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include <Layer.hpp> + +namespace armnn +{ + +class ConvertFp32ToFp16Layer : public Layer +{ +public: + virtual std::unique_ptr<IWorkload> CreateWorkload(const Graph& graph, + const IWorkloadFactory& factory) const override; + + ConvertFp32ToFp16Layer* Clone(Graph& graph) const override; + + void ValidateTensorShapesFromInputs() override; + +protected: + ConvertFp32ToFp16Layer(const char* name); + ~ConvertFp32ToFp16Layer() = default; +}; + +} // namespace diff --git a/src/armnn/layers/Convolution2dLayer.cpp b/src/armnn/layers/Convolution2dLayer.cpp index 3829f129bb..05c25bf3a0 100644 --- a/src/armnn/layers/Convolution2dLayer.cpp +++ b/src/armnn/layers/Convolution2dLayer.cpp @@ -20,11 +20,15 @@ Convolution2dLayer::Convolution2dLayer(const Convolution2dDescriptor& param, con std::unique_ptr<IWorkload> Convolution2dLayer::CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const { + // on this level constant data should not be released.. + BOOST_ASSERT_MSG(m_Weight != nullptr, "Convolution2dLayer: Weights data should not be null."); + Convolution2dQueueDescriptor descriptor; descriptor.m_Weight = m_Weight.get(); if (m_Param.m_BiasEnabled) { + BOOST_ASSERT_MSG(m_Bias != nullptr, "Convolution2dLayer: Bias data should not be null."); descriptor.m_Bias = m_Bias.get(); } return factory.CreateConvolution2d(descriptor, PrepInfoAndDesc(descriptor, graph)); @@ -33,6 +37,7 @@ std::unique_ptr<IWorkload> Convolution2dLayer::CreateWorkload(const Graph& graph Convolution2dLayer* Convolution2dLayer::Clone(Graph& graph) const { auto layer = CloneBase<Convolution2dLayer>(graph, m_Param, GetName()); + layer->m_Weight = m_Weight ? std::make_unique<ScopedCpuTensorHandle>(*m_Weight) : nullptr; if (layer->m_Param.m_BiasEnabled) @@ -43,17 +48,11 @@ Convolution2dLayer* Convolution2dLayer::Clone(Graph& graph) const return std::move(layer); } -void Convolution2dLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> Convolution2dLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "Convolution2dLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "Convolution2dLayer: TensorInfo must be set on connected OutputSlot."); - - - IOutputSlot* input = GetInputSlot(0).GetConnection(); - const TensorShape& inputShape = input->GetTensorInfo().GetShape(); - const TensorShape filterShape = m_Weight->GetTensorInfo().GetShape(); + BOOST_ASSERT(inputShapes.size() == 2); + const TensorShape& inputShape = inputShapes[0]; + const TensorShape filterShape = inputShapes[1]; // If we support multiple batch dimensions in the future, then this assert will need to change. BOOST_ASSERT_MSG(inputShape.GetNumDimensions() == 4, "Convolutions will always have 4D input."); @@ -73,11 +72,31 @@ void Convolution2dLayer::ValidateTensorShapesFromInputs() unsigned int outChannels = filterShape[0]; unsigned int outBatchSize = inBatchSize; - TensorShape shapeOut({outBatchSize, outChannels, outHeight, outWidth}); + return std::vector<TensorShape>({ TensorShape({outBatchSize, outChannels, outHeight, outWidth})}); +} + +void Convolution2dLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(1, CHECK_LOCATION()); + + // check if we m_Weight data is not nullptr + BOOST_ASSERT_MSG(m_Weight != nullptr, "Convolution2dLayer: Weights data should not be null."); + + auto inferredShapes = InferOutputShapes({ + GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(), + m_Weight->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( "Convolution2dLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - shapeOut); + inferredShapes[0]); +} + +Layer::ConstantTensors Convolution2dLayer::GetConstantTensorsByRef() +{ + return {m_Weight, m_Bias}; } } // namespace armnn diff --git a/src/armnn/layers/Convolution2dLayer.hpp b/src/armnn/layers/Convolution2dLayer.hpp index 4d2c6505d3..8659fe540d 100644 --- a/src/armnn/layers/Convolution2dLayer.hpp +++ b/src/armnn/layers/Convolution2dLayer.hpp @@ -24,9 +24,13 @@ public: void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; + protected: Convolution2dLayer(const Convolution2dDescriptor& param, const char* name); ~Convolution2dLayer() = default; + + ConstantTensors GetConstantTensorsByRef() override; }; } // namespace diff --git a/src/armnn/layers/DepthwiseConvolution2dLayer.cpp b/src/armnn/layers/DepthwiseConvolution2dLayer.cpp index 0442de6c60..471bf015a9 100644 --- a/src/armnn/layers/DepthwiseConvolution2dLayer.cpp +++ b/src/armnn/layers/DepthwiseConvolution2dLayer.cpp @@ -22,11 +22,15 @@ DepthwiseConvolution2dLayer::DepthwiseConvolution2dLayer(const DepthwiseConvolut std::unique_ptr<IWorkload> DepthwiseConvolution2dLayer::CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const { + // on this level constant data should not be released.. + BOOST_ASSERT_MSG(m_Weight != nullptr, "DepthwiseConvolution2dLayer: Weights data should not be null."); + DepthwiseConvolution2dQueueDescriptor descriptor; descriptor.m_Weight = m_Weight.get(); if (m_Param.m_BiasEnabled) { + BOOST_ASSERT_MSG(m_Bias != nullptr, "DepthwiseConvolution2dLayer: Bias data should not be null."); descriptor.m_Bias = m_Bias.get(); } return factory.CreateDepthwiseConvolution2d(descriptor, PrepInfoAndDesc(descriptor, graph)); @@ -45,16 +49,12 @@ DepthwiseConvolution2dLayer* DepthwiseConvolution2dLayer::Clone(Graph& graph) co return std::move(layer); } -void DepthwiseConvolution2dLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> +DepthwiseConvolution2dLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "DepthwiseConvolution2dLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "DepthwiseConvolution2dLayer: TensorInfo must be set on connected OutputSlot."); - - IOutputSlot* input = GetInputSlot(0).GetConnection(); - const TensorShape& inputShape = input->GetTensorInfo().GetShape(); - const TensorShape filterShape = m_Weight->GetTensorInfo().GetShape(); + BOOST_ASSERT(inputShapes.size() == 2); + const TensorShape& inputShape = inputShapes[0]; + const TensorShape filterShape = inputShapes[1]; BOOST_ASSERT_MSG(inputShape.GetNumDimensions() == 4, "Convolutions will always have 4D input."); @@ -74,12 +74,32 @@ void DepthwiseConvolution2dLayer::ValidateTensorShapesFromInputs() unsigned int outChannels = filterShape[1]*depthMultiplier; unsigned int outBatchSize = inBatchSize; - TensorShape outShape({outBatchSize, outChannels, outHeight, outWidth}); + return std::vector<TensorShape>({ TensorShape({outBatchSize, outChannels, outHeight, outWidth})}); +} + +void DepthwiseConvolution2dLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(1, CHECK_LOCATION()); + + // on this level constant data should not be released.. + BOOST_ASSERT_MSG(m_Weight != nullptr, "DepthwiseConvolution2dLayer: Weights data should not be null."); + + auto inferredShapes = InferOutputShapes({ + GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(), + m_Weight->GetTensorInfo().GetShape() + }); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( - "DepthwiseConvolution2dLayer: " - "TensorShape set on OutputSlot[0] does not match the inferred shape.", + "DepthwiseConvolution2dLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); +} + +Layer::ConstantTensors DepthwiseConvolution2dLayer::GetConstantTensorsByRef() +{ + return {m_Weight, m_Bias}; } } // namespace armnn diff --git a/src/armnn/layers/DepthwiseConvolution2dLayer.hpp b/src/armnn/layers/DepthwiseConvolution2dLayer.hpp index 60691bf73c..e3be152432 100644 --- a/src/armnn/layers/DepthwiseConvolution2dLayer.hpp +++ b/src/armnn/layers/DepthwiseConvolution2dLayer.hpp @@ -24,9 +24,13 @@ public: void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; + protected: DepthwiseConvolution2dLayer(const DepthwiseConvolution2dDescriptor& param, const char* name); ~DepthwiseConvolution2dLayer() = default; + + ConstantTensors GetConstantTensorsByRef() override; }; } // namespace diff --git a/src/armnn/layers/FakeQuantizationLayer.cpp b/src/armnn/layers/FakeQuantizationLayer.cpp index 24b53b2e37..7bda1c1f78 100644 --- a/src/armnn/layers/FakeQuantizationLayer.cpp +++ b/src/armnn/layers/FakeQuantizationLayer.cpp @@ -32,20 +32,16 @@ FakeQuantizationLayer* FakeQuantizationLayer::Clone(Graph& graph) const void FakeQuantizationLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "FakeQuantizationLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "FakeQuantizationLayer: TensorInfo must be set on connected OutputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); - IOutputSlot* input = GetInputSlot(0).GetConnection(); + BOOST_ASSERT(inferredShapes.size() == 1); - // input and output shapes are the same - TensorShape const& outShape = input->GetTensorInfo().GetShape(); ConditionalThrowIfNotEqual<LayerValidationException>( "FakeQuantizationLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/FloorLayer.cpp b/src/armnn/layers/FloorLayer.cpp index a9ddcca60c..e88600b354 100644 --- a/src/armnn/layers/FloorLayer.cpp +++ b/src/armnn/layers/FloorLayer.cpp @@ -32,18 +32,16 @@ FloorLayer* FloorLayer::Clone(Graph& graph) const void FloorLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "FloorLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "FloorLayer: TensorInfo must be set on connected OutputSlot."); - - // input and output shapes are the same - IOutputSlot* input = GetInputSlot(0).GetConnection(); - TensorShape const& outShape = input->GetTensorInfo().GetShape(); + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( "FloorLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/FullyConnectedLayer.cpp b/src/armnn/layers/FullyConnectedLayer.cpp index 1597e8c2c3..8b8f010bdb 100644 --- a/src/armnn/layers/FullyConnectedLayer.cpp +++ b/src/armnn/layers/FullyConnectedLayer.cpp @@ -22,11 +22,15 @@ FullyConnectedLayer::FullyConnectedLayer(const FullyConnectedDescriptor& param, std::unique_ptr<IWorkload> FullyConnectedLayer::CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const { + // on this level constant data should not be released.. + BOOST_ASSERT_MSG(m_Weight != nullptr, "FullyConnectedLayer: Weights data should not be null."); + FullyConnectedQueueDescriptor descriptor; descriptor.m_Weight = m_Weight.get(); if (m_Param.m_BiasEnabled) { + BOOST_ASSERT_MSG(m_Bias != nullptr, "FullyConnectedLayer: Bias data should not be null."); descriptor.m_Bias = m_Bias.get(); } return factory.CreateFullyConnected(descriptor, PrepInfoAndDesc(descriptor, graph)); @@ -45,25 +49,41 @@ FullyConnectedLayer* FullyConnectedLayer::Clone(Graph& graph) const return std::move(layer); } +std::vector<TensorShape> FullyConnectedLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const +{ + BOOST_ASSERT(inputShapes.size() == 2); + const TensorShape& inputShape = inputShapes[0]; + const TensorShape weightShape = inputShapes[1]; + + // Output for FC is [1, w[1]]. + unsigned int batches = inputShape[0]; + unsigned int dimIdx = m_Param.m_TransposeWeightMatrix ? 0 : 1; + + return std::vector<TensorShape>({ TensorShape({batches, weightShape[dimIdx]})}); +} + void FullyConnectedLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "FullyConnectedLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "FullyConnectedLayer: TensorInfo must be set on connected OutputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); + // check if we m_Weight data is not nullptr + BOOST_ASSERT_MSG(m_Weight != nullptr, "FullyConnectedLayer: Weights data should not be null."); - TensorShape const& weightShape = m_Weight->GetTensorInfo().GetShape(); + auto inferredShapes = InferOutputShapes({ + GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(), + m_Weight->GetTensorInfo().GetShape() }); - // output for FC is [1, w[1]] - unsigned int batches = GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape()[0]; - unsigned int dimIdx = m_Param.m_TransposeWeightMatrix ? 0 : 1; - TensorShape outShape({batches, weightShape[dimIdx]}); + BOOST_ASSERT(inferredShapes.size() == 1); ConditionalThrowIfNotEqual<LayerValidationException>( "FullyConnectedLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); +} + +Layer::ConstantTensors FullyConnectedLayer::GetConstantTensorsByRef() +{ + return {m_Weight, m_Bias}; } } // namespace armnn diff --git a/src/armnn/layers/FullyConnectedLayer.hpp b/src/armnn/layers/FullyConnectedLayer.hpp index 1d6cb7cf8d..6300cafd62 100644 --- a/src/armnn/layers/FullyConnectedLayer.hpp +++ b/src/armnn/layers/FullyConnectedLayer.hpp @@ -23,10 +23,13 @@ public: FullyConnectedLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; protected: FullyConnectedLayer(const FullyConnectedDescriptor& param, const char* name); ~FullyConnectedLayer() = default; + + ConstantTensors GetConstantTensorsByRef() override; }; } // namespace diff --git a/src/armnn/layers/L2NormalizationLayer.cpp b/src/armnn/layers/L2NormalizationLayer.cpp index 07020bfdca..7249bc3b5c 100644 --- a/src/armnn/layers/L2NormalizationLayer.cpp +++ b/src/armnn/layers/L2NormalizationLayer.cpp @@ -32,19 +32,16 @@ L2NormalizationLayer* L2NormalizationLayer::Clone(Graph& graph) const void L2NormalizationLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "L2NormalizationLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "L2NormalizationLayer: TensorInfo must be set on connected OutputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); - IOutputSlot* input = GetInputSlot(0).GetConnection(); + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); - // input and output shapes are the same - TensorShape const& outShape = input->GetTensorInfo().GetShape(); ConditionalThrowIfNotEqual<LayerValidationException>( "L2NormalizationLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/LayerWithParameters.hpp b/src/armnn/layers/LayerWithParameters.hpp index e3eb40a273..c071c15c21 100644 --- a/src/armnn/layers/LayerWithParameters.hpp +++ b/src/armnn/layers/LayerWithParameters.hpp @@ -18,7 +18,7 @@ public: const Parameters& GetParameters() const { return m_Param; } /// Helper to serialize the layer parameters to string - /// (currently used in DotSerializer and company) + /// (currently used in DotSerializer and company). void SerializeLayerParameters(ParameterStringifyFunction & fn) const { StringifyLayerParameters<Parameters>::Serialize(fn, m_Param); @@ -37,7 +37,7 @@ protected: ~LayerWithParameters() = default; - /// Helper function to reduce duplication in *Layer::CreateWorkload + /// Helper function to reduce duplication in *Layer::CreateWorkload. template <typename QueueDescriptor> WorkloadInfo PrepInfoAndDesc(QueueDescriptor& descriptor, const Graph& graph) const { @@ -45,7 +45,7 @@ protected: return Layer::PrepInfoAndDesc(descriptor, graph); } - /// The parameters for the layer (not including tensor-valued weights etc.) + /// The parameters for the layer (not including tensor-valued weights etc.). Parameters m_Param; }; diff --git a/src/armnn/layers/LstmLayer.cpp b/src/armnn/layers/LstmLayer.cpp new file mode 100644 index 0000000000..30c41bc9b8 --- /dev/null +++ b/src/armnn/layers/LstmLayer.cpp @@ -0,0 +1,259 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#include "LstmLayer.hpp" + +#include "LayerCloneBase.hpp" + +#include <armnn/TypesUtils.hpp> +#include <backends/CpuTensorHandle.hpp> +#include <backends/WorkloadFactory.hpp> + +namespace armnn +{ + +LstmLayer::LstmLayer(const LstmDescriptor& param, const char* name) + : LayerWithParameters(3, 4, LayerType::Lstm, param, name) +{ +} + +std::unique_ptr<IWorkload> LstmLayer::CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const +{ + LstmQueueDescriptor descriptor; + + // Basic parameters + descriptor.m_InputToForgetWeights = m_BasicParameters.m_InputToForgetWeights.get(); + descriptor.m_InputToCellWeights = m_BasicParameters.m_InputToCellWeights.get(); + descriptor.m_InputToOutputWeights = m_BasicParameters.m_InputToOutputWeights.get(); + descriptor.m_RecurrentToForgetWeights = m_BasicParameters.m_RecurrentToForgetWeights.get(); + descriptor.m_RecurrentToCellWeights = m_BasicParameters.m_RecurrentToCellWeights.get(); + descriptor.m_RecurrentToOutputWeights = m_BasicParameters.m_RecurrentToOutputWeights.get(); + descriptor.m_ForgetGateBias = m_BasicParameters.m_ForgetGateBias.get(); + descriptor.m_CellBias = m_BasicParameters.m_CellBias.get(); + descriptor.m_OutputGateBias = m_BasicParameters.m_OutputGateBias.get(); + + // Cifg parameters + if (!m_Param.m_CifgEnabled) + { + descriptor.m_InputToInputWeights = m_CifgParameters.m_InputToInputWeights.get(); + descriptor.m_RecurrentToInputWeights = m_CifgParameters.m_RecurrentToInputWeights.get(); + descriptor.m_CellToInputWeights = m_CifgParameters.m_CellToInputWeights.get(); + descriptor.m_InputGateBias = m_CifgParameters.m_InputGateBias.get(); + } + + // Projection parameters + if (m_Param.m_ProjectionEnabled) + { + descriptor.m_ProjectionWeights = m_ProjectionParameters.m_ProjectionWeights.get(); + descriptor.m_ProjectionBias = m_ProjectionParameters.m_ProjectionBias.get(); + } + + // Peephole parameters + if (m_Param.m_PeepholeEnabled) + { + descriptor.m_CellToForgetWeights = m_PeepholeParameters.m_CellToForgetWeights.get(); + descriptor.m_CellToOutputWeights = m_PeepholeParameters.m_CellToOutputWeights.get(); + } + return factory.CreateLstm(descriptor, PrepInfoAndDesc(descriptor, graph)); +} + +LstmLayer* LstmLayer::Clone(Graph& graph) const +{ + auto layer = CloneBase<LstmLayer>(graph, m_Param, GetName()); + + layer->m_BasicParameters.m_InputToForgetWeights = m_BasicParameters.m_InputToForgetWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_InputToForgetWeights) + : nullptr; + layer->m_BasicParameters.m_InputToCellWeights = m_BasicParameters.m_InputToCellWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_InputToCellWeights) : nullptr; + layer->m_BasicParameters.m_InputToOutputWeights = m_BasicParameters.m_InputToOutputWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_InputToOutputWeights) : nullptr; + layer->m_BasicParameters.m_RecurrentToForgetWeights = m_BasicParameters.m_RecurrentToForgetWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_RecurrentToForgetWeights) : nullptr; + layer->m_BasicParameters.m_RecurrentToCellWeights = m_BasicParameters.m_RecurrentToCellWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_RecurrentToCellWeights) : nullptr; + layer->m_BasicParameters.m_RecurrentToOutputWeights = m_BasicParameters.m_RecurrentToOutputWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_RecurrentToOutputWeights) : nullptr; + layer->m_BasicParameters.m_ForgetGateBias = m_BasicParameters.m_ForgetGateBias ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_ForgetGateBias) : nullptr; + layer->m_BasicParameters.m_CellBias = m_BasicParameters.m_CellBias ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_CellBias) : nullptr; + layer->m_BasicParameters.m_OutputGateBias = m_BasicParameters.m_OutputGateBias ? + std::make_unique<ScopedCpuTensorHandle>(*m_BasicParameters.m_OutputGateBias) : nullptr; + + if (!m_Param.m_CifgEnabled) + { + layer->m_CifgParameters.m_InputToInputWeights = m_CifgParameters.m_InputToInputWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_CifgParameters.m_InputToInputWeights) : nullptr; + layer->m_CifgParameters.m_RecurrentToInputWeights = m_CifgParameters.m_RecurrentToInputWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_CifgParameters.m_RecurrentToInputWeights) : nullptr; + layer->m_CifgParameters.m_CellToInputWeights = m_CifgParameters.m_CellToInputWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_CifgParameters.m_CellToInputWeights) : nullptr; + layer->m_CifgParameters.m_InputGateBias = m_CifgParameters.m_InputGateBias ? + std::make_unique<ScopedCpuTensorHandle>(*m_CifgParameters.m_InputGateBias) : nullptr; + } + + if (m_Param.m_ProjectionEnabled) + { + layer->m_ProjectionParameters.m_ProjectionWeights = m_ProjectionParameters.m_ProjectionWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_ProjectionParameters.m_ProjectionWeights) : nullptr; + layer->m_ProjectionParameters.m_ProjectionBias = m_ProjectionParameters.m_ProjectionBias ? + std::make_unique<ScopedCpuTensorHandle>(*m_ProjectionParameters.m_ProjectionBias) : nullptr; + } + + if (m_Param.m_PeepholeEnabled) + { + layer->m_PeepholeParameters.m_CellToForgetWeights = m_PeepholeParameters.m_CellToForgetWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_PeepholeParameters.m_CellToForgetWeights) : nullptr; + layer->m_PeepholeParameters.m_CellToOutputWeights = m_PeepholeParameters.m_CellToOutputWeights ? + std::make_unique<ScopedCpuTensorHandle>(*m_PeepholeParameters.m_CellToOutputWeights) : nullptr; + } + + return std::move(layer); +} + +std::vector<TensorShape> LstmLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const +{ + BOOST_ASSERT(inputShapes.size() == 3); + + // Get input values for validation + unsigned int batchSize = inputShapes[0][0]; + unsigned int outputSize = inputShapes[1][1]; + unsigned int numUnits = inputShapes[2][1]; + + std::vector<TensorShape> outShapes; + if (!m_Param.m_CifgEnabled) + { + outShapes.push_back(TensorShape({batchSize, numUnits*3})); + } + else + { + outShapes.push_back(TensorShape({batchSize, numUnits*4})); + } + outShapes.push_back(TensorShape({batchSize, outputSize})); + outShapes.push_back(TensorShape({batchSize, numUnits})); + outShapes.push_back(TensorShape({batchSize, outputSize})); + + return outShapes; +} + +void LstmLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(3, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes( { + GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(), + GetInputSlot(1).GetConnection()->GetTensorInfo().GetShape(), + GetInputSlot(2).GetConnection()->GetTensorInfo().GetShape()} + ); + + BOOST_ASSERT(inferredShapes.size() == 4); + + // Check if the weights are nullptr + BOOST_ASSERT_MSG(m_BasicParameters.m_InputToForgetWeights != nullptr, + "LstmLayer: m_BasicParameters.m_InputToForgetWeights should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_InputToCellWeights != nullptr, + "LstmLayer: m_BasicParameters.m_InputToCellWeights should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_InputToOutputWeights != nullptr, + "LstmLayer: m_BasicParameters.m_InputToOutputWeights should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_RecurrentToForgetWeights != nullptr, + "LstmLayer: m_BasicParameters.m_RecurrentToForgetWeights should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_RecurrentToCellWeights != nullptr, + "LstmLayer: m_BasicParameters.m_RecurrentToCellWeights should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_RecurrentToOutputWeights != nullptr, + "LstmLayer: m_BasicParameters.m_RecurrentToOutputWeights should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_ForgetGateBias != nullptr, + "LstmLayer: m_BasicParameters.m_ForgetGateBias should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_CellBias != nullptr, + "LstmLayer: m_BasicParameters.m_CellBias should not be null."); + BOOST_ASSERT_MSG(m_BasicParameters.m_OutputGateBias != nullptr, + "LstmLayer: m_BasicParameters.m_OutputGateBias should not be null."); + + if (!m_Param.m_CifgEnabled) + { + BOOST_ASSERT_MSG(m_CifgParameters.m_InputToInputWeights != nullptr, + "LstmLayer: m_CifgParameters.m_InputToInputWeights should not be null."); + BOOST_ASSERT_MSG(m_CifgParameters.m_RecurrentToInputWeights != nullptr, + "LstmLayer: m_CifgParameters.m_RecurrentToInputWeights should not be null."); + BOOST_ASSERT_MSG(m_CifgParameters.m_InputGateBias != nullptr, + "LstmLayer: m_CifgParameters.m_InputGateBias should not be null."); + + ConditionalThrowIfNotEqual<LayerValidationException>( + "LstmLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", + GetOutputSlot(0).GetTensorInfo().GetShape(), + inferredShapes[0]); + } + else + { + BOOST_ASSERT_MSG(m_CifgParameters.m_InputToInputWeights == nullptr, + "LstmLayer: m_CifgParameters.m_InputToInputWeights should not have a value when CIFG is enabled."); + BOOST_ASSERT_MSG(m_CifgParameters.m_RecurrentToInputWeights == nullptr, + "LstmLayer: m_CifgParameters.m_RecurrentToInputWeights should not have a value when CIFG is enabled."); + BOOST_ASSERT_MSG(m_CifgParameters.m_CellToInputWeights == nullptr, + "LstmLayer: m_CifgParameters.m_CellToInputWeights should not have a value when CIFG is enabled."); + BOOST_ASSERT_MSG(m_CifgParameters.m_InputGateBias == nullptr, + "LstmLayer: m_CifgParameters.m_InputGateBias should not have a value when CIFG is enabled."); + + ConditionalThrowIfNotEqual<LayerValidationException>( + "LstmLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", + GetOutputSlot(0).GetTensorInfo().GetShape(), + inferredShapes[0]); + } + + if (m_Param.m_ProjectionEnabled) + { + BOOST_ASSERT_MSG(m_ProjectionParameters.m_ProjectionWeights != nullptr, + "LstmLayer: m_ProjectionParameters.m_ProjectionWeights should not be null."); + } + + if (m_Param.m_PeepholeEnabled) + { + BOOST_ASSERT_MSG(m_PeepholeParameters.m_CellToForgetWeights != nullptr, + "LstmLayer: m_PeepholeParameters.m_CellToForgetWeights should not be null."); + BOOST_ASSERT_MSG(m_PeepholeParameters.m_CellToOutputWeights != nullptr, + "LstmLayer: m_PeepholeParameters.m_CellToOutputWeights should not be null."); + } + + ConditionalThrowIfNotEqual<LayerValidationException>( + "LstmLayer: TensorShape set on OutputSlot[1] does not match the inferred shape.", + GetOutputSlot(1).GetTensorInfo().GetShape(), + inferredShapes[1]); + ConditionalThrowIfNotEqual<LayerValidationException>( + "LstmLayer: TensorShape set on OutputSlot[2] does not match the inferred shape.", + GetOutputSlot(2).GetTensorInfo().GetShape(), + inferredShapes[2]); + ConditionalThrowIfNotEqual<LayerValidationException>( + "LstmLayer: TensorShape set on OutputSlot[3] does not match the inferred shape.", + GetOutputSlot(3).GetTensorInfo().GetShape(), + inferredShapes[3]); +} + +Layer::ConstantTensors LstmLayer::GetConstantTensorsByRef() +{ + return {m_BasicParameters.m_InputToForgetWeights, + m_BasicParameters.m_InputToCellWeights, + m_BasicParameters.m_InputToOutputWeights, + m_BasicParameters.m_RecurrentToForgetWeights, + m_BasicParameters.m_RecurrentToCellWeights, + m_BasicParameters.m_RecurrentToOutputWeights, + m_BasicParameters.m_ForgetGateBias, + m_BasicParameters.m_CellBias, + m_BasicParameters.m_OutputGateBias, + + // Cifg parameters + m_CifgParameters.m_InputToInputWeights, + m_CifgParameters.m_RecurrentToInputWeights, + m_CifgParameters.m_CellToInputWeights, + m_CifgParameters.m_InputGateBias, + + // Projection parameters + m_ProjectionParameters.m_ProjectionWeights, + m_ProjectionParameters.m_ProjectionBias, + + // Peephole parameters + m_PeepholeParameters.m_CellToForgetWeights, + m_PeepholeParameters.m_CellToOutputWeights}; +} + +} // namespace armnn diff --git a/src/armnn/layers/LstmLayer.hpp b/src/armnn/layers/LstmLayer.hpp new file mode 100644 index 0000000000..7133ad26a5 --- /dev/null +++ b/src/armnn/layers/LstmLayer.hpp @@ -0,0 +1,70 @@ +// +// Copyright © 2017 Arm Ltd. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "LayerWithParameters.hpp" + +namespace armnn +{ + +class ScopedCpuTensorHandle; + +struct LstmOptCifgParameters +{ + std::unique_ptr<ScopedCpuTensorHandle> m_InputToInputWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_RecurrentToInputWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_CellToInputWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_InputGateBias; +}; + +struct LstmOptProjectionParameters +{ + std::unique_ptr<ScopedCpuTensorHandle> m_ProjectionWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_ProjectionBias; +}; + +struct LstmOptPeepholeParameters +{ + std::unique_ptr<ScopedCpuTensorHandle> m_CellToForgetWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_CellToOutputWeights; +}; + +struct LstmBasicParameters +{ + std::unique_ptr<ScopedCpuTensorHandle> m_InputToForgetWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_InputToCellWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_InputToOutputWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_RecurrentToForgetWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_RecurrentToCellWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_RecurrentToOutputWeights; + std::unique_ptr<ScopedCpuTensorHandle> m_ForgetGateBias; + std::unique_ptr<ScopedCpuTensorHandle> m_CellBias; + std::unique_ptr<ScopedCpuTensorHandle> m_OutputGateBias; +}; + +class LstmLayer : public LayerWithParameters<LstmDescriptor> +{ +public: + + LstmBasicParameters m_BasicParameters; + LstmOptCifgParameters m_CifgParameters; + LstmOptProjectionParameters m_ProjectionParameters; + LstmOptPeepholeParameters m_PeepholeParameters; + + virtual std::unique_ptr<IWorkload> CreateWorkload(const Graph& graph, + const IWorkloadFactory& factory) const override; + LstmLayer* Clone(Graph& graph) const override; + + void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; + +protected: + LstmLayer(const LstmDescriptor& param, const char* name); + ~LstmLayer() = default; + + Layer::ConstantTensors GetConstantTensorsByRef() override; +}; + +} // namespace diff --git a/src/armnn/layers/MemCopyLayer.cpp b/src/armnn/layers/MemCopyLayer.cpp index 973a756b21..83f77edf58 100644 --- a/src/armnn/layers/MemCopyLayer.cpp +++ b/src/armnn/layers/MemCopyLayer.cpp @@ -9,6 +9,7 @@ #include <armnn/TypesUtils.hpp> #include <backends/WorkloadData.hpp> #include <backends/WorkloadFactory.hpp> +#include <backends/MemCopyWorkload.hpp> namespace armnn { @@ -26,23 +27,23 @@ MemCopyLayer* MemCopyLayer::Clone(Graph& graph) const std::unique_ptr<IWorkload> MemCopyLayer::CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const { MemCopyQueueDescriptor descriptor; - return factory.CreateMemCopy(descriptor, PrepInfoAndDesc(descriptor, graph)); + + //This is different from other workloads. Does not get created by the workload factory. + return std::make_unique<CopyMemGenericWorkload>(descriptor, PrepInfoAndDesc(descriptor, graph)); } void MemCopyLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "MemCopyLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "MemCopyLayer: TensorInfo must be set on connected OutputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); - IOutputSlot* input = GetInputSlot(0).GetConnection(); + BOOST_ASSERT(inferredShapes.size() == 1); ConditionalThrowIfNotEqual<LayerValidationException>( "MemCopyLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - input->GetTensorInfo().GetShape()); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/MergerLayer.cpp b/src/armnn/layers/MergerLayer.cpp index 065fc86a1b..e810b5e0bb 100644 --- a/src/armnn/layers/MergerLayer.cpp +++ b/src/armnn/layers/MergerLayer.cpp @@ -23,7 +23,7 @@ std::unique_ptr<IWorkload> MergerLayer::CreateWorkload(const Graph& graph, const { MergerQueueDescriptor descriptor; - // copy the view origins to the descriptor + // Copies the view origins to the descriptor. descriptor.m_ViewOrigins.reserve(m_Param.GetNumViews()); for (unsigned int i = 0; i < m_Param.GetNumViews(); ++i) { @@ -36,9 +36,9 @@ std::unique_ptr<IWorkload> MergerLayer::CreateWorkload(const Graph& graph, const void MergerLayer::CreateTensorHandles(Graph& graph, const IWorkloadFactory& factory) { - //if sub tensors are supported than the merger + //If sub tensors are supported than the merger //just needs to make sure that the outputs of the prev layer - //are made subtensors of the output of the merger layer + //are made subtensors of the output of the merger layer. m_OutputHandlers[0].CreateTensorHandles(factory); if (factory.SupportsSubTensors()) { @@ -76,33 +76,28 @@ MergerLayer* MergerLayer::Clone(Graph& graph) const return CloneBase<MergerLayer>(graph, m_Param, GetName()); } -void MergerLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> MergerLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - // Validate Merger layer - ConditionalThrowIfNotEqual<LayerValidationException>( - "MergerLayer: Num Inputs must match num views.", - m_Param.GetNumViews(), - GetNumInputSlots()); + BOOST_ASSERT(inputShapes.size() == m_Param.GetNumViews()); unsigned int numDims = m_Param.GetNumDimensions(); - for (unsigned int i=0; i<GetNumInputSlots(); i++) + for (unsigned int i=0; i< inputShapes.size(); i++) { - auto& inputInfo = GetInputSlot(i).GetConnection()->GetTensorInfo(); + auto& inputShape = inputShapes[i]; - boost::ignore_unused(inputInfo); ConditionalThrowIfNotEqual<LayerValidationException>( "MergerLayer: Num Dimensions must match all inputs.", numDims, - inputInfo.GetNumDimensions()); + inputShape.GetNumDimensions()); } - // Find the bounding box (extents) of all the views + // Finds the bounding box (extents) of all the views. std::vector<unsigned int> extentMin(numDims); std::vector<unsigned int> extentMax(numDims); - for (unsigned int i = 0; i < GetNumInputSlots(); i++) + for (unsigned int i = 0; i < inputShapes.size(); i++) { const uint32_t* origin = m_Param.GetViewOrigin(i); - const armnn::TensorShape& shape = GetInputSlot(i).GetConnection()->GetTensorInfo().GetShape(); + const armnn::TensorShape& shape = inputShapes[i]; for (unsigned int d = 0; d < numDims; d++) { extentMin[d] = std::min(extentMin[d], origin[d]); @@ -110,23 +105,23 @@ void MergerLayer::ValidateTensorShapesFromInputs() } } - // Check that the bounding box starts at the origin + // Checks that the bounding box starts at the origin. if (!std::all_of(extentMin.begin(), extentMin.end(), [](unsigned int s) { return s == 0; })) { throw LayerValidationException("MergerLayer: there is no view that starts at the origin"); } - // Check that there are no overlaps of views (this would lead to undefined output at those locations). - // Check each pair of views against each other - // (and don't bother to check against self, or check the same pair both ways round) - for (unsigned int a = 0; a < GetNumInputSlots(); a++) + // Checks that there are no overlaps of views (this would lead to undefined output at those locations). + // Checks each pair of views against each other + // (and doesn't bother to check against self, or check the same pair both ways round). + for (unsigned int a = 0; a < inputShapes.size(); a++) { const uint32_t* aOrigin = m_Param.GetViewOrigin(a); - const armnn::TensorShape& aShape = GetInputSlot(a).GetConnection()->GetTensorInfo().GetShape(); + const armnn::TensorShape& aShape = inputShapes[a]; for (unsigned int b = 0; b < a; b++) { const uint32_t* bOrigin = m_Param.GetViewOrigin(b); - const armnn::TensorShape& bShape = GetInputSlot(b).GetConnection()->GetTensorInfo().GetShape(); + const armnn::TensorShape& bShape = inputShapes[b]; bool allAxesOverlap = true; for (unsigned int d = 0; d < numDims && allAxesOverlap; d++) @@ -149,13 +144,13 @@ void MergerLayer::ValidateTensorShapesFromInputs() } } - // Check that there are no "holes", i.e. regions of the output which is not covered by a view. + // Checks that there are no "holes", i.e. regions of the output which is not covered by a view. // Because we already checked that there are no overlaps, this can be done simply by checking that // the total 'volume' of the views is the same as the output. unsigned int totalViewsVolume = 0; - for (unsigned int i = 0; i < GetNumInputSlots(); i++) + for (unsigned int i = 0; i < inputShapes.size(); i++) { - totalViewsVolume += GetInputSlot(i).GetConnection()->GetTensorInfo().GetNumElements(); + totalViewsVolume += inputShapes[i].GetNumElements(); } unsigned int outputVolume = 1; for (unsigned int d = 0; d < numDims; d++) @@ -168,11 +163,33 @@ void MergerLayer::ValidateTensorShapesFromInputs() totalViewsVolume, outputVolume); - TensorShape outShape(numDims, extentMax.data()); + return std::vector<TensorShape>({ TensorShape({numDims, extentMax.data()}) }); +} + +void MergerLayer::ValidateTensorShapesFromInputs() +{ + // Validates Merger layer. + ConditionalThrowIfNotEqual<LayerValidationException>( + "MergerLayer: Num Inputs must match num views.", + m_Param.GetNumViews(), + GetNumInputSlots()); + + VerifyLayerConnections(m_Param.GetNumViews(), CHECK_LOCATION()); + + std::vector<TensorShape> inputShapes; + for (uint i = 0; i < GetNumInputSlots(); ++i) + { + inputShapes.push_back(GetInputSlot(i).GetConnection()->GetTensorInfo().GetShape()); + } + + auto inferredShapes = InferOutputShapes(inputShapes); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( "MergerLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn armnn diff --git a/src/armnn/layers/MergerLayer.hpp b/src/armnn/layers/MergerLayer.hpp index ad94cb5f3a..b6261027d4 100644 --- a/src/armnn/layers/MergerLayer.hpp +++ b/src/armnn/layers/MergerLayer.hpp @@ -19,6 +19,7 @@ public: MergerLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; protected: MergerLayer(const OriginsDescriptor& param, const char* name); diff --git a/src/armnn/layers/MultiplicationLayer.cpp b/src/armnn/layers/MultiplicationLayer.cpp index af40a23007..ed7683da5f 100644 --- a/src/armnn/layers/MultiplicationLayer.cpp +++ b/src/armnn/layers/MultiplicationLayer.cpp @@ -31,41 +31,51 @@ MultiplicationLayer* MultiplicationLayer::Clone(Graph& graph) const return CloneBase<MultiplicationLayer>(graph, GetName()); } -void MultiplicationLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> MultiplicationLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - auto& input0 = GetInputSlot(0).GetConnection()->GetTensorInfo(); - auto& input1 = GetInputSlot(1).GetConnection()->GetTensorInfo(); + BOOST_ASSERT(inputShapes.size() == 2); + auto& input0 = inputShapes[0]; + auto& input1 = inputShapes[1]; - // Get the max of the inputs + // Get the max of the inputs. BOOST_ASSERT(input0.GetNumDimensions() == input1.GetNumDimensions()); unsigned int numDims = input0.GetNumDimensions(); std::vector<unsigned int> dims(numDims); - // validate inputs are broadcast compatible -#if !NDEBUG for (unsigned int i = 0; i < numDims; i++) { - unsigned int dim0 = input0.GetShape()[i]; - unsigned int dim1 = input1.GetShape()[i]; + unsigned int dim0 = input0[i]; + unsigned int dim1 = input1[i]; + + // Validates inputs are broadcast compatible. +#if !NDEBUG if (dim0 != dim1) { BOOST_ASSERT_MSG(dim0 == 1 || dim1 == 1, "Dimensions should either match or one should be of size 1."); } - } #endif - for (unsigned int i = 0; i < numDims; i++) - { - unsigned int dim0 = input0.GetShape()[i]; - unsigned int dim1 = input1.GetShape()[i]; dims[i] = std::max(dim0, dim1); } - TensorShape outShape(numDims, dims.data()); + return std::vector<TensorShape>({ TensorShape(numDims, dims.data()) }); +} + +void MultiplicationLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(2, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ + GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(), + GetInputSlot(1).GetConnection()->GetTensorInfo().GetShape() + }); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( "MultiplicationLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/MultiplicationLayer.hpp b/src/armnn/layers/MultiplicationLayer.hpp index 48db9f4d01..bbfd1ee694 100644 --- a/src/armnn/layers/MultiplicationLayer.hpp +++ b/src/armnn/layers/MultiplicationLayer.hpp @@ -18,6 +18,7 @@ public: MultiplicationLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; protected: MultiplicationLayer(const char* name); diff --git a/src/armnn/layers/NormalizationLayer.cpp b/src/armnn/layers/NormalizationLayer.cpp index cacd348444..261b16a307 100644 --- a/src/armnn/layers/NormalizationLayer.cpp +++ b/src/armnn/layers/NormalizationLayer.cpp @@ -31,14 +31,16 @@ NormalizationLayer* NormalizationLayer::Clone(Graph& graph) const void NormalizationLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "NormalizationLayer: Input slot must be connected."); + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); - const TensorShape& outShape = GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(); ConditionalThrowIfNotEqual<LayerValidationException>( "NormalizationLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/OutputLayer.cpp b/src/armnn/layers/OutputLayer.cpp index cadcf2da2f..748f275d74 100644 --- a/src/armnn/layers/OutputLayer.cpp +++ b/src/armnn/layers/OutputLayer.cpp @@ -29,7 +29,7 @@ OutputLayer* OutputLayer::Clone(Graph& graph) const void OutputLayer::ValidateTensorShapesFromInputs() { - // Just validate the input is connected + // Just validates that the input is connected. ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, "OutputLayer: Input slot must be connected."); } diff --git a/src/armnn/layers/PermuteLayer.cpp b/src/armnn/layers/PermuteLayer.cpp index 35692756a1..444de81320 100644 --- a/src/armnn/layers/PermuteLayer.cpp +++ b/src/armnn/layers/PermuteLayer.cpp @@ -31,19 +31,25 @@ PermuteLayer* PermuteLayer::Clone(Graph& graph) const return CloneBase<PermuteLayer>(graph, m_Param, GetName()); } +std::vector<TensorShape> PermuteLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const +{ + BOOST_ASSERT(inputShapes.size() == 1); + const TensorShape& inShape = inputShapes[0]; + return std::vector<TensorShape> ({armnnUtils::Permuted(inShape, m_Param.m_DimMappings)}); +} + void PermuteLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "PermuteLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "PermuteLayer: TensorInfo must be set on connected InputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); - const TensorInfo& infoIn = GetInputSlot(0).GetConnection()->GetTensorInfo(); - TensorShape shapeOut = armnnUtils::Permuted(infoIn.GetShape(), m_Param.m_DimMappings); ConditionalThrowIfNotEqual<LayerValidationException>( "PermuteLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - shapeOut); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/PermuteLayer.hpp b/src/armnn/layers/PermuteLayer.hpp index c060a16390..2700dd2c7b 100644 --- a/src/armnn/layers/PermuteLayer.hpp +++ b/src/armnn/layers/PermuteLayer.hpp @@ -18,6 +18,7 @@ public: PermuteLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; const PermutationVector& GetPermutation() const { diff --git a/src/armnn/layers/Pooling2dLayer.cpp b/src/armnn/layers/Pooling2dLayer.cpp index ede37d7604..68049101e7 100644 --- a/src/armnn/layers/Pooling2dLayer.cpp +++ b/src/armnn/layers/Pooling2dLayer.cpp @@ -29,15 +29,10 @@ Pooling2dLayer* Pooling2dLayer::Clone(Graph& graph) const return CloneBase<Pooling2dLayer>(graph, m_Param, GetName()); } -void Pooling2dLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> Pooling2dLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "Pooling2dLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "Pooling2dLayer: TensorInfo must be set on connected InputSlot."); - - IOutputSlot* input = GetInputSlot(0).GetConnection(); - const TensorShape& inputShape = input->GetTensorInfo().GetShape(); + BOOST_ASSERT(inputShapes.size() == 1); + const TensorShape& inputShape = inputShapes[0]; // If we support multiple batch dimensions in the future, then this assert will need to change. BOOST_ASSERT_MSG(inputShape.GetNumDimensions() == 4, "Pooling2dLayer will always have 4D input."); @@ -75,8 +70,8 @@ void Pooling2dLayer::ValidateTensorShapesFromInputs() BOOST_ASSERT_MSG(false, "Unsupported Output Shape Rounding"); } - // Make sure that border operations will start from inside the input and not the padded area - // This is what both Caffe and CL does... + // MakeS sure that border operations will start from inside the input and not the padded area. + // This is what both Caffe and CL do... if ((size - 1)*stride >= inSize + lowPad) { --size; @@ -89,18 +84,25 @@ void Pooling2dLayer::ValidateTensorShapesFromInputs() m_Param.m_PaddingMethod, m_Param.m_OutputShapeRounding); outHeight= CalcSize(inHeight, m_Param.m_PadTop, m_Param.m_PadBottom, m_Param.m_PoolHeight, m_Param.m_StrideY, m_Param.m_PaddingMethod, m_Param.m_OutputShapeRounding); - - } unsigned int outChannels = inChannels; unsigned int outBatchSize = inBatchSize; - TensorShape shapeOut({outBatchSize, outChannels, outHeight, outWidth}); + return std::vector<TensorShape>({ TensorShape({outBatchSize, outChannels, outHeight, outWidth}) }); +} + +void Pooling2dLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); ConditionalThrowIfNotEqual<LayerValidationException>( "Pooling2dLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - shapeOut); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/Pooling2dLayer.hpp b/src/armnn/layers/Pooling2dLayer.hpp index af39dbb5ec..d5950d6ec3 100644 --- a/src/armnn/layers/Pooling2dLayer.hpp +++ b/src/armnn/layers/Pooling2dLayer.hpp @@ -9,19 +9,20 @@ namespace armnn { -class SoftmaxLayer : public LayerWithParameters<SoftmaxDescriptor> +class Pooling2dLayer : public LayerWithParameters<Pooling2dDescriptor> { public: virtual std::unique_ptr<IWorkload> CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const override; - SoftmaxLayer* Clone(Graph& graph) const override; + Pooling2dLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; protected: - SoftmaxLayer(const SoftmaxDescriptor& param, const char* name); - ~SoftmaxLayer() = default; + Pooling2dLayer(const Pooling2dDescriptor& param, const char* name); + ~Pooling2dLayer() = default; }; } // namespace diff --git a/src/armnn/layers/ReshapeLayer.cpp b/src/armnn/layers/ReshapeLayer.cpp index df5d9d5bb0..248a45c491 100644 --- a/src/armnn/layers/ReshapeLayer.cpp +++ b/src/armnn/layers/ReshapeLayer.cpp @@ -30,17 +30,23 @@ ReshapeLayer* ReshapeLayer::Clone(Graph& graph) const return CloneBase<ReshapeLayer>(graph, m_Param, GetName()); } +std::vector<TensorShape> ReshapeLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const +{ + return std::vector<TensorShape>({ m_Param.m_TargetShape }); +} + void ReshapeLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "ReshapeLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "ReshapeLayer: TensorInfo must be set on connected OutputSlot."); + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ }); + + BOOST_ASSERT(inferredShapes.size() == 1); ConditionalThrowIfNotEqual<LayerValidationException>( "ReshapeLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - m_Param.m_TargetShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/ReshapeLayer.hpp b/src/armnn/layers/ReshapeLayer.hpp index 8a3cf3a698..4435ba9bf8 100644 --- a/src/armnn/layers/ReshapeLayer.hpp +++ b/src/armnn/layers/ReshapeLayer.hpp @@ -18,6 +18,7 @@ public: ReshapeLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; bool IsEqual(const Layer& other) const { diff --git a/src/armnn/layers/ResizeBilinearLayer.cpp b/src/armnn/layers/ResizeBilinearLayer.cpp index 204d5afae8..6477fa375a 100644 --- a/src/armnn/layers/ResizeBilinearLayer.cpp +++ b/src/armnn/layers/ResizeBilinearLayer.cpp @@ -30,23 +30,31 @@ ResizeBilinearLayer* ResizeBilinearLayer::Clone(Graph& graph) const return CloneBase<ResizeBilinearLayer>(graph, m_Param, GetName()); } -void ResizeBilinearLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> ResizeBilinearLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "MemCopyLayer: InputSlot must be connected to an OutputSlot"); - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection()->IsTensorInfoSet(), - "MemCopyLayer: TensorInfo must be set on connected OutputSlot."); + BOOST_ASSERT(inputShapes.size() == 1); + const TensorShape& inputShape = inputShapes[0]; - const TensorShape& inputShape = GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(); unsigned int outWidth = m_Param.m_TargetWidth; unsigned int outHeight = m_Param.m_TargetHeight; unsigned int outChannels = inputShape[1]; unsigned int outBatch = inputShape[0]; - TensorShape outShape({outBatch, outChannels, outHeight, outWidth}); + + return std::vector<TensorShape>({ TensorShape({outBatch, outChannels, outHeight, outWidth}) }); +} + +void ResizeBilinearLayer::ValidateTensorShapesFromInputs() +{ + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); + ConditionalThrowIfNotEqual<LayerValidationException>( "ResizeBilinearLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/ResizeBilinearLayer.hpp b/src/armnn/layers/ResizeBilinearLayer.hpp index 2cefedb0b8..e6798ce531 100644 --- a/src/armnn/layers/ResizeBilinearLayer.hpp +++ b/src/armnn/layers/ResizeBilinearLayer.hpp @@ -18,6 +18,7 @@ public: ResizeBilinearLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; protected: ResizeBilinearLayer(const ResizeBilinearDescriptor& param, const char* name); diff --git a/src/armnn/layers/SoftmaxLayer.cpp b/src/armnn/layers/SoftmaxLayer.cpp index 2bd0c1d106..7c42b7a3c9 100644 --- a/src/armnn/layers/SoftmaxLayer.cpp +++ b/src/armnn/layers/SoftmaxLayer.cpp @@ -31,14 +31,16 @@ SoftmaxLayer* SoftmaxLayer::Clone(Graph& graph) const void SoftmaxLayer::ValidateTensorShapesFromInputs() { - ConditionalThrow<LayerValidationException>(GetInputSlot(0).GetConnection() != nullptr, - "SoftmaxLayer: Input slot must be connected."); + VerifyLayerConnections(1, CHECK_LOCATION()); + + auto inferredShapes = InferOutputShapes({ GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape() }); + + BOOST_ASSERT(inferredShapes.size() == 1); - const TensorShape& outShape = GetInputSlot(0).GetConnection()->GetTensorInfo().GetShape(); ConditionalThrowIfNotEqual<LayerValidationException>( "SoftmaxLayer: TensorShape set on OutputSlot[0] does not match the inferred shape.", GetOutputSlot(0).GetTensorInfo().GetShape(), - outShape); + inferredShapes[0]); } } // namespace armnn diff --git a/src/armnn/layers/SoftmaxLayer.hpp b/src/armnn/layers/SoftmaxLayer.hpp index ff60a08a91..af39dbb5ec 100644 --- a/src/armnn/layers/SoftmaxLayer.hpp +++ b/src/armnn/layers/SoftmaxLayer.hpp @@ -9,19 +9,19 @@ namespace armnn { -class Pooling2dLayer : public LayerWithParameters<Pooling2dDescriptor> +class SoftmaxLayer : public LayerWithParameters<SoftmaxDescriptor> { public: virtual std::unique_ptr<IWorkload> CreateWorkload(const Graph& graph, const IWorkloadFactory& factory) const override; - Pooling2dLayer* Clone(Graph& graph) const override; + SoftmaxLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; protected: - Pooling2dLayer(const Pooling2dDescriptor& param, const char* name); - ~Pooling2dLayer() = default; + SoftmaxLayer(const SoftmaxDescriptor& param, const char* name); + ~SoftmaxLayer() = default; }; } // namespace diff --git a/src/armnn/layers/SplitterLayer.cpp b/src/armnn/layers/SplitterLayer.cpp index 630921e4d8..5e737a245e 100644 --- a/src/armnn/layers/SplitterLayer.cpp +++ b/src/armnn/layers/SplitterLayer.cpp @@ -22,7 +22,7 @@ std::unique_ptr<IWorkload> SplitterLayer::CreateWorkload(const Graph& graph, con { SplitterQueueDescriptor descriptor; - // copy the window origins to the descriptor + // Copies the window origins to the descriptor. for (unsigned int i = 0; i < m_Param.GetNumViews(); ++i) { descriptor.m_ViewOrigins.emplace_back( @@ -34,14 +34,14 @@ std::unique_ptr<IWorkload> SplitterLayer::CreateWorkload(const Graph& graph, con void SplitterLayer::CreateTensorHandles(Graph& graph, const IWorkloadFactory& factory) { - //if sub tensors are supported than all the "splitter" need to do is to + //If sub tensors are supported than all the "splitter" need to do is to //set the outputs to be appropriate sub tensors of the input. if (factory.SupportsSubTensors()) { const OutputHandler& outputHandler = GetInputSlots()[0].GetConnectedOutputSlot()->GetOutputHandler(); ITensorHandle* inputData = outputHandler.GetData(); - //create the outputs as subtensors of the input + //Creates the outputs as subtensors of the input. for (unsigned int i = 0; i < m_Param.GetNumViews(); ++i) { m_OutputHandlers[i].SetData(factory.CreateSubTensorHandle(*inputData, @@ -63,18 +63,38 @@ SplitterLayer* SplitterLayer::Clone(Graph& graph) const return CloneBase<SplitterLayer>(graph, m_Param, GetName()); } -void SplitterLayer::ValidateTensorShapesFromInputs() +std::vector<TensorShape> SplitterLayer::InferOutputShapes(const std::vector<TensorShape>& inputShapes) const { + BOOST_ASSERT(inputShapes.size() == m_Param.GetNumViews()); + std::vector<TensorShape> outShapes; //Output shapes must match View shapes. for (unsigned int viewIdx = 0; viewIdx < m_Param.GetNumViews(); viewIdx++) { const uint32_t* sizes = m_Param.GetViewSizes(viewIdx); + outShapes.push_back(TensorShape(m_Param.GetNumDimensions(), sizes)); + } + return outShapes; +} + +void SplitterLayer::ValidateTensorShapesFromInputs() +{ + std::vector<TensorShape> views; + for (unsigned int viewIdx = 0; viewIdx < m_Param.GetNumViews(); viewIdx++) + { + const uint32_t* sizes = m_Param.GetViewSizes(viewIdx); + views.push_back(TensorShape(m_Param.GetNumDimensions(), sizes)); + } + + auto inferredShapes = InferOutputShapes(views); - TensorShape outShape(m_Param.GetNumDimensions(), sizes); + BOOST_ASSERT(inferredShapes.size() == m_Param.GetNumViews()); + + for (unsigned int viewIdx = 0; viewIdx < m_Param.GetNumViews(); viewIdx++) + { ConditionalThrowIfNotEqual<LayerValidationException>( "SplitterLayer: View sizes must match output tensor shapes.", GetOutputSlot(viewIdx).GetTensorInfo().GetShape(), - outShape); + inferredShapes[viewIdx]); } } diff --git a/src/armnn/layers/SplitterLayer.hpp b/src/armnn/layers/SplitterLayer.hpp index 7e5bbd2668..8e361b4d5c 100644 --- a/src/armnn/layers/SplitterLayer.hpp +++ b/src/armnn/layers/SplitterLayer.hpp @@ -19,6 +19,7 @@ public: SplitterLayer* Clone(Graph& graph) const override; void ValidateTensorShapesFromInputs() override; + std::vector<TensorShape> InferOutputShapes(const std::vector<TensorShape>& inputShapes) const override; protected: SplitterLayer(const ViewsDescriptor& param, const char* name); |