text: //
  // Copyright © 2017 Arm Ltd and Contributors. All rights reserved.
  // SPDX-License-Identifier: MIT
  //
  
  #include "ConcatTestImpl.hpp"
  
  #include <QuantizeHelper.hpp>
  #include <ResolveType.hpp>
  
  
  #include <armnnUtils/Permute.hpp>
  
  #include <backendsCommon/test/TensorCopyUtils.hpp>
  #include <backendsCommon/test/WorkloadTestUtils.hpp>
  
  #include <test/TensorHelpers.hpp>
  
  using namespace armnn;
  using namespace armnnUtils;
  
  //
  // Helper functions and templates
  //
  
  OriginsDescriptor CreateDescriptorForConcat(
      const std::vector<TensorInfo> & inputTensorInfos,
      unsigned int concatDim)
  {
      std::vector<TensorShape> shapes;
      shapes.reserve(inputTensorInfos.size());
      for (const TensorInfo& it: inputTensorInfos)
      {
          shapes.push_back(it.GetShape());
      }
  
      return CreateDescriptorForConcatenation(shapes.begin(), shapes.end(), concatDim);
  }
  
  //
  // Concat is only supported for N and C dimensions for NCHW and the inner most dimension
  // In case of <4 dimensions we need to make sure that the concat dimensions are at least
  // the 3rd slowest iterating one or the inner most dimension.
  //
  
  bool NeedPermuteForConcat(
      const std::vector<TensorInfo> & inputTensorInfos,
      unsigned int concatDim)
  {
      // See note above. Additionally we expect the input shapes to have the
      // same number of dimensions.
      unsigned int nDimensions = 0;
  
      // Determine the number of dimensions as well as sanity check them
      // agains test implementation issues.
      for (auto && tensorInfo : inputTensorInfos)
      {
          if (!nDimensions)
          {
              nDimensions = tensorInfo.GetShape().GetNumDimensions();
          }
          else
          {
              ARMNN_ASSERT_MSG(nDimensions == tensorInfo.GetShape().GetNumDimensions(),
                  "Input shapes must have the same number of dimensions");
          }
      }
  
      return (nDimensions < 3 || (nDimensions == 3 && (nDimensions-concatDim) < 3 && (nDimensions-concatDim) != 1));
  }
  
  TensorShape ExpandTensorShapeTo3dForPermute(const TensorShape & inputShape)
  {
      unsigned int numDims = inputShape.GetNumDimensions();
      if (numDims >= 3)
      {
          // Nothing to do if the inputShape has at least 3 dimensions.
          return inputShape;
      }
  
      std::vector<unsigned int> newDims(size_t(3), 1u);
      unsigned int expandedBy = 3 - numDims;
      for (unsigned int i=0; i<numDims; ++i)
      {
          newDims[expandedBy+i] = inputShape[i];
      }
      return TensorShape(3u, &newDims[0]);
  }
  
  void Generate3dPermuteVectorForConcat(
      unsigned int numDimensions,
      unsigned int & concatDim,
      std::pair<PermutationVector, PermutationVector> & permutations)
  {
      ARMNN_ASSERT_MSG(numDimensions <= 3,
         "Only dimensions 1,2 and 3 are supported by this helper");
      unsigned int expandedBy = 3 - numDimensions;
      unsigned int expandedConcatAxis = concatDim + expandedBy;
  
      if (expandedConcatAxis == 2)
      {
          concatDim = 0;
          PermutationVector forwardPermutation({1, 2, 0});
          PermutationVector reversePermutation({2, 0, 1});
          permutations = std::make_pair(forwardPermutation, reversePermutation);
      }
      else if (expandedConcatAxis == 1)
      {
          concatDim = 0;
          PermutationVector forwardPermutation({2, 0, 1});
          PermutationVector reversePermutation({1, 2, 0});
          permutations = std::make_pair(forwardPermutation, reversePermutation);
      }
      else
      {
          ARMNN_ASSERT(expandedConcatAxis == 0);
          concatDim = 0;
      }
  }
  
  template<typename T> void PermuteTensorData(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      const PermutationVector& mappings,
      TensorInfo & inputTensorInfo,
      const T * inputData,
      std::vector<T>& outputData)
  {
      IgnoreUnused(memoryManager);
      ARMNN_ASSERT_MSG(inputData != nullptr, "inputData must not be null");
      if (inputData == nullptr)
      {
          // Nullptr is an error in the test. By returning without doing the concatenation
          // I expect the caller to fail the test. It still makes sense to report this as
          // an assert for Debug builds.
          return;
      }
  
      TensorInfo outputTensorInfo = armnnUtils::Permuted(inputTensorInfo, mappings);
      std::unique_ptr<ITensorHandle> inputHandle = tensorHandleFactory.CreateTensorHandle(inputTensorInfo);
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      PermuteQueueDescriptor queueDescriptor;
      queueDescriptor.m_Parameters = PermuteDescriptor{mappings};
      WorkloadInfo workloadInfo;
      AddInputToWorkload(queueDescriptor, workloadInfo, inputTensorInfo, inputHandle.get());
      AddOutputToWorkload(queueDescriptor, workloadInfo, outputTensorInfo, outputHandle.get());
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreatePermute(queueDescriptor, workloadInfo);
  
      inputHandle->Allocate();
      outputHandle->Allocate();
  
      CopyDataToITensorHandle(inputHandle.get(), inputData);
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      outputData.resize(outputTensorInfo.GetNumElements());
      CopyDataFromITensorHandle(&outputData[0], outputHandle.get());
      inputTensorInfo = outputTensorInfo;
  }
  
  //
  // Permute the input tensors so we can do a supported concatenation.
  // Also treat lower than 3d tensors as 3d by adding dummy 1 dimensions
  // at the front. Finally this function tells what the output shape
  // of the permuted concatenated tensor is going to be.
  //
  template<typename T> void PermuteInputsForConcat(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      std::vector<TensorInfo> & inputTensorInfos,
      std::vector<T *> & inputData,
      std::vector<std::vector<T>> & inputDataStorage,
      PermutationVector & permuteVector,
      unsigned int & concatDim,
      TensorInfo & outputTensorInfo)
  {
      IgnoreUnused(memoryManager);
      ARMNN_ASSERT_MSG(inputTensorInfos.size() > 1,
          "Expecting more than one tensor to be concatenated here");
  
      unsigned int numDims = 0;
      unsigned int nthInput = 0;
      const PermutationVector identity({0, 1, 2});
  
      std::pair<PermutationVector, PermutationVector> permutations =
          std::make_pair(identity, identity);
  
      inputDataStorage.resize(inputData.size());
  
      for (auto && tensorInfo : inputTensorInfos)
      {
          if (numDims == 0)
          {
              numDims = tensorInfo.GetShape().GetNumDimensions();
              Generate3dPermuteVectorForConcat(numDims, concatDim, permutations);
  
              // Store the reverese permutation.
              permuteVector = permutations.second;
              ARMNN_ASSERT_MSG(!permuteVector.IsEqual(identity),
                  "Test logic error, we don't need permutation, so we shouldn't arrive here");
          }
          else
          {
              ARMNN_ASSERT_MSG(numDims == tensorInfo.GetShape().GetNumDimensions(),
                  "All inputs must have the same number of dimensions");
          }
  
          TensorInfo newTensorInfo = tensorInfo;
          newTensorInfo.SetShape(ExpandTensorShapeTo3dForPermute(tensorInfo.GetShape()));
  
          PermuteTensorData<T>(workloadFactory,
                               memoryManager,
                               tensorHandleFactory,
                               permutations.first,
                               newTensorInfo,
                               inputData[nthInput],
                               inputDataStorage[nthInput]);
  
          inputData[nthInput] = inputDataStorage[nthInput].data();
          inputTensorInfos[nthInput] = newTensorInfo;
  
          ++nthInput;
      }
  
      outputTensorInfo.SetShape(
          armnnUtils::Permuted(
              ExpandTensorShapeTo3dForPermute(outputTensorInfo.GetShape()),
              permutations.first));
  }
  
  //
  // This is the pair of PermuteInputsForConcat(...) which permutes back
  // the output of the concatenation so we can check it against an expected
  // output.
  //
  template <typename T> void PermuteOutputForConcat(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      const TensorInfo & tensorInfo,
      const PermutationVector & permuteVector,
      std::unique_ptr<ITensorHandle> && inputDataHandle,
      T * data)
  {
      ARMNN_ASSERT_MSG(data != nullptr, "data must not be null");
      if (data == nullptr)
      {
          // Nullptr is an error in the test. By returning without doing the permutation
          // I expect the caller to fail the test. It still makes sense to report this as
          // an assert for Debug builds.
          return;
      }
  
      TensorInfo resultTensorInfo = tensorInfo;
      std::vector<T> inputData(tensorInfo.GetNumElements());
      std::vector<T> outputData;
  
      CopyDataFromITensorHandle(&inputData[0], inputDataHandle.get());
  
      PermuteTensorData<T>(workloadFactory,
                           memoryManager,
                           tensorHandleFactory,
                           permuteVector,
                           resultTensorInfo,
                           &inputData[0],
                           outputData);
  
      ::memcpy(data, &outputData[0], sizeof(T)*outputData.size());
  }
  
  template<typename T> void Concatenate(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      std::initializer_list<const TensorInfo> inputTensorInfosOrig,
      std::initializer_list<T *> inputsOrig,
      const TensorInfo& outputTensorInfoOrig,
      T * output,
      unsigned int concatDim,
      bool useSubtensor)
  {
      ARMNN_ASSERT_MSG(output != nullptr, "output must not be null");
      if (output == nullptr)
      {
          // Nullptr is an error in the test. By returning without doing the permutation
          // I expect the caller to fail the test. It still makes sense to report this as
          // an assert for Debug builds.
          return;
      }
  
      // Saves a copy of the parameters which we might need to change.
      std::vector<TensorInfo> inputTensorInfos(inputTensorInfosOrig.begin(), inputTensorInfosOrig.end());
      std::vector<T *> inputs            = inputsOrig;
      TensorInfo outputTensorInfo = outputTensorInfoOrig;
  
      PermutationVector permuteVector{0, 1, 2};
  
      // Holds and automatically releases memory for the reshaped input data.
      std::vector<std::vector<T>> tmpInputDataStorage;
  
      const size_t inputCount = inputTensorInfos.size();
  
      bool needPermuteForConcat = NeedPermuteForConcat(inputTensorInfos, concatDim);
  
      if (needPermuteForConcat)
      {
          //
          // We need to permute the inputs, because concatenation along
          // the requested axis is not supported.
          //
          PermuteInputsForConcat<T>(workloadFactory,
                                    memoryManager,
                                    tensorHandleFactory,
                                    inputTensorInfos,
                                    inputs,
                                    tmpInputDataStorage,
                                    permuteVector,
                                    concatDim,
                                    outputTensorInfo);
      }
  
      WorkloadInfo workloadInfo;
  
      std::vector<std::unique_ptr<ITensorHandle>> inputHandles;
      inputHandles.reserve(inputCount);
  
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      ConcatQueueDescriptor queueDescriptor;
      OriginsDescriptor viewsDescriptor = CreateDescriptorForConcat(inputTensorInfos, concatDim);
      queueDescriptor.m_Parameters = viewsDescriptor;
  
      if (useSubtensor)
      {
          queueDescriptor.m_ViewOrigins.reserve(viewsDescriptor.GetNumViews());
          for (unsigned int i = 0; i < viewsDescriptor.GetNumViews(); ++i)
          {
              queueDescriptor.m_ViewOrigins.emplace_back(std::vector<unsigned int>(viewsDescriptor.GetViewOrigin(i),
                  viewsDescriptor.GetViewOrigin(i) + viewsDescriptor.GetNumDimensions()));
          }
  
          outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
          const bool subTensorsSupported = workloadFactory.SupportsSubTensors();
          for (unsigned int i = 0; i < inputCount; ++i)
          {
              const TensorInfo& inputTensorInfo = inputTensorInfos[i];
  
              std::unique_ptr<ITensorHandle> inputHandle =
                  subTensorsSupported ?
                      tensorHandleFactory.CreateSubTensorHandle(*outputHandle,
                                                            inputTensorInfo.GetShape(),
                                                            queueDescriptor.m_ViewOrigins[i].m_Origin.data()) :
                                                            tensorHandleFactory.CreateTensorHandle(inputTensorInfo);
  
              inputHandles.emplace_back(std::move(inputHandle));
          }
  
  
      }
      else
      {
          for (unsigned int i = 0; i < inputCount; ++i)
          {
              std::unique_ptr<ITensorHandle> inputHandle = tensorHandleFactory.CreateTensorHandle(inputTensorInfos[i]);
              inputHandles.emplace_back(std::move(inputHandle));
          }
      }
  
      for (unsigned int i = 0; i < inputCount; ++i)
      {
          AddInputToWorkload(queueDescriptor, workloadInfo, inputTensorInfos[i], inputHandles[i].get());
      }
  
      AddOutputToWorkload(queueDescriptor, workloadInfo, outputTensorInfo, outputHandle.get());
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreateConcat(queueDescriptor, workloadInfo);
  
      for (auto& inputHandle : inputHandles)
      {
          inputHandle->Allocate();
      }
  
      outputHandle->Allocate();
  
      unsigned int nextInputId = 0;
      for (auto& inputHandle : inputHandles)
      {
          CopyDataToITensorHandle(inputHandle.get(), inputs[nextInputId]);
          ++nextInputId;
      }
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      if (needPermuteForConcat)
      {
          PermuteOutputForConcat<T>(workloadFactory,
                                    memoryManager,
                                    tensorHandleFactory,
                                    outputTensorInfo,
                                    permuteVector,
                                    std::move(outputHandle),
                                    output);
      }
      else
      {
          CopyDataFromITensorHandle(output, outputHandle.get());
      }
  }
  
  //
  // Implementation templates
  //
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 1> Concat1dTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo inputTensorInfo({ 3 }, ArmnnType, qScale, qOffset);
  
      auto input0 = MakeTensor<T, 1>(inputTensorInfo, QuantizedVector<T>({ 1.0f, 2.0f, 3.0f }, qScale, qOffset));
      auto input1 = MakeTensor<T, 1>(inputTensorInfo, QuantizedVector<T>({ 4.0f, 5.0f, 6.0f }, qScale, qOffset));
      auto input2 = MakeTensor<T, 1>(inputTensorInfo, QuantizedVector<T>({ 7.0f, 8.0f, 9.0f }, qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 9 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 1> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { inputTensorInfo, inputTensorInfo, inputTensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     0,
                     true);
  
      result.output         = MakeTensor<T, 1>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 1>(outputTensorInfo, QuantizedVector<T>(
          {
              1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 2> Concat2dTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      const TensorInfo& outputTensorInfo,
      unsigned int dimension,
      const float qScale,
      const int32_t qOffset)
  {
      TensorInfo inputTensorInfo({ 2, 3 }, ArmnnType, qScale, qOffset);
  
      auto input0 = MakeTensor<T, 2>(inputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f,
          },
          qScale, qOffset));
  
      auto input1 = MakeTensor<T, 2>(inputTensorInfo, QuantizedVector<T>(
           {
              // Batch 0
              4.0f, 5.0f, 6.0f,
  
              // Batch 1
              13.0f, 14.0f, 15.0f,
          },
          qScale, qOffset));
  
      auto input2 = MakeTensor<T, 2>(inputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              7.0f, 8.0f, 9.0f,
  
              // Batch 1
              16.0f, 17.0f, 18.0f,
          },
          qScale, qOffset));
  
      LayerTestResult<T, 2> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { inputTensorInfo, inputTensorInfo, inputTensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     true);
  
      result.output = MakeTensor<T, 2>(outputTensorInfo, output);
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 2> Concat2dDim0TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 6, 3 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 2> result = Concat2dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 0, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 2>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f,
  
              // Batch 2
              4.0f, 5.0f, 6.0f,
  
              // Batch 3
              13.0f, 14.0f, 15.0f,
  
              // Batch 4
              7.0f, 8.0f, 9.0f,
  
              // Batch 5
              16.0f, 17.0f, 18.0f,
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 2> Concat2dDim1TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 2, 9 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 2> result = Concat2dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 1, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 2>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 2> Concat2dDim0DiffInputDimsTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo input0TensorInfo({ 2, 3 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 2>(input0TensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f,
          },
          qScale, qOffset));
  
      TensorInfo input1TensorInfo({ 3, 3 }, ArmnnType, qScale, qOffset);
      auto input1 = MakeTensor<T, 2>(input1TensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              4.0f, 5.0f, 6.0f,
  
              // Batch 1
              13.0f, 14.0f, 15.0f,
  
              // Batch 0
              7.0f, 8.0f, 9.0f,
          },
          qScale, qOffset));
  
      TensorInfo input2TensorInfo({ 1, 3 }, ArmnnType, qScale, qOffset);
      auto input2 = MakeTensor<T, 2>(input2TensorInfo, QuantizedVector<T>(
          {
              // Batch 1
              16.0f, 17.0f, 18.0f,
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 6, 3 }, ArmnnType, qScale, qOffset);
      LayerTestResult<T, 2> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { input0TensorInfo, input1TensorInfo, input2TensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     0,
                     true);
  
      result.output = MakeTensor<T, 2>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 2>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f,
  
              // Batch 2
              4.0f, 5.0f, 6.0f,
  
              // Batch 3
              13.0f, 14.0f, 15.0f,
  
              // Batch 4
              7.0f, 8.0f, 9.0f,
  
              // Batch 5
              16.0f, 17.0f, 18.0f,
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 2> Concat2dDim1DiffInputDimsTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo input0TensorInfo({ 2, 3 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 2>(input0TensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f,
          },
          qScale, qOffset));
  
      TensorInfo input1TensorInfo({ 2, 5 }, ArmnnType, qScale, qOffset);
      auto input1 = MakeTensor<T, 2>(input1TensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              4.0f, 5.0f, 6.0f, 7.0f, 8.0f,
  
              // Batch 1
              13.0f, 14.0f, 15.0f, 16.0f, 17.0f,
          },
          qScale, qOffset));
  
      TensorInfo input2TensorInfo({ 2, 1 }, ArmnnType, qScale, qOffset);
      auto input2 = MakeTensor<T, 2>(input2TensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              9.0f,
  
              // Batch 1
              18.0f
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 2, 9 }, ArmnnType, qScale, qOffset);
      LayerTestResult<T, 2> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { input0TensorInfo, input1TensorInfo, input2TensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     1,
                     true);
  
      result.output = MakeTensor<T, 2>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 2>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0
              1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f,
  
              // Batch 1
              10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 16.0f, 17.0f, 18.0f,
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      const TensorInfo& outputTensorInfo,
      unsigned int dimension,
      bool useSubtensor,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo inputTensorInfo({ 2, 3, 2 }, ArmnnType, qScale, qOffset);
  
      auto input0 = MakeTensor<T, 3>(inputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f
          },
          qScale, qOffset));
  
      auto input1 = MakeTensor<T, 3>(inputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              7.0f, 8.0f,
  
              // Batch 0, Channel 1
              9.0f, 10.0f,
  
              // Batch 0, Channel 2
              11.0f, 12.0f,
  
              // Batch 1, Channel 0
              25.0f, 26.0f,
  
              // Batch 1, Channel 1
              27.0f, 28.0f,
  
              // Batch 1, Channel 2
              29.0f, 30.0f
          },
          qScale, qOffset));
  
      auto input2 = MakeTensor<T, 3>(inputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              13.0f, 14.0f,
  
              // Batch 0, Channel 1
              15.0f, 16.0f,
  
              // Batch 0, Channel 2
              17.0f, 18.0f,
  
              // Batch 1, Channel 0
              31.0f, 32.0f,
  
              // Batch 1, Channel 1
              33.0f, 34.0f,
  
              // Batch 1, Channel 2
              35.0f, 36.0f
          },
          qScale, qOffset));
  
      LayerTestResult<T, 3> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { inputTensorInfo, inputTensorInfo, inputTensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     useSubtensor);
  
      result.output = MakeTensor<T, 3>(outputTensorInfo, output);
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dDim0TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 6, 3, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 3> result = Concat3dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 0, true, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 3>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f,
  
              // Batch 2, Channel 0
              7.0f, 8.0f,
  
              // Batch 2, Channel 1
              9.0f, 10.0f,
  
              // Batch 2, Channel 2
              11.0f, 12.0f,
  
              // Batch 3, Channel 0
              25.0f, 26.0f,
  
              // Batch 3, Channel 1
              27.0f, 28.0f,
  
              // Batch 3, Channel 2
              29.0f, 30.0f,
  
              // Batch 4, Channel 0
              13.0f, 14.0f,
  
              // Batch 4, Channel 1
              15.0f, 16.0f,
  
              // Batch 4, Channel 2
              17.0f, 18.0f,
  
              // Batch 5, Channel 0
              31.0f, 32.0f,
  
              // Batch 5, Channel 1
              33.0f, 34.0f,
  
              // Batch 5, Channel 2
              35.0f, 36.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dDim1TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 2, 9, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 3> result = Concat3dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 1, true, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 3>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 0, Channel 3
              7.0f, 8.0f,
  
              // Batch 0, Channel 4
              9.0f, 10.0f,
  
              // Batch 0, Channel 5
              11.0f, 12.0f,
  
              // Batch 0, Channel 6
              13.0f, 14.0f,
  
              // Batch 0, Channel 7
              15.0f, 16.0f,
  
              // Batch 0, Channel 8
              17.0f, 18.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f,
  
              // Batch 1, Channel 3
              25.0f, 26.0f,
  
              // Batch 1, Channel 4
              27.0f, 28.0f,
  
              // Batch 1, Channel 5
              29.0f, 30.0f,
  
              // Batch 1, Channel 6
              31.0f, 32.0f,
  
              // Batch 1, Channel 7
              33.0f, 34.0f,
  
              // Batch 1, Channel 8
              35.0f, 36.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dDim2TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 2, 3, 6 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 3> result = Concat3dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 2, useSubtensor, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 3>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f, 7.0f, 8.0f, 13.0f, 14.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f, 9.0f, 10.0f, 15.0f, 16.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f, 11.0f, 12.0f, 17.0f, 18.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f, 25.0f, 26.0f, 31.0f, 32.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f, 27.0f, 28.0f, 33.0f, 34.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f, 29.0f, 30.0f, 35.0f, 36.0f,
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dDim0DiffInputDimsTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo input0TensorInfo({ 2, 3, 2 }, ArmnnType);
      auto input0 = MakeTensor<T, 3>(input0TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f
          },
          qScale, qOffset));
  
      TensorInfo input1TensorInfo({ 1, 3, 2 }, ArmnnType);
      auto input1 = MakeTensor<T, 3>(input1TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              7.0f, 8.0f,
  
              // Batch 0, Channel 1
              9.0f, 10.0f,
  
              // Batch 0, Channel 2
              11.0f, 12.0f,
          },
          qScale, qOffset));
  
      TensorInfo input2TensorInfo({ 3, 3, 2 }, ArmnnType);
      auto input2 = MakeTensor<T, 3>(input2TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              25.0f, 26.0f,
  
              // Batch 0, Channel 1
              27.0f, 28.0f,
  
              // Batch 0, Channel 2
              29.0f, 30.0f,
  
              // Batch 1, Channel 0
              13.0f, 14.0f,
  
              // Batch 1, Channel 1
              15.0f, 16.0f,
  
              // Batch 1, Channel 2
              17.0f, 18.0f,
  
              // Batch 2, Channel 0
              31.0f, 32.0f,
  
              // Batch 2, Channel 1
              33.0f, 34.0f,
  
              // Batch 2, Channel 2
              35.0f, 36.0f
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 6, 3, 2 }, ArmnnType);
      LayerTestResult<T, 3> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { input0TensorInfo, input1TensorInfo, input2TensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     0,
                     true);
  
      result.output = MakeTensor<T, 3>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 3>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f,
  
              // Batch 2, Channel 0
              7.0f, 8.0f,
  
              // Batch 2, Channel 1
              9.0f, 10.0f,
  
              // Batch 2, Channel 2
              11.0f, 12.0f,
  
              // Batch 3, Channel 0
              25.0f, 26.0f,
  
              // Batch 3, Channel 1
              27.0f, 28.0f,
  
              // Batch 3, Channel 2
              29.0f, 30.0f,
  
              // Batch 4, Channel 0
              13.0f, 14.0f,
  
              // Batch 4, Channel 1
              15.0f, 16.0f,
  
              // Batch 4, Channel 2
              17.0f, 18.0f,
  
              // Batch 5, Channel 0
              31.0f, 32.0f,
  
              // Batch 5, Channel 1
              33.0f, 34.0f,
  
              // Batch 5, Channel 2
              35.0f, 36.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dDim1DiffInputDimsTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo input0TensorInfo({ 2, 3, 2 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 3>(input0TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f
          },
          qScale, qOffset));
  
      TensorInfo input1TensorInfo({ 2, 4, 2 }, ArmnnType, qScale, qOffset);
      auto input1 = MakeTensor<T, 3>(input1TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              7.0f, 8.0f,
  
              // Batch 0, Channel 1
              9.0f, 10.0f,
  
              // Batch 0, Channel 2
              11.0f, 12.0f,
  
              // Batch 0, Channel 3
              25.0f, 26.0f,
  
              // Batch 1, Channel 0
              27.0f, 28.0f,
  
              // Batch 1, Channel 1
              29.0f, 30.0f,
  
              // Batch 1, Channel 2
              13.0f, 14.0f,
  
              // Batch 1, Channel 3
              15.0f, 16.0f,
          },
          qScale, qOffset));
  
      TensorInfo input2TensorInfo({ 2, 1, 2 }, ArmnnType, qScale, qOffset);
      auto input2 = MakeTensor<T, 3>(input2TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              17.0f, 18.0f,
  
              // Batch 1, Channel 0
              31.0f, 32.0f,
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 2, 8, 2 }, ArmnnType, qScale, qOffset);
      LayerTestResult<T, 3> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { input0TensorInfo, input1TensorInfo, input2TensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     1,
                     true);
  
      result.output = MakeTensor<T, 3>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 3>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 0, Channel 3
              7.0f, 8.0f,
  
              // Batch 0, Channel 4
              9.0f, 10.0f,
  
              // Batch 0, Channel 5
              11.0f, 12.0f,
  
              // Batch 0, Channel 6
              25.0f, 26.0f,
  
              // Batch 0, Channel 7
              17.0f, 18.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f,
  
              // Batch 1, Channel 3
              27.0f, 28.0f,
  
              // Batch 1, Channel 4
              29.0f, 30.0f,
  
              // Batch 1, Channel 5
              13.0f, 14.0f,
  
              // Batch 1, Channel 6
              15.0f, 16.0f,
  
              // Batch 1, Channel 7
              31.0f, 32.0f,
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 3> Concat3dDim2DiffInputDimsTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo input0TensorInfo({ 2, 3, 2 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 3>(input0TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f
          },
          qScale, qOffset));
  
      TensorInfo input1TensorInfo({ 2, 3, 1 }, ArmnnType, qScale, qOffset);
      auto input1 = MakeTensor<T, 3>(input1TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              7.0f,
  
              // Batch 0, Channel 1
              9.0f,
  
              // Batch 0, Channel 2
              11.0f,
  
              // Batch 1, Channel 0
              25.0f,
  
              // Batch 1, Channel 1
              27.0f,
  
              // Batch 1, Channel 2
              29.0f
          },
          qScale, qOffset));
  
      TensorInfo input2TensorInfo({ 2, 3, 3 }, ArmnnType, qScale, qOffset);
      auto input2 = MakeTensor<T, 3>(input2TensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              13.0f, 14.0f, 50.0f,
  
              // Batch 0, Channel 1
              15.0f, 16.0f, 51.0f,
  
              // Batch 0, Channel 2
              17.0f, 18.0f, 52.0f,
  
              // Batch 1, Channel 0
              31.0f, 32.0f, 53.0f,
  
              // Batch 1, Channel 1
              33.0f, 34.0f, 54.0f,
  
              // Batch 1, Channel 2
              35.0f, 36.0f, 55.0f,
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 2, 3, 6 }, ArmnnType, qScale, qOffset);
      LayerTestResult<T, 3> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory, memoryManager, tensorHandleFactory,
                     { input0TensorInfo, input1TensorInfo, input2TensorInfo },
                     { input0.data(), input1.data(), input2.data() },
                     outputTensorInfo,
                     output.data(),
                     2,
                     useSubtensor);
  
      result.output = MakeTensor<T, 3>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 3>(outputTensorInfo, QuantizedVector<T>(
          {
              // Batch 0, Channel 0
              1.0f, 2.0f, 7.0f, 13.0f, 14.0f, 50.0f,
  
              // Batch 0, Channel 1
              3.0f, 4.0f, 9.0f, 15.0f, 16.0f, 51.0f,
  
              // Batch 0, Channel 2
              5.0f, 6.0f, 11.0f, 17.0f, 18.0f, 52.0f,
  
              // Batch 1, Channel 0
              19.0f, 20.0f, 25.0f, 31.0f, 32.0f, 53.0f,
  
              // Batch 1, Channel 1
              21.0f, 22.0f, 27.0f, 33.0f, 34.0f, 54.0f,
  
              // Batch 1, Channel 2
              23.0f, 24.0f, 29.0f, 35.0f, 36.0f, 55.0f,
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dTestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      const TensorInfo& outputTensorInfo,
      unsigned int dimension,
      bool useSubtensor,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo inputTensorInfo({ 1, 3, 2, 2 }, ArmnnType, qScale, qOffset);
  
      auto input0 = MakeTensor<T, 4>(inputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f
          },
          qScale, qOffset));
  
      auto input1 = MakeTensor<T, 4>(inputTensorInfo, QuantizedVector<T>(
          {
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f
          },
          qScale, qOffset));
  
      auto input2 = MakeTensor<T, 4>(inputTensorInfo, QuantizedVector<T>(
          {
              21.0f, 22.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f,
              29.0f, 30.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      LayerTestResult<T, 4> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
  
      Concatenate<T>(workloadFactory,
                     memoryManager,
                     tensorHandleFactory,
                     {inputTensorInfo, inputTensorInfo, inputTensorInfo},
                     {input0.data(), input1.data(), input2.data()},
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     useSubtensor);
  
      result.output = MakeTensor<T, 4>(outputTensorInfo, output);
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDim0TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 3, 3, 2, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result = Concat4dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 0, true, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f,
  
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
  
              21.0f, 22.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f,
              29.0f, 30.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDim1TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 1, 9, 2, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result = Concat4dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 1, true, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f,
  
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
  
              21.0f, 22.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f,
              29.0f, 30.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDim2TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      TensorInfo outputTensorInfo({ 1, 3, 6, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result = Concat4dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 2, true, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
              11.0f, 12.0f,
              13.0f, 14.0f,
              21.0f, 22.0f,
              23.0f, 24.0f,
  
               5.0f,  6.0f,
               7.0f,  8.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              25.0f, 26.0f,
              27.0f, 28.0f,
  
               9.0f, 10.0f,
              11.0f, 12.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
              29.0f, 30.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDim3TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset,
      bool useSubtensor)
  {
      TensorInfo outputTensorInfo({ 1, 3, 2, 6 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result = Concat4dTestImpl<ArmnnType>(
          workloadFactory, memoryManager, tensorHandleFactory, outputTensorInfo, 3, useSubtensor, qScale, qOffset);
  
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
              11.0f, 12.0f,
              21.0f, 22.0f,
               3.0f,  4.0f,
              13.0f, 14.0f,
              23.0f, 24.0f,
  
               5.0f,  6.0f,
              15.0f, 16.0f,
              25.0f, 26.0f,
               7.0f,  8.0f,
              17.0f, 18.0f,
              27.0f, 28.0f,
  
               9.0f, 10.0f,
              19.0f, 20.0f,
              29.0f, 30.0f,
              11.0f, 12.0f,
              21.0f, 22.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDiffShapeDim0TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      constexpr unsigned int dimension = 0u;
  
      TensorInfo inputTensorInfo0({ 1, 3, 2, 2 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 4>(inputTensorInfo0, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f
          },
          qScale, qOffset));
  
      TensorInfo inputTensorInfo1({ 2, 3, 2, 2 }, ArmnnType, qScale, qOffset);
  
      auto input1 = MakeTensor<T, 4>(inputTensorInfo1, QuantizedVector<T>(
          {
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
  
              21.0f, 22.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f,
              29.0f, 30.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 3, 3, 2, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory,
                     memoryManager,
                     tensorHandleFactory,
                     {inputTensorInfo0, inputTensorInfo1},
                     {input0.data(), input1.data()},
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     true);
  
      result.output = MakeTensor<T, 4>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f, 2.0f,
               3.0f, 4.0f,
               5.0f, 6.0f,
               7.0f, 8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f,
  
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
  
              21.0f, 22.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f,
              29.0f, 30.0f,
              31.0f, 32.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDiffShapeDim1TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      constexpr unsigned int dimension = 1u;
  
      TensorInfo inputTensorInfo0({ 1, 3, 2, 2 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 4>(inputTensorInfo0, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f
          },
          qScale, qOffset));
  
      TensorInfo inputTensorInfo1({ 1, 2, 2, 2 }, ArmnnType, qScale, qOffset);
  
      auto input1 = MakeTensor<T, 4>(inputTensorInfo1, QuantizedVector<T>(
          {
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 1, 5, 2, 2 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory,
                     memoryManager,
                     tensorHandleFactory,
                     {inputTensorInfo0, inputTensorInfo1},
                     {input0.data(), input1.data()},
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     true);
  
      result.output = MakeTensor<T, 4>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f,
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDiffShapeDim2TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset)
  {
      constexpr unsigned int dimension = 2u;
  
      TensorInfo inputTensorInfo0({ 1, 3, 2, 2 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 4>(inputTensorInfo0, QuantizedVector<T>(
          {
               1.0f, 2.0f,
               3.0f, 4.0f,
               5.0f, 6.0f,
               7.0f, 8.0f,
              9.0f, 10.0f,
              11.0f, 12.0f
          },
          qScale, qOffset));
  
      TensorInfo inputTensorInfo1({ 1, 3, 3, 2 }, ArmnnType, qScale, qOffset);
      auto input1 = MakeTensor<T, 4>(inputTensorInfo1, QuantizedVector<T>(
          {
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 1, 3, 5, 2 }, ArmnnType, qScale, qOffset);
      LayerTestResult<T, 4> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory,
                     memoryManager,
                     tensorHandleFactory,
                     {inputTensorInfo0, inputTensorInfo1},
                     {input0.data(), input1.data()},
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     true);
  
      result.output         = MakeTensor<T, 4>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
              11.0f, 12.0f,
              13.0f, 14.0f,
              15.0f, 16.0f,
  
               5.0f,  6.0f,
               7.0f,  8.0f,
              17.0f, 18.0f,
              19.0f, 20.0f,
              21.0f, 22.0f,
  
               9.0f, 10.0f,
              11.0f, 12.0f,
              23.0f, 24.0f,
              25.0f, 26.0f,
              27.0f, 28.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T = ResolveType<ArmnnType>>
  LayerTestResult<T, 4> Concat4dDiffShapeDim3TestImpl(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      float qScale,
      int32_t qOffset,
      bool useSubtensor)
  {
      constexpr unsigned int dimension = 3u;
  
      TensorInfo inputTensorInfo0({ 1, 3, 2, 2 }, ArmnnType, qScale, qOffset);
      auto input0 = MakeTensor<T, 4>(inputTensorInfo0, QuantizedVector<T>(
          {
               1.0f,  2.0f,
               3.0f,  4.0f,
               5.0f,  6.0f,
               7.0f,  8.0f,
               9.0f, 10.0f,
              11.0f, 12.0f
          },
          qScale, qOffset));
  
      TensorInfo inputTensorInfo1({ 1, 3, 2, 3 }, ArmnnType, qScale, qOffset);
      auto input1 = MakeTensor<T, 4>(inputTensorInfo1, QuantizedVector<T>(
          {
              11.0f, 12.0f, 13.0f,
              14.0f, 15.0f, 16.0f,
  
              17.0f, 18.0f, 19.0f,
              20.0f, 21.0f, 22.0f,
  
              23.0f, 24.0f, 25.0f,
              26.0f, 27.0f, 28.0f
          },
          qScale, qOffset));
  
      TensorInfo outputTensorInfo({ 1, 3, 2, 5 }, ArmnnType, qScale, qOffset);
  
      LayerTestResult<T, 4> result(outputTensorInfo);
  
      std::vector<T> output;
      output.resize(outputTensorInfo.GetNumElements());
      Concatenate<T>(workloadFactory,
                     memoryManager,
                     tensorHandleFactory,
                     {inputTensorInfo0, inputTensorInfo1},
                     {input0.data(), input1.data()},
                     outputTensorInfo,
                     output.data(),
                     dimension,
                     useSubtensor);
  
      result.output = MakeTensor<T, 4>(outputTensorInfo, output);
      result.outputExpected = MakeTensor<T, 4>(outputTensorInfo, QuantizedVector<T>(
          {
              1.0f, 2.0f, 11.0f, 12.0f, 13.0f,
              3.0f, 4.0f, 14.0f, 15.0f, 16.0f,
              5.0f, 6.0f, 17.0f, 18.0f, 19.0f,
              7.0f, 8.0f, 20.0f, 21.0f, 22.0f,
              9.0f, 10.0f, 23.0f, 24.0f, 25.0f,
              11.0f, 12.0f, 26.0f, 27.0f, 28.0f
          },
          qScale, qOffset));
  
      return result;
  }
  
  template<DataType ArmnnType, typename T>
  LayerTestResult<T, 3> ConcatDifferentInputOutputQParamTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      IgnoreUnused(memoryManager);
  
      // Defines the tensor descriptors.
      TensorInfo outputTensorInfo({ 3, 6, 3 }, ArmnnType);
      TensorInfo inputTensorInfo1({ 3, 6, 2 }, ArmnnType);
      TensorInfo inputTensorInfo2({ 3, 6, 1 }, ArmnnType);
  
      std::vector<TensorShape> inputTensorShapes({inputTensorInfo1.GetShape(), inputTensorInfo2.GetShape()});
  
      // Quantized input1 tensor.
      const float inputScale1 = 0.5f;
      const int32_t inputOffset1 = 5;
  
      auto input1 = MakeTensor<T, 3>(inputTensorInfo1, std::vector<T>(
      {
          1, 2, 3,
          4, 5, 6,
          7, 8, 9,
          10, 11, 12,
          13, 14, 15,
          16, 17, 18,
  
          19, 20, 21,
          22, 23, 24,
          25, 26, 27,
          28, 29, 30,
          31, 32, 33,
          34, 35, 36
      }));
  
      // Quatized input2 tensor.
      const float inputScale2 = 0.2f;
      const int32_t inputOffset2 = 10;
  
      auto input2 = MakeTensor<T, 3>(inputTensorInfo2, std::vector<T>(
      {
          37, 38, 39,
          40, 41, 42,
          43, 44, 45,
          46, 47, 48,
          49, 50, 51,
          52, 53, 54
      }));
  
      // Quantized output tensor.
      const float outputScale = 0.1f;
      const int32_t outputOffset = 20;
  
      LayerTestResult<T, 3> ret(outputTensorInfo);
  
      ret.outputExpected = MakeTensor<T, 3>(outputTensorInfo, std::vector<T>(
      {
          0,   5,  74,
          10,  15,  76,
          20,  25,  78,
          30,  35,  80,
          40,  45,  82,
          50,  55,  84,
  
          60,  65,  86,
          70,  75,  88,
          80,  85,  90,
          90,  95,  92,
          100, 105,  94,
          110, 115,  96,
  
          120, 125,  98,
          130, 135, 100,
          140, 145, 102,
          150, 155, 104,
          160, 165, 106,
          170, 175, 108
      }));
  
      outputTensorInfo.SetQuantizationScale(outputScale);
      outputTensorInfo.SetQuantizationOffset(outputOffset);
      inputTensorInfo1.SetQuantizationScale(inputScale1);
      inputTensorInfo1.SetQuantizationOffset(inputOffset1);
      inputTensorInfo2.SetQuantizationScale(inputScale2);
      inputTensorInfo2.SetQuantizationOffset(inputOffset2);
  
      std::vector<unsigned int> wOrigin1 = { 0, 0, 0 }; //Extent of the window is defined by size of input[0].
      ConcatQueueDescriptor::ViewOrigin window1(wOrigin1);
  
      std::vector<unsigned int> wOrigin2 = { 0, 0, 2 }; //Extent of the window is defined by size of input[1].
      ConcatQueueDescriptor::ViewOrigin window2(wOrigin2);
  
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      bool subTensorsSupported = useSubtensor && workloadFactory.SupportsSubTensors();
  
      std::unique_ptr<ITensorHandle> inputHandle1 =
              subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo1.GetShape(), wOrigin1.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo1);
  
      std::unique_ptr<ITensorHandle> inputHandle2 =
              subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo2.GetShape(), wOrigin2.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo2);
  
      ConcatQueueDescriptor data;
      OriginsDescriptor desc = CreateDescriptorForConcatenation(
              inputTensorShapes.begin(),inputTensorShapes.end(), 2);
      data.m_Parameters = desc;
  
      WorkloadInfo info;
      AddInputToWorkload(data, info, inputTensorInfo1, inputHandle1.get());
      AddInputToWorkload(data, info, inputTensorInfo2, inputHandle2.get());
      AddOutputToWorkload(data, info, outputTensorInfo, outputHandle.get());
  
      data.m_ViewOrigins.push_back(window1);
      data.m_ViewOrigins.push_back(window2);
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreateConcat(data, info);
  
      inputHandle1->Allocate();
      inputHandle2->Allocate();
      outputHandle->Allocate();
  
      CopyDataToITensorHandle(inputHandle1.get(), &input1[0][0][0]);
      CopyDataToITensorHandle(inputHandle2.get(), &input2[0][0][0]);
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      CopyDataFromITensorHandle(&ret.output[0][0][0], outputHandle.get());
  
      return ret;
  }
  
  //
  // Explicit template specializations
  //
  
  template LayerTestResult<ResolveType<DataType::QAsymmU8>, 3>
  ConcatDifferentInputOutputQParamTest<DataType::QAsymmU8>(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor);
  
  template LayerTestResult<ResolveType<DataType::QSymmS16>, 3>
  ConcatDifferentInputOutputQParamTest<DataType::QSymmS16>(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor);
  
  //
  // Implementation functions
  //
  
  LayerTestResult<float,3> ConcatTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      IgnoreUnused(memoryManager);
  
      unsigned int outputWidth = 3;
      unsigned int outputHeight = 6;
      unsigned int outputChannels = 3;
  
      unsigned int inputWidth1 = 3;
      unsigned int inputHeight1 = 6;
      unsigned int inputChannels1 = 2;
  
      unsigned int inputWidth2 = 3;
      unsigned int inputHeight2 = 6;
      unsigned int inputChannels2 = 1;
  
      // Define the tensor descriptors.
      TensorInfo outputTensorInfo({ outputChannels, outputHeight, outputWidth }, DataType::Float32);
      TensorInfo inputTensorInfo1({ inputChannels1, inputHeight1, inputWidth1 }, DataType::Float32);
      TensorInfo inputTensorInfo2({ inputChannels2, inputHeight2, inputWidth2 }, DataType::Float32);
  
      LayerTestResult<float,3> ret(outputTensorInfo);
  
      ret.outputExpected = MakeTensor<float, 3>(outputTensorInfo, std::vector<float>(
      {
              1.0f, 2.0f, 3.0f,
              4.0f, 5.0f, 6.0f,
              7.0f, 8.0f, 9.0f,
              10.0f, 11.0f, 12.0f,
              13.0f, 14.0f, 15.0f,
              16.0f, 17.0f, 18.0f,
  
              19.0f, 20.0f, 21.0f,
              22.0f, 23.0f, 24.0f,
              25.0f, 26.0f, 27.0f,
              28.0f, 29.0f, 30.0f,
              31.0f, 32.0f, 33.0f,
              34.0f, 35.0f, 36.0f,
  
              37.0f, 38.0f, 39.0f,
              40.0f, 41.0f, 42.0f,
              43.0f, 44.0f, 45.0f,
              46.0f, 47.0f, 48.0f,
              49.0f, 50.0f, 51.0f,
              52.0f, 53.0f, 54.0f,
          })
      );
  
      auto input1 = MakeTensor<float, 3>(inputTensorInfo1, std::vector<float>(
          {
              1.0f, 2.0f, 3.0f,
              4.0f, 5.0f, 6.0f,
              7.0f, 8.0f, 9.0f,
              10.0f, 11.0f, 12.0f,
              13.0f, 14.0f, 15.0f,
              16.0f, 17.0f, 18.0f,
  
              19.0f, 20.0f, 21.0f,
              22.0f, 23.0f, 24.0f,
              25.0f, 26.0f, 27.0f,
              28.0f, 29.0f, 30.0f,
              31.0f, 32.0f, 33.0f,
              34.0f, 35.0f, 36.0f,
          })
      );
  
      auto input2 = MakeTensor<float, 3>(inputTensorInfo2, std::vector<float>(
          {
              37.0f, 38.0f, 39.0f,
              40.0f, 41.0f, 42.0f,
              43.0f, 44.0f, 45.0f,
              46.0f, 47.0f, 48.0f,
              49.0f, 50.0f, 51.0f,
              52.0f, 53.0f, 54.0f,
          })
      );
  
      std::vector<unsigned int> wOrigin1 = {0, 0, 0}; //Extent of the window is defined by size of input[0].
      ConcatQueueDescriptor::ViewOrigin window1(wOrigin1);
  
      std::vector<unsigned int> wOrigin2 = {2, 0, 0}; //Extent of the window is defined by size of input[1].
      ConcatQueueDescriptor::ViewOrigin window2(wOrigin2);
  
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      bool subTensorsSupported = workloadFactory.SupportsSubTensors();
  
      std::unique_ptr<ITensorHandle> inputHandle1 =
          subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo1.GetShape(), wOrigin1.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo1);
  
      std::unique_ptr<ITensorHandle> inputHandle2  =
          subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo2.GetShape(), wOrigin2.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo2);
  
      ConcatQueueDescriptor data;
      WorkloadInfo info;
      AddInputToWorkload(data, info, inputTensorInfo1, inputHandle1.get());
      AddInputToWorkload(data, info, inputTensorInfo2, inputHandle2.get());
      AddOutputToWorkload(data, info, outputTensorInfo, outputHandle.get());
  
      data.m_ViewOrigins.push_back(window1);
      data.m_ViewOrigins.push_back(window2);
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreateConcat(data, info);
  
      inputHandle1->Allocate();
      inputHandle2->Allocate();
      outputHandle->Allocate();
  
      CopyDataToITensorHandle(inputHandle1.get(), &input1[0][0][0]);
      CopyDataToITensorHandle(inputHandle2.get(), &input2[0][0][0]);
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      CopyDataFromITensorHandle(&ret.output[0][0][0], outputHandle.get());
  
      return ret;
  }
  
  LayerTestResult<float, 1> Concat1dTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat1dTestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 2> Concat2dDim0Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim0TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 2> Concat2dDim1Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim1TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 2> Concat2dDim0DiffInputDimsTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim0DiffInputDimsTestImpl<DataType::Float32>(workloadFactory, memoryManager,
                                                                  tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 2> Concat2dDim1DiffInputDimsTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim1DiffInputDimsTestImpl<DataType::Float32>(workloadFactory,
                                                                  memoryManager,
                                                                  tensorHandleFactory,
                                                                  0.0f,
                                                                  0);
  }
  
  LayerTestResult<float, 3> Concat3dDim0Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim0TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 3> Concat3dDim1Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim1TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 3> Concat3dDim2Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat3dDim2TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory,
                                                     useSubtensor, 0.0f, 0);
  }
  
  LayerTestResult<float, 3> Concat3dDim0DiffInputDimsTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim0DiffInputDimsTestImpl<DataType::Float32>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 3> Concat3dDim1DiffInputDimsTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim1DiffInputDimsTestImpl<DataType::Float32>(workloadFactory, memoryManager,
                                                                  tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 3> Concat3dDim2DiffInputDimsTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat3dDim2DiffInputDimsTestImpl<DataType::Float32>(
          workloadFactory, memoryManager, tensorHandleFactory, useSubtensor, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDim0Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDim0TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDim1Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDim1TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDim2Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDim2TestImpl<DataType::Float32>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDim3Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat4dDim3TestImpl<DataType::Float32>(workloadFactory, memoryManager,
                                                     tensorHandleFactory, 0.0f, 0, useSubtensor);
  }
  
  LayerTestResult<float, 4> Concat4dDiffShapeDim0Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDiffShapeDim0TestImpl<DataType::Float32>(workloadFactory, memoryManager,
                                                              tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDiffShapeDim1Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDiffShapeDim1TestImpl<DataType::Float32>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDiffShapeDim2Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDiffShapeDim2TestImpl<DataType::Float32>(workloadFactory, memoryManager,
                                                              tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<float, 4> Concat4dDiffShapeDim3Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat4dDiffShapeDim3TestImpl<DataType::Float32>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0, useSubtensor);
  }
  
  LayerTestResult<Half, 3> ConcatFloat16Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim1TestImpl<DataType::Float16>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<BFloat16, 3> ConcatBFloat16Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim1TestImpl<DataType::BFloat16>(workloadFactory, memoryManager, tensorHandleFactory, 0.0f, 0);
  }
  
  LayerTestResult<uint8_t, 3> ConcatUint8DifferentQParamsTest(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      IgnoreUnused(memoryManager);
  
      unsigned int outputWidth = 3;
      unsigned int outputHeight = 6;
      unsigned int outputChannels = 3;
  
      unsigned int inputWidth1 = 3;
      unsigned int inputHeight1 = 6;
      unsigned int inputChannels1 = 2;
  
      unsigned int inputWidth2 = 3;
      unsigned int inputHeight2 = 6;
      unsigned int inputChannels2 = 1;
  
      // Defines the tensor descriptors.
      TensorInfo outputTensorInfo({ outputChannels, outputHeight, outputWidth }, DataType::QAsymmU8);
      TensorInfo inputTensorInfo1({ inputChannels1, inputHeight1, inputWidth1 }, DataType::QAsymmU8);
      TensorInfo inputTensorInfo2({ inputChannels2, inputHeight2, inputWidth2 }, DataType::QAsymmU8);
  
      // Quantized input1 tensor. Range [-3, 1]
      const float inputScale1 = 0.015686f;
      const int32_t inputOffset1 = 192;
  
      auto input1 = MakeTensor<uint8_t, 3>(inputTensorInfo1, std::vector<uint8_t>(
      {
          1, 2, 3,
          4, 5, 6,
          7, 8, 9,
          10, 11, 12,
          13, 14, 15,
          16, 17, 18,
  
          19, 20, 21,
          22, 23, 24,
          25, 26, 27,
          28, 29, 30,
          31, 32, 33,
          34, 35, 36,
      })
      );
  
      // Quatized input2 tensor. Range [-1, 4]
      const float inputScale2 = 0.019608f;
      const int32_t inputOffset2 = 50;
  
      auto input2 = MakeTensor<uint8_t, 3>(inputTensorInfo2, std::vector<uint8_t>(
      {
          37, 38, 39,
          40, 41, 42,
          43, 44, 45,
          46, 47, 48,
          49, 50, 51,
          52, 53, 54,
      })
      );
  
      // Output has the same quantization parameters than input1,
      // so that only the requantization of input2 is required
      const float outputScale = 0.015686f;
      const int32_t outputOffset = 192;
  
      LayerTestResult<uint8_t, 3> ret(outputTensorInfo);
  
      ret.outputExpected = MakeTensor<uint8_t, 3>(outputTensorInfo, std::vector<uint8_t>(
      {
          1, 2, 3,
          4, 5, 6,
          7, 8, 9,
          10, 11, 12,
          13, 14, 15,
          16, 17, 18,
  
          19, 20, 21,
          22, 23, 24,
          25, 26, 27,
          28, 29, 30,
          31, 32, 33,
          34, 35, 36,
  
          176, 177, 178,
          179, 181, 182,
          183, 184, 186,
          187, 188, 189,
          191, 192, 193,
          195, 196, 197,
      })
      );
  
      outputTensorInfo.SetQuantizationScale(outputScale);
      outputTensorInfo.SetQuantizationOffset(outputOffset);
      inputTensorInfo1.SetQuantizationScale(inputScale1);
      inputTensorInfo1.SetQuantizationOffset(inputOffset1);
      inputTensorInfo2.SetQuantizationScale(inputScale2);
      inputTensorInfo2.SetQuantizationOffset(inputOffset2);
  
      std::vector<unsigned int> wOrigin1 = { 0, 0, 0 }; //Extent of the window is defined by size of input[0].
      ConcatQueueDescriptor::ViewOrigin window1(wOrigin1);
  
      std::vector<unsigned int> wOrigin2 = { 2, 0, 0 }; //Extent of the window is defined by size of input[1].
      ConcatQueueDescriptor::ViewOrigin window2(wOrigin2);
  
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      bool subTensorsSupported = workloadFactory.SupportsSubTensors();
  
      std::unique_ptr<ITensorHandle> inputHandle1 =
              subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo1.GetShape(), wOrigin1.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo1);
  
      std::unique_ptr<ITensorHandle> inputHandle2 =
              subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo2.GetShape(), wOrigin2.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo2);
  
      ConcatQueueDescriptor data;
      WorkloadInfo info;
      AddInputToWorkload(data, info, inputTensorInfo1, inputHandle1.get());
      AddInputToWorkload(data, info, inputTensorInfo2, inputHandle2.get());
      AddOutputToWorkload(data, info, outputTensorInfo, outputHandle.get());
  
      data.m_ViewOrigins.push_back(window1);
      data.m_ViewOrigins.push_back(window2);
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreateConcat(data, info);
  
      inputHandle1->Allocate();
      inputHandle2->Allocate();
      outputHandle->Allocate();
  
      CopyDataToITensorHandle(inputHandle1.get(), &input1[0][0][0]);
      CopyDataToITensorHandle(inputHandle2.get(), &input2[0][0][0]);
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      CopyDataFromITensorHandle(&ret.output[0][0][0], outputHandle.get());
  
      return ret;
  }
  
  LayerTestResult<uint8_t, 3> ConcatUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      IgnoreUnused(memoryManager);
  
      unsigned int outputWidth = 3;
      unsigned int outputHeight = 6;
      unsigned int outputChannels = 3;
  
      unsigned int inputWidth1 = 3;
      unsigned int inputHeight1 = 6;
      unsigned int inputChannels1 = 2;
  
      unsigned int inputWidth2 = 3;
      unsigned int inputHeight2 = 6;
      unsigned int inputChannels2 = 1;
  
      // Defines the tensor descriptors.
      TensorInfo outputTensorInfo({ outputChannels, outputHeight, outputWidth }, DataType::QAsymmU8);
      TensorInfo inputTensorInfo1({ inputChannels1, inputHeight1, inputWidth1 }, DataType::QAsymmU8);
      TensorInfo inputTensorInfo2({ inputChannels2, inputHeight2, inputWidth2 }, DataType::QAsymmU8);
  
      // Arbitrary scale and offsets. They don't really matter as the Concat operator doesn't dequantize/quantize them.
      const float scale = 0.13497836f;
      const int32_t offset = -7;
  
      outputTensorInfo.SetQuantizationScale(scale);
      outputTensorInfo.SetQuantizationOffset(offset);
      inputTensorInfo1.SetQuantizationScale(scale);
      inputTensorInfo1.SetQuantizationOffset(offset);
      inputTensorInfo2.SetQuantizationScale(scale);
      inputTensorInfo2.SetQuantizationOffset(offset);
  
      LayerTestResult<uint8_t, 3> ret(outputTensorInfo);
  
      ret.outputExpected = MakeTensor<uint8_t, 3>(outputTensorInfo, std::vector<uint8_t>(
          {
              1, 2, 3,
              4, 5, 6,
              7, 8, 9,
              10, 11, 12,
              13, 14, 15,
              16, 17, 18,
  
              19, 20, 21,
              22, 23, 24,
              25, 26, 27,
              28, 29, 30,
              31, 32, 33,
              34, 35, 36,
  
              37, 38, 39,
              40, 41, 42,
              43, 44, 45,
              46, 47, 48,
              49, 50, 51,
              52, 53, 54,
          })
      );
  
      auto input1 = MakeTensor<uint8_t, 3>(inputTensorInfo1, std::vector<uint8_t>(
      {
          1, 2, 3,
          4, 5, 6,
          7, 8, 9,
          10, 11, 12,
          13, 14, 15,
          16, 17, 18,
  
          19, 20, 21,
          22, 23, 24,
          25, 26, 27,
          28, 29, 30,
          31, 32, 33,
          34, 35, 36,
      })
      );
  
      auto input2 = MakeTensor<uint8_t, 3>(inputTensorInfo2, std::vector<uint8_t>(
      {
          37, 38, 39,
          40, 41, 42,
          43, 44, 45,
          46, 47, 48,
          49, 50, 51,
          52, 53, 54,
      })
      );
  
      std::vector<unsigned int> wOrigin1 = { 0, 0, 0 }; //Extent of the window is defined by size of input[0].
      ConcatQueueDescriptor::ViewOrigin window1(wOrigin1);
  
      std::vector<unsigned int> wOrigin2 = { 2, 0, 0 }; //Extent of the window is defined by size of input[1].
      ConcatQueueDescriptor::ViewOrigin window2(wOrigin2);
  
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      bool subTensorsSupported = workloadFactory.SupportsSubTensors();
  
      std::unique_ptr<ITensorHandle> inputHandle1 =
          subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo1.GetShape(), wOrigin1.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo1);
  
      std::unique_ptr<ITensorHandle> inputHandle2 =
          subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo2.GetShape(), wOrigin2.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo2);
  
  
      ConcatQueueDescriptor data;
      WorkloadInfo info;
      AddInputToWorkload(data, info, inputTensorInfo1, inputHandle1.get());
      AddInputToWorkload(data, info, inputTensorInfo2, inputHandle2.get());
      AddOutputToWorkload(data, info, outputTensorInfo, outputHandle.get());
  
      data.m_ViewOrigins.push_back(window1);
      data.m_ViewOrigins.push_back(window2);
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreateConcat(data, info);
  
      inputHandle1->Allocate();
      inputHandle2->Allocate();
      outputHandle->Allocate();
  
      CopyDataToITensorHandle(inputHandle1.get(), &input1[0][0][0]);
      CopyDataToITensorHandle(inputHandle2.get(), &input2[0][0][0]);
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      CopyDataFromITensorHandle(&ret.output[0][0][0], outputHandle.get());
  
      return ret;
  }
  
  LayerTestResult<uint16_t, 3> ConcatUint16Test(
          IWorkloadFactory& workloadFactory,
          const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
          const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      IgnoreUnused(memoryManager);
  
      unsigned int outputWidth = 3;
      unsigned int outputHeight = 6;
      unsigned int outputChannels = 3;
  
      unsigned int inputWidth1 = 3;
      unsigned int inputHeight1 = 6;
      unsigned int inputChannels1 = 2;
  
      unsigned int inputWidth2 = 3;
      unsigned int inputHeight2 = 6;
      unsigned int inputChannels2 = 1;
  
      // Defines the tensor descriptors.
      TensorInfo outputTensorInfo({ outputChannels, outputHeight, outputWidth }, DataType::QSymmS16);
      TensorInfo inputTensorInfo1({ inputChannels1, inputHeight1, inputWidth1 }, DataType::QSymmS16);
      TensorInfo inputTensorInfo2({ inputChannels2, inputHeight2, inputWidth2 }, DataType::QSymmS16);
  
      // Arbitrary scale and offsets. They don't really matter as the Concat operator doesn't dequantize/quantize them.
      const float scale = 0.13497836f;
      const int32_t offset = -7;
  
      outputTensorInfo.SetQuantizationScale(scale);
      outputTensorInfo.SetQuantizationOffset(offset);
      inputTensorInfo1.SetQuantizationScale(scale);
      inputTensorInfo1.SetQuantizationOffset(offset);
      inputTensorInfo2.SetQuantizationScale(scale);
      inputTensorInfo2.SetQuantizationOffset(offset);
  
      LayerTestResult<uint16_t, 3> ret(outputTensorInfo);
  
      ret.outputExpected = MakeTensor<uint16_t, 3>(outputTensorInfo, std::vector<uint16_t>(
      {
          1, 2, 3,
          4, 5, 6,
          7, 8, 9,
          10, 11, 12,
          13, 14, 15,
          16, 17, 18,
  
          19, 20, 21,
          22, 23, 24,
          25, 26, 27,
          28, 29, 30,
          31, 32, 33,
          34, 35, 36,
  
          37, 38, 39,
          40, 41, 42,
          43, 44, 45,
          46, 47, 48,
          49, 50, 51,
          52, 53, 54,
      }));
  
      auto input1 = MakeTensor<uint16_t, 3>(inputTensorInfo1, std::vector<uint16_t>(
      {
          1, 2, 3,
          4, 5, 6,
          7, 8, 9,
          10, 11, 12,
          13, 14, 15,
          16, 17, 18,
  
          19, 20, 21,
          22, 23, 24,
          25, 26, 27,
          28, 29, 30,
          31, 32, 33,
          34, 35, 36,
      }));
  
      auto input2 = MakeTensor<uint16_t, 3>(inputTensorInfo2, std::vector<uint16_t>(
      {
          37, 38, 39,
          40, 41, 42,
          43, 44, 45,
          46, 47, 48,
          49, 50, 51,
          52, 53, 54,
      }));
  
      std::vector<unsigned int> wOrigin1 = { 0, 0, 0 }; //Extent of the window is defined by size of input[0].
      ConcatQueueDescriptor::ViewOrigin window1(wOrigin1);
  
      std::vector<unsigned int> wOrigin2 = { 2, 0, 0 }; //Extent of the window is defined by size of input[1].
      ConcatQueueDescriptor::ViewOrigin window2(wOrigin2);
  
  
      std::unique_ptr<ITensorHandle> outputHandle = tensorHandleFactory.CreateTensorHandle(outputTensorInfo);
  
      bool subTensorsSupported = workloadFactory.SupportsSubTensors();
  
      std::unique_ptr<ITensorHandle> inputHandle1 =
              subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo1.GetShape(), wOrigin1.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo1);
  
      std::unique_ptr<ITensorHandle> inputHandle2 =
              subTensorsSupported ?
              tensorHandleFactory.CreateSubTensorHandle(*outputHandle, inputTensorInfo2.GetShape(), wOrigin2.data()) :
              tensorHandleFactory.CreateTensorHandle(inputTensorInfo2);
      
  
      ConcatQueueDescriptor data;
      WorkloadInfo info;
      AddInputToWorkload(data, info, inputTensorInfo1, inputHandle1.get());
      AddInputToWorkload(data, info, inputTensorInfo2, inputHandle2.get());
      AddOutputToWorkload(data, info, outputTensorInfo, outputHandle.get());
  
      data.m_ViewOrigins.push_back(window1);
      data.m_ViewOrigins.push_back(window2);
  
      std::unique_ptr<IWorkload> workload = workloadFactory.CreateConcat(data, info);
  
      inputHandle1->Allocate();
      inputHandle2->Allocate();
      outputHandle->Allocate();
  
      CopyDataToITensorHandle(inputHandle1.get(), &input1[0][0][0]);
      CopyDataToITensorHandle(inputHandle2.get(), &input2[0][0][0]);
  
      workload->PostAllocationConfigure();
      workload->Execute();
  
      CopyDataFromITensorHandle(&ret.output[0][0][0], outputHandle.get());
  
      return ret;
  }
  
  LayerTestResult<uint8_t, 1> Concat1dUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat1dTestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 2> Concat2dDim0Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim0TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 2> Concat2dDim1Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim1TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 2> Concat2dDim0DiffInputDimsUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim0DiffInputDimsTestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 2> Concat2dDim1DiffInputDimsUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat2dDim1DiffInputDimsTestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 3> Concat3dDim0Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim0TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 3> Concat3dDim1Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim1TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 3> Concat3dDim2Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat3dDim2TestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, useSubtensor, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 3> Concat3dDim0DiffInputDimsUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim0TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 3> Concat3dDim1DiffInputDimsUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat3dDim1DiffInputDimsTestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 3> Concat3dDim2DiffInputDimsUint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat3dDim2DiffInputDimsTestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, useSubtensor, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDim0Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDim0TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDim1Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDim1TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDim2Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDim2TestImpl<DataType::QAsymmU8>(workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDim3Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory, bool useSubtensor)
  {
      return Concat4dDim3TestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1, useSubtensor);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDiffShapeDim0Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDiffShapeDim0TestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDiffShapeDim1Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDiffShapeDim1TestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDiffShapeDim2Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory)
  {
      return Concat4dDiffShapeDim2TestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1);
  }
  
  LayerTestResult<uint8_t, 4> Concat4dDiffShapeDim3Uint8Test(
      IWorkloadFactory& workloadFactory,
      const IBackendInternal::IMemoryManagerSharedPtr& memoryManager,
      const armnn::ITensorHandleFactory& tensorHandleFactory,
      bool useSubtensor)
  {
      return Concat4dDiffShapeDim3TestImpl<DataType::QAsymmU8>(
          workloadFactory, memoryManager, tensorHandleFactory, 0.5f, -1, useSubtensor);
  }
  