diff options
author | Kevin May <kevin.may@arm.com> | 2023-12-12 11:18:46 +0000 |
---|---|---|
committer | Kevin May <kevin.may@arm.com> | 2023-12-14 10:05:56 +0000 |
commit | 1bea6beb042635c7716ae43220ee19eedb2de9ff (patch) | |
tree | 638a60e9128e28efb613bee96e2de62f2c08b3fc /src/backends/tosaCommon | |
parent | ce65588484ed1e553bdebf24123a30b5575f1bce (diff) | |
download | armnn-1bea6beb042635c7716ae43220ee19eedb2de9ff.tar.gz |
Add Split support to TOSA Reference Backend
* Resolves IVGCVSW-7918
Signed-off-by: Kevin May <kevin.may@arm.com>
Change-Id: Ic2afaa55f7ee88ce4c9b8ea696eef5f28663f8c6
Diffstat (limited to 'src/backends/tosaCommon')
8 files changed, 316 insertions, 1 deletions
diff --git a/src/backends/tosaCommon/TosaMappings.cpp b/src/backends/tosaCommon/TosaMappings.cpp index 6567026de0..0bdb1fec3d 100644 --- a/src/backends/tosaCommon/TosaMappings.cpp +++ b/src/backends/tosaCommon/TosaMappings.cpp @@ -84,6 +84,11 @@ TosaSerializationBasicBlock* GetTosaMapping(const Layer* layer, auto sliceDesc = PolymorphicDowncast<const SliceDescriptor*>(&descriptor); return ConvertSliceToTosaOperator(layer, inputs, outputs, sliceDesc); } + case LayerType::Splitter: + { + auto splitDesc = PolymorphicDowncast<const SplitterDescriptor*>(&descriptor); + return ConvertSplitToTosaOperator(layer, inputs, outputs, splitDesc); + } case LayerType::TransposeConvolution2d: { auto transposeConv2dDesc = PolymorphicDowncast<const TransposeConvolution2dDescriptor*>(&descriptor); diff --git a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt index c864544241..279b193db7 100644 --- a/src/backends/tosaCommon/operatorMappings/CMakeLists.txt +++ b/src/backends/tosaCommon/operatorMappings/CMakeLists.txt @@ -24,6 +24,8 @@ list(APPEND armnnTosaBackendOperators_sources ResizeOperator.cpp SliceOperator.hpp SliceOperator.cpp + SplitOperator.hpp + SplitOperator.cpp TosaOperatorUtils.hpp TransposeConv2dOperator.hpp TransposeConv2dOperator.cpp diff --git a/src/backends/tosaCommon/operatorMappings/SplitOperator.cpp b/src/backends/tosaCommon/operatorMappings/SplitOperator.cpp new file mode 100644 index 0000000000..5231f96c36 --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/SplitOperator.cpp @@ -0,0 +1,116 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Copyright © 2020 The TensorFlow Authors. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +// + +#include "SplitOperator.hpp" + +// This function is paraphrased from: +// tensorflow/compiler/mlir/tosa/transforms/legalize_common.cc from function convertSplitOp +TosaSerializationBasicBlock* ConvertSplitToTosaOperator(const Layer* layer, + const std::vector<const TensorInfo*>& inputs, + const std::vector<const TensorInfo*>& outputs, + const SplitterDescriptor* splitDescriptor) +{ + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( inputs.size() == 1, + "ConvertSplitToTosaOperator: Split must have only one input" ); + + ARMNN_THROW_INVALIDARG_MSG_IF_FALSE( outputs.size() < 1, + "ConvertSplitToTosaOperator: Split must have more than one output" ); + + if (!inputs[0]->GetShape().AreAllDimensionsSpecified()) + { + throw armnn::Exception("ConvertSplitToTosaOperator: Dynamic input dimensions are unsupported."); + } + + std::string inputName = std::string("input0_"); + std::vector<std::string> outputNames; + std::string blockName = std::string("Op_SPLIT_block_") + GetUniqueTosaMappingID(); + + unsigned int numSplit = splitDescriptor->GetNumViews(); + // If a layer is present then the block will be used for execution, so input and output names need to be determined + // using the previous and following layers so the graph is connected correctly. For validation this doesn't matter. + if(layer != nullptr) + { + // Get the layers connected to the input slots and determine unique tensor names. + Layer& connectedLayer = layer->GetInputSlot(0).GetConnectedOutputSlot()->GetOwningLayer(); + inputName = GenerateUniqueName(connectedLayer, 0); + + for (unsigned int i=0; i < numSplit; ++i) + { + // Determine unique output(s) tensor name. + std::string outputName = GenerateUniqueOutputName(*layer, i); + outputNames.push_back(outputName); + } + } + else + { + for (unsigned int i=0; i < numSplit; ++i) + { + // Determine unique output(s) tensor name. + std::string outputName = "output" + std::to_string(i) + "_"; + outputNames.push_back(outputName); + } + } + + // Each slice op has a different beginning point. + // The size is the same for each slice op. + std::vector<int32_t> beginVals; + beginVals.reserve(inputs[0]->GetNumDimensions()); + std::vector<int32_t> sizeVals; + sizeVals.reserve(inputs[0]->GetNumDimensions()); + for (unsigned int j = 0; j < inputs[0]->GetNumDimensions(); ++j) + { + beginVals.emplace_back(0); + uint32_t dim = inputs[0]->GetShape()[j]; + sizeVals.emplace_back(dim); + } + + uint32_t axis = static_cast<uint32_t>(splitDescriptor->GetAxis()); + sizeVals[axis] = sizeVals[axis] / static_cast<int32_t>(numSplit); + + std::vector<TosaSerializationOperator*> ops; + for (unsigned int i=0; i < numSplit; ++i) + { + beginVals[axis] = static_cast<int>(i) * sizeVals[axis]; + TosaSliceAttribute attribute(beginVals, sizeVals); + auto* op = new TosaSerializationOperator(Op_SLICE, + Attribute_SliceAttribute, + &attribute, + {inputName}, + {outputNames[i]}); + + ops.push_back(op); + } + + std::vector<TosaSerializationTensor*> tensors; + // Only add input tensors if connected layer is an input layer. + // As intermediate or constant tensors will be created separately. + // There also can't be duplicate tensor. + if(inputName.find("input0_") != std::string::npos) + { + std::vector<int32_t> inputShape = GetTosaTensorShape(inputs[0]->GetShape()); + DType inputDType = ArmNNToDType(inputs[0]->GetDataType()); + + tensors.push_back(new TosaSerializationTensor(inputName, inputShape, inputDType, {})); + } + + std::vector<int32_t> outputShape = GetTosaTensorShape(outputs[0]->GetShape()); + DType outputDType = ArmNNToDType(outputs[0]->GetDataType()); + + for (unsigned int i=0; i < numSplit; ++i) + { + tensors.push_back(new TosaSerializationTensor(outputNames[i], outputShape, outputDType, {})); + } + // operatorInputNames/operatorOutputNames ends up being the same as + // blockInputNames/blockOutputNames for one-to-one ArmNN to TOSA mappings + return new TosaSerializationBasicBlock(blockName, // name + mainName, // region name + ops, // operators + tensors, // tensors + {inputName}, // inputs + outputNames); // outputs +}
\ No newline at end of file diff --git a/src/backends/tosaCommon/operatorMappings/SplitOperator.hpp b/src/backends/tosaCommon/operatorMappings/SplitOperator.hpp new file mode 100644 index 0000000000..93091cd9bd --- /dev/null +++ b/src/backends/tosaCommon/operatorMappings/SplitOperator.hpp @@ -0,0 +1,16 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "TosaOperatorUtils.hpp" + +using namespace armnn; +using namespace tosa; + +TosaSerializationBasicBlock* ConvertSplitToTosaOperator(const Layer* layer, + const std::vector<const TensorInfo*>& inputs, + const std::vector<const TensorInfo*>& outputs, + const SplitterDescriptor* splitDescriptor);
\ No newline at end of file diff --git a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp index 369b37f35e..749e8764aa 100644 --- a/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp +++ b/src/backends/tosaCommon/operatorMappings/TosaCommonOperators.hpp @@ -15,5 +15,6 @@ #include "ReshapeOperator.hpp" #include "ResizeOperator.hpp" #include "SliceOperator.hpp" +#include "SplitOperator.hpp" #include "TransposeConv2dOperator.hpp" #include "TransposeOperator.hpp" diff --git a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp index b8d28f0405..94dd537a30 100644 --- a/src/backends/tosaCommon/test/OneToManyMappingTests.cpp +++ b/src/backends/tosaCommon/test/OneToManyMappingTests.cpp @@ -1,9 +1,10 @@ // -// Copyright © 2022 Arm Ltd and Contributors. All rights reserved. +// Copyright © 2022-2023 Arm Ltd and Contributors. All rights reserved. // SPDX-License-Identifier: MIT // #include "AvgPool2DIgnoreValueChecker.hpp" +#include "SplitChecker.hpp" #include <armnn/IRuntime.hpp> using namespace armnn; @@ -82,4 +83,69 @@ TEST_CASE("GetTosaMappingFromLayer_AvgPool2DIgnoreValueLayer") intermediateShape, descriptor); } + +TEST_CASE("GetTosaMapping_SplitLayer") +{ + const unsigned int numViews = 3; + const unsigned int numDimensions = 4; + armnn::ViewsDescriptor descriptor(numViews, numDimensions); + descriptor.SetAxis(static_cast<int32_t>(1)); + + std::vector<std::vector<int32_t>> inShape = {{ 1, 18, 4, 4 }}; + std::vector<std::vector<int32_t>> outShape = {{ 1, 6, 4, 4 },{ 1, 6, 4, 4 },{ 1, 6, 4, 4 }}; + + armnn::TensorInfo inputTensorInfo({1, 18, 4, 4}, DataType::Float32); + armnn::TensorInfo outputTensorInfo({1, 6, 4, 4}, DataType::Float32); + + TosaSerializationBasicBlock* basicBlock = + GetTosaMapping(nullptr, LayerType::Splitter, {&inputTensorInfo}, {&outputTensorInfo}, descriptor); + + VerifySplit(basicBlock, + inShape, + outShape, + descriptor); +} + +TEST_CASE("GetTosaMappingFromLayer_SplitLayer") +{ + IRuntime::CreationOptions options; + IRuntimePtr runtime(IRuntime::Create(options)); + + // Builds up the structure of the network. + INetworkPtr net(INetwork::Create()); + + const unsigned int numViews = 3; + const unsigned int numDimensions = 4; + armnn::ViewsDescriptor descriptor(numViews, numDimensions); + descriptor.SetAxis(static_cast<int32_t>(1)); + + std::vector<std::vector<int32_t>> inShape = {{ 1, 18, 4, 4 }}; + std::vector<std::vector<int32_t>> outShape = {{ 1, 6, 4, 4 },{ 1, 6, 4, 4 },{ 1, 6, 4, 4 }}; + + IConnectableLayer* input0 = net->AddInputLayer(0, "input0"); + IConnectableLayer* split = net->AddSplitterLayer(descriptor, "split"); + IConnectableLayer* output0 = net->AddOutputLayer(0, "output0"); + IConnectableLayer* output1 = net->AddOutputLayer(1, "output1"); + IConnectableLayer* output2 = net->AddOutputLayer(2, "output2"); + + input0->GetOutputSlot(0).Connect(split->GetInputSlot(0)); + split->GetOutputSlot(0).Connect(output0->GetInputSlot(0)); + split->GetOutputSlot(1).Connect(output1->GetInputSlot(0)); + split->GetOutputSlot(2).Connect(output2->GetInputSlot(0)); + + armnn::TensorInfo inputTensorInfo({1, 18, 4, 4}, DataType::Float32); + armnn::TensorInfo outputTensorInfo({1, 6, 4, 4}, DataType::Float32); + + input0->GetOutputSlot(0).SetTensorInfo(inputTensorInfo); + split->GetOutputSlot(0).SetTensorInfo(outputTensorInfo); + split->GetOutputSlot(1).SetTensorInfo(outputTensorInfo); + split->GetOutputSlot(2).SetTensorInfo(outputTensorInfo); + + TosaSerializationBasicBlock* basicBlock = GetTosaMappingFromLayer(PolymorphicDowncast<Layer*>(split)); + + VerifySplit(basicBlock, + inShape, + outShape, + descriptor); +} }
\ No newline at end of file diff --git a/src/backends/tosaCommon/test/SplitChecker.hpp b/src/backends/tosaCommon/test/SplitChecker.hpp new file mode 100644 index 0000000000..edef4a1cf9 --- /dev/null +++ b/src/backends/tosaCommon/test/SplitChecker.hpp @@ -0,0 +1,77 @@ +// +// Copyright © 2023 Arm Ltd and Contributors. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "TosaTestUtils.hpp" + +using namespace armnn; +using namespace tosa; + +void VerifySplit(TosaSerializationBasicBlock* splitBlock, + std::vector<std::vector<int32_t>> inputShape, + std::vector<std::vector<int32_t>> outputShape, + const BaseDescriptor& splitDescriptor, + DType dataType = DType_FP32) +{ + uint32_t numInputs = static_cast<uint32_t>(inputShape.size()); + uint32_t numOutputs = static_cast<uint32_t>(outputShape.size()); + + std::string blockStr = "Op_SPLIT_block_"; + CHECK(splitBlock->GetName().find(blockStr) != std::string::npos); + CHECK(splitBlock->GetInputs().size() == numInputs); + CHECK(splitBlock->GetOutputs().size() == numOutputs); + CHECK(splitBlock->GetOperators().size() == 3); + CHECK(splitBlock->GetTensors().size() == 4); + + // + // Verify slice operator + // + + for (uint32_t i = 0; i < splitBlock->GetOperators().size(); i++) + { + TosaSerializationOperator *sliceOp = splitBlock->GetOperators().at(i); + uint32_t sliceOpOutputs = 1; + CHECK(sliceOp->GetInputTensorNames().size() == numInputs); + CHECK(sliceOp->GetOutputTensorNames().size() == sliceOpOutputs); + + std::basic_string<char> blockInputName = splitBlock->GetInputs()[0]; + std::basic_string<char> operatorInputName = sliceOp->GetInputTensorNames()[0]; + + std::string opInputStr = "input" + std::to_string(0) + "_"; + + CHECK(blockInputName == operatorInputName); + CHECK(splitBlock->GetTensorByName(blockInputName)); + CHECK(blockInputName.find(opInputStr) != std::string::npos); + + TosaSerializationTensor* inputTensor = splitBlock->GetTensorByName(operatorInputName); + CHECK(inputTensor->GetDtype() == dataType); + CHECK(inputTensor->GetData().size() == 0); + CHECK(inputTensor->GetShape() == inputShape[0]); + + std::basic_string<char> blockOutputName = splitBlock->GetOutputs()[i]; + std::basic_string<char> operatorOutputName = sliceOp->GetOutputTensorNames()[0]; + + std::string opOutputStr = "output" + std::to_string(i) + "_"; + + CHECK(blockOutputName == operatorOutputName); + CHECK(splitBlock->GetTensorByName(blockOutputName)); + CHECK(blockOutputName.find(opOutputStr) != std::string::npos); + + TosaSerializationTensor* outputTensor = splitBlock->GetTensorByName(operatorOutputName); + CHECK(outputTensor->GetDtype() == dataType); + CHECK(outputTensor->GetData().size() == 0); + CHECK(outputTensor->GetShape() == outputShape[0]); + + CHECK(sliceOp->GetAttributeType() == Attribute_SliceAttribute); + CHECK(sliceOp->GetOp() == Op_SLICE); + + VerifyTosaAttribute(splitDescriptor, + sliceOp->GetAttribute(), + inputShape[0], + outputShape[0], + LayerType::Splitter, + i); + } + +}
\ No newline at end of file diff --git a/src/backends/tosaCommon/test/TosaTestUtils.hpp b/src/backends/tosaCommon/test/TosaTestUtils.hpp index 87ff5ff532..05dd164b50 100644 --- a/src/backends/tosaCommon/test/TosaTestUtils.hpp +++ b/src/backends/tosaCommon/test/TosaTestUtils.hpp @@ -193,6 +193,38 @@ inline void VerifyTosaAttribute(const BaseDescriptor& descriptor, break; } + case LayerType::Splitter: + { + auto splitDesc = PolymorphicDowncast<const SplitterDescriptor*>(&descriptor); + TosaSliceAttribute sliceAttribute(attribute); + + // Each slice op has a different beginning point. + // The size is the same for each slice op. + std::vector<int32_t> beginVals; + beginVals.reserve(inputShape.size()); + std::vector<int32_t> sizeVals; + sizeVals.reserve(inputShape.size()); + for (unsigned int j = 0; j < inputShape.size(); ++j) + { + beginVals.emplace_back(0); + int32_t dim = inputShape[j]; + sizeVals.emplace_back(dim); + } + + uint32_t axis = static_cast<uint32_t>(splitDesc->GetAxis()); + sizeVals[axis] = sizeVals[axis] / static_cast<int32_t>(splitDesc->GetNumViews()); + beginVals[axis] = static_cast<int>(mappingOpNumber) * sizeVals[axis]; + CHECK(beginVals == sliceAttribute.start()); + CHECK(sizeVals == sliceAttribute.size()); + + CHECK(beginVals.size() == inputShape.size()); + CHECK(sizeVals.size() == inputShape.size()); + + CHECK(beginVals.size() == outputShape.size()); + CHECK(sizeVals.size() == outputShape.size()); + + break; + } case LayerType::TransposeConvolution2d: { auto transposeConv2dDesc = PolymorphicDowncast<const TransposeConvolution2dDescriptor*>(&descriptor); |