text: //
  // Copyright © 2017 Arm Ltd. All rights reserved.
  // SPDX-License-Identifier: MIT
  //
  
  #include <backendsCommon/TensorHandle.hpp>
  #include <backendsCommon/WorkloadData.hpp>
  #include <armnnUtils/DataLayoutIndexed.hpp>
  #include <armnnUtils/TensorUtils.hpp>
  #include <armnn/utility/NumericCast.hpp>
  #include <armnn/Logging.hpp>
  
  #include <algorithm>
  #include <iomanip>
  #include <string>
  #include <sstream>
  
  #include <fmt/format.h>
  
  using namespace armnnUtils;
  
  namespace armnn
  {
  
  //---------------------------------------------------------------
  DataType GetBiasDataType(DataType inputDataType)
  {
      switch (inputDataType)
      {
          case DataType::Float16:
              return DataType::Float16;
          case DataType::BFloat16:
          case DataType::Float32:
              return DataType::Float32;
          case DataType::QAsymmS8:
              return DataType::Signed32;
          case DataType::QAsymmU8:
              return DataType::Signed32;
          case DataType::QSymmS8:
              return DataType::Signed32;
          case DataType::QSymmS16:
              return DataType::Signed32;
          default:
              ARMNN_ASSERT_MSG(false, "Invalid input data type");
              return DataType::Float32;
      }
  }
  
  namespace
  {
  
  //---------------------------------------------------------------
  //android ndk does not support std::to_string function.
  template <typename T>
  std::string to_string(T value)
  {
      std::ostringstream os;
      os << value;
      return os.str();
  }
  
  //---------------------------------------------------------------
  void ValidatePointer(const void* ptr, std::string const& descName, std::string const& paramName)
  {
      if (!ptr)
      {
          throw InvalidArgumentException(descName +  ": Invalid null pointer. The " +
                                        paramName + " parameter must be set.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensorShapesMatch(const TensorInfo& first,
                                 const TensorInfo& second,
                                 std::string const& descName,
                                 std::string const& firstName,
                                 std::string const& secondName)
  {
      if (first.GetShape() != second.GetShape())
      {
          throw InvalidArgumentException(descName + ": "
                                         + firstName + " & " + secondName + " must have identical shapes");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateNumInputs(const WorkloadInfo& workloadInfo, std::string const& descName, const unsigned int expectedSize)
  {
      if (workloadInfo.m_InputTensorInfos.size() != expectedSize)
      {
          throw InvalidArgumentException(descName +
                                         ": Requires exactly " + to_string(expectedSize) + "input(s). " +
                                         to_string(workloadInfo.m_InputTensorInfos.size()) + " have been provided.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateNumOutputs(const WorkloadInfo& workloadInfo, std::string const& descName, const unsigned int expectedSize)
  {
      if (workloadInfo.m_OutputTensorInfos.size() != expectedSize)
      {
          throw InvalidArgumentException(descName +
                                         ": Requires exactly " + to_string(expectedSize) + " output(s). " +
                                         to_string(workloadInfo.m_OutputTensorInfos.size()) + " has been provided.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensorNumDimensions(const TensorInfo& tensor,
                                   std::string const& descName,
                                   unsigned int numDimensions,
                                   std::string const& tensorName)
  {
      if (tensor.GetNumDimensions() != numDimensions)
      {
          throw InvalidArgumentException(descName + ": Expected " + to_string(numDimensions) + " but got " +
              to_string(tensor.GetNumDimensions()) + " dimensions for " +
              tensorName + " tensor.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensorNumElements(const TensorInfo& tensor,
                                 std::string const& descName,
                                 unsigned int numElements,
                                 std::string const& tensorName)
  {
      if (tensor.GetNumElements() != numElements)
      {
          throw InvalidArgumentException(descName + ": Expected " + to_string(numElements) + " but got " +
                                         to_string(tensor.GetNumElements()) + " elements for " +
                                         tensorName + " tensor.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensorNumDimNumElem(const TensorInfo& tensorInfo,
                                   unsigned int numDimension,
                                   unsigned int numElements,
                                   std::string const& tensorName)
  {
      const std::string functionName{"ValidateTensorNumDimNumElem"};
      ValidateTensorNumDimensions(tensorInfo, functionName, numDimension, tensorName);
      ValidateTensorNumElements(tensorInfo, functionName, numElements, tensorName);
  }
  
  //---------------------------------------------------------------
  void ValidateTensorDataType(const TensorInfo& tensor, DataType dataType,
      const std::string& descName, std::string const& tensorName)
  {
      if (tensor.GetDataType() != dataType)
      {
          throw InvalidArgumentException(descName + ": Expected data type " + GetDataTypeName(dataType) + " but got " +
              GetDataTypeName(tensor.GetDataType()) + " for " + tensorName + " tensor.");
      }
  }
  
  void ValidPerAxisQuantizedDataType(const TensorInfo& tensor, const std::string& descName, const std::string& tensorName)
  {
      ARMNN_NO_DEPRECATE_WARN_BEGIN
      if (tensor.GetDataType() != DataType::QSymmS8 &&
          tensor.GetDataType() != DataType::QuantizedSymm8PerAxis)
      {
          throw InvalidArgumentException(descName +
              ": Expected data type which supports per-axis quantization scheme but got " +
              GetDataTypeName(tensor.GetDataType()) + " for " + tensorName + " tensor.");
      }
      ARMNN_NO_DEPRECATE_WARN_END
  }
  
  //---------------------------------------------------------------
  void ValidateTensorQuantizationSpace(const TensorInfo& first,
                                       const TensorInfo& second,
                                       const std::string& descName,
                                       std::string const& firstName,
                                       std::string const& secondName)
  {
      if (!first.IsQuantized() ||
          !second.IsQuantized())
      {
          // Not a quantized type, ignore the validation
          return;
      }
  
      DataType firstDataType  = first.GetDataType();
      DataType secondDataType = second.GetDataType();
  
      if (firstDataType != secondDataType)
      {
          throw InvalidArgumentException(descName + ": " + firstName + " and " + secondName +
                                         " must be of the same quantized type, " +
                                         firstName + " is " + GetDataTypeName(firstDataType) + ", " +
                                         secondName + " is " + GetDataTypeName(secondDataType));
      }
  
      if (!first.IsTypeSpaceMatch(second))
      {
          throw InvalidArgumentException(descName + ": " + firstName + " and " + secondName +
                                         " must have the same quantization space, " +
                                         firstName + " has offset " + to_string(first.GetQuantizationOffset()) +
                                         " and scale " + to_string(first.GetQuantizationScale()) + ", " +
                                         secondName + " has offset " + to_string(second.GetQuantizationOffset()) +
                                         " and scale " + to_string(second.GetQuantizationScale()));
      }
  }
  
  //---------------------------------------------------------------
  void ValidateBiasTensorQuantization(const TensorInfo& biasTensor,
                                      const TensorInfo& inputTensorInfo,
                                      const TensorInfo& weightsTensorInfo,
                                      const std::string& descName)
  {
      // Helper lambda function to validate a single bias quantization scale value
      auto VerifyBiasQuantizationScale = [&descName](float biasScale, float expectedScale) -> void
      {
          constexpr float tolerance = 0.0001f;
          if (std::abs(biasScale - expectedScale) > tolerance)
          {
              // Print the float values with extra precision to see very small differences
              ARMNN_LOG(warning) << std::setprecision(6) << descName << ": Expected " << expectedScale <<
                  " for bias quantization scale (product of input and weight scales), but got " <<
                  biasScale << ". Using scale provided.";
          }
      };
  
      if (biasTensor.GetQuantizationOffset() != 0)
      {
          throw InvalidArgumentException(descName + ": Expected zero quantization offset for bias tensor but got " +
              to_string(biasTensor.GetQuantizationOffset()));
      }
  
      if (biasTensor.HasMultipleQuantizationScales() || weightsTensorInfo.HasMultipleQuantizationScales())
      {
          // Validate per-axis quantization scales
          const std::vector<float>& weightScales = weightsTensorInfo.GetQuantizationScales();
          const std::vector<float>& biasScales   = biasTensor.GetQuantizationScales();
  
          if (weightScales.size() != biasScales.size())
          {
              std::stringstream msg;
              msg << descName << ": Expected matching number of per-axis quantization scales for weights and bias, "
                  << "but got different values. This is currently unsupported: weights=" << weightScales.size()
                  << ", biases=" << biasScales.size();
              throw InvalidArgumentException(msg.str(), CHECK_LOCATION());
          }
  
          for (size_t i = 0ul; i < biasScales.size(); ++i)
          {
              const float expectedScale = inputTensorInfo.GetQuantizationScale() * weightScales[i];
              VerifyBiasQuantizationScale(biasScales[i], expectedScale);
          }
      }
      else
      {
          // Validate per-tensor quantization scale
          const float expectedScale = inputTensorInfo.GetQuantizationScale() * weightsTensorInfo.GetQuantizationScale();
          VerifyBiasQuantizationScale(biasTensor.GetQuantizationScale(), expectedScale);
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensors(const std::vector<ITensorHandle*>& vec,
      unsigned int numExpected,
      const std::string& descName,
      const std::string& varName)
  {
      if (vec.empty() && numExpected > 0)
      {
          throw InvalidArgumentException(descName + ": Invalid empty " + varName + " array.");
      }
  
      for (unsigned int i = 0; i < numExpected; ++i)
      {
          if (!vec[i])
          {
              throw InvalidArgumentException(descName + ": Invalid NULL for " + varName + to_string(i));
          }
      }
  }
  
  //---------------------------------------------------------------
  void ValidateBroadcastTensorShapesMatch(const TensorInfo& first,
                                          const TensorInfo& second,
                                          const TensorInfo& output,
                                          std::string const& descName,
                                          std::string const& firstName,
                                          std::string const& secondName)
  {
      // Tensors must have the same number of dimensions in order to be explicit about which dimensions will get
      // broadcasted.
      if (first.GetNumDimensions() != second.GetNumDimensions())
      {
          throw InvalidArgumentException(descName  + ": Tensors "
              + firstName + " & " + secondName
              + " must have the same number of dimensions in order to be broadcasted");
      }
      uint32_t numDims = first.GetNumDimensions();
      std::vector<uint32_t> outputDims(numDims, 0u);
      for (uint32_t i = 0; i < numDims; i++)
      {
          const bool dimsNotEqual = first.GetShape()[i] != second.GetShape()[i];
          const bool dimsNotOne = (first.GetShape()[i] != 1) && (second.GetShape()[i] != 1);
          if (dimsNotEqual && dimsNotOne)
          {
              throw InvalidArgumentException("Broadcasting is not possible for incompatible shapes");
          }
          outputDims[i] = std::max(first.GetShape()[i], second.GetShape()[i]);
      }
      TensorShape broadcastShape = TensorShape(armnn::numeric_cast<unsigned int>(outputDims.size()), outputDims.data());
      if (broadcastShape != output.GetShape())
      {
          throw InvalidArgumentException(descName + ": The tensor shape resulting from adding "
                                         + firstName + " & " + secondName
                                         + " does not match the output shape");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateDataTypes(const TensorInfo& info,
                         const std::vector<armnn::DataType>& supportedTypes,
                         std::string const& descName)
  {
      auto iterator = std::find(supportedTypes.begin(), supportedTypes.end(), info.GetDataType());
      if (iterator == supportedTypes.end())
      {
          throw InvalidArgumentException(descName  + ": " + " Tensor type is not supported.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensorDataTypesMatch(const TensorInfo& first,
                                    const TensorInfo& second,
                                    std::string const& descName,
                                    std::string const& firstName,
                                    std::string const& secondName)
  {
      if (first.GetDataType() != second.GetDataType())
      {
          throw InvalidArgumentException(descName + ": " + firstName + " & " + secondName +
                                         " must have identical data types.");
      }
  }
  
  //---------------------------------------------------------------
  void ValidateTensorNumElementsMatch(const TensorInfo& first,
                                      const TensorInfo& second,
                                      std::string const& descName,
                                      std::string const& firstName,
                                      std::string const& secondName)
  {
      if (first.GetNumElements() != second.GetNumElements())
      {
          throw InvalidArgumentException(descName + ": " + firstName + " & " + secondName +
                                         " must have the same number of elements.");
      }
  }
  
  void ValidateWeightDataType(const TensorInfo& inputInfo,
                              const TensorInfo& weightInfo,
                              const std::string& descName)
  {
      const DataType inputType = inputInfo.GetDataType();
      if (IsQuantized8BitType(inputType))
      {
          ARMNN_NO_DEPRECATE_WARN_BEGIN
          const std::vector<DataType> validTypes =
          {
              DataType::QAsymmS8,
              DataType::QAsymmU8,
              DataType::QSymmS8,
              DataType::QuantizedSymm8PerAxis // deprecated
          };
          ARMNN_NO_DEPRECATE_WARN_END
  
          ValidateDataTypes(weightInfo, validTypes, descName);
      }
      else
      {
          ValidateTensorDataTypesMatch(inputInfo, weightInfo, descName, "input", "weight");
      }
  }
  
  void ValidatePerAxisQuantizationDimension(const TensorInfo& tensorInfo,
                                            const std::string& descName,
                                            const std::string& tensorName)
  {
      const Optional<unsigned int>& quantizationDim = tensorInfo.GetQuantizationDim();
      if (!quantizationDim.has_value())
      {
          throw InvalidArgumentException(fmt::format("{0}: Quantization dimension for per-axis quantization "
                                                     "not set on tensor {1}.", descName, tensorName));
      }
  
      if (quantizationDim.value() != 0)
      {
          throw InvalidArgumentException(fmt::format(
              "{0}: Quantization dimension for per-axis quantization expected to be 0 on tensor {1}, "
              "but got: {2}", descName, tensorName, quantizationDim.value()));
      }
  }
  
  void ValidatePerAxisQuantizationOffset(const TensorInfo& tensorInfo,
                                         const std::string& descName,
                                         const std::string& tensorName)
  {
      int32_t quantizationOffset = tensorInfo.GetQuantizationOffset();
      if (quantizationOffset != 0)
      {
          throw InvalidArgumentException(fmt::format(
              "{0}: Quantization offset for per-axis quantization expected to be 0 on tensor {1}, but got: {2}",
              descName, tensorName, quantizationOffset));
      }
  }
  
  void ValidatePerAxisQuantization(const TensorInfo& inputInfo,
                                   const TensorInfo& outputInfo,
                                   const TensorInfo& weightInfo,
                                   const Optional<TensorInfo>& optionalBiasInfo,
                                   const std::string& descName)
  {
      if (weightInfo.HasPerAxisQuantization())
      {
          const DataType inputDataType  = inputInfo.GetDataType();
          const DataType outputDataType = outputInfo.GetDataType();
  
          const bool canHavePerAxisQuantization = (IsQuantized8BitType(inputDataType)) && inputDataType == outputDataType;
  
          if (!canHavePerAxisQuantization)
          {
              throw InvalidArgumentException(fmt::format(
                  "{0}: Per-axis quantization parameters set on tensor {1}, but data type does not support "
                  "per-axis quantization.", descName, "weight"));
          }
  
  
          ValidPerAxisQuantizedDataType(weightInfo, descName, "weight");
          ValidatePerAxisQuantizationDimension(weightInfo, descName, "weight");
          ValidatePerAxisQuantizationOffset(weightInfo, descName, "weight");
  
          if (optionalBiasInfo.has_value())
          {
              const TensorInfo& biasInfo = optionalBiasInfo.value();
              if (!biasInfo.HasPerAxisQuantization())
              {
                  throw InvalidArgumentException(fmt::format(
                          "{}: Per-axis quantization parameters not set on bias tensor, "
                          "despite being set on weight tensor.", descName));
              }
  
              ValidateTensorDataType(biasInfo, DataType::Signed32, descName, "bias");
              ValidatePerAxisQuantizationDimension(biasInfo, descName, "bias");
              ValidatePerAxisQuantizationOffset(biasInfo, descName, "bias");
          }
      }
  }
  
  } // anonymous namespace
  
  void QueueDescriptor::ValidateInputsOutputs(const std::string& descName,
      unsigned int numExpectedIn, unsigned int numExpectedOut) const
  {
      ValidateTensors(m_Inputs, numExpectedIn, descName, "input");
      ValidateTensors(m_Outputs, numExpectedOut, descName, "output");
  }
  
  //---------------------------------------------------------------
  void MapQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"MapQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 0);
  
      for (unsigned int i = 0; i < m_Inputs.size(); ++i)
      {
          if (!m_Inputs[i])
          {
              throw InvalidArgumentException(
                  fmt::format("{}: Invalid NULL input {}.", descriptorName, static_cast<int>(i)));
          }
      }
  }
  
  //---------------------------------------------------------------
  void UnmapQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"UnmapQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 0);
  
      for (unsigned int i = 0; i < m_Inputs.size(); ++i)
      {
          if (!m_Inputs[i])
          {
              throw InvalidArgumentException(
                  fmt::format("{}: Invalid NULL input {}.", descriptorName, static_cast<int>(i)));
          }
      }
  }
  
  //---------------------------------------------------------------
  void MemCopyQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"MemCopyQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName , 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumElementsMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      if (m_Inputs.size() != m_Outputs.size())
      {
          throw InvalidArgumentException(fmt::format(
              "{0}: Number of inputs ({1}) does not match the number of outputs ({2}).",
              descriptorName, m_Inputs.size(), m_Outputs.size()));
      }
  
      for (unsigned int i = 0; i < m_Inputs.size(); ++i)
      {
          if (!m_Inputs[i])
          {
              throw InvalidArgumentException(fmt::format(
                  "{0}: Invalid NULL input {1}.", descriptorName, i));
          }
  
          if (!m_Outputs[i])
          {
              throw InvalidArgumentException(fmt::format("{0}: Invalid NULL output {1}", descriptorName, i));
          }
      }
  }
  
  //---------------------------------------------------------------
  void MemImportQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      ValidateNumInputs(workloadInfo, "MemImportQueueDescriptor", 1);
      ValidateNumOutputs(workloadInfo, "MemImportQueueDescriptor" , 1);
  
      if (workloadInfo.m_InputTensorInfos.size() != 1)
      {
          throw InvalidArgumentException(fmt::format("Number of input infos ({}) is not 1.",
                                                     workloadInfo.m_InputTensorInfos.size()));
  
      }
  
      if (workloadInfo.m_InputTensorInfos.size() != workloadInfo.m_OutputTensorInfos.size())
      {
          throw InvalidArgumentException(fmt::format(
              "Number of input infos ({0}) does not match the number of output infos ({1})",
              workloadInfo.m_InputTensorInfos.size(), workloadInfo.m_OutputTensorInfos.size()));
      }
  
      for (std::size_t i = 0; i < workloadInfo.m_InputTensorInfos.size(); ++i)
      {
          if (workloadInfo.m_InputTensorInfos[i].GetNumElements() !=
              workloadInfo.m_OutputTensorInfos[i].GetNumElements())
          {
              throw InvalidArgumentException(fmt::format(
                  "Number of elements for tensor input and output {} does not match", i ));
          }
      }
  
      if (m_Inputs.size() != 1)
      {
          throw InvalidArgumentException(fmt::format("Number of inputs ({}) is not 1.", m_Inputs.size()));
      }
  
      if (m_Inputs.size() != m_Outputs.size())
      {
          throw InvalidArgumentException(fmt::format(
              "Number of inputs ({0}) does not match the number of outputs ({1})",
              m_Inputs.size(), m_Outputs.size()));
      }
  
      for (unsigned int i = 0; i < m_Inputs.size(); ++i)
      {
          if (!m_Inputs[i])
          {
              throw InvalidArgumentException(fmt::format("Invalid null input {}", i));
          }
  
          if (!m_Outputs[i])
          {
              throw InvalidArgumentException(fmt::format("Invalid null output {}", i));
          }
      }
  }
  
  //---------------------------------------------------------------
  void MemSyncQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      ValidateNumInputs(workloadInfo, "MemSyncQueueDescriptor", 1);
      ValidateNumOutputs(workloadInfo, "MemSyncQueueDescriptor" , 1);
  
      if (m_Inputs.size() != 1)
      {
          throw InvalidArgumentException(fmt::format("Number of inputs ({}) is not 1.", m_Inputs.size()));
      }
  
      if (m_Outputs.size() != 0)
      {
          throw InvalidArgumentException(fmt::format("Number of outputs ({}) is not 0.", m_Outputs.size()));
      }
  
      if (!m_Inputs[0])
      {
          throw InvalidArgumentException(fmt::format("Invalid null input 0"));
      }
  }
  
  //---------------------------------------------------------------
  void ActivationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ActivationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void ArgMinMaxQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ArgMinMaxQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (outputTensorInfo.GetDataType() != DataType::Signed32 &&
          outputTensorInfo.GetDataType() != DataType::Signed64)
      {
          throw InvalidArgumentException(descriptorName + ": Output of ArgMinMax layer must be Int32 or Int64.");
      }
  
      std::vector<DataType> supportedInputTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32,
          DataType::Signed64
      };
  
      ValidateDataTypes(inputTensorInfo, supportedInputTypes, descriptorName);
  
      auto inputShape = inputTensorInfo.GetShape();
      auto outputShape = outputTensorInfo.GetShape();
  
      auto inputNumDimensions = inputShape.GetNumDimensions();
      auto unsignedAxis = armnnUtils::GetUnsignedAxis(inputNumDimensions, m_Parameters.m_Axis);
  
      const std::string outputShapeError{": Output tensor shape does not match shape inferred from input tensor."};
  
      // 1D input shape results in scalar output shape
      if (inputShape.GetNumDimensions() == 1)
      {
          if (outputShape.GetNumDimensions() != 1 && outputShape[0] != 1)
          {
              throw InvalidArgumentException(descriptorName + outputShapeError);
          }
      }
      else
      {
          for (unsigned int i = 0; i < unsignedAxis; ++i)
          {
              if (outputShape[i] != inputShape[i])
              {
                  throw InvalidArgumentException(descriptorName + outputShapeError);
              }
          }
  
          for (auto i = unsignedAxis + 1; i < inputNumDimensions; ++i)
          {
              if (outputShape[i - 1] != inputShape[i])
              {
                  throw InvalidArgumentException(descriptorName + outputShapeError);
              }
          }
      }
  }
  
  void CastQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"CastQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
              DataType::BFloat16,
              DataType::Float16,
              DataType::Float32,
              DataType::QAsymmS8,
              DataType::QAsymmU8,
              DataType::QSymmS8,
              DataType::QSymmS16,
              DataType::Signed32,
              DataType::Signed64
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void SoftmaxQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"SoftmaxQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void SplitterQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"SplitterQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo, descriptorName, 1);
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::Boolean,
          DataType::Signed32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      const TensorInfo& inputTensorInfo = workloadInfo.m_InputTensorInfos[0];
      for (unsigned long i = 0ul; i < workloadInfo.m_OutputTensorInfos.size(); ++i)
      {
          const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[i];
          ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
          const std::string outputName = "output_" + std::to_string(i);
          ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", outputName);
      }
  
      if (workloadInfo.m_OutputTensorInfos.size() <= 0)
      {
          throw InvalidArgumentException(descriptorName + ": At least one output needs to be provided.");
      }
  
      if (workloadInfo.m_OutputTensorInfos.size() != m_ViewOrigins.size())
      {
          throw InvalidArgumentException(
              descriptorName + ": Number of split windows "
              "has to match number of workloadInfo.m_OutputTensorInfos. "
              "Number of windows: " +
              to_string(m_ViewOrigins.size()) +
              ". Number of workloadInfo.m_OutputTensorInfos: " + to_string(workloadInfo.m_OutputTensorInfos.size()));
      }
  
      //The dimensionality of all the windows has to match the dimensionality (not shape) of the input.
      std::size_t inputDims = workloadInfo.m_InputTensorInfos[0].GetNumDimensions();
      for(unsigned int w = 0; w < m_ViewOrigins.size(); ++w )
      {
          //Checks that the dimensionality of input is same as the split windows.
          ViewOrigin const& e = m_ViewOrigins[w];
          if (e.m_Origin.size() != inputDims)
          {
              throw InvalidArgumentException(descriptorName + ": Window origin have to "
                                             "have the same dimensionality as the input tensor. "
                                             "Window origin (index: " +
                                             to_string(w) + ") has " + to_string(e.m_Origin.size()) +
                                             " dimensions, the input "
                                             "tensor has " +
                                             to_string(inputDims) + " dimensions.");
          }
          for (unsigned int i = 0; i < e.m_Origin.size(); ++i)
          {
              if (e.m_Origin[i] + workloadInfo.m_OutputTensorInfos[w].GetShape()[i] >
                  workloadInfo.m_InputTensorInfos[0].GetShape()[i])
              {
                  throw InvalidArgumentException(descriptorName + ": Window extent coordinates have to "
                                                 "be smaller or equal than the size of the input in that coord.");
              }
          }
      }
  }
  
  void ConcatQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ConcatQueueDescriptor"};
  
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      if (m_Inputs.size() <= 0)
      {
          throw InvalidArgumentException(descriptorName + ": At least one input needs to be provided.");
      }
      if (m_Outputs.size() <= 0)
      {
          throw InvalidArgumentException(descriptorName + ": At least one output needs to be provided.");
      }
  
      if (workloadInfo.m_InputTensorInfos.size() <= 0)
      {
          throw InvalidArgumentException(descriptorName + ": At least one TensorInfo input needs to be provided.");
      }
      if (workloadInfo.m_OutputTensorInfos.size() <= 0)
      {
          throw InvalidArgumentException(descriptorName + ": At least one TensorInfo output needs to be provided.");
      }
  
      if(m_Parameters.GetConcatAxis() > workloadInfo.m_InputTensorInfos[0].GetShape().GetNumDimensions())
      {
          throw InvalidArgumentException(descriptorName + ": Invalid concatenation axis provided.");
      }
  
      if (workloadInfo.m_InputTensorInfos[0].GetShape().GetNumDimensions() - m_Parameters.GetConcatAxis() == 1)
      {
          return;
      }
  
      if (workloadInfo.m_InputTensorInfos.size() != m_ViewOrigins.size())
      {
          throw InvalidArgumentException(
              descriptorName + ": Number of split windows "
              "has to match number of workloadInfo.m_InputTensorInfos. "
              "Number of windows: " +
              to_string(m_ViewOrigins.size()) +
              ". Number of workloadInfo.m_InputTensorInfos: " + to_string(workloadInfo.m_InputTensorInfos.size()));
      }
  
      //The dimensionality of all the windows has to match the dimensionality (not shape) of the output.
      std::size_t outputDims = workloadInfo.m_OutputTensorInfos[0].GetNumDimensions();
      for(unsigned int w = 0; w < m_ViewOrigins.size(); ++w )
      {
          //Checks that the dimensionality of output is same as the split windows.
          ViewOrigin const& e = m_ViewOrigins[w];
          if (e.m_Origin.size() != outputDims)
          {
              throw InvalidArgumentException(descriptorName + ": Window origin have to "
                                             "have the same dimensionality as the output tensor. "
                                             "Window origin (index: " +
                                             to_string(w) + ") has " + to_string(e.m_Origin.size()) +
                                             " dimensions, the output "
                                             "tensor has " +
                                             to_string(outputDims) + " dimensions.");
          }
          //Checks that the merge windows are within the output tensor.
          for (unsigned int i = 0; i < e.m_Origin.size(); ++i)
          {
              if (e.m_Origin[i] + workloadInfo.m_InputTensorInfos[w].GetShape()[i]
                  > workloadInfo.m_OutputTensorInfos[0].GetShape()[i])
              {
                  throw InvalidArgumentException(descriptorName + ": Window extent coordinates have to "
                                                 "be smaller or equal than the size of the output in that coord.");
              }
          }
      }
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::Boolean,
          DataType::Signed32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
      for (unsigned long i = 0ul; i < workloadInfo.m_InputTensorInfos.size(); ++i)
      {
          const TensorInfo& inputTensorInfo = workloadInfo.m_InputTensorInfos[i];
          ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
  
          const std::string inputName = "input_" + std::to_string(i);
          ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, inputName, "output");
      }
  }
  
  void StackQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"StackQueueDescriptor"};
  
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      if (m_Parameters.m_NumInputs != workloadInfo.m_InputTensorInfos.size())
      {
          throw InvalidArgumentException(descriptorName + ": Must have the defined number of input tensors.");
      }
  
      // All inputs must have the same shape, which is defined in parameters
      const TensorShape& inputShape = m_Parameters.m_InputShape;
      for (unsigned int i = 0; i < workloadInfo.m_InputTensorInfos.size(); ++i)
      {
          if (workloadInfo.m_InputTensorInfos[i].GetShape() != inputShape)
          {
              throw InvalidArgumentException(descriptorName + ": All input tensor shapes must match the defined shape.");
          }
      }
  
      if (inputShape.GetNumDimensions() > 4)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor may have up to 4 dimensions.");
      }
  
      // m_Axis is 0-based and may take values from 0 to the number of input dimensions (inclusive),
      // since the output tensor has an additional dimension.
      if (m_Parameters.m_Axis > inputShape.GetNumDimensions())
      {
          throw InvalidArgumentException(descriptorName + ": Axis may not be greater "
                                         "than the number of input dimensions.");
      }
  
      // Output shape must be as inferred from the input shape
      const TensorShape& outputShape = workloadInfo.m_OutputTensorInfos[0].GetShape();
      for (unsigned int i = 0; i < m_Parameters.m_Axis; ++i)
      {
          if (outputShape[i] != inputShape[i])
          {
              throw InvalidArgumentException(descriptorName + ": Output tensor must "
                                             "match shape inferred from input tensor.");
          }
      }
  
      if (outputShape[m_Parameters.m_Axis] != m_Parameters.m_NumInputs)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor must "
                                         "match shape inferred from input tensor.");
      }
  
      for (unsigned int i = m_Parameters.m_Axis + 1; i < inputShape.GetNumDimensions() + 1; ++i)
      {
          if (outputShape[i] != inputShape[i-1])
          {
              throw InvalidArgumentException(descriptorName + ": Output tensor must "
                                             "match shape inferred from input tensor.");
          }
      }
  
      if (outputShape.GetNumDimensions() > 5)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor may have up to 5 dimensions.");
      }
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::Boolean,
          DataType::Signed32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(workloadInfo.m_InputTensorInfos[0], supportedTypes, descriptorName);
  
      for (unsigned int i = 1ul; i < workloadInfo.m_InputTensorInfos.size(); ++i)
      {
          ValidateTensorDataTypesMatch(workloadInfo.m_InputTensorInfos[0],
                                       workloadInfo.m_InputTensorInfos[i],
                                       descriptorName,
                                       "input_0",
                                       "input_" + std::to_string(i));
      }
  
      ValidateTensorDataTypesMatch(workloadInfo.m_InputTensorInfos[0],
                                   workloadInfo.m_OutputTensorInfos[0],
                                   descriptorName,
                                   "input_0",
                                   "output");
  }
  
  void FillQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"FillQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo, descriptorName, 1, "input");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::Signed32
      };
  
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  }
  
  void FullyConnectedQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"FullyConnectedQueueDescriptor"};
  
      uint32_t numInputs = 1;
      if (!m_Parameters.m_ConstantWeights)
      {
          numInputs = 2;
          if (m_Parameters.m_BiasEnabled)
          {
              numInputs = 3;
          }
      }
      ValidateNumInputs(workloadInfo, descriptorName, numInputs);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 2, "output");
  
      if (!(inputTensorInfo.GetNumDimensions() == 2 || inputTensorInfo.GetNumDimensions() == 4))
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor must have 2 or 4 dimensions.");
      }
  
      TensorInfo weightTensorInfo;
      if (m_Parameters.m_ConstantWeights)
      {
          ValidatePointer(m_Weight, descriptorName, "weight");
          weightTensorInfo = m_Weight->GetTensorInfo();
      }
      else
      {
          weightTensorInfo  = workloadInfo.m_InputTensorInfos[1];
      }
      ValidateTensorNumDimensions(weightTensorInfo, descriptorName, 2, "weight");
  
      if (m_Parameters.m_BiasEnabled)
      {
          TensorInfo biasTensorInfo;
          if (m_Parameters.m_ConstantWeights)
          {
              ValidatePointer(m_Bias, descriptorName, "bias");
              biasTensorInfo = m_Bias->GetTensorInfo();
          }
          else
          {
              biasTensorInfo  = workloadInfo.m_InputTensorInfos[2];
          }
          // Validates type and quantization values.
          ValidateBiasTensorQuantization(biasTensorInfo, inputTensorInfo, weightTensorInfo, descriptorName);
          ValidateTensorDataType(biasTensorInfo, GetBiasDataType(inputTensorInfo.GetDataType()), descriptorName, "bias");
          ValidateTensorNumDimensions(biasTensorInfo, descriptorName, 1, "bias");
      }
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
  
      // For FullyConnected, we allow to have BFloat16 input with Float32 output for optimization.
      if (inputTensorInfo.GetDataType() == DataType::BFloat16)
      {
          if (outputTensorInfo.GetDataType() != DataType::BFloat16 && outputTensorInfo.GetDataType() != DataType::Float32)
          {
              throw InvalidArgumentException(descriptorName  + ": " + " Output tensor type must be BFloat16 or Float32 "
                                             "for BFloat16 input.");
          }
      }
      else
      {
          ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
      }
  }
  
  void NormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"NormalizationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void AdditionQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"AdditionQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputTensorInfo0, inputTensorInfo1, descriptorName, "input_0", "input_1");
      ValidateTensorDataTypesMatch(inputTensorInfo1, outputTensorInfo, descriptorName, "input_1", "output");
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  }
  
  void MultiplicationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"MultiplicationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputTensorInfo0, inputTensorInfo1, descriptorName, "input_0", "input_1");
      ValidateTensorDataTypesMatch(inputTensorInfo1, outputTensorInfo, descriptorName, "input_1", "output");
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  }
  
  void BatchNormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"BatchNormalizationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      ValidatePointer(m_Mean,     descriptorName, "mean");
      ValidatePointer(m_Variance, descriptorName, "variance");
      ValidatePointer(m_Beta,     descriptorName, "beta");
      ValidatePointer(m_Gamma,    descriptorName, "gamma");
  
      const TensorInfo& mean     = m_Mean->GetTensorInfo();
      const TensorInfo& variance = m_Variance->GetTensorInfo();
      const TensorInfo& beta     = m_Beta->GetTensorInfo();
      const TensorInfo& gamma    = m_Gamma->GetTensorInfo();
  
      ValidateTensorNumDimensions(mean,     descriptorName, 1, "mean");
      ValidateTensorNumDimensions(variance, descriptorName, 1, "variance");
      ValidateTensorNumDimensions(beta,     descriptorName, 1, "beta");
      ValidateTensorNumDimensions(gamma,    descriptorName, 1, "gamma");
  
      ValidateTensorShapesMatch(mean, variance, descriptorName, "mean", "variance");
      ValidateTensorShapesMatch(mean, beta,     descriptorName, "mean", "beta");
      ValidateTensorShapesMatch(mean, gamma,    descriptorName, "mean", "gamma");
  }
  
  void Convolution2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"Convolution2dQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      ValidatePointer(m_Weight, descriptorName, "weight");
  
      const TensorInfo& weightTensorInfo = m_Weight->GetTensorInfo();
      ValidateTensorNumDimensions(weightTensorInfo, descriptorName, 4, "weight");
  
      ValidateWeightDataType(inputTensorInfo, weightTensorInfo, descriptorName);
  
      Optional<TensorInfo> optionalBiasTensorInfo;
      if (m_Parameters.m_BiasEnabled)
      {
          ValidatePointer(m_Bias, descriptorName, "bias");
  
          optionalBiasTensorInfo = MakeOptional<TensorInfo>(m_Bias->GetTensorInfo());
          const TensorInfo& biasTensorInfo = optionalBiasTensorInfo.value();
  
          ValidateTensorDataType(biasTensorInfo, GetBiasDataType(inputTensorInfo.GetDataType()), descriptorName, "bias");
          ValidateBiasTensorQuantization(biasTensorInfo, inputTensorInfo, weightTensorInfo, descriptorName);
      }
  
      if (m_Parameters.m_StrideX <= 0 || m_Parameters.m_StrideY <= 0  )
      {
          throw InvalidArgumentException(
              fmt::format("{}: strideX (provided {}) and strideY (provided {}) "
                          "cannot be either negative or 0.",
                          descriptorName, m_Parameters.m_StrideX, m_Parameters.m_StrideY));
      }
  
      ValidatePerAxisQuantization(inputTensorInfo,
                                  outputTensorInfo,
                                  weightTensorInfo,
                                  optionalBiasTensorInfo,
                                  descriptorName);
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::QSymmS8
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
  
      // For Convolution2d, we allow to have BFloat16 input with Float32 output for optimization.
      if (inputTensorInfo.GetDataType() == DataType::BFloat16)
      {
          if (outputTensorInfo.GetDataType() != DataType::BFloat16 && outputTensorInfo.GetDataType() != DataType::Float32)
          {
              throw InvalidArgumentException(descriptorName  + ": " + " Output tensor type must be BFloat16 or Float32 "
                                             "for BFloat16 input.");
          }
      }
      else
      {
          ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
      }
  }
  
  void DepthwiseConvolution2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"DepthwiseConvolution2dQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      ValidatePointer(m_Weight, descriptorName, "weight");
  
      const TensorInfo& weightTensorInfo = m_Weight->GetTensorInfo();
      ValidateTensorNumDimensions(weightTensorInfo, descriptorName, 4, "weight");
  
      if (m_Parameters.m_DilationX < 1 || m_Parameters.m_DilationY < 1 )
      {
          throw InvalidArgumentException(
              fmt::format("{}: dilationX (provided {}) and dilationY (provided {}) "
                          "cannot be smaller than 1.",
                          descriptorName, m_Parameters.m_DilationX, m_Parameters.m_DilationX));
      }
  
      if (m_Parameters.m_StrideX <= 0 || m_Parameters.m_StrideY <= 0  )
      {
          throw InvalidArgumentException(
              fmt::format("{}: strideX (provided {}) and strideY (provided {}) "
                          "cannot be either negative or 0.",
                          descriptorName, m_Parameters.m_StrideX, m_Parameters.m_StrideY));
      }
  
      const unsigned int channelIndex = (m_Parameters.m_DataLayout == DataLayout::NCHW) ? 1 : 3;
  
      // Expected weight shape: [ M, I, H, W ] - This shape does NOT depend on the data layout
      // inputChannels * channelMultiplier should be equal to outputChannels.
      const unsigned int numWeightChannelMultiplier = weightTensorInfo.GetShape()[0];
      const unsigned int numWeightInputChannels     = weightTensorInfo.GetShape()[1];
      const unsigned int numWeightOutputChannels    = outputTensorInfo.GetShape()[channelIndex];
      if (numWeightChannelMultiplier * numWeightInputChannels != numWeightOutputChannels)
      {
          throw InvalidArgumentException(fmt::format(
              "{0}: output_channels (provided {1}) should be equal to input_channels (provided {2}) "
              "multiplied by channel_multiplier (provided {3}).",
              descriptorName, numWeightOutputChannels, numWeightInputChannels, numWeightChannelMultiplier));
      }
  
      ValidateWeightDataType(inputTensorInfo, weightTensorInfo, descriptorName);
  
      Optional<TensorInfo> optionalBiasTensorInfo;
      if (m_Parameters.m_BiasEnabled)
      {
          ValidatePointer(m_Bias, descriptorName, "bias");
  
          optionalBiasTensorInfo = MakeOptional<TensorInfo>(m_Bias->GetTensorInfo());
          const TensorInfo& biasTensorInfo = optionalBiasTensorInfo.value();
  
          ValidateBiasTensorQuantization(biasTensorInfo, inputTensorInfo, weightTensorInfo, descriptorName);
          ValidateTensorDataType(biasTensorInfo, GetBiasDataType(inputTensorInfo.GetDataType()), descriptorName, "bias");
      }
      ValidatePerAxisQuantization(inputTensorInfo,
                                  outputTensorInfo,
                                  weightTensorInfo,
                                  optionalBiasTensorInfo,
                                  descriptorName);
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void PermuteQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"PermuteQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const PermutationVector& mapping = m_Parameters.m_DimMappings;
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, mapping.GetSize(), "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, mapping.GetSize(), "output");
  
      for (unsigned int i = 0u; i < mapping.GetSize(); ++i)
      {
          if (inputTensorInfo.GetShape()[i] != outputTensorInfo.GetShape()[mapping[i]])
          {
              throw InvalidArgumentException(descriptorName + ": src dimension " + to_string(i) +
                                             " (=" + to_string(inputTensorInfo.GetShape()[i]) + ") " +
                                             "must match dst dimension " + to_string(mapping[i]) +
                                             " (=" + to_string(outputTensorInfo.GetShape()[mapping[i]]) + ")");
          }
      }
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void Pooling2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"Pooling2dQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void ResizeBilinearQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ResizeBilinearQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      // ResizeBilinear only changes width and height: batch and channel count must match.
      const unsigned int inputBatchSize  = inputTensorInfo.GetShape()[0];
      const unsigned int outputBatchSize = outputTensorInfo.GetShape()[0];
      if (inputBatchSize != outputBatchSize)
      {
          throw InvalidArgumentException(
              fmt::format("{}: Input batch size ({}) does not match output batch size ({})",
                          descriptorName, inputBatchSize, outputBatchSize));
      }
  
      DataLayoutIndexed dimensionIndices(m_Parameters.m_DataLayout);
      const unsigned int inputChannelCount  = inputTensorInfo.GetShape()[dimensionIndices.GetChannelsIndex()];
      const unsigned int outputChannelCount = outputTensorInfo.GetShape()[dimensionIndices.GetChannelsIndex()];
      if (inputChannelCount != outputChannelCount)
      {
          throw InvalidArgumentException(
              fmt::format("{}: Input channel count ({}) does not match output channel count ({})",
                          descriptorName, inputChannelCount, outputChannelCount));
      }
  }
  
  void ResizeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ResizeQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      // Resize only changes width and height: batch and channel count must match.
      const unsigned int inputBatchSize  = inputTensorInfo.GetShape()[0];
      const unsigned int outputBatchSize = outputTensorInfo.GetShape()[0];
      if (inputBatchSize != outputBatchSize)
      {
          throw InvalidArgumentException(
                  fmt::format("{}: Input batch size ({}) does not match output batch size ({})",
                              descriptorName, inputBatchSize, outputBatchSize));
      }
  
      DataLayoutIndexed dimensionIndices(m_Parameters.m_DataLayout);
      const unsigned int inputChannelCount  = inputTensorInfo.GetShape()[dimensionIndices.GetChannelsIndex()];
      const unsigned int outputChannelCount = outputTensorInfo.GetShape()[dimensionIndices.GetChannelsIndex()];
      if (inputChannelCount != outputChannelCount)
      {
          throw InvalidArgumentException(
                  fmt::format("{}: Input channel count ({}) does not match output channel count ({})",
                              descriptorName, inputChannelCount, outputChannelCount));
      }
  }
  
  void FakeQuantizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"FakeQuantizationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 2, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 2, "output");
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo,  descriptorName, "input", "output");
  
      if (m_Parameters.m_Min > m_Parameters.m_Max)
      {
          throw InvalidArgumentException(descriptorName + ": min cannot be greater than max");
      }
  }
  
  void InstanceNormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"InstanceNormalizationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (inputTensorInfo.GetNumDimensions() > 4)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensors with rank greater than 4 are not supported.");
      }
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
          {
              DataType::BFloat16,
              DataType::Float32,
              DataType::Float16
          };
  
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void L2NormalizationQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"L2NormalizationQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (inputTensorInfo.GetNumDimensions() > 4)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensors with rank greater than 4 are not supported.");
      }
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void LogSoftmaxQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"LogSoftmaxQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
      };
  
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void ConstantQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ConstantQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 0);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      if (!m_LayerOutput)
      {
          throw InvalidArgumentException(descriptorName + ": No const input specified.");
      }
  
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
      ValidateTensorShapesMatch(m_LayerOutput->GetTensorInfo(), outputTensorInfo, descriptorName, "constant", "output");
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  }
  
  void ReshapeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ReshapeQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumElementsMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      // Check the supported data types
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32,
          DataType::Boolean
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void SpaceToBatchNdQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"SpaceToBatchNdQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      if (m_Parameters.m_BlockShape.size() != 2)
      {
          throw InvalidArgumentException(descriptorName + ": Block Shape must contain 2 spatial dimensions.");
      }
  
      if (m_Parameters.m_BlockShape.size() != m_Parameters.m_PadList.size())
      {
          throw InvalidArgumentException(descriptorName + ": Pad List must contain the same number of "
                                         "dimensions as Block Shape.");
      }
  
      const TensorShape& inputShape = inputTensorInfo.GetShape();
  
      std::pair<unsigned int, unsigned int> heightPad = m_Parameters.m_PadList[0];
      std::pair<unsigned int, unsigned int> widthPad  = m_Parameters.m_PadList[1];
  
      DataLayoutIndexed dimensionIndices(m_Parameters.m_DataLayout);
  
      const unsigned int inputWidth  = inputShape[dimensionIndices.GetWidthIndex()] +
                                       widthPad.first + widthPad.second;
      const unsigned int inputHeight = inputShape[dimensionIndices.GetHeightIndex()] +
                                       heightPad.first + heightPad.second;
  
      const unsigned int numInputElements  = inputShape[0] * inputHeight * inputWidth *
                                             inputShape[dimensionIndices.GetChannelsIndex()];
      const unsigned int numOutputElements = outputTensorInfo.GetNumElements();
  
      if (numOutputElements != numInputElements)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor has " +
              to_string(numInputElements) + " after padding but output tensor has " +
              to_string(numOutputElements) + " elements.");
      }
  
      if (inputHeight % m_Parameters.m_BlockShape[0] != 0 || inputWidth % m_Parameters.m_BlockShape[1] != 0)
      {
          throw InvalidArgumentException(descriptorName + ": Input shape after padding must be "
                                         "divisible by Block Shape in all spatial dimensions");
      }
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void SpaceToDepthQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"SpaceToDepthQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorNumElementsMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      if (m_Parameters.m_BlockSize == 0)
      {
          throw InvalidArgumentException(descriptorName + ": Block size cannot be 0.");
      }
  
      DataLayoutIndexed dimensionIndices(m_Parameters.m_DataLayout);
      const unsigned int wIndex = dimensionIndices.GetWidthIndex();
      const unsigned int hIndex = dimensionIndices.GetHeightIndex();
      const unsigned int cIndex = dimensionIndices.GetChannelsIndex();
  
      const TensorShape& inputShape = inputTensorInfo.GetShape();
      if (inputShape[hIndex] % m_Parameters.m_BlockSize != 0 || inputShape[wIndex]  % m_Parameters.m_BlockSize != 0)
      {
          throw InvalidArgumentException(descriptorName + ": Input shape must be divisible "
                                         "by block size in all spatial dimensions");
      }
  
      const TensorShape& outputShape = outputTensorInfo.GetShape();
      if (outputShape[cIndex] % (m_Parameters.m_BlockSize * m_Parameters.m_BlockSize) != 0)
      {
          throw InvalidArgumentException(descriptorName + ": The depth of the output tensor"
                                         "must be divisible by the square of block size." );
      }
  }
  
  void FloorQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"FloorQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
  
      if (inputTensorInfo != outputTensorInfo)
      {
          throw InvalidArgumentException(descriptorName + ": Input and output tensor infos do not match.");
      }
  }
  
  void LstmQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      // ported from android/ml/nn/common/operations/LSTM.cpp CheckInputTensorDimensions()
  
      const std::string descriptorName{"LstmQueueDescriptor"};
  
      // check dimensions of all inputs and outputs
      if (workloadInfo.m_InputTensorInfos.size() != 3)
      {
          throw InvalidArgumentException(descriptorName + ": Invalid number of inputs.");
      }
      if (workloadInfo.m_OutputTensorInfos.size() != 4)
      {
          throw InvalidArgumentException(descriptorName + ": Invalid number of outputs.");
      }
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QSymmS16
      };
  
      // check for supported type of one input and match them with all the other input and output
      ValidateDataTypes(workloadInfo.m_InputTensorInfos[0], supportedTypes, descriptorName);
  
      // type matches all other inputs
      for (uint32_t i = 1u; i < workloadInfo.m_InputTensorInfos.size(); ++i)
      {
          ValidateTensorDataTypesMatch(workloadInfo.m_InputTensorInfos[0],
                                       workloadInfo.m_InputTensorInfos[i],
                                       descriptorName,
                                       "input_0",
                                       "input_" + std::to_string(i));
      }
      // type matches all other outputs
      for (uint32_t i = 0u; i < workloadInfo.m_OutputTensorInfos.size(); ++i)
      {
          ValidateTensorDataTypesMatch(workloadInfo.m_InputTensorInfos[0],
                                       workloadInfo.m_OutputTensorInfos[i],
                                       "LstmQueueDescriptor",
                                       "input_0",
                                       "output_" + std::to_string(i));
      }
  
      // Making sure clipping parameters have valid values.
      // == 0 means no clipping
      //  > 0 means clipping
      if (m_Parameters.m_ClippingThresCell < 0.0f)
      {
          throw InvalidArgumentException(descriptorName + ": negative cell clipping threshold is invalid");
      }
      if (m_Parameters.m_ClippingThresProj < 0.0f)
      {
          throw InvalidArgumentException(descriptorName + ": negative projection clipping threshold is invalid");
      }
  
  
      // Inferring batch size, number of outputs and number of cells from the inputs.
      const uint32_t n_input = workloadInfo.m_InputTensorInfos[0].GetShape()[1];
      const uint32_t n_batch = workloadInfo.m_InputTensorInfos[0].GetShape()[0];
      ValidatePointer(m_InputToOutputWeights, "Null pointer check", "InputToOutputWeights");
      const uint32_t n_cell = m_InputToOutputWeights->GetShape()[0];
      ValidatePointer(m_RecurrentToOutputWeights, "Null pointer check", "RecurrentToOutputWeights");
      const uint32_t n_output = m_RecurrentToOutputWeights->GetShape()[1];
  
      // input tensor
      ValidateTensorNumDimNumElem(workloadInfo.m_InputTensorInfos[0], 2, (n_batch * n_input),
                                  descriptorName + " input_0");
      // outputStateInTensor
      ValidateTensorNumDimNumElem(workloadInfo.m_InputTensorInfos[1], 2, (n_batch * n_output),
                                  descriptorName + " input_1");
      // outputStateInTensor
      ValidateTensorNumDimNumElem(workloadInfo.m_InputTensorInfos[2], 2, (n_batch * n_cell),
                                  descriptorName + " input_2");
      // scratchBufferTensor
      unsigned int scratchBufferSize = m_Parameters.m_CifgEnabled ? n_cell * 3 : n_cell * 4;
      ValidateTensorNumDimNumElem(workloadInfo.m_OutputTensorInfos[0], 2, (n_batch * scratchBufferSize),
                                  descriptorName + " output_0");
      // outputStateOutTensor
      ValidateTensorNumDimNumElem(workloadInfo.m_OutputTensorInfos[1], 2, (n_batch * n_output),
                                  descriptorName + " output_1");
      // cellStateOutTensor
      ValidateTensorNumDimNumElem(workloadInfo.m_OutputTensorInfos[2], 2, (n_batch * n_cell),
                                  descriptorName + " output_2");
      // outputTensor
      ValidateTensorNumDimNumElem(workloadInfo.m_OutputTensorInfos[3], 2, (n_batch * n_output),
                                  descriptorName + " output_3");
  
  
      // check that dimensions of inputs/outputs and QueueDescriptor data match with each other
      if ( m_InputToInputWeights )
      {
          ValidateTensorNumDimNumElem(m_InputToInputWeights->GetTensorInfo(), 2,
                                        (n_cell * n_input), "InputLayerNormWeights");
      }
  
      ValidatePointer(m_InputToForgetWeights, "Null pointer check", "InputToForgetWeights");
      ValidateTensorNumDimNumElem(m_InputToForgetWeights->GetTensorInfo(), 2,
                                    (n_cell * n_input), "InputToForgetWeights");
  
      ValidatePointer(m_InputToCellWeights, "Null pointer check", "InputToCellWeights");
      ValidateTensorNumDimNumElem(m_InputToCellWeights->GetTensorInfo(), 2,
                                    (n_cell * n_input), "InputToCellWeights");
  
      if ( m_RecurrentToInputWeights )
      {
          ValidateTensorNumDimNumElem(m_RecurrentToInputWeights->GetTensorInfo(), 2,
                                        (n_cell * n_output), "RecurrentToInputWeights");
      }
  
      ValidatePointer(m_RecurrentToForgetWeights, "Null pointer check", "RecurrentToForgetWeights");
      ValidateTensorNumDimNumElem(m_RecurrentToForgetWeights->GetTensorInfo(), 2,
                                    (n_cell * n_output), "RecurrentToForgetWeights");
  
      ValidatePointer(m_RecurrentToCellWeights, "Null pointer check", "RecurrentToCellWeights");
      ValidateTensorNumDimNumElem(m_RecurrentToCellWeights->GetTensorInfo(), 2,
                                    (n_cell * n_output), "RecurrentToCellWeights");
  
      // Make sure the input-gate's parameters are either both present (regular
      // LSTM) or not at all (CIFG-LSTM). And CifgEnable is set accordingly.
      bool cifg_weights_all_or_none = ((m_InputToInputWeights && m_RecurrentToInputWeights &&
                                       !m_Parameters.m_CifgEnabled) ||
                                       (!m_InputToInputWeights && !m_RecurrentToInputWeights &&
                                       m_Parameters.m_CifgEnabled));
      if (!cifg_weights_all_or_none)
      {
          throw InvalidArgumentException(descriptorName + ": Input-Gate's parameters InputToInputWeights and "
                                         "RecurrentToInputWeights must either both be present (regular LSTM) "
                                         "or both not present (CIFG-LSTM). In addition CifgEnable must be set "
                                         "accordingly.");
      }
  
      if ( m_CellToInputWeights )
      {
          ValidateTensorNumDimNumElem(m_CellToInputWeights->GetTensorInfo(), 1,
                                        n_cell, "CellToInputWeights");
      }
      if ( m_CellToForgetWeights )
      {
          ValidateTensorNumDimNumElem(m_CellToForgetWeights->GetTensorInfo(), 1,
                                        n_cell, "CellToForgetWeights");
      }
      if ( m_CellToOutputWeights )
      {
          ValidateTensorNumDimNumElem(m_CellToOutputWeights->GetTensorInfo(), 1,
                                        n_cell, "CellToOutputWeights");
      }
  
      // Making sure the peephole weights are there all or none. And PeepholeEnable is set accordingly.
      bool peephole_weights_all_or_none =
              (((m_CellToInputWeights || m_Parameters.m_CifgEnabled) &&  m_CellToForgetWeights
              && m_CellToOutputWeights && m_Parameters.m_PeepholeEnabled)
              || ( !m_CellToInputWeights && !m_CellToForgetWeights
              && !m_CellToOutputWeights && !m_Parameters.m_PeepholeEnabled));
      if (!peephole_weights_all_or_none)
      {
          throw InvalidArgumentException(descriptorName + ": Invalid combination of peephole parameters.");
      }
  
      // Make sure the input gate bias is present only when not a CIFG-LSTM.
      if (m_Parameters.m_CifgEnabled)
      {
          if (m_InputGateBias)
          {
              throw InvalidArgumentException(descriptorName + ": InputGateBias is present and CIFG-LSTM is enabled.");
          }
      }
      else
      {
          if (!m_InputGateBias)
          {
              throw InvalidArgumentException(descriptorName + ": If CIFG-LSTM is disabled InputGateBias "
                                             "must be present.");
          }
          ValidateTensorNumDimNumElem(m_InputGateBias->GetTensorInfo(), 1,
                                        n_cell, "InputGateBias");
      }
  
      ValidatePointer(m_ForgetGateBias, "Null pointer check", "ForgetGateBias");
      ValidateTensorNumDimNumElem(m_ForgetGateBias->GetTensorInfo(), 1, n_cell, "ForgetGateBias");
  
      ValidatePointer(m_CellBias, "Null pointer check", "CellBias");
      ValidateTensorNumDimNumElem(m_CellBias->GetTensorInfo(), 1, n_cell, "CellBias");
  
      ValidatePointer(m_OutputGateBias, "Null pointer check", "OutputGateBias");
      ValidateTensorNumDimNumElem(m_OutputGateBias->GetTensorInfo(), 1, n_cell, "OutputGateBias");
  
      if (m_ProjectionWeights)
      {
          ValidateTensorNumDimNumElem(m_ProjectionWeights->GetTensorInfo(), 2,
                                        (n_cell * n_output), "ProjectionWeights");
      }
      if (m_ProjectionBias)
      {
          ValidateTensorNumDimNumElem(m_ProjectionBias->GetTensorInfo(), 1, n_output, "ProjectionBias");
      }
  
      // Making sure the projection tensors are consistent:
      // 1) If projection weight is not present, then projection bias should not be
      // present.
      // 2) If projection weight is present, then projection bias is optional.
      bool projecton_tensors_consistent = ((!m_ProjectionWeights && !m_ProjectionBias &&
                                          !m_Parameters.m_ProjectionEnabled)
                                          || (m_ProjectionWeights && !m_ProjectionBias &&
                                          m_Parameters.m_ProjectionEnabled)
                                          || (m_ProjectionWeights && m_ProjectionBias &&
                                          m_Parameters.m_ProjectionEnabled));
      if (!projecton_tensors_consistent)
      {
          throw InvalidArgumentException(descriptorName + ": Projection tensors are inconsistent.");
      }
  
      // The four layer normalization weights either all have values or none of them have values. Additionally, if
      // CIFG is used, input layer normalization weights tensor is omitted and the other layer normalization weights
      // either all have values or none of them have values. Layer normalization is used when the values of all the
      // layer normalization weights are present
      if (m_InputLayerNormWeights)
      {
          ValidateTensorNumDimNumElem(m_InputLayerNormWeights->GetTensorInfo(), 1, n_cell, "InputLayerNormWeights");
      }
      if (m_ForgetLayerNormWeights)
      {
          ValidateTensorNumDimNumElem(m_ForgetLayerNormWeights->GetTensorInfo(), 1, n_cell, "ForgetLayerNormWeights");
      }
      if (m_CellLayerNormWeights)
      {
          ValidateTensorNumDimNumElem(m_CellLayerNormWeights->GetTensorInfo(), 1, n_cell, "CellLayerNormWeights");
      }
      if (m_OutputLayerNormWeights)
      {
          ValidateTensorNumDimNumElem(m_OutputLayerNormWeights->GetTensorInfo(), 1, n_cell, "OutputLayerNormWeights");
      }
  
      if (m_Parameters.m_LayerNormEnabled)
      {
          if (!m_Parameters.m_CifgEnabled)
          {
              if (!m_InputLayerNormWeights)
              {
                  throw InvalidArgumentException(descriptorName + ": Layer normalisation is enabled and CIFG-LSTM is "
                                                 "disabled but InputLayerNormWeights are not present");
              }
              ValidateTensorNumDimNumElem(m_InputLayerNormWeights->GetTensorInfo(),
                                            1, n_cell, "InputLayerNormWeights");
          }
          else if (m_InputLayerNormWeights)
          {
              throw InvalidArgumentException(descriptorName + ":InputLayerNormWeights are present while CIFG is "
                                             "enabled");
          }
  
          ValidatePointer(m_ForgetLayerNormWeights, "Null pointer check layer normalisation enabled",
                          "ForgetLayerNormWeights");
          ValidateTensorNumDimNumElem(m_ForgetLayerNormWeights->GetTensorInfo(), 1, n_cell, "ForgetLayerNormWeights");
  
          ValidatePointer(m_OutputLayerNormWeights, "Null pointer check layer normalisation enabled",
                          "OutputLayerNormWeights");
          ValidateTensorNumDimNumElem(m_OutputLayerNormWeights->GetTensorInfo(), 1, n_cell, "OutputLayerNormWeights");
  
          ValidatePointer(m_CellLayerNormWeights, "Null pointer check layer normalisation enabled",
                          "CellLayerNormWeights");
          ValidateTensorNumDimNumElem(m_CellLayerNormWeights->GetTensorInfo(), 1, n_cell, "CellLayerNormWeights");
      }
      else if (m_InputLayerNormWeights || m_ForgetLayerNormWeights || m_OutputLayerNormWeights || m_CellLayerNormWeights)
      {
          throw InvalidArgumentException(descriptorName + ": Layer normalisation is disabled but one or more layer "
                                         "normalisation weights are present.");
      }
  }
  
  void ConvertBf16ToFp32QueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ConvertBf16ToFp32QueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (inputTensorInfo.GetDataType() != DataType::BFloat16)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor type must be BFloat16.");
      }
  
      if (outputTensorInfo.GetDataType() != DataType::Float32)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Float32.");
      }
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void ConvertFp32ToBf16QueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ConvertFp32ToBf16QueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (inputTensorInfo.GetDataType() != DataType::Float32)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor type must be Float32.");
      }
  
      if (outputTensorInfo.GetDataType() != DataType::BFloat16)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be BFloat16.");
      }
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void ConvertFp32ToFp16QueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ConvertFp32ToFp16QueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (inputTensorInfo.GetDataType() != DataType::Float32)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor type must be Float32.");
      }
  
      if (outputTensorInfo.GetDataType() != DataType::Float16)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Float16.");
      }
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void ConvertFp16ToFp32QueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ConvertFp16ToFp32QueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (inputTensorInfo.GetDataType() != DataType::Float16)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor type must be Float16.");
      }
  
      if (outputTensorInfo.GetDataType() != DataType::Float32)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Float32.");
      }
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void DivisionQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"DivisionQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  }
  
  void SubtractionQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"SubtractionQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32,
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  }
  
  void MaximumQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"MaximumQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  }
  
  void MeanQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"MeanQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      // First check if input tensor data type is supported, then
      // check if this data type matches the output tensor data type
      ValidateDataTypes(inputTensorInfo,  supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      if (m_Parameters.m_KeepDims)
      {
          ValidateTensorNumDimensions(outputTensorInfo, descriptorName, inputTensorInfo.GetNumDimensions(), "output");
      }
      else if (m_Parameters.m_Axis.empty())
      {
          ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 1, "output");
      }
      else
      {
          unsigned int outputDim =
              inputTensorInfo.GetNumDimensions() - armnn::numeric_cast<unsigned int>(m_Parameters.m_Axis.size());
          ValidateTensorNumDimensions(outputTensorInfo,
                                      descriptorName,
                                      outputDim > 0 ? outputDim : 1,
                                      "output");
      }
  }
  
  void PadQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"PadQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      // input and output should have the same number of dimensions
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, inputTensorInfo.GetNumDimensions(), "output");
  
      // there should be entry in the pad list for each dimension in the input tensor
      if (m_Parameters.m_PadList.size() != inputTensorInfo.GetNumDimensions()) {
          throw InvalidArgumentException(descriptorName + ":Pad List should contain the same number of entries "
                                         "as there are dimensions in the input tensor that is " +
                                         std::to_string(inputTensorInfo.GetNumDimensions()) + " entries " +
                                         " not " + std::to_string(m_Parameters.m_PadList.size()) + " entries.");
      }
  }
  
  void QuantizeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"QuantizeQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QSymmS8,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
  
      if (!IsQuantizedType(outputTensorInfo.GetDataType()))
      {
          throw InvalidArgumentException(descriptorName + ": Output of quantized layer must be quantized type.");
      }
  }
  
  void BatchToSpaceNdQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"BatchToSpaceNdQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void StridedSliceQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"StridedSliceQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      ValidateTensorQuantizationSpace(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      const uint32_t rank = inputTensorInfo.GetNumDimensions();
      if (rank > 4)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensors with rank greater than 4 are not supported.");
      }
  
      // Begin, End & Stride length must be of rank(input0)
      if (m_Parameters.m_Begin.size() != rank)
      {
          throw InvalidArgumentException(descriptorName + ": Begin length must be of rank " + std::to_string(rank));
      }
  
      if (m_Parameters.m_End.size() != rank)
      {
          throw InvalidArgumentException(descriptorName + ": End length must be of rank " + std::to_string(rank));
      }
  
      if (m_Parameters.m_Stride.size() != rank)
      {
          throw InvalidArgumentException(descriptorName + ": Stride length must be of rank " + std::to_string(rank));
      }
  
      // Stride entries must be non-zero
      for (auto& stride : m_Parameters.m_Stride)
      {
          if (stride == 0)
          {
              throw InvalidArgumentException(descriptorName + ": Stride entries must be non-zero.");
          }
      }
  }
  
  void MinimumQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"MinimumQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  }
  
  void DebugQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"DebugQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  }
  
  void EqualQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"EqualQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  
      if (outputTensorInfo.GetDataType() != DataType::Boolean)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Boolean.");
      }
  }
  
  void GreaterQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"GreaterQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  
      if (outputTensorInfo.GetDataType() != DataType::Boolean)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Boolean.");
      }
  }
  
  void RsqrtQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"RsqrtQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void GatherQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"GatherQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& indicesTensorInfo = workloadInfo.m_InputTensorInfos[1];
      if (indicesTensorInfo.GetDataType() != DataType::Signed32)
      {
          throw InvalidArgumentException(descriptorName + ": Indices tensor type must be Int32.");
      }
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32,
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      unsigned int outputDim  = inputTensorInfo.GetNumDimensions() + indicesTensorInfo.GetNumDimensions() - 1;
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, outputDim, "output");
  }
  
  void DetectionPostProcessQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string& descriptorName{"DetectionPostProcessQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo, descriptorName, 2);
  
      if (workloadInfo.m_OutputTensorInfos.size() != 4)
      {
          throw InvalidArgumentException(descriptorName + ": Requires exactly four outputs. " +
                                         to_string(workloadInfo.m_OutputTensorInfos.size()) + " has been provided.");
      }
  
      if (m_Anchors == nullptr)
      {
          throw InvalidArgumentException(descriptorName + ": Anchors tensor descriptor is missing.");
      }
  
      const TensorInfo& boxEncodingsInfo =  workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& scoresInfo       =  workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& anchorsInfo      = m_Anchors->GetTensorInfo();
  
      const TensorInfo& detectionBoxesInfo   = workloadInfo.m_OutputTensorInfos[0];
      const TensorInfo& detectionClassesInfo = workloadInfo.m_OutputTensorInfos[1];
      const TensorInfo& detectionScoresInfo  = workloadInfo.m_OutputTensorInfos[2];
      const TensorInfo& numDetectionsInfo    = workloadInfo.m_OutputTensorInfos[3];
  
      ValidateTensorNumDimensions(boxEncodingsInfo, descriptorName, 3, "box encodings");
      ValidateTensorNumDimensions(scoresInfo, descriptorName, 3, "scores");
      ValidateTensorNumDimensions(anchorsInfo, descriptorName, 2, "anchors");
  
      const std::vector<DataType> supportedInputTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(boxEncodingsInfo, supportedInputTypes, descriptorName);
      ValidateDataTypes(scoresInfo, supportedInputTypes, descriptorName);
      ValidateDataTypes(anchorsInfo, supportedInputTypes, descriptorName);
  
      ValidateTensorNumDimensions(detectionBoxesInfo, descriptorName, 3, "detection boxes");
      ValidateTensorNumDimensions(detectionScoresInfo, descriptorName, 2, "detection scores");
      ValidateTensorNumDimensions(detectionClassesInfo, descriptorName, 2, "detection classes");
      ValidateTensorNumDimensions(numDetectionsInfo, descriptorName, 1, "num detections");
  
      // NOTE: Output is always Float32 regardless of input type
      ValidateTensorDataType(detectionBoxesInfo, DataType::Float32, descriptorName, "detection boxes");
      ValidateTensorDataType(detectionScoresInfo, DataType::Float32, descriptorName, "detection scores");
      ValidateTensorDataType(detectionClassesInfo, DataType::Float32, descriptorName, "detection classes");
      ValidateTensorDataType(numDetectionsInfo, DataType::Float32, descriptorName, "num detections");
  
      if (m_Parameters.m_NmsIouThreshold <= 0.0f || m_Parameters.m_NmsIouThreshold > 1.0f)
      {
          throw InvalidArgumentException(descriptorName + ": Intersection over union threshold "
                                         "must be positive and less than or equal to 1.");
      }
  
      if (scoresInfo.GetShape()[2] != m_Parameters.m_NumClasses + 1)
      {
          throw InvalidArgumentException(descriptorName + ": Number of classes with background "
                                         "should be equal to number of classes + 1.");
      }
  }
  
  void DequantizeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string& descriptorName{"DequantizeQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      if (!IsQuantizedType(inputTensorInfo.GetDataType()))
      {
          throw InvalidArgumentException(descriptorName + ": Input to dequantize layer must be quantized type.");
      }
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16
      };
  
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  }
  
  void MergeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string& descriptorName{"MergeQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorShapesMatch(inputTensorInfo0, inputTensorInfo1, descriptorName, "input_0", "input_1");
      ValidateTensorShapesMatch(inputTensorInfo0, outputTensorInfo, descriptorName, "input_0", "output");
  
      ValidateTensorDataTypesMatch(inputTensorInfo0, inputTensorInfo1, descriptorName, "input_0", "input_1");
      ValidateTensorDataTypesMatch(inputTensorInfo0, outputTensorInfo, descriptorName, "input_0", "output");
  }
  
  void SwitchQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string& descriptorName{"SwitchQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 2);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
  
      const TensorInfo& outputTensorInfo0 = workloadInfo.m_OutputTensorInfos[0];
      const TensorInfo& outputTensorInfo1 = workloadInfo.m_OutputTensorInfos[1];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(inputTensorInfo1, supportedTypes, descriptorName);
  
      ValidateDataTypes(outputTensorInfo0, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo1, supportedTypes, descriptorName);
  
      ValidateTensorShapesMatch(inputTensorInfo0,
                                outputTensorInfo0,
                                descriptorName,
                                "input_0",
                                "output_0");
  
      ValidateTensorShapesMatch(inputTensorInfo0,
                                outputTensorInfo1,
                                descriptorName,
                                "input_0",
                                "output_1");
  }
  
  void PreCompiledQueueDescriptor::Validate(const WorkloadInfo& /*workloadInfo*/) const
  {
      // This is internally generated so it should not need validation.
  }
  
  void PreluQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string& descriptorName{"PreluQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& alphaTensorInfo  = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateDataTypes(alphaTensorInfo, supportedTypes, descriptorName);
  
      ValidateDataTypes(outputTensorInfo, supportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputTensorInfo, alphaTensorInfo,  descriptorName, "input", "alpha");
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "ouptut");
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo,
                                         alphaTensorInfo,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input",
                                         "alpha");
  }
  
  void TransposeConvolution2dQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"TransposeConvolution2dQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 4, "output");
  
      ValidatePointer(m_Weight, descriptorName, "weight");
  
      const TensorInfo& weightTensorInfo = m_Weight->GetTensorInfo();
      ValidateTensorNumDimensions(weightTensorInfo, descriptorName, 4, "weight");
  
      ValidateWeightDataType(inputTensorInfo, weightTensorInfo, descriptorName);
  
      Optional<TensorInfo> optionalBiasTensorInfo;
      if (m_Parameters.m_BiasEnabled)
      {
          ValidatePointer(m_Bias, descriptorName, "bias");
  
          optionalBiasTensorInfo = MakeOptional<TensorInfo>(m_Bias->GetTensorInfo());
          const TensorInfo& biasTensorInfo = optionalBiasTensorInfo.value();
  
          ValidateTensorDataType(biasTensorInfo, GetBiasDataType(inputTensorInfo.GetDataType()), descriptorName, "bias");
          ValidateBiasTensorQuantization(biasTensorInfo, inputTensorInfo, weightTensorInfo, descriptorName);
      }
  
      ValidatePerAxisQuantization(inputTensorInfo,
                                  outputTensorInfo,
                                  weightTensorInfo,
                                  optionalBiasTensorInfo,
                                  descriptorName);
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void TransposeQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"TransposeQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const PermutationVector& mapping = m_Parameters.m_DimMappings;
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputTensorInfo,  descriptorName, mapping.GetSize(), "input");
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, mapping.GetSize(), "output");
  
      for (unsigned int i = 0u; i < mapping.GetSize(); ++i)
      {
          if (inputTensorInfo.GetShape()[mapping[i]] != outputTensorInfo.GetShape()[i])
          {
              throw InvalidArgumentException(descriptorName + ": src dimension " + to_string(mapping[i]) +
                                             " (=" + to_string(inputTensorInfo.GetShape()[mapping[i]]) + ") " +
                                             "must match dst dimension " + to_string(i) +
                                             " (=" + to_string(outputTensorInfo.GetShape()[i]) + ")");
          }
      }
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void QLstmQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"QLstmQueueDescriptor"};
  
      // Validate number of inputs/outputs
      ValidateNumInputs(workloadInfo,  descriptorName, 3);
      ValidateNumOutputs(workloadInfo, descriptorName, 3);
  
      // Input/output tensor info
      auto inputInfo = workloadInfo.m_InputTensorInfos[0];
      auto outputStateInInfo = workloadInfo.m_InputTensorInfos[1];
      auto cellStateInInfo = workloadInfo.m_InputTensorInfos[2];
  
      auto outputStateOutInfo = workloadInfo.m_OutputTensorInfos[0];
      auto cellStateOutInfo = workloadInfo.m_OutputTensorInfos[1];
      auto outputInfo = workloadInfo.m_OutputTensorInfos[2];
  
      // Supported types for various tensors in QLSTM
      std::vector<DataType> inputOutputSupportedTypes =
      {
          DataType::QAsymmS8
      };
  
      std::vector<DataType> cellStateSupportedTypes =
      {
          DataType::QSymmS16
      };
  
      std::vector<DataType> weightsSupportedTypes =
      {
          DataType::QSymmS8
      };
  
      std::vector<DataType> layerNormPeepholeWeightsSupportedTypes =
      {
          DataType::QSymmS16
      };
  
      std::vector<DataType> biasSupportedTypes =
      {
          DataType::Signed32
      };
  
      // Validate types of input/output tensors
      ValidateDataTypes(inputInfo, inputOutputSupportedTypes, descriptorName);
      ValidateDataTypes(outputStateInInfo, inputOutputSupportedTypes, descriptorName);
      ValidateDataTypes(cellStateInInfo, cellStateSupportedTypes, descriptorName);
  
      ValidateDataTypes(outputStateOutInfo, inputOutputSupportedTypes, descriptorName);
      ValidateDataTypes(cellStateOutInfo, cellStateSupportedTypes, descriptorName);
      ValidateDataTypes(outputInfo, inputOutputSupportedTypes, descriptorName);
  
      // Validate matching types of input/output tensors
      ValidateTensorDataTypesMatch(inputInfo, outputStateInInfo, descriptorName, "input", "outputStateIn");
      ValidateTensorDataTypesMatch(outputStateInInfo, outputStateOutInfo, descriptorName,
                                   "outputStateIn", "outputStateOut");
      ValidateTensorDataTypesMatch(cellStateInInfo, cellStateOutInfo, descriptorName, "cellStateIn", "cellStateOut");
  
      // Infer number of batches, number of units, input size and output size from tensor dimensions
      const uint32_t numBatches = inputInfo.GetShape()[0];
      const uint32_t inputSize  = inputInfo.GetShape()[1];
      const uint32_t outputSize = outputStateInInfo.GetShape()[1];
      const uint32_t numUnits = cellStateInInfo.GetShape()[1];
  
      // Validate number of dimensions and number of elements for input/output tensors
      ValidateTensorNumDimNumElem(inputInfo, 2, (numBatches * inputSize), descriptorName + " input");
      ValidateTensorNumDimNumElem(outputStateInInfo, 2, (numBatches * outputSize), descriptorName + " outputStateIn");
      ValidateTensorNumDimNumElem(cellStateInInfo, 2, (numBatches * numUnits), descriptorName + " cellStateIn");
  
      ValidateTensorNumDimNumElem(outputStateOutInfo, 2, (numBatches * outputSize), descriptorName + " outputStateOut");
      ValidateTensorNumDimNumElem(cellStateOutInfo, 2, (numBatches * numUnits), descriptorName + " cellStateOut");
      ValidateTensorNumDimNumElem(outputInfo, 2, (numBatches * outputSize), descriptorName + " output");
  
      // Validate number of dimensions and number of elements for MANDATORY weight tensors
      ValidatePointer(m_InputToForgetWeights, descriptorName, "InputToForgetWeights");
      auto inputToForgetWeightsInfo = m_InputToForgetWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToForgetWeightsInfo, 2, (numUnits * inputSize), " InputToForgetWeights");
  
      ValidatePointer(m_InputToCellWeights, descriptorName, "InputToCellWeights");
      auto inputToCellWeightsInfo = m_InputToCellWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToCellWeightsInfo, 2, (numUnits * inputSize), " InputToCellWeights");
  
      ValidatePointer(m_InputToOutputWeights, descriptorName, "InputToOutputWeights");
      auto inputToOutputWeightsInfo = m_InputToOutputWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToOutputWeightsInfo, 2, (numUnits * inputSize), " InputToOutputWeights");
  
      ValidatePointer(m_RecurrentToForgetWeights, descriptorName, "RecurrentToForgetWeights");
      auto recurrentToForgetWeightsInfo = m_RecurrentToForgetWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToForgetWeightsInfo, 2, (numUnits * outputSize),
                                  " RecurrentToForgetWeights");
  
      ValidatePointer(m_RecurrentToCellWeights, descriptorName, "RecurrentToCellWeights");
      auto recurrentToCellWeightsInfo = m_RecurrentToCellWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToCellWeightsInfo, 2, (numUnits * outputSize), " RecurrentToCellWeights");
  
      ValidatePointer(m_RecurrentToOutputWeights, descriptorName, "RecurrentToOutputWeights");
      auto recurrentToOutputWeightsInfo = m_RecurrentToOutputWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToOutputWeightsInfo, 2, (numUnits * outputSize), " RecurrentToCellWeights");
  
      // Validate data types for MANDATORY weights tensors (all should match each other)
      ValidateDataTypes(inputToForgetWeightsInfo, weightsSupportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, inputToCellWeightsInfo, descriptorName,
                                   "inputToForgetWeights", "inputToCellWeights");
      ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, inputToOutputWeightsInfo, descriptorName,
                                   "inputToForgetWeights", "inputToOutputWeights");
  
      ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, recurrentToForgetWeightsInfo, descriptorName,
                                   "inputToForgetWeights", "recurrentToForgeteights");
      ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, recurrentToCellWeightsInfo, descriptorName,
                                   "inputToForgetWeights", "recurrentToCellWeights");
      ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, recurrentToOutputWeightsInfo, descriptorName,
                                   "inputToForgetWeights", "recurrentToOutputWeights");
  
      // Validate number of dimensions and number of elements for MANDATORY bias tensors
      ValidatePointer(m_ForgetGateBias, descriptorName, "ForgetGateBias");
      auto forgetGateBiasInfo = m_ForgetGateBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(forgetGateBiasInfo, 1, numUnits, " ForgetGateBias");
  
      ValidatePointer(m_CellBias, descriptorName, "CellBias");
      auto cellBiasInfo = m_CellBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(cellBiasInfo, 1, numUnits, " CellBias");
  
      ValidatePointer(m_OutputGateBias, descriptorName, "OutputGateBias");
      auto outputGateBiasInfo = m_OutputGateBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(outputGateBiasInfo, 1, numUnits, " OutputGateBias");
  
      // Validate data types for MANDATORY bias tensors
      ValidateDataTypes(forgetGateBiasInfo, biasSupportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(forgetGateBiasInfo, cellBiasInfo, descriptorName,
                                   "forgetGateBias", "cellBias");
      ValidateTensorDataTypesMatch(forgetGateBiasInfo, outputGateBiasInfo, descriptorName,
                                   "forgetGateBias", "outputGateBias");
  
      // Validate OPTIONAL params: CIFG (inputToInputWeights, recurrentToInputWeights, inputGateBias)
      const bool allCifgParamsPresentOrNot = ((m_InputToInputWeights && m_RecurrentToInputWeights && m_InputGateBias &&
                                               !m_Parameters.m_CifgEnabled) ||
                                              (!m_InputToInputWeights && !m_RecurrentToInputWeights &&
                                               !m_InputGateBias && m_Parameters.m_CifgEnabled));
  
      if (!allCifgParamsPresentOrNot)
      {
          throw InvalidArgumentException(descriptorName +
                  ": InputToInputWeights, RecurrentToInputWeights and InputGateBias must either all be present "
                  "(CIFG disabled) or not be present at all (CIFG enabled). m_Parameters.m_CifgEnabled should be "
                  "set appropriately.");
      }
  
      if (!m_Parameters.m_CifgEnabled)
      {
          // Validate number of dimensions and number of elements
          auto inputToInputWeightsInfo = m_InputToInputWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(inputToInputWeightsInfo, 2, (numUnits * inputSize), " InputToInputWeights");
  
          auto recurrentToInputWeightsInfo = m_RecurrentToInputWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(recurrentToInputWeightsInfo, 2, (numUnits * outputSize),
                                      " RecurrentToInputWeights");
  
          auto inputGateBiasInfo = m_InputGateBias->GetTensorInfo();
          ValidateTensorNumDimNumElem(inputGateBiasInfo, 1, numUnits, " InputGateBias");
  
          // Validate data types
          ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, inputToInputWeightsInfo, descriptorName,
                                       "inputToForgetWeights", "inputToInputWeights");
          ValidateTensorDataTypesMatch(inputToForgetWeightsInfo, recurrentToInputWeightsInfo, descriptorName,
                                       "inputToForgetWeights", "recurrentToInputWeights");
          ValidateTensorDataTypesMatch(forgetGateBiasInfo, inputGateBiasInfo, descriptorName,
                                       "forgetGateBias", "inputGateBias");
      }
  
      // Validate OPTIONAL params: Peephole (cellToInputWeights, cellToForgetWeights, cellToOutputWeights)
      bool allPeepholeWeightsPresentOrNot =
              (((m_CellToInputWeights || m_Parameters.m_CifgEnabled) && m_CellToForgetWeights
                && m_CellToOutputWeights && m_Parameters.m_PeepholeEnabled)
               || (!m_CellToInputWeights && !m_CellToForgetWeights
                   && !m_CellToOutputWeights && !m_Parameters.m_PeepholeEnabled));
  
      if (!allPeepholeWeightsPresentOrNot)
      {
          throw InvalidArgumentException(descriptorName +
                  ": CellToInputWeights, CellToForgetWeights and CellToOutputWeights should all be present (Peephole "
                  "enabled) or not be present at all (Peephole disabled). CellToInputWeights should only be present "
                  "when Peephole is enabled and CIFG is disabled. m_Parameters.m_PeepholeEnabled should be set "
                  "appropriately.");
      }
  
      if (m_Parameters.m_PeepholeEnabled)
      {
          auto cellToForgetWeightsInfo = m_CellToForgetWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(cellToForgetWeightsInfo, 1, numUnits, " cellToForgetWeights");
          ValidateDataTypes(cellToForgetWeightsInfo, layerNormPeepholeWeightsSupportedTypes, descriptorName);
  
          auto cellToOutputWeightsInfo = m_CellToOutputWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(cellToOutputWeightsInfo, 1, numUnits, " cellToOutputWeights");
          ValidateTensorDataTypesMatch(cellToForgetWeightsInfo, cellToOutputWeightsInfo, descriptorName,
                                       "cellToForgetWeight", "cellToOutputWeights");
  
          if (!m_Parameters.m_CifgEnabled)
          {
              auto cellToInputWeightsInfo = m_CellToInputWeights->GetTensorInfo();
              ValidateTensorNumDimNumElem(cellToInputWeightsInfo, 1, numUnits, " cellToInputWeights");
              ValidateTensorDataTypesMatch(cellToForgetWeightsInfo, cellToInputWeightsInfo, descriptorName,
                                           "cellToForgetWeights", "cellToInputWeights");
          }
      }
  
      // Validate OPTIONAL params: Layer Norm Weights
      bool allLayerNormWeightsPresentOrNot =
              (((m_InputLayerNormWeights || m_Parameters.m_CifgEnabled) && m_ForgetLayerNormWeights
                && m_CellLayerNormWeights && m_OutputLayerNormWeights && m_Parameters.m_LayerNormEnabled)
               || (!m_InputLayerNormWeights && !m_ForgetLayerNormWeights && !m_CellLayerNormWeights
                   && !m_OutputLayerNormWeights && !m_Parameters.m_LayerNormEnabled));
  
      if (!allLayerNormWeightsPresentOrNot)
      {
          throw InvalidArgumentException(descriptorName +
                                         ": InputLayerNormWeights, ForgetLayerNormWeights, m_OutputLayerNormWeights "
                                         "and CellLayerNormWeights should all be present (Layer Norm enabled) or not "
                                         "be present at all (Layer Norm disabled). InputLayerNormWeights should "
                                         "only be present when Layer Norm is enabled and CIFG is disabled. "
                                         "m_Parameters.m_LayerNormEnabled should be set appropriately.");
      }
  
      if (m_Parameters.m_LayerNormEnabled)
      {
          auto forgetLayerNormWeightsInfo = m_ForgetLayerNormWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(forgetLayerNormWeightsInfo, 1, numUnits, " forgetLayerNormWeights");
          ValidateDataTypes(forgetLayerNormWeightsInfo, layerNormPeepholeWeightsSupportedTypes, descriptorName);
  
          auto cellLayerNormWeightsInfo = m_CellLayerNormWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(cellLayerNormWeightsInfo, 1, numUnits, " cellLayerNormWeights");
          ValidateTensorDataTypesMatch(forgetLayerNormWeightsInfo, cellLayerNormWeightsInfo, descriptorName,
                                       "forgetLayerNormWeights", "cellLayerNormWeights");
  
          auto outputLayerNormWeightsInfo = m_OutputLayerNormWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(outputLayerNormWeightsInfo, 1, numUnits, " outputLayerNormWeights");
          ValidateTensorDataTypesMatch(forgetLayerNormWeightsInfo, outputLayerNormWeightsInfo, descriptorName,
                                       "forgetLayerNormWeights", "outputLayerNormWeights");
  
          if (!m_Parameters.m_CifgEnabled)
          {
              auto inputLayerNormWeightsInfo = m_InputLayerNormWeights->GetTensorInfo();
              ValidateTensorNumDimNumElem(inputLayerNormWeightsInfo, 1, numUnits, " inputLayerNormWeights");
              ValidateTensorDataTypesMatch(forgetLayerNormWeightsInfo, inputLayerNormWeightsInfo, descriptorName,
                                           "forgetLayerNormWeights", "inputLayerNormWeights");
          }
      }
  
      // Validate OPTIONAL params: Projection (projectionWeights, projectionBias)
      bool correctProjectionTensorsPresent =
              ((!m_ProjectionWeights && !m_ProjectionBias && !m_Parameters.m_ProjectionEnabled) ||
              (m_ProjectionWeights && !m_ProjectionBias && m_Parameters.m_ProjectionEnabled) ||
              (m_ProjectionWeights && m_ProjectionBias && m_Parameters.m_ProjectionEnabled));
  
      if (!correctProjectionTensorsPresent)
      {
          throw InvalidArgumentException(descriptorName +
                                         ": If projection is enabled, ProjectionWeights should be present and "
                                         "ProjectionBias is optional. If projection is disabled, neither "
                                         "ProjectionWeights nor ProjectionBias should be present.");
      }
  
      if (m_Parameters.m_ProjectionEnabled)
      {
          auto projectionWeightsInfo = m_ProjectionWeights->GetTensorInfo();
          ValidateTensorNumDimNumElem(projectionWeightsInfo, 2, (numUnits * outputSize), "ProjectionWeights");
          ValidateDataTypes(projectionWeightsInfo, weightsSupportedTypes, descriptorName);
  
          if (m_ProjectionBias)
          {
              auto projectionBiasInfo = m_ProjectionBias->GetTensorInfo();
              ValidateTensorNumDimNumElem(projectionBiasInfo, 1, outputSize, "ProjectionBias");
              ValidateDataTypes(projectionBiasInfo, biasSupportedTypes, descriptorName);
          }
  
      }
      else if ((outputInfo.GetQuantizationScale() != m_Parameters.m_HiddenStateScale) &&
                outputInfo.GetQuantizationOffset() != m_Parameters.m_HiddenStateZeroPoint) {
          throw InvalidArgumentException(descriptorName +
                                         ": If projection is disabled, output quantization info (scale, offset) "
                                         "should match HiddenStateScale and HiddenStateZeroPoint.");
      }
  
  }
  
  void QuantizedLstmQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"QuantizedLstmQueueDescriptor"};
  
      // Validate number of inputs/outputs
      ValidateNumInputs(workloadInfo,  descriptorName, 3);
      ValidateNumOutputs(workloadInfo, descriptorName, 2);
  
      // Input/output tensor infos
      auto inputInfo = workloadInfo.m_InputTensorInfos[0];
      auto cellStateInInfo = workloadInfo.m_InputTensorInfos[1];
      auto outputStateInInfo = workloadInfo.m_InputTensorInfos[2];
  
      auto cellStateOutInfo = workloadInfo.m_OutputTensorInfos[0];
      auto outputStateOutInfo = workloadInfo.m_OutputTensorInfos[1];
  
      std::vector<DataType> inputOutputSupportedTypes =
      {
          DataType::QAsymmU8
      };
  
      std::vector<DataType> cellStateSupportedTypes =
      {
          DataType::QSymmS16
      };
  
      std::vector<DataType> weightsSupportedTypes =
      {
          DataType::QAsymmU8
      };
  
      std::vector<DataType> biasSupportedTypes =
      {
          DataType::Signed32
      };
  
      // Validate types of input/output tensors
      ValidateDataTypes(inputInfo, inputOutputSupportedTypes, descriptorName);
      ValidateDataTypes(cellStateInInfo, cellStateSupportedTypes, descriptorName);
      ValidateDataTypes(outputStateInInfo, inputOutputSupportedTypes, descriptorName);
  
      ValidateDataTypes(cellStateOutInfo, cellStateSupportedTypes, descriptorName);
      ValidateDataTypes(outputStateOutInfo, inputOutputSupportedTypes, descriptorName);
  
      // Validate matching types of input/output tensors
      ValidateTensorDataTypesMatch(inputInfo, outputStateInInfo, descriptorName, "input", "outputStateIn");
      ValidateTensorDataTypesMatch(outputStateInInfo, outputStateOutInfo, descriptorName,
                                   "outputStateIn", "outputStateOut");
      ValidateTensorDataTypesMatch(cellStateInInfo, cellStateOutInfo, descriptorName, "cellStateIn", "cellStateOut");
  
      // Validate matching quantization info for input/output tensors
      ValidateTensorQuantizationSpace(inputInfo, outputStateInInfo, descriptorName, "input", "outputStateIn");
      ValidateTensorQuantizationSpace(inputInfo, outputStateOutInfo, descriptorName, "input", "outputStateOut");
      ValidateTensorQuantizationSpace(cellStateInInfo, cellStateOutInfo, descriptorName, "cellStateIn", "cellStateOut");
  
      // Infer number of batches, input size and output size from tensor dimensions
      const uint32_t numBatches = inputInfo.GetShape()[0];
      const uint32_t inputSize  = inputInfo.GetShape()[1];
      const uint32_t outputSize = cellStateInInfo.GetShape()[1];
  
      // Validate number of dimensions and number of elements for input/output tensors
      ValidateTensorNumDimNumElem(inputInfo, 2, (numBatches * inputSize), descriptorName + " input");
      ValidateTensorNumDimNumElem(cellStateInInfo, 2, (numBatches * outputSize), descriptorName + " cellStateIn");
      ValidateTensorNumDimNumElem(outputStateInInfo, 2, (numBatches * outputSize), descriptorName + " outputStateIn");
      ValidateTensorNumDimNumElem(cellStateOutInfo, 2, (numBatches * outputSize), descriptorName + " cellStateOut");
      ValidateTensorNumDimNumElem(outputStateOutInfo, 2, (numBatches * outputSize), descriptorName + " outputStateOut");
  
      // Validate number of dimensions and number of elements for weights tensors
      ValidatePointer(m_InputToInputWeights, descriptorName, "InputToInputWeights");
      auto inputToInputWeightsInfo = m_InputToInputWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToInputWeightsInfo, 2, (outputSize * inputSize), " InputToInputWeights");
  
      ValidatePointer(m_InputToForgetWeights, descriptorName, "InputToForgetWeights");
      auto inputToForgetWeightsInfo = m_InputToForgetWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToForgetWeightsInfo, 2, (outputSize * inputSize), " InputToForgetWeights");
  
      ValidatePointer(m_InputToCellWeights, descriptorName, "InputToCellWeights");
      auto inputToCellWeightsInfo = m_InputToCellWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToCellWeightsInfo, 2, (outputSize * inputSize), " InputToCellWeights");
  
      ValidatePointer(m_InputToOutputWeights, descriptorName, "InputToOutputWeights");
      auto inputToOutputWeightsInfo = m_InputToOutputWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputToOutputWeightsInfo, 2, (outputSize * inputSize), " InputToOutputWeights");
  
      ValidatePointer(m_RecurrentToInputWeights, descriptorName, "RecurrentToInputWeights");
      auto recurrentToInputWeightsInfo = m_RecurrentToInputWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToInputWeightsInfo, 2, (outputSize * outputSize), " RecurrentToInputWeights");
  
      ValidatePointer(m_RecurrentToForgetWeights, descriptorName, "RecurrentToForgetWeights");
      auto recurrentToForgetWeightsInfo = m_RecurrentToForgetWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToForgetWeightsInfo, 2, (outputSize * outputSize),
                                  " RecurrentToForgetWeights");
  
      ValidatePointer(m_RecurrentToCellWeights, descriptorName, "RecurrentToCellWeights");
      auto recurrentToCellWeightsInfo = m_RecurrentToCellWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToCellWeightsInfo, 2, (outputSize * outputSize), " RecurrentToCellWeights");
  
      ValidatePointer(m_RecurrentToOutputWeights, descriptorName, "RecurrentToOutputWeights");
      auto recurrentToOutputWeightsInfo = m_RecurrentToOutputWeights->GetTensorInfo();
      ValidateTensorNumDimNumElem(recurrentToOutputWeightsInfo, 2, (outputSize * outputSize), " RecurrentToCellWeights");
  
      // Validate data types for weights tensors (all should match each other)
      ValidateDataTypes(inputToInputWeightsInfo, weightsSupportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, inputToForgetWeightsInfo, descriptorName,
                                   "inputToInputWeights", "inputToForgetWeights");
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, inputToCellWeightsInfo, descriptorName,
                                   "inputToInputWeights", "inputToCellWeights");
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, inputToOutputWeightsInfo, descriptorName,
                                   "inputToInputWeights", "inputToOutputWeights");
  
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, recurrentToInputWeightsInfo, descriptorName,
                                   "inputToInputWeights", "recurrentToInputWeights");
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, recurrentToForgetWeightsInfo, descriptorName,
                                   "inputToInputWeights", "recurrentToForgeteights");
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, recurrentToCellWeightsInfo, descriptorName,
                                   "inputToInputWeights", "recurrentToCellWeights");
      ValidateTensorDataTypesMatch(inputToInputWeightsInfo, recurrentToOutputWeightsInfo, descriptorName,
                                   "inputToInputWeights", "recurrentToOutputWeights");
  
      // Validate matching quantization info for weight tensors (all should match each other)
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, inputToForgetWeightsInfo,
                                      descriptorName, "inputToInputWeights", "inputToForgetWeights");
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, inputToCellWeightsInfo,
                                      descriptorName, "inputToInputWeights", "inputToCellWeights");
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, inputToOutputWeightsInfo,
                                      descriptorName, "inputToInputWeights", "inputToOutputWeights");
  
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, recurrentToInputWeightsInfo,
                                      descriptorName, "inputToInputWeights", "recurrentToInputWeights");
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, recurrentToForgetWeightsInfo,
                                      descriptorName, "inputToInputWeights", "recurrentToForgetWeights");
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, recurrentToCellWeightsInfo,
                                      descriptorName, "inputToInputWeights", "recurrentToCellWeights");
      ValidateTensorQuantizationSpace(inputToInputWeightsInfo, recurrentToOutputWeightsInfo,
                                      descriptorName, "inputToInputWeights", "recurrentToOutputWeights");
  
      // Validate number of dimensions and number of elements in bias tensors
      ValidatePointer(m_InputGateBias, descriptorName, "InputGateBias");
      auto inputGateBiasInfo = m_InputGateBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(inputGateBiasInfo, 1, outputSize, " InputGateBias");
  
      ValidatePointer(m_ForgetGateBias, descriptorName, "ForgetGateBias");
      auto forgetGateBiasInfo = m_ForgetGateBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(forgetGateBiasInfo, 1, outputSize, " ForgetGateBias");
  
      ValidatePointer(m_CellBias, descriptorName, "CellBias");
      auto cellBiasInfo = m_CellBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(cellBiasInfo, 1, outputSize, " CellBias");
  
      ValidatePointer(m_OutputGateBias, descriptorName, "OutputGateBias");
      auto outputGateBiasInfo = m_OutputGateBias->GetTensorInfo();
      ValidateTensorNumDimNumElem(outputGateBiasInfo, 1, outputSize, " OutputGateBias");
  
      // Validate data types for bias tensors (all should match each other)
      ValidateDataTypes(inputGateBiasInfo, biasSupportedTypes, descriptorName);
  
      ValidateTensorDataTypesMatch(inputGateBiasInfo, forgetGateBiasInfo, descriptorName,
                                   "inputGateBias", "forgetGateBias");
      ValidateTensorDataTypesMatch(inputGateBiasInfo, cellBiasInfo, descriptorName,
                                   "inputGateBias", "cellBias");
      ValidateTensorDataTypesMatch(inputGateBiasInfo, outputGateBiasInfo, descriptorName,
                                   "inputGateBias", "outputGateBias");
  
      // Validate bias tensor quantization info
      ValidateBiasTensorQuantization(inputGateBiasInfo, inputInfo, inputToInputWeightsInfo, descriptorName);
      ValidateBiasTensorQuantization(forgetGateBiasInfo, inputInfo, inputToInputWeightsInfo, descriptorName);
      ValidateBiasTensorQuantization(cellBiasInfo, inputInfo, inputToInputWeightsInfo, descriptorName);
      ValidateBiasTensorQuantization(outputGateBiasInfo, inputInfo, inputToInputWeightsInfo, descriptorName);
  }
  
  void AbsQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"AbsQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void SliceQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"SliceQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      const unsigned int rank = inputTensorInfo.GetNumDimensions();
      if (rank > 4)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensors with rank greater than 4 are not supported.");
      }
  
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, rank, "output");
  
      // Check if m_Begin and m_Size have the expected length
      if (m_Parameters.m_Begin.size() != rank)
      {
          throw InvalidArgumentException(descriptorName +
              ": Length of begin offset descriptor must equal rank " + std::to_string(rank));
      }
      if (m_Parameters.m_Size.size() != rank)
      {
          throw InvalidArgumentException(descriptorName +
              ": Length of size descriptor must equal rank " + std::to_string(rank));
      }
  
      // Check if the shape of the output tensor matches m_Size
      const TensorShape& outputShape = outputTensorInfo.GetShape();
      for (unsigned int i = 0u; i < rank; ++i)
      {
          if (m_Parameters.m_Size[i] != outputShape[i])
          {
              throw InvalidArgumentException(descriptorName + ": Size descriptor does not match output tensor.");
          }
      }
  
      // Check if the sum of begin offset and size in a given dimension
      // does not exceed the size of corresponding input
      const TensorShape& inputShape  = inputTensorInfo.GetShape();
      for(unsigned int i = 0u; i < rank; ++i)
      {
          if (m_Parameters.m_Begin[i] + m_Parameters.m_Size[i] > inputShape[i])
          {
              throw InvalidArgumentException(descriptorName + ": Sum of begin offset and size for dimension " +
                  std::to_string(i) + " exceeds input size.");
          }
      }
  }
  
  void DepthToSpaceQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"DepthToSpaceQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(inputInfo,  descriptorName, 4, "input");
      ValidateTensorNumDimensions(outputInfo, descriptorName, 4, "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float32,
          DataType::Float16,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16
      };
  
      ValidateDataTypes(inputInfo,  supportedTypes, descriptorName);
      ValidateDataTypes(outputInfo, supportedTypes, descriptorName);
  
      ValidateTensorNumElementsMatch(inputInfo, outputInfo, descriptorName, "input", "output");
  
      if (m_Parameters.m_BlockSize == 0)
      {
          throw InvalidArgumentException(descriptorName + ": Block size cannot be 0.");
      }
  
      DataLayoutIndexed dimensionIndices(m_Parameters.m_DataLayout);
      const unsigned int wIndex = dimensionIndices.GetWidthIndex();
      const unsigned int hIndex = dimensionIndices.GetHeightIndex();
      const unsigned int cIndex = dimensionIndices.GetChannelsIndex();
  
      const TensorShape& outputShape = outputInfo.GetShape();
      if (outputShape[hIndex] % m_Parameters.m_BlockSize != 0 || outputShape[wIndex]  % m_Parameters.m_BlockSize != 0)
      {
          throw InvalidArgumentException(descriptorName + ": Output width and height shape"
                                         "must be divisible by block size.");
      }
  
      const TensorShape& inputShape = inputInfo.GetShape();
      if (inputShape[cIndex] % (m_Parameters.m_BlockSize * m_Parameters.m_BlockSize) != 0)
      {
          throw InvalidArgumentException(descriptorName + ": The depth of the input tensor"
                                         "must be divisible by the square of block size." );
      }
  }
  
  void ComparisonQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ComparisonQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  
      if (outputTensorInfo.GetDataType() != DataType::Boolean)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Boolean.");
      }
  }
  
  void ElementwiseUnaryQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ElementwiseUnaryQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorShapesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      std::vector<DataType> logicalSupportedTypes =
      {
          DataType::Boolean
      };
  
      if (m_Parameters.m_Operation == UnaryOperation::LogicalNot)
      {
          ValidateDataTypes(inputTensorInfo, logicalSupportedTypes, descriptorName);
      }
      else
      {
          ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      }
  
  
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  void RankQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"RankQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateTensorNumDimensions(outputTensorInfo, descriptorName, 1, "output");
      ValidateTensorNumElements(outputTensorInfo, descriptorName, 1, "output");
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateDataTypes(outputTensorInfo, { DataType::Signed32 }, descriptorName);
  }
  
  void LogicalBinaryQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"LogicalBinaryQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 2);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo0 = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& inputTensorInfo1 = workloadInfo.m_InputTensorInfos[1];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      ValidateBroadcastTensorShapesMatch(inputTensorInfo0,
                                         inputTensorInfo1,
                                         outputTensorInfo,
                                         descriptorName,
                                         "input_0",
                                         "input_1");
  
      if (inputTensorInfo0.GetDataType() != DataType::Boolean)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor 0 type must be Boolean.");
      }
  
      if (inputTensorInfo1.GetDataType() != DataType::Boolean)
      {
          throw InvalidArgumentException(descriptorName + ": Input tensor 1 type must be Boolean.");
      }
  
      if (outputTensorInfo.GetDataType() != DataType::Boolean)
      {
          throw InvalidArgumentException(descriptorName + ": Output tensor type must be Boolean.");
      }
  }
  
  void ReduceQueueDescriptor::Validate(const WorkloadInfo& workloadInfo) const
  {
      const std::string descriptorName{"ReduceQueueDescriptor"};
  
      ValidateNumInputs(workloadInfo,  descriptorName, 1);
      ValidateNumOutputs(workloadInfo, descriptorName, 1);
  
      const TensorInfo& inputTensorInfo  = workloadInfo.m_InputTensorInfos[0];
      const TensorInfo& outputTensorInfo = workloadInfo.m_OutputTensorInfos[0];
  
      std::vector<DataType> supportedTypes =
      {
          DataType::BFloat16,
          DataType::Float16,
          DataType::Float32,
          DataType::QAsymmS8,
          DataType::QAsymmU8,
          DataType::QSymmS16,
          DataType::Signed32
      };
  
      ValidateDataTypes(inputTensorInfo, supportedTypes, descriptorName);
      ValidateTensorDataTypesMatch(inputTensorInfo, outputTensorInfo, descriptorName, "input", "output");
  }
  
  } // namespace armnn