From 56749a84ee733b995532823d5ecf6e93d2f45fa0 Mon Sep 17 00:00:00 2001 From: Hariharan Seshadri Date: Wed, 10 Apr 2019 22:06:05 -0700 Subject: [PATCH] Implement opset v10 changes for Slice operator (#772) --- .../providers/cpu/cpu_execution_provider.cc | 80 ++- onnxruntime/core/providers/cpu/nn/Unpool.cc | 4 +- onnxruntime/core/providers/cpu/tensor/pad.cc | 8 +- .../core/providers/cpu/tensor/slice.cc | 212 +++++-- onnxruntime/core/providers/cpu/tensor/slice.h | 19 +- onnxruntime/core/providers/cpu/tensor/utils.h | 93 ++- .../providers/cuda/cuda_execution_provider.cc | 8 +- .../core/providers/cuda/tensor/slice.cc | 35 +- .../providers/cpu/tensor/slice_op.test.cc | 583 ++++++++++++++---- .../test/python/onnx_backend_test_series.py | 7 - 10 files changed, 813 insertions(+), 236 deletions(-) diff --git a/onnxruntime/core/providers/cpu/cpu_execution_provider.cc b/onnxruntime/core/providers/cpu/cpu_execution_provider.cc index eee21bb8a8..11e5f8ecce 100644 --- a/onnxruntime/core/providers/cpu/cpu_execution_provider.cc +++ b/onnxruntime/core/providers/cpu/cpu_execution_provider.cc @@ -160,19 +160,19 @@ class ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDoma class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 5, Reshape); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, Shape); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, Size); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, bool, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, float, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, double, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, MLFloat16, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, uint8_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, uint16_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, uint32_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, uint64_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, int8_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, int16_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, int32_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, int64_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, string, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, bool, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, float, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, double, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, MLFloat16, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, uint8_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, uint16_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, uint32_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, uint64_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, int8_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, int16_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, int32_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, int64_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 9, string, Slice); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, SpaceToDepth); class ONNX_OPERATOR_VERSIONED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 1, 4, DepthToSpace); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 2, Split); @@ -247,6 +247,19 @@ class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, QL class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, MatMulInteger); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, ConvInteger); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, QLinearConv); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, bool, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, float, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, double, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, MLFloat16, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, uint8_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, uint16_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, uint32_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, uint64_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, int8_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, int16_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, int32_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, int64_t, Slice); +class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCpuExecutionProvider, kOnnxDomain, 10, string, Slice); void RegisterOnnxOperatorKernels(KernelRegistry& kernel_registry) { kernel_registry.Register(BuildKernelCreateInfo()); @@ -395,21 +408,19 @@ void RegisterOnnxOperatorKernels(KernelRegistry& kernel_registry) { kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); - - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); @@ -484,6 +495,19 @@ void RegisterOnnxOperatorKernels(KernelRegistry& kernel_registry) { kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); } // Forward declarations of ml op kernels diff --git a/onnxruntime/core/providers/cpu/nn/Unpool.cc b/onnxruntime/core/providers/cpu/nn/Unpool.cc index aa16a51f81..7f3f8c0135 100644 --- a/onnxruntime/core/providers/cpu/nn/Unpool.cc +++ b/onnxruntime/core/providers/cpu/nn/Unpool.cc @@ -171,12 +171,12 @@ Status MaxUnpool::Compute(OpKernelContext* context) const { size_t inner_axis = dimension_count - 1; TensorAxisCounters input_counters(*p_tensor); - SliceIterator input(*p_tensor, input_starts, input_extents); + SliceIterator input(*p_tensor, input_starts, input_extents, {}); while (input_counters) { Y_data += alignSkip; { - Y_data = input.CopyInnermostAxis(Y_data); + Y_data = input.CopyInnermostAxisSolitaryInnerStep(Y_data); int64_t prePad = inferredPads[inner_axis]; int64_t postPad = inferredPads[inner_axis + dimension_count]; Y_data += postPad; diff --git a/onnxruntime/core/providers/cpu/tensor/pad.cc b/onnxruntime/core/providers/cpu/tensor/pad.cc index 7ca719f385..c093b4add4 100644 --- a/onnxruntime/core/providers/cpu/tensor/pad.cc +++ b/onnxruntime/core/providers/cpu/tensor/pad.cc @@ -131,7 +131,7 @@ Status Pad::Compute(OpKernelContext* ctx) const { TensorShape output_shape(output_dims); TensorShape input_shape(reshaped_input_dims); - SliceIterator input(input_tensor, input_shape, input_starts, input_extents); + SliceIterator input(input_tensor, input_shape, input_starts, input_extents, {}); // output_shape need to keep original. auto& output_tensor = *ctx->Output(0, output_shape); @@ -155,7 +155,7 @@ Status Pad::Compute(OpKernelContext* ctx) const { output += alignSkip; { float* axisStart = output; - output = input.CopyInnermostAxis(output); + output = input.CopyInnermostAxisSolitaryInnerStep(output); int64_t prePad = reshaped_pad[inner_axis]; int64_t postPad = reshaped_pad[inner_axis + new_dims_count]; @@ -186,7 +186,7 @@ Status Pad::Compute(OpKernelContext* ctx) const { output += alignSkip; { float* axisStart = output; - output = input.CopyInnermostAxis(output); + output = input.CopyInnermostAxisSolitaryInnerStep(output); int64_t prePad = reshaped_pad[inner_axis]; int64_t postPad = reshaped_pad[inner_axis + new_dims_count]; @@ -217,7 +217,7 @@ Status Pad::Compute(OpKernelContext* ctx) const { output += alignSkip; { float* axisStart = output; - output = input.CopyInnermostAxis(output); + output = input.CopyInnermostAxisSolitaryInnerStep(output); int64_t prePad = reshaped_pad[inner_axis]; int64_t postPad = reshaped_pad[inner_axis + new_dims_count]; diff --git a/onnxruntime/core/providers/cpu/tensor/slice.cc b/onnxruntime/core/providers/cpu/tensor/slice.cc index 86e5772a37..ce831909d1 100644 --- a/onnxruntime/core/providers/cpu/tensor/slice.cc +++ b/onnxruntime/core/providers/cpu/tensor/slice.cc @@ -3,31 +3,34 @@ #include "core/providers/cpu/tensor/slice.h" #include "core/providers/cpu/tensor/utils.h" +#include +#include + using namespace ::onnxruntime::common; using namespace std; namespace onnxruntime { -#define ADD_TYPED_SLICE_OP(data_type) \ - ONNX_CPU_OPERATOR_TYPED_KERNEL( \ +#define ADD_TYPED_SLICE_V9_OP(data_type) \ + ONNX_CPU_OPERATOR_VERSIONED_TYPED_KERNEL( \ Slice, \ - 1, \ + 1, 9, \ data_type, \ KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType()), \ Slice); -ADD_TYPED_SLICE_OP(uint8_t); -ADD_TYPED_SLICE_OP(uint16_t); -ADD_TYPED_SLICE_OP(uint32_t); -ADD_TYPED_SLICE_OP(uint64_t); -ADD_TYPED_SLICE_OP(int8_t); -ADD_TYPED_SLICE_OP(int16_t); -ADD_TYPED_SLICE_OP(int32_t); -ADD_TYPED_SLICE_OP(int64_t); -ADD_TYPED_SLICE_OP(float); -ADD_TYPED_SLICE_OP(double); -ADD_TYPED_SLICE_OP(MLFloat16); -ADD_TYPED_SLICE_OP(bool); -ADD_TYPED_SLICE_OP(string); +ADD_TYPED_SLICE_V9_OP(uint8_t); +ADD_TYPED_SLICE_V9_OP(uint16_t); +ADD_TYPED_SLICE_V9_OP(uint32_t); +ADD_TYPED_SLICE_V9_OP(uint64_t); +ADD_TYPED_SLICE_V9_OP(int8_t); +ADD_TYPED_SLICE_V9_OP(int16_t); +ADD_TYPED_SLICE_V9_OP(int32_t); +ADD_TYPED_SLICE_V9_OP(int64_t); +ADD_TYPED_SLICE_V9_OP(float); +ADD_TYPED_SLICE_V9_OP(double); +ADD_TYPED_SLICE_V9_OP(MLFloat16); +ADD_TYPED_SLICE_V9_OP(bool); +ADD_TYPED_SLICE_V9_OP(string); #ifndef DISABLE_CONTRIB_OPS namespace contrib { @@ -57,6 +60,31 @@ ADD_TYPED_DYNAMIC_SLICE_OP(string); } // namespace contrib #endif + +#define ADD_TYPED_SLICE_V10_OP(data_type) \ + ONNX_CPU_OPERATOR_TYPED_KERNEL( \ + Slice, \ + 10, \ + data_type, \ + KernelDefBuilder().TypeConstraint("T", DataTypeImpl::GetTensorType()) \ + .TypeConstraint("Tind", {DataTypeImpl::GetTensorType(), \ + DataTypeImpl::GetTensorType()}), \ + Slice); + +ADD_TYPED_SLICE_V10_OP(uint8_t); +ADD_TYPED_SLICE_V10_OP(uint16_t); +ADD_TYPED_SLICE_V10_OP(uint32_t); +ADD_TYPED_SLICE_V10_OP(uint64_t); +ADD_TYPED_SLICE_V10_OP(int8_t); +ADD_TYPED_SLICE_V10_OP(int16_t); +ADD_TYPED_SLICE_V10_OP(int32_t); +ADD_TYPED_SLICE_V10_OP(int64_t); +ADD_TYPED_SLICE_V10_OP(float); +ADD_TYPED_SLICE_V10_OP(double); +ADD_TYPED_SLICE_V10_OP(MLFloat16); +ADD_TYPED_SLICE_V10_OP(bool); +ADD_TYPED_SLICE_V10_OP(string); + namespace { // std::clamp doesn't exist until C++17 so create a local version template @@ -67,6 +95,7 @@ const T& clamp(const T& v, const T& lo, const T& hi) { } } // namespace +// Slice V1-9 & DynamicSlice Status SliceBase::PrepareForCompute(const std::vector& raw_starts, const std::vector& raw_ends, const std::vector& raw_axes, @@ -75,46 +104,138 @@ Status SliceBase::PrepareForCompute(const std::vector& raw_starts, std::vector& output_dims) const { // Initialize axes to the provided axes attribute or to the default sequence std::vector axes(raw_axes); - if (axes.size() == 0) { + if (axes.empty()) { //axes are omitted, they are set to[0, ..., ndim - 1] axes.resize(starts.size()); std::iota(axes.begin(), axes.end(), 0); } // Iterate through the provided axes and override the start/end ranges + std::unordered_set unique_axes; const auto& dimension_count = input_dimensions.size(); - for (size_t axesIndex = 0; axesIndex < axes.size(); axesIndex++) { - auto axis = axes[axesIndex] < 0 ? axes[axesIndex] + static_cast(dimension_count) : axes[axesIndex]; + for (size_t axis_index = 0, axes_count = axes.size(); axis_index < axes_count; ++axis_index) { + auto axis = axes[axis_index] < 0 ? axes[axis_index] + static_cast(dimension_count) : axes[axis_index]; if (axis >= static_cast(dimension_count) || axis < 0) return Status(ONNXRUNTIME, INVALID_ARGUMENT, "'axes' has an axis outside of the tensor dimension count"); - auto start = raw_starts[axesIndex]; + if (unique_axes.find(axis) != unique_axes.end()) + return Status(ONNXRUNTIME, INVALID_ARGUMENT, "'axes' has duplicates"); + unique_axes.insert(axis); + + // process start + auto start = raw_starts[axis_index]; if (start < 0) start += input_dimensions[axis]; starts[axis] = clamp(start, int64_t{0}, input_dimensions[axis]); - auto end = raw_ends[axesIndex]; + // process end + auto end = raw_ends[axis_index]; if (end < 0) end += input_dimensions[axis]; - output_dims[axis] = clamp(end, int64_t{0}, input_dimensions[axis]) - starts[axis]; - if (output_dims[axis] < 0) - return Status(ONNXRUNTIME, INVALID_ARGUMENT, "'starts' and 'ends' values resulted in a negative dimension"); + + // find output dim value for this axis + auto temp = clamp(end, int64_t{0}, input_dimensions[axis]) - starts[axis]; + if (temp < 0) + output_dims[axis] = 0; + else + output_dims[axis] = temp; } return Status::OK(); } +// DynamicSlice & Slice V10 +Status SliceBase::PrepareForCompute(const std::vector& raw_starts, + const std::vector& raw_ends, + const std::vector& raw_axes, + const std::vector& raw_steps, + const std::vector& input_dimensions, + std::vector& starts, + std::vector& steps, + std::vector& output_dims) const { + // Initialize axes to the provided axes attribute or to the default sequence + std::vector axes(raw_axes); + if (axes.empty()) { + // axes are omitted, they are set to[0, ..., ndim - 1] + axes.resize(starts.size()); + std::iota(axes.begin(), axes.end(), 0); + } + + // Iterate through the provided axes and override the start/end/steps ranges + std::unordered_set unique_axes; + const auto& dimension_count = input_dimensions.size(); + for (size_t axis_index = 0, axes_count = axes.size(); axis_index < axes_count; ++axis_index) { + auto axis = axes[axis_index] < 0 ? axes[axis_index] + static_cast(dimension_count) : axes[axis_index]; + if (axis >= static_cast(dimension_count) || axis < 0) + return Status(ONNXRUNTIME, INVALID_ARGUMENT, "'axes' has an axis outside of the tensor dimension count"); + if (unique_axes.find(axis) != unique_axes.end()) + return Status(ONNXRUNTIME, INVALID_ARGUMENT, "'axes' has duplicates"); + unique_axes.insert(axis); + + // process step + auto step = axis_index < raw_steps.size() ? raw_steps[axis_index] : 1; + if (step == 0) + return Status(ONNXRUNTIME, INVALID_ARGUMENT, "'step' value cannot be 0"); + steps[axis] = step; + + // process start + auto start = raw_starts[axis_index]; + if (start < 0) + start += input_dimensions[axis]; + if (step < 0) + starts[axis] = clamp(start, int64_t{0}, input_dimensions[axis] - 1); + else + starts[axis] = clamp(start, int64_t{0}, input_dimensions[axis]); + + // process end + auto end = raw_ends[axis_index]; + // INT_MAX has a special meaning for end according to spec + // equivalent to 'None' in numpy + // it represent slicing to the end of the dimension + if (end == std::numeric_limits::max() || + end == std::numeric_limits::max()) { + end = step < 0 ? -1 : input_dimensions[axis]; + } + + else { + if (end < 0) + end += input_dimensions[axis]; + if (step < 0) + end = clamp(end, int64_t{-1}, input_dimensions[axis]); + else + end = clamp(end, int64_t{0}, input_dimensions[axis]); + } + + // find output dim value for this axis + auto temp = static_cast(ceil(1.0 * (end - starts[axis]) / step)); + if (temp < 0) + output_dims[axis] = 0; + else + output_dims[axis] = temp; + } + return Status::OK(); +} + +// Slice V10 & DynamicSlice void SliceBase::FillVectorsFromInput(const OpKernelContext* context, std::vector& input_starts, std::vector& input_ends, - std::vector& input_axes) const { + std::vector& input_axes, + std::vector& input_steps) const { auto start_tensor = context->Input(1); auto ends_tensor = context->Input(2); - auto axes_tensor = context->Input(3); + const Tensor* axes_tensor = nullptr; + if (context->InputCount() >= 4) + axes_tensor = context->Input(3); + // Slice V10 (optional input) + const Tensor* steps_tensor = nullptr; + if (context->InputCount() == 5) + steps_tensor = context->Input(4); ORT_ENFORCE(nullptr != start_tensor && start_tensor->Shape().NumDimensions() == 1, "Starts must be a 1-D array"); ORT_ENFORCE(nullptr != ends_tensor && ends_tensor->Shape().NumDimensions() == 1, "Ends must be a 1-D array"); ORT_ENFORCE(start_tensor->Shape() == ends_tensor->Shape(), "Starts and ends shape mismatch"); ORT_ENFORCE(nullptr == axes_tensor || start_tensor->Shape() == axes_tensor->Shape(), "Starts and axes shape mismatch"); + ORT_ENFORCE(nullptr == steps_tensor || steps_tensor->Shape() == axes_tensor->Shape(), "Steps and axes shape mismatch"); const auto& dtype = start_tensor->DataType(); const auto& size = start_tensor->Shape().Size(); @@ -122,12 +243,18 @@ void SliceBase::FillVectorsFromInput(const OpKernelContext* context, input_ends.resize(size); if (nullptr != axes_tensor) input_axes.resize(size); + // Slice V10 + if (nullptr != steps_tensor) + input_steps.resize(size); if (dtype == DataTypeImpl::GetType()) { std::copy(start_tensor->Data(), start_tensor->Data() + size, input_starts.begin()); std::copy(ends_tensor->Data(), ends_tensor->Data() + size, input_ends.begin()); if (nullptr != axes_tensor) std::copy(axes_tensor->Data(), axes_tensor->Data() + size, input_axes.begin()); + // Slice V10 + if (nullptr != steps_tensor) + std::copy(steps_tensor->Data(), steps_tensor->Data() + size, input_steps.begin()); } else if (dtype == DataTypeImpl::GetType()) { @@ -135,6 +262,9 @@ void SliceBase::FillVectorsFromInput(const OpKernelContext* context, std::copy(ends_tensor->Data(), ends_tensor->Data() + size, input_ends.begin()); if (nullptr != axes_tensor) std::copy(axes_tensor->Data(), axes_tensor->Data() + size, input_axes.begin()); + // Slice V10 + if (nullptr != steps_tensor) + std::copy(steps_tensor->Data(), steps_tensor->Data() + size, input_steps.begin()); } // should not reach this as no kernel is registered for this condition to be triggered - just an additional safety check @@ -145,15 +275,21 @@ void SliceBase::FillVectorsFromInput(const OpKernelContext* context, template Status SliceImpl(OpKernelContext* ctx, - const Tensor& input_tensor, + const Tensor& input_tensor, std::vector& output_dims, - const std::vector& starts) { + const std::vector& starts, + const std::vector& steps) { TensorShape output_shape(output_dims); auto& output_tensor = *ctx->Output(0, output_shape); + + // output tensor's size is 0, nothing to fill - return + if (output_shape.Size() == 0) + return Status::OK(); + auto* output = output_tensor.template MutableData(); const auto* output_end = output + output_tensor.Shape().Size(); - SliceIterator input_iterator(input_tensor, starts, output_tensor.Shape().GetDims()); + SliceIterator input_iterator(input_tensor, starts, output_dims, steps); while (output != output_end) *output++ = *input_iterator++; @@ -166,21 +302,27 @@ Status Slice::Compute(OpKernelContext* ctx) const { ORT_ENFORCE(input_tensor_ptr != nullptr, "Missing input tensor to be processed"); const auto& input_tensor = *input_tensor_ptr; const auto& input_dimensions = input_tensor.Shape().GetDims(); + if (input_dimensions.size() < 1) + return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Cannot slice scalars"); // Initialize the starts & ends to the actual tensor shape std::vector starts(input_dimensions.size(), 0); + std::vector steps(input_dimensions.size(), 1); std::vector output_dims(input_dimensions); + // Slice V10 & DynamicSlice if (dynamic) { - std::vector input_starts, input_ends, input_axes; - FillVectorsFromInput(ctx, input_starts, input_ends, input_axes); - ORT_RETURN_IF_ERROR(PrepareForCompute(input_starts, input_ends, input_axes, - input_dimensions, starts, output_dims)); - } else { + std::vector input_starts, input_ends, input_axes, input_steps; + FillVectorsFromInput(ctx, input_starts, input_ends, input_axes, input_steps); + ORT_RETURN_IF_ERROR(PrepareForCompute(input_starts, input_ends, input_axes, input_steps, + input_dimensions, starts, steps, output_dims)); + } + // Slice V1-9 + else { ORT_RETURN_IF_ERROR(PrepareForCompute(attr_starts_, attr_ends_, attr_axes_, input_dimensions, starts, output_dims)); } - return SliceImpl(ctx, input_tensor, output_dims, starts); + return SliceImpl(ctx, input_tensor, output_dims, starts, steps); } } // namespace onnxruntime diff --git a/onnxruntime/core/providers/cpu/tensor/slice.h b/onnxruntime/core/providers/cpu/tensor/slice.h index 3e545bf1e5..7c83285b08 100644 --- a/onnxruntime/core/providers/cpu/tensor/slice.h +++ b/onnxruntime/core/providers/cpu/tensor/slice.h @@ -21,6 +21,7 @@ class SliceBase { } } + // compute output_dims without steps (Slice V1-9 & DynamicSlice) Status PrepareForCompute(const std::vector& raw_starts, const std::vector& raw_ends, const std::vector& raw_axes, @@ -28,10 +29,22 @@ class SliceBase { std::vector& starts, std::vector& output_dims) const; + // compute output_dims with steps (Slice V10) + Status PrepareForCompute(const std::vector& raw_starts, + const std::vector& raw_ends, + const std::vector& raw_axes, + const std::vector& raw_steps, + const std::vector& input_dimensions, + std::vector& starts, + std::vector& steps, + std::vector& output_dims) const; + + // Slice V10 & DynamicSlice void FillVectorsFromInput(const OpKernelContext* context, - std::vector& raw_starts, - std::vector& raw_ends, - std::vector& raw_axes) const; + std::vector& input_starts, + std::vector& input_ends, + std::vector& input_axes, + std::vector& input_steps) const; std::vector attr_starts_, attr_ends_, attr_axes_; }; diff --git a/onnxruntime/core/providers/cpu/tensor/utils.h b/onnxruntime/core/providers/cpu/tensor/utils.h index 81f69d8f5b..0eb46d8ce9 100644 --- a/onnxruntime/core/providers/cpu/tensor/utils.h +++ b/onnxruntime/core/providers/cpu/tensor/utils.h @@ -119,47 +119,70 @@ struct ExtentAxisCounters { gsl::span extents_; // The extents of each axis }; -// A std::vector that holds the number of entries to skip to go to the next axis start given an extent in each axis +// A std::vector that holds the number of entries to skip to go to the next axis start given an extent +// and optionally steps along each axis: // This is used by the SliceIterator to iterate over a slice of a tensor struct SliceSkips : std::vector { - SliceSkips(const TensorShape& input_shape, gsl::span extents) - : std::vector(input_shape.NumDimensions(), 0) { + SliceSkips(const TensorShape& input_shape, gsl::span extents, gsl::span steps) + : std::vector(input_shape.NumDimensions(), 0) { auto& dims = input_shape.GetDims(); - ORT_ENFORCE(static_cast(dims.size()) == extents.size()); - size_t pitch = dims.back(); - back() = pitch - extents[size() - 1]; - for (size_t i = size() - 1; i-- > 0;) { + ORT_ENFORCE(static_cast(dims.size()) == extents.size() && + static_cast(dims.size()) >= steps.size()); + + int64_t inner_most_dim = dims.size() - 1; + // assume step == 1 if not present + ptrdiff_t steps_size = steps.size(); + int64_t steps_i = 1; + if (inner_most_dim >= 0 && static_cast(inner_most_dim) < steps_size) + steps_i = steps[inner_most_dim]; + + size_t pitch = 1; + for (size_t i = size(); i-- > 0;) { auto prevPitch = pitch; pitch *= dims[i]; - operator[](i) = pitch - prevPitch * extents[i]; + + // assume step == 1 if not present + int64_t steps_i_minus_1 = 1; + if (i > 0 && static_cast(i) - 1 < steps_size) + steps_i_minus_1 = steps[i - 1]; + + // first "revert" back to the old starting position (term with -ve sign) + // and then "step" over the pitch accordingly (term with +ve sign) + operator[](i) = steps_i_minus_1 * pitch - steps_i * extents[i] * prevPitch; + + steps_i = steps_i_minus_1; } } }; -// This provides easy sequential iteration over a subset of a tensor given a span of starts & extents +// This provides easy sequential iteration over a subset of a tensor given a span of starts, extents & optionally steps template struct SliceIterator { - SliceIterator(const Tensor& tensor, gsl::span starts, gsl::span extents) - : tensor_(tensor), extents_(extents), skips_(tensor_.Shape(), extents), indices_(extents.size(), 0) { + SliceIterator(const Tensor& tensor, gsl::span starts, + gsl::span extents, gsl::span steps) + : tensor_(tensor), extents_(extents), skips_(tensor_.Shape(), extents, steps), indices_(extents.size(), 0) { auto& dims = tensor_.Shape().GetDims(); - - Init(dims, starts); + Init(dims, starts, steps); } - // This construct takes a explicit tensor_shape which might be different from the shape defined in input tensor. - // The explicit tensor_shape usually has inner most axis flattened. For example, given shape[1,4,4,2], if last axis - // does not have padding or slice, then it will be flattened as [1,4,8] for better performance (One inner most copy instead of 4). - SliceIterator(const Tensor& tensor, const TensorShape& tensor_shape, gsl::span starts, gsl::span extents) - : tensor_(tensor), extents_(extents), skips_(tensor_shape, extents), indices_(extents.size(), 0) { + // This construct takes a explicit tensor_shape which might be different from the shape defined in input tensor. + // The explicit tensor_shape usually has inner most axis flattened. For example, given shape[1,4,4,2], if last axis + // does not have padding or slice, then it will be flattened as [1,4,8] for better performance (One inner most copy instead of 4). + // Also supports arbitrary positive and negative stepping along individual axes + SliceIterator(const Tensor& tensor, const TensorShape& tensor_shape, gsl::span starts, + gsl::span extents, gsl::span steps) + : tensor_(tensor), extents_(extents), skips_(tensor_shape, extents, steps), indices_(extents.size(), 0) { auto& dims = tensor_shape.GetDims(); - - Init(dims, starts); + Init(dims, starts, steps); } // Initialize initial skip and inner_extent. - void Init(const std::vector& dims, gsl::span starts) { + void Init(const std::vector& dims, gsl::span starts, + gsl::span steps) { - ORT_ENFORCE(static_cast(dims.size()) == starts.size() && static_cast(dims.size()) == extents_.size()); + ORT_ENFORCE(static_cast(dims.size()) == starts.size() && + static_cast(dims.size()) == extents_.size() && + static_cast(dims.size()) >= steps.size()); size_t pitch = 1; // Initial skip, so that input_ points to the first element to copy @@ -169,6 +192,8 @@ struct SliceIterator { } inner_extent_ = extents_[dims.size() - 1]; + inner_step_ = static_cast(dims.size()) == steps.size() + ? steps[dims.size() - 1] : 1; } void AdvanceOverInnerExtent() { @@ -181,7 +206,8 @@ struct SliceIterator { } const T* operator++(int) { - const T* input = input_++; + const T* input = input_; + input_ += inner_step_; if (++inner_counter_ == inner_extent_) { inner_counter_ = 0; AdvanceOverInnerExtent(); @@ -189,7 +215,13 @@ struct SliceIterator { return input; } - T* CopyInnermostAxis(T* output) { + // spliting the function that copies the innermost dimension into 2 separate methods, + // as this is most likely being called within a loop + // and we want to avoid the check inside to avoid overhead + // upto the caller to call the relevant one + + // Assumes inner_step_ == 1 + T* CopyInnermostAxisSolitaryInnerStep(T* output) { std::copy(input_, input_ + inner_extent_, output); input_ += inner_extent_; output += inner_extent_; @@ -197,11 +229,20 @@ struct SliceIterator { return output; } + // Assumes generic inner_step_ + T* CopyInnermostAxisNonSolitaryInnerStep(T* output) { + for (size_t i = 0; i < inner_extent_; ++i) { + *output++ = *input_; + input_ += inner_step_; + } + return output; + } + private: const Tensor& tensor_; const T* input_{tensor_.template Data()}; gsl::span extents_; - size_t inner_counter_{}, inner_extent_; + size_t inner_counter_{}, inner_extent_, inner_step_; SliceSkips skips_; std::vector indices_; // There is no index for innermost axis since it's a special case }; @@ -221,4 +262,4 @@ inline void CopyCpuTensor(const Tensor* src, Tensor* tgt) { } } -} // namespace onnxruntime +} // namespace onnxruntime \ No newline at end of file diff --git a/onnxruntime/core/providers/cuda/cuda_execution_provider.cc b/onnxruntime/core/providers/cuda/cuda_execution_provider.cc index 0cb021a976..7031e5892f 100644 --- a/onnxruntime/core/providers/cuda/cuda_execution_provider.cc +++ b/onnxruntime/core/providers/cuda/cuda_execution_provider.cc @@ -492,8 +492,8 @@ class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 7, float, LSTM); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 7, double, LSTM); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 7, MLFloat16, LSTM); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, int32_t, Slice); -class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, int64_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, 9, int32_t, Slice); +class ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, 9, int64_t, Slice); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, int32_t, DynamicSlice); class ONNX_OPERATOR_TYPED_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 1, int64_t, DynamicSlice); class ONNX_OPERATOR_KERNEL_CLASS_NAME(kCudaExecutionProvider, kOnnxDomain, 9, Compress); @@ -756,8 +756,8 @@ static void RegisterCudaKernels(KernelRegistry& kernel_registry) { kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); - kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); + kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); kernel_registry.Register(BuildKernelCreateInfo()); diff --git a/onnxruntime/core/providers/cuda/tensor/slice.cc b/onnxruntime/core/providers/cuda/tensor/slice.cc index e3b8197ad6..d51cbc87ac 100644 --- a/onnxruntime/core/providers/cuda/tensor/slice.cc +++ b/onnxruntime/core/providers/cuda/tensor/slice.cc @@ -7,10 +7,26 @@ namespace onnxruntime { namespace cuda { +#define REGISTER_VERSIONED_TYPED_SLICE(TIND) \ + ONNX_OPERATOR_VERSIONED_TYPED_KERNEL_EX( \ + Slice, \ + kOnnxDomain, \ + 1, 9, \ + TIND, \ + kCudaExecutionProvider, \ + KernelDefBuilder().InputMemoryType(1). \ + InputMemoryType(2). \ + InputMemoryType(3). \ + TypeConstraint("T", DataTypeImpl::AllFixedSizeTensorTypes()). \ + TypeConstraint("Tind", DataTypeImpl::GetTensorType()), \ + Slice); -#define REGISTER_TYPED_SLICE(NAME, TIND, DYNAMIC) \ +REGISTER_VERSIONED_TYPED_SLICE(int32_t) +REGISTER_VERSIONED_TYPED_SLICE(int64_t) + +#define REGISTER_TYPED_DYNAMICSLICE(TIND) \ ONNX_OPERATOR_TYPED_KERNEL_EX( \ - NAME, \ + DynamicSlice, \ kOnnxDomain, \ 1, \ TIND, \ @@ -20,12 +36,10 @@ namespace cuda { InputMemoryType(3). \ TypeConstraint("T", DataTypeImpl::AllFixedSizeTensorTypes()). \ TypeConstraint("Tind", DataTypeImpl::GetTensorType()), \ - Slice); + Slice); -REGISTER_TYPED_SLICE(Slice, int32_t, false) -REGISTER_TYPED_SLICE(Slice, int64_t, false) -REGISTER_TYPED_SLICE(DynamicSlice, int32_t, true ) -REGISTER_TYPED_SLICE(DynamicSlice, int64_t, true ) +REGISTER_TYPED_DYNAMICSLICE(int32_t) +REGISTER_TYPED_DYNAMICSLICE(int64_t) template Status Slice::ComputeInternal(OpKernelContext* ctx) const { @@ -36,13 +50,14 @@ Status Slice::ComputeInternal(OpKernelContext* ctx) const { // Initialize the starts & ends to the actual tensor shape const size_t dimension_count = input_dimensions.size(); std::vector starts(dimension_count, 0); + std::vector steps(input_dimensions.size(), 1); std::vector output_dims(input_dimensions); if (dynamic) { - std::vector input_starts, input_ends, input_axes; - FillVectorsFromInput(ctx, input_starts, input_ends, input_axes); + std::vector input_starts, input_ends, input_axes, input_steps; + FillVectorsFromInput(ctx, input_starts, input_ends, input_axes, input_steps); ORT_RETURN_IF_ERROR(PrepareForCompute(input_starts, input_ends, input_axes, - input_dimensions, starts, output_dims)); + input_steps, input_dimensions, starts, steps, output_dims)); } else { ORT_RETURN_IF_ERROR(PrepareForCompute(attr_starts_, attr_ends_, attr_axes_, diff --git a/onnxruntime/test/providers/cpu/tensor/slice_op.test.cc b/onnxruntime/test/providers/cpu/tensor/slice_op.test.cc index 00c91a3bba..5ae3b83f73 100644 --- a/onnxruntime/test/providers/cpu/tensor/slice_op.test.cc +++ b/onnxruntime/test/providers/cpu/tensor/slice_op.test.cc @@ -7,156 +7,505 @@ namespace onnxruntime { namespace test { -TEST(SliceTest, Slice1D) { - OpTester test("Slice"); +template +void RunSliceTest(const std::vector& input_dims, + const std::vector& input_vals, + const std::vector& starts, + const std::vector& ends, + const std::vector& axes, + const std::vector& steps, + const std::vector& output_dims, + const std::vector& output_vals, + bool v10_only = false) { + // V1-9 + if (!v10_only) + { + OpTester testv9("Slice", 9); + testv9.AddAttribute("starts", starts); + testv9.AddAttribute("ends", ends); + if (axes.size() != 0) + testv9.AddAttribute("axes", axes); + testv9.AddInput("data", input_dims, input_vals); + testv9.AddOutput("output", output_dims, output_vals); + testv9.Run(); + } - test.AddAttribute("axes", std::vector{0}); - test.AddAttribute("starts", std::vector{2}); - test.AddAttribute("ends", std::vector{4}); + // V10 + OpTester testv10("Slice", 10); + testv10.AddInput("data", input_dims, input_vals); + testv10.AddInput("starts", {static_cast(starts.size())}, starts); + testv10.AddInput("ends", {static_cast(ends.size())}, ends); + if (axes.size() != 0) + testv10.AddInput("axes", {static_cast(axes.size())}, axes); + if (steps.size() != 0) + testv10.AddInput("steps", {static_cast(steps.size())}, steps); + testv10.AddOutput("output", output_dims, output_vals); + testv10.Run(); +} - test.AddInput("data", {6}, {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}); - test.AddOutput("output", {2}, {2.0f, 3.0f}); - test.Run(); +// Slice V1-9 & Slice V10 can both run the following tests +TEST(SliceTest, Slice1D_InvalidStartEndRange) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {3}, + {2}, + {0}, + {}, + {0}, + {}); +} + +TEST(SliceTest, Slice1D_ValidStartEndRange_NoOutput) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {2}, + {2}, + {0}, + {}, + {0}, + {}); +} + +TEST(SliceTest, Slice1D_Regular) { + RunSliceTest + ({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {2}, + {4}, + {0}, + {}, + {2}, + {2.0f, 3.0f}); } TEST(SliceTest, Slice1D_Perf) { - OpTester test("Slice"); - - test.AddAttribute("axes", std::vector{0}); - test.AddAttribute("starts", std::vector{2}); - test.AddAttribute("ends", std::vector{502}); - std::vector input(1000, 2.0f); std::vector output(500, 2.0f); - test.AddInput("data", {1000}, input); - test.AddOutput("output", {500}, output); - test.Run(); + RunSliceTest({1000}, + input, + {2}, + {502}, + {0}, + {}, + {500}, + output); } -TEST(SliceTest, Slice2D_OutOfBounds) { - OpTester test("Slice"); +TEST(SliceTest, Slice1D_EndOutOfBounds) { + RunSliceTest({6}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}, + {0}, + {10}, + {}, + {}, + {6}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}); +} - test.AddAttribute("axes", std::vector{0, 1}); - test.AddAttribute("starts", std::vector{0, 1000}); - test.AddAttribute("ends", std::vector{10, 1000}); +TEST(SliceTest, Slice1D_StartAndEndOutOfBounds) { + RunSliceTest({6}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}, + {1000}, + {1001}, + {}, + {}, + {0}, + {}); +} - test.AddInput("data", {2, 3}, {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}); - test.AddOutput("output", {2, 0}, {}); - test.Run(); +TEST(SliceTest, Slice2D_StartAndEndOutOfBounds) { + RunSliceTest({2, 3}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}, + {0, 1000}, + {10, 1000}, + {0, 1}, + {}, + {2, 0}, + {}); } TEST(SliceTest, Slice2D_OneAxis) { - OpTester test("Slice"); - - test.AddAttribute("axes", std::vector{0}); - test.AddAttribute("starts", std::vector{1}); - test.AddAttribute("ends", std::vector{3}); - - test.AddInput("data", {6, 4}, - {00.0f, 01.0f, 02.0f, 03.0f, - 10.0f, 11.0f, 12.0f, 13.0f, - 20.0f, 21.0f, 22.0f, 23.0f, - 30.0f, 31.0f, 32.0f, 33.0f, - 40.0f, 41.0f, 42.0f, 43.0f, - 50.0f, 51.0f, 52.0f, 53.0f}); - test.AddOutput("output", {2, 4}, - {10.0f, 11.0f, 12.0f, 13.0f, - 20.0f, 21.0f, 22.0f, 23.0f}); - test.Run(); + RunSliceTest({6, 4}, + {00.0f, 01.0f, 02.0f, 03.0f, + 10.0f, 11.0f, 12.0f, 13.0f, + 20.0f, 21.0f, 22.0f, 23.0f, + 30.0f, 31.0f, 32.0f, 33.0f, + 40.0f, 41.0f, 42.0f, 43.0f, + 50.0f, 51.0f, 52.0f, 53.0f}, + {1}, + {3}, + {0}, + {}, + {2, 4}, + {10.0f, 11.0f, 12.0f, 13.0f, + 20.0f, 21.0f, 22.0f, 23.0f}); } TEST(SliceTest, Slice2D_TwoAxes) { - OpTester test("Slice"); - - test.AddAttribute("axes", std::vector{1, 0}); - test.AddAttribute("starts", std::vector{2, 3}); - test.AddAttribute("ends", std::vector{1000, -1}); - - test.AddInput("data", {6, 4}, - {00.0f, 01.0f, 02.0f, 03.0f, - 10.0f, 11.0f, 12.0f, 13.0f, - 20.0f, 21.0f, 22.0f, 23.0f, - 30.0f, 31.0f, 32.0f, 33.0f, - 40.0f, 41.0f, 42.0f, 43.0f, - 50.0f, 51.0f, 52.0f, 53.0f}); - test.AddOutput("output", {2, 2}, - {32.0f, 33.0f, - 42.0f, 43.0f}); - test.Run(); + RunSliceTest({6, 4}, + {00.0f, 01.0f, 02.0f, 03.0f, + 10.0f, 11.0f, 12.0f, 13.0f, + 20.0f, 21.0f, 22.0f, 23.0f, + 30.0f, 31.0f, 32.0f, 33.0f, + 40.0f, 41.0f, 42.0f, 43.0f, + 50.0f, 51.0f, 52.0f, 53.0f}, + {2, 3}, + {1000 ,-1}, + {1, 0}, + {}, + {2, 2}, + {32.0f, 33.0f, + 42.0f, 43.0f}); } TEST(SliceTest, Slice2D_TwoAxesEque) { - OpTester test("Slice"); - - test.AddAttribute("axes", std::vector{1, 0}); - test.AddAttribute("starts", std::vector{2, 3}); - test.AddAttribute("ends", std::vector{1000, 3}); - - test.AddInput("data", {6, 4}, - {00.0f, 01.0f, 02.0f, 03.0f, - 10.0f, 11.0f, 12.0f, 13.0f, - 20.0f, 21.0f, 22.0f, 23.0f, - 30.0f, 31.0f, 32.0f, 33.0f, - 40.0f, 41.0f, 42.0f, 43.0f, - 50.0f, 51.0f, 52.0f, 53.0f}); - test.AddOutput("output", {0, 2}, - {}); - test.Run(); + RunSliceTest({6, 4}, + {00.0f, 01.0f, 02.0f, 03.0f, + 10.0f, 11.0f, 12.0f, 13.0f, + 20.0f, 21.0f, 22.0f, 23.0f, + 30.0f, 31.0f, 32.0f, 33.0f, + 40.0f, 41.0f, 42.0f, 43.0f, + 50.0f, 51.0f, 52.0f, 53.0f}, + {2, 3}, + {1000, 3}, + {1, 0}, + {}, + {0, 2}, + {}); } TEST(SliceTest, Slice3D) { - OpTester test("Slice"); + RunSliceTest({3, 3, 3}, + {111.0f, 112.0f, 113.0f, + 121.0f, 122.0f, 123.0f, + 131.0f, 132.0f, 133.0f, - test.AddAttribute("starts", std::vector{0, 1, 1}); - test.AddAttribute("ends", std::vector{1000, 1000, 1000}); + 211.0f, 212.0f, 213.0f, + 221.0f, 222.0f, 223.0f, + 231.0f, 232.0f, 233.0f, - test.AddInput("data", {3, 3, 3}, - {111.0f, 112.0f, 113.0f, - 121.0f, 122.0f, 123.0f, - 131.0f, 132.0f, 133.0f, + 311.0f, 312.0f, 313.0f, + 321.0f, 322.0f, 323.0f, + 331.0f, 332.0f, 333.0f}, + {0, 1, 1}, + {1000, 1000, 1000}, + {}, + {}, + {3, 2, 2}, + {122.0f, 123.0f, + 132.0f, 133.0f, - 211.0f, 212.0f, 213.0f, - 221.0f, 222.0f, 223.0f, - 231.0f, 232.0f, 233.0f, + 222.0f, 223.0f, + 232.0f, 233.0f, - 311.0f, 312.0f, 313.0f, - 321.0f, 322.0f, 323.0f, - 331.0f, 332.0f, 333.0f}); - test.AddOutput("output", {3, 2, 2}, - {122.0f, 123.0f, - 132.0f, 133.0f, - - 222.0f, 223.0f, - 232.0f, 233.0f, - - 322.0f, 323.0f, - 332.0f, 333.0f}); - test.Run(); + 322.0f, 323.0f, + 332.0f, 333.0f}); } TEST(SliceTest, Slice1D_Int) { - OpTester test("Slice"); - - test.AddAttribute("axes", std::vector{0}); - test.AddAttribute("starts", std::vector{2}); - test.AddAttribute("ends", std::vector{4}); - - test.AddInput("data", {6}, {0L, 1L, 2L, 3L, 4L, 5L}); - test.AddOutput("output", {2}, {2L, 3L}); - test.Run(); + RunSliceTest({6}, + {0L, 1L, 2L, 3L, 4L, 5L}, + {2}, + {4}, + {0}, + {}, + {2}, + {2L, 3L}); } TEST(SliceTest, Slice1D_String) { - OpTester test("Slice"); - - test.AddAttribute("axes", std::vector{0}); - test.AddAttribute("starts", std::vector{2}); - test.AddAttribute("ends", std::vector{4}); - - test.AddInput("data", {6}, {"0", "1", "2", "3", "4", "5"}); - test.AddOutput("output", {2}, {"2", "3"}); - test.Run(); + RunSliceTest({6}, + {"0", "1", "2", "3", "4", "5"}, + {2}, + {4}, + {0}, + {}, + {2}, + {"2", "3"}); } -} // namespace Test +// Only Slice V10 can run the following tests +TEST(SliceTest, Slice1D_WithPositiveSteps) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {0}, + {6}, + {0}, + {2}, + {3}, + {0.0f, 2.0f, 4.0f}, + true); +} + +// In numpy: +// x = np.array([1, 2, 3, 4]) +// y = x[-1:-4:-1] +TEST(SliceTest, Slice1D_WithNegativeSteps_Regular) { + RunSliceTest({4}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1}, + {-4}, + {0}, + {-1}, + {3}, + {4.0f, 3.0f, 2.0f}, + true); +} + +TEST(SliceTest, Slice1D_WithNegativeSteps_EndOutOfBounds_1) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {0}, + {6}, + {0}, + {-1}, + {0}, + {}, + true); +} + +TEST(SliceTest, Slice1D_WithNegativeSteps_EndOutOfBounds_2) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {0}, + {-10}, + {0}, + {-1}, + {1}, + {0.0f}, + true); +} + +TEST(SliceTest, Slice1D_WithNegativeSteps_ValidStartEndRange) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {5}, + {0}, + {0}, + {-1}, + {5}, + {5.0f, 4.0f, 3.0f, 2.0f, 1.0f}, + true); +} + + +TEST(SliceTest, Slice1D_WithNegativeSteps_StartOutOfBounds) { + RunSliceTest({6}, + {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f}, + {7}, + {0}, + {0}, + {-3}, + {2}, + {5.0f, 2.0f}, + true); +} + +TEST(SliceTest, Slice2D_WithPositiveSteps_1) { + RunSliceTest({2, 4}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}, + {1, 0}, + {2, 3}, + {0, 1}, + {1, 2}, + {1, 2}, + {5.0f, 7.0f}, + true); +} + +TEST(SliceTest, Slice2D_WithPositiveSteps_2) { + RunSliceTest({2, 4}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}, + {0, 1}, + {-1, 1000}, + {}, // default axes + {}, // default steps + {1, 3}, + {2.0f, 3.0f, 4.0f}, + true); +} + +TEST(SliceTest, Slice2D_WithNegativeSteps_1) { + RunSliceTest({2, 4}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}, + {1, 0}, + {2, 3}, + {0, 1}, + {-1, -2}, + {0, 0}, + {}, + true); +} + +TEST(SliceTest, Slice2D_WithNegativeSteps_2) { + RunSliceTest({2, 4}, + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}, + {1, 3}, + {0, 1}, + {0, 1}, + {-1, -2}, + {1, 1}, + {8.0f}, + true); +} + +TEST(SliceTest, Slice3D_WithPositiveSteps_AllAxes) { + RunSliceTest({3, 3, 3}, + {27, 20, 2, + 4, 26, 11, + 26, 5, 17, + + 0, 21, 6, + 22, 13, 29, + 19, 17, 27, + + 4, 20, 12, + 3, 9, 24, + 17, 6, 24}, + {0, 1, 1}, + {1000, 1000, 1000}, + {0, 1, 2}, + {2, 2, 2}, + {2, 1, 1}, + {26, 9}, + true); +} + +TEST(SliceTest, Slice3D_WithPositiveAndNegativeSteps_SubsetOfAxes_1) { + RunSliceTest({3, 3, 3}, + {27, 20, 2, + 4, 26, 11, + 26, 5, 17, + + 0, 21, 6, + 22, 13, 29, + 19, 17, 27, + + 4, 20, 12, + 3, 9, 24, + 17, 6, 24}, + {1, 4}, + {1000, 1}, + {1, 2}, + {3, -2}, + {3, 1, 1}, + {11, 29, 24}, + true); +} + +TEST(SliceTest, Slice3D_WithPositiveAndNegativeSteps_SubsetOfAxes_2) { + RunSliceTest({3, 3, 3}, + {27, 20, 2, + 4, 26, 11, + 26, 5, 17, + + 0, 21, 6, + 22, 13, 29, + 19, 17, 27, + + 4, 20, 12, + 3, 9, 24, + 17, 6, 24}, + {1, 4}, + {1000, 2}, + {1, 2}, + {3, -2}, + {3, 1, 0}, + {}, + true); +} + +// Slice for Reversing +// With numeric_limit_max, it means slice to the end of a dimension +// (whichever direction we are stepping) +TEST(SliceTest, Slice1D_ReverseAllAxes_1) { + RunSliceTest({4}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1}, + {std::numeric_limits::max()}, + {0}, + {-1}, + {4}, + {4.0f, 3.0f, 2.0f, 1.0f}, + true); +} + +// With numeric_limit_min, the end value should be clamped to -1 +TEST(SliceTest, Slice1D_ReverseAllAxes_2) { + RunSliceTest({4}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1}, + {std::numeric_limits::min()}, + {0}, + {-1}, + {4}, + {4.0f, 3.0f, 2.0f, 1.0f}, + true); +} + +// giving an end value < -{dim_value} should also clamp it to -1 +TEST(SliceTest, Slice1D_ReverseAllAxes_3) { + RunSliceTest({4}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1}, + {-5}, + {0}, + {-1}, + {4}, + {4.0f, 3.0f, 2.0f, 1.0f}, + true); +} + +TEST(SliceTest, Slice2D_ReverseAllAxes) { + RunSliceTest({2, 2}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1, -1}, + {std::numeric_limits::max(), std::numeric_limits::max()}, + {0, 1}, + {-1, -1}, + {2, 2}, + {4.0f, 3.0f, 2.0f, 1.0f}, + true); +} + + +TEST(SliceTest, Slice2D_ReverseSubsetOfAxes_1) { + RunSliceTest({2, 2}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1}, + {std::numeric_limits::max()}, + {1}, // axis = 1 only + {-1}, + {2, 2}, + {2.0f, 1.0f, 4.0f, 3.0f}, + true); +} + + +TEST(SliceTest, Slice2D_ReverseSubsetOfAxes_2) { + RunSliceTest({2, 2}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {-1}, + {std::numeric_limits::max()}, // end of dimension + {0}, // axis = 0 only + {-1}, + {2, 2}, + {3.0f, 4.0f, 1.0f, 2.0f}, + true); +} + +// Slice for implicit copy +TEST(SliceTest, Slice2D_ImplicitCopyBySlicingADimensionFully) { + RunSliceTest({2, 2}, + {1.0f, 2.0f, 3.0f, 4.0f}, + {0}, + {std::numeric_limits::max()}, // end of dimension + {1}, // axis = 1 only + {1}, + {2, 2}, + {1.0f, 2.0, 3.0f, 4.0f}, + true); +} + +} // namespace test } // namespace onnxruntime diff --git a/onnxruntime/test/python/onnx_backend_test_series.py b/onnxruntime/test/python/onnx_backend_test_series.py index 00d80ea97a..8582506e8d 100644 --- a/onnxruntime/test/python/onnx_backend_test_series.py +++ b/onnxruntime/test/python/onnx_backend_test_series.py @@ -83,13 +83,6 @@ backend_test.exclude(r'(' '|^test_vgg19_cpu.*' '|^test_zfnet512_cpu.*' '|^test_gru_seq_length_cpu.*' -'|^test_slice_cpu.*' -'|^test_slice_default_axes_cpu.*' -'|^test_slice_default_steps_cpu.*' -'|^test_slice_end_out_of_bounds_cpu.*' -'|^test_slice_neg_cpu.*' -'|^test_slice_neg_steps_cpu.*' -'|^test_slice_start_out_of_bounds_cpu.*' '|^test_averagepool_2d_ceil_cpu.*' '|^test_maxpool_2d_ceil_cpu.*' '|^test_maxpool_2d_dilations_cpu.*'