diff --git a/docs/OperatorKernels.md b/docs/OperatorKernels.md
index cc3c30a2c3a7a..ac00b213d3551 100644
--- a/docs/OperatorKernels.md
+++ b/docs/OperatorKernels.md
@@ -115,8 +115,8 @@ The **OpSet Version** column uses the following notation:
|||[17, 19]|**T1** = tensor(double), tensor(float)
**T2** = tensor(int32), tensor(int64)|
|DeformConv|*in* X:**T**
*in* W:**T**
*in* offset:**T**
*in* B:**T**
*in* mask:**T**
*out* Y:**T**|22+|**T** = tensor(double), tensor(float)|
|||[19, 21]|**T** = tensor(double), tensor(float)|
-|DepthToSpace|*in* input:**T**
*out* output:**T**|13+|**T** = tensor(double), tensor(float), tensor(uint8)|
-|||[11, 12]|**T** = tensor(double), tensor(float), tensor(uint8)|
+|DepthToSpace|*in* input:**T**
*out* output:**T**|13+|**T** = tensor(double), tensor(float), tensor(int8), tensor(uint8)|
+|||[11, 12]|**T** = tensor(double), tensor(float), tensor(int8), tensor(uint8)|
|||[1, 10]|**T** = tensor(double), tensor(float)|
|DequantizeLinear|*in* x:**T**
*in* x_scale:**tensor(float)**
*in* x_zero_point:**T**
*out* y:**tensor(float)**
or
*in* x:**T1**
*in* x_scale:**T2**
*in* x_zero_point:**T1**
*out* y:**T2**
or
*in* x:**T1**
*in* x_scale:**T2**
*in* x_zero_point:**T1**
*out* y:**T3**|25+|**T1** = tensor(float8e4m3fn), tensor(float8e4m3fnuz), tensor(float8e5m2), tensor(float8e5m2fnuz), tensor(int16), tensor(int2), tensor(int32), tensor(int4), tensor(int8), tensor(uint16), tensor(uint2), tensor(uint4), tensor(uint8)
**T2** = tensor(float), tensor(float16)|
|||24|**T1** = tensor(float8e4m3fn), tensor(float8e4m3fnuz), tensor(float8e5m2), tensor(float8e5m2fnuz), tensor(int16), tensor(int32), tensor(int4), tensor(int8), tensor(uint16), tensor(uint4), tensor(uint8)
**T2** = tensor(float), tensor(float16)|
@@ -477,8 +477,8 @@ The **OpSet Version** column uses the following notation:
|||[1, 21]|**T** = tensor(float)|
|Softsign|*in* input:**T**
*out* output:**T**|22+|**T** = tensor(float)|
|||[1, 21]|**T** = tensor(float)|
-|SpaceToDepth|*in* input:**T**
*out* output:**T**|13+|**T** = tensor(double), tensor(float)|
-|||[1, 12]|**T** = tensor(double), tensor(float)|
+|SpaceToDepth|*in* input:**T**
*out* output:**T**|13+|**T** = tensor(double), tensor(float), tensor(int8), tensor(uint8)|
+|||[1, 12]|**T** = tensor(double), tensor(float), tensor(int8), tensor(uint8)|
|Split|*in* input:**T**
*in* split:**T**
*out* outputs...:**T**
or
*in* input:**T**
*in* split:**tensor(int64)**
*out* outputs:**T**
or
*in* input:**T**
*out* outputs:**T**|18+|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
|||[13, 17]|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
|||[11, 12]|**T** = tensor(bfloat16), tensor(bool), tensor(double), tensor(float), tensor(float16), tensor(int16), tensor(int32), tensor(int64), tensor(int8), tensor(string), tensor(uint16), tensor(uint32), tensor(uint64), tensor(uint8)|
diff --git a/onnxruntime/core/providers/cpu/tensor/space_depth_ops.cc b/onnxruntime/core/providers/cpu/tensor/space_depth_ops.cc
index 7e1049c402210..3f5d07cef4b7c 100644
--- a/onnxruntime/core/providers/cpu/tensor/space_depth_ops.cc
+++ b/onnxruntime/core/providers/cpu/tensor/space_depth_ops.cc
@@ -18,7 +18,9 @@ ONNX_CPU_OPERATOR_VERSIONED_KERNEL(
12,
KernelDefBuilder()
.TypeConstraint("T", {DataTypeImpl::GetTensorType(),
- DataTypeImpl::GetTensorType()}),
+ DataTypeImpl::GetTensorType(),
+ DataTypeImpl::GetTensorType(),
+ DataTypeImpl::GetTensorType()}),
SpaceToDepth);
ONNX_CPU_OPERATOR_KERNEL(
@@ -26,7 +28,9 @@ ONNX_CPU_OPERATOR_KERNEL(
13,
KernelDefBuilder()
.TypeConstraint("T", {DataTypeImpl::GetTensorType(),
- DataTypeImpl::GetTensorType()}),
+ DataTypeImpl::GetTensorType(),
+ DataTypeImpl::GetTensorType(),
+ DataTypeImpl::GetTensorType()}),
SpaceToDepth);
ONNX_CPU_OPERATOR_VERSIONED_KERNEL(
@@ -44,7 +48,8 @@ ONNX_CPU_OPERATOR_VERSIONED_KERNEL(
KernelDefBuilder()
.TypeConstraint("T", {DataTypeImpl::GetTensorType(),
DataTypeImpl::GetTensorType(),
- DataTypeImpl::GetTensorType()}),
+ DataTypeImpl::GetTensorType(),
+ DataTypeImpl::GetTensorType()}),
DepthToSpace);
ONNX_CPU_OPERATOR_KERNEL(
@@ -53,7 +58,8 @@ ONNX_CPU_OPERATOR_KERNEL(
KernelDefBuilder()
.TypeConstraint("T", {DataTypeImpl::GetTensorType(),
DataTypeImpl::GetTensorType(),
- DataTypeImpl::GetTensorType()}),
+ DataTypeImpl::GetTensorType(),
+ DataTypeImpl::GetTensorType()}),
DepthToSpace);
// intermediate tensor shapes are:
@@ -70,15 +76,15 @@ using ConstEigenTensorMap = Eigen::TensorMap
-static void SpaceDepthOpCpuImpl(const Tensor& input, Tensor& output,
+static void SpaceDepthOpCpuImpl(const T* input_data, T* output_data,
const std::array& permutation,
const Eigen::DenseIndex batch_size, // dim0 in both input and output
const Eigen::DenseIndex in_dim1, const Eigen::DenseIndex in_dim2, const Eigen::DenseIndex in_dim3,
const Eigen::DenseIndex in_dim4, const Eigen::DenseIndex in_dim5,
const Eigen::DenseIndex out_dim1, const Eigen::DenseIndex out_dim2, const Eigen::DenseIndex out_dim3,
const Eigen::DenseIndex out_dim4, const Eigen::DenseIndex out_dim5) {
- EigenTensorMap(output.MutableData(), batch_size, out_dim1, out_dim2, out_dim3, out_dim4, out_dim5) =
- ConstEigenTensorMap(input.Data(), batch_size,
+ EigenTensorMap(output_data, batch_size, out_dim1, out_dim2, out_dim3, out_dim4, out_dim5) =
+ ConstEigenTensorMap(input_data, batch_size,
in_dim1, in_dim2, in_dim3, in_dim4, in_dim5)
.shuffle(permutation);
}
@@ -109,7 +115,7 @@ Status SpaceToDepth::Compute(OpKernelContext* context) const {
std::array permutation{{0, 3, 5, 1, 2, 4}};
if (input.IsDataType()) {
- SpaceDepthOpCpuImpl(input, output, permutation,
+ SpaceDepthOpCpuImpl(input.Data(), output.MutableData(), permutation,
onnxruntime::narrow(batch),
onnxruntime::narrow(input_depth),
onnxruntime::narrow(input_height / blocksize_),
@@ -122,7 +128,7 @@ Status SpaceToDepth::Compute(OpKernelContext* context) const {
onnxruntime::narrow(input_height / blocksize_),
onnxruntime::narrow(input_width / blocksize_));
} else if (input.IsDataType()) {
- SpaceDepthOpCpuImpl(input, output, permutation,
+ SpaceDepthOpCpuImpl(input.Data(), output.MutableData(), permutation,
onnxruntime::narrow(batch),
onnxruntime::narrow(input_depth),
onnxruntime::narrow(input_height / blocksize_),
@@ -134,8 +140,24 @@ Status SpaceToDepth::Compute(OpKernelContext* context) const {
onnxruntime::narrow(input_depth),
onnxruntime::narrow(input_height / blocksize_),
onnxruntime::narrow(input_width / blocksize_));
+ } else if (input.IsDataType() || input.IsDataType()) {
+ // uint8_t and int8_t share a single implementation: the op only moves 8-bit
+ // elements around, so the signedness of the data does not matter.
+ SpaceDepthOpCpuImpl(static_cast(input.DataRaw()),
+ static_cast(output.MutableDataRaw()), permutation,
+ onnxruntime::narrow(batch),
+ onnxruntime::narrow(input_depth),
+ onnxruntime::narrow(input_height / blocksize_),
+ onnxruntime::narrow(blocksize_),
+ onnxruntime::narrow(input_width / blocksize_),
+ onnxruntime::narrow(blocksize_),
+ onnxruntime::narrow(blocksize_),
+ onnxruntime::narrow(blocksize_),
+ onnxruntime::narrow(input_depth),
+ onnxruntime::narrow(input_height / blocksize_),
+ onnxruntime::narrow(input_width / blocksize_));
} else {
- // user will not see this as the kernel doesn't claim support for types other than float and double
+ // user will not see this as the kernel doesn't claim support for types other than float, double, uint8_t and int8_t
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Unsupported input type in SpaceToDepth op: ", input.DataType());
}
@@ -173,7 +195,7 @@ Status DepthToSpace::Compute(OpKernelContext* context) const {
: std::array{{0, 1, 4, 2, 5, 3}};
if (input.IsDataType()) {
- SpaceDepthOpCpuImpl(input, output, permutation,
+ SpaceDepthOpCpuImpl(input.Data(), output.MutableData(), permutation,
onnxruntime::narrow(batch),
onnxruntime::narrow(dim1),
onnxruntime::narrow(blocksize_),
@@ -186,7 +208,7 @@ Status DepthToSpace::Compute(OpKernelContext* context) const {
onnxruntime::narrow(input_width),
onnxruntime::narrow(blocksize_));
} else if (input.IsDataType()) {
- SpaceDepthOpCpuImpl(input, output, permutation,
+ SpaceDepthOpCpuImpl(input.Data(), output.MutableData(), permutation,
onnxruntime::narrow(batch),
onnxruntime::narrow(dim1),
onnxruntime::narrow(blocksize_),
@@ -198,8 +220,11 @@ Status DepthToSpace::Compute(OpKernelContext* context) const {
onnxruntime::narrow(blocksize_),
onnxruntime::narrow(input_width),
onnxruntime::narrow(blocksize_));
- } else if (input.IsDataType()) {
- SpaceDepthOpCpuImpl(input, output, permutation,
+ } else if (input.IsDataType() || input.IsDataType()) {
+ // uint8_t and int8_t share a single implementation: the op only moves 8-bit
+ // elements around, so the signedness of the data does not matter.
+ SpaceDepthOpCpuImpl(static_cast(input.DataRaw()),
+ static_cast(output.MutableDataRaw()), permutation,
onnxruntime::narrow(batch),
onnxruntime::narrow(dim1),
onnxruntime::narrow(blocksize_),
@@ -212,7 +237,7 @@ Status DepthToSpace::Compute(OpKernelContext* context) const {
onnxruntime::narrow(input_width),
onnxruntime::narrow(blocksize_));
} else {
- // user will not see this as the kernel doesn't claim support for types other than float and double
+ // user will not see this as the kernel doesn't claim support for types other than float, double, uint8_t and int8_t
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL, "Unsupported input type in DepthToSpace op: ", input.DataType());
}
diff --git a/onnxruntime/test/providers/cpu/tensor/space_depth_ops_test.cc b/onnxruntime/test/providers/cpu/tensor/space_depth_ops_test.cc
index f97de7a54bc99..05c4374df1df8 100644
--- a/onnxruntime/test/providers/cpu/tensor/space_depth_ops_test.cc
+++ b/onnxruntime/test/providers/cpu/tensor/space_depth_ops_test.cc
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
+#include
+#include
+
#include "gtest/gtest.h"
#include "test/providers/provider_test_utils.h"
#include "core/providers/cpu/tensor/space_depth_ops.h"
@@ -13,7 +16,7 @@ template
class TensorOpTest : public ::testing::Test {
};
-using TensorOpTestTypes = ::testing::Types;
+using TensorOpTestTypes = ::testing::Types;
TYPED_TEST_SUITE(TensorOpTest, TensorOpTestTypes);
TEST(TensorOpTest, SpaceToDepthTest_1) {
@@ -159,6 +162,183 @@ TEST(TensorOpTest, SpaceToDepthTest_3) {
test.Run();
}
+TYPED_TEST(TensorOpTest, SpaceToDepthTest_int) {
+ // Same data as SpaceToDepthTest_2 (values 0..107 fit in both int8_t and uint8_t).
+ // The typed suite covers float, MLFloat16, uint8_t and int8_t. The CPU kernel under
+ // test supports float/uint8_t/int8_t; MLFloat16 only runs on EPs that support it (e.g. CUDA).
+ OpTester test("SpaceToDepth");
+ constexpr int64_t blocksize = 3;
+ test.AddAttribute("blocksize", blocksize);
+ constexpr int64_t N = 2, C = 3, H = 3, W = 6;
+ const std::vector X = {
+ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10.,
+ 11., 12., 13., 14., 15., 16., 17., 18., 19., 20., 21.,
+ 22., 23., 24., 25., 26., 27., 28., 29., 30., 31., 32.,
+ 33., 34., 35., 36., 37., 38., 39., 40., 41., 42., 43.,
+ 44., 45., 46., 47., 48., 49., 50., 51., 52., 53., 54.,
+ 55., 56., 57., 58., 59., 60., 61., 62., 63., 64., 65.,
+ 66., 67., 68., 69., 70., 71., 72., 73., 74., 75., 76.,
+ 77., 78., 79., 80., 81., 82., 83., 84., 85., 86., 87.,
+ 88., 89., 90., 91., 92., 93., 94., 95., 96., 97., 98.,
+ 99., 100., 101., 102., 103., 104., 105., 106., 107.};
+
+ const std::vector result = {
+ 0., 3., 18., 21., 36., 39., 1., 4., 19., 22., 37.,
+ 40., 2., 5., 20., 23., 38., 41., 6., 9., 24., 27.,
+ 42., 45., 7., 10., 25., 28., 43., 46., 8., 11., 26.,
+ 29., 44., 47., 12., 15., 30., 33., 48., 51., 13., 16.,
+ 31., 34., 49., 52., 14., 17., 32., 35., 50., 53., 54.,
+ 57., 72., 75., 90., 93., 55., 58., 73., 76., 91., 94.,
+ 56., 59., 74., 77., 92., 95., 60., 63., 78., 81., 96.,
+ 99., 61., 64., 79., 82., 97., 100., 62., 65., 80., 83.,
+ 98., 101., 66., 69., 84., 87., 102., 105., 67., 70., 85.,
+ 88., 103., 106., 68., 71., 86., 89., 104., 107.};
+
+ const std::vector output_shape = {2, 27, 1, 2};
+
+ if constexpr (std::is_same::value) {
+ test.AddInput("input", {N, C, H, W}, X);
+ test.AddOutput("output", output_shape, result);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_fp16(X.size());
+ std::vector result_fp16(result.size());
+ ConvertFloatToMLFloat16(X.data(), X_fp16.data(), X.size());
+ ConvertFloatToMLFloat16(result.data(), result_fp16.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_fp16);
+ test.AddOutput("output", output_shape, result_fp16);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_u8(X.size());
+ std::vector result_u8(result.size());
+ ConvertFloatToUint8_t(X.data(), X_u8.data(), X.size());
+ ConvertFloatToUint8_t(result.data(), result_u8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_u8);
+ test.AddOutput("output", output_shape, result_u8);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_i8(X.size());
+ std::vector result_i8(result.size());
+ ConvertFloatToInt8_t(X.data(), X_i8.data(), X.size());
+ ConvertFloatToInt8_t(result.data(), result_i8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_i8);
+ test.AddOutput("output", output_shape, result_i8);
+ } else {
+ ORT_THROW("Type not supported");
+ }
+
+ // Exclude the QNN EP, which does not support all of the tested element types (e.g. MLFloat16 and 8-bit integer).
+ std::unordered_set excluded_eps = {kQnnExecutionProvider};
+ if constexpr (std::is_same::value) {
+ // TensorRT does not reject int8 input up front (as it does for uint8); instead it accepts the node
+ // and then fails at engine build time because int8 I/O without Q/DQ layers needs a calibrator or a
+ // set dynamic range. This op's 8-bit integer support targets the CPU EP, so exclude TensorRT here.
+ excluded_eps.insert(kTensorrtExecutionProvider);
+ }
+ test.Run(OpTester::ExpectResult::kExpectSuccess, "", excluded_eps);
+}
+
+// Explicitly exercises the opset 13 SpaceToDepth kernel registration with the supported element types.
+// Uses small data within int8_t's range [-128, 127] so the float -> int8_t conversion is exact.
+TYPED_TEST(TensorOpTest, SpaceToDepthTest_int_opset13) {
+ OpTester test("SpaceToDepth", 13); // create an opset 13 model
+ constexpr int64_t blocksize = 2;
+ test.AddAttribute("blocksize", blocksize);
+ constexpr int64_t N = 1, C = 2, H = 2, W = 4;
+ const std::vector X = {0., 1., 2., 3., 4., 5., 6., 7.,
+ 8., 9., 10., 11., 12., 13., 14., 15.};
+ const std::vector result = {0., 2., 8., 10., 1., 3., 9., 11.,
+ 4., 6., 12., 14., 5., 7., 13., 15.};
+ const std::vector output_shape = {N, C * blocksize * blocksize, H / blocksize, W / blocksize};
+
+ if constexpr (std::is_same::value) {
+ test.AddInput("input", {N, C, H, W}, X);
+ test.AddOutput("output", output_shape, result);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_fp16(X.size());
+ std::vector result_fp16(result.size());
+ ConvertFloatToMLFloat16(X.data(), X_fp16.data(), X.size());
+ ConvertFloatToMLFloat16(result.data(), result_fp16.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_fp16);
+ test.AddOutput("output", output_shape, result_fp16);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_u8(X.size());
+ std::vector result_u8(result.size());
+ ConvertFloatToUint8_t(X.data(), X_u8.data(), X.size());
+ ConvertFloatToUint8_t(result.data(), result_u8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_u8);
+ test.AddOutput("output", output_shape, result_u8);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_i8(X.size());
+ std::vector result_i8(result.size());
+ ConvertFloatToInt8_t(X.data(), X_i8.data(), X.size());
+ ConvertFloatToInt8_t(result.data(), result_i8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_i8);
+ test.AddOutput("output", output_shape, result_i8);
+ } else {
+ ORT_THROW("Type not supported");
+ }
+
+ // Exclude the QNN EP, which does not support all of the tested element types (e.g. MLFloat16 and 8-bit integer).
+ std::unordered_set excluded_eps = {kQnnExecutionProvider};
+ if constexpr (std::is_same::value) {
+ // TensorRT does not reject int8 input up front (as it does for uint8); instead it accepts the node
+ // and then fails at engine build time because int8 I/O without Q/DQ layers needs a calibrator or a
+ // set dynamic range. This op's 8-bit integer support targets the CPU EP, so exclude TensorRT here.
+ excluded_eps.insert(kTensorrtExecutionProvider);
+ }
+ test.Run(OpTester::ExpectResult::kExpectSuccess, "", excluded_eps);
+}
+
+// Explicitly exercises the opset 13 DepthToSpace kernel registration with the supported element types.
+// Uses small data within int8_t's range [-128, 127] so the float -> int8_t conversion is exact.
+TYPED_TEST(TensorOpTest, DepthToSpaceTest_int_opset13) {
+ OpTester test("DepthToSpace", 13); // create an opset 13 model (default mode = "DCR")
+ constexpr int64_t blocksize = 2;
+ test.AddAttribute("blocksize", blocksize);
+ constexpr int64_t N = 1, C = 8, H = 1, W = 2;
+ const std::vector X = {0., 1., 2., 3., 4., 5., 6., 7.,
+ 8., 9., 10., 11., 12., 13., 14., 15.};
+ const std::vector result = {0., 4., 1., 5., 8., 12., 9., 13.,
+ 2., 6., 3., 7., 10., 14., 11., 15.};
+ const std::vector output_shape = {N, C / (blocksize * blocksize), H * blocksize, W * blocksize};
+
+ if constexpr (std::is_same::value) {
+ test.AddInput("input", {N, C, H, W}, X);
+ test.AddOutput("output", output_shape, result);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_fp16(X.size());
+ std::vector result_fp16(result.size());
+ ConvertFloatToMLFloat16(X.data(), X_fp16.data(), X.size());
+ ConvertFloatToMLFloat16(result.data(), result_fp16.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_fp16);
+ test.AddOutput("output", output_shape, result_fp16);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_u8(X.size());
+ std::vector result_u8(result.size());
+ ConvertFloatToUint8_t(X.data(), X_u8.data(), X.size());
+ ConvertFloatToUint8_t(result.data(), result_u8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_u8);
+ test.AddOutput("output", output_shape, result_u8);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_i8(X.size());
+ std::vector result_i8(result.size());
+ ConvertFloatToInt8_t(X.data(), X_i8.data(), X.size());
+ ConvertFloatToInt8_t(result.data(), result_i8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_i8);
+ test.AddOutput("output", output_shape, result_i8);
+ } else {
+ ORT_THROW("Type not supported");
+ }
+
+ // Exclude the QNN EP, which does not support all of the tested element types (e.g. MLFloat16 and 8-bit integer).
+ std::unordered_set excluded_eps = {kQnnExecutionProvider};
+ if constexpr (std::is_same::value) {
+ // TensorRT does not reject int8 input up front (as it does for uint8); instead it accepts the node
+ // and then fails at engine build time because int8 I/O without Q/DQ layers needs a calibrator or a
+ // set dynamic range. This op's 8-bit integer support targets the CPU EP, so exclude TensorRT here.
+ excluded_eps.insert(kTensorrtExecutionProvider);
+ }
+ test.Run(OpTester::ExpectResult::kExpectSuccess, "", excluded_eps);
+}
+
TEST(TensorOpTest, DepthToSpaceTest_1) {
OpTester test("DepthToSpace", 7); // create an opset 7 model
constexpr int64_t blocksize = 2;
@@ -319,12 +499,30 @@ TYPED_TEST(TensorOpTest, DepthToSpaceTest_3) {
ConvertFloatToUint8_t(result.data(), result_u8.data(), result.size());
test.AddInput("input", {N, C, H, W}, X_u8);
test.AddOutput("output", {2, 3, 6, 4}, result_u8);
+ } else if constexpr (std::is_same::value) {
+ // Note: this shared float data runs up to 143, outside int8_t's range [-128, 127], so the
+ // float -> int8_t conversion of those values is implementation-defined. That is intentional and
+ // harmless here: DepthToSpace is a pure permutation and the same conversion is applied to both the
+ // input and the expected output, so the values wrap identically on both sides and still match.
+ std::vector X_i8(X.size());
+ std::vector result_i8(result.size());
+ ConvertFloatToInt8_t(X.data(), X_i8.data(), X.size());
+ ConvertFloatToInt8_t(result.data(), result_i8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_i8);
+ test.AddOutput("output", {2, 3, 6, 4}, result_i8);
} else {
ORT_THROW("Type not supported");
}
// type not supported by QNN EP: MLFloat16 and unsigned char
- test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
+ std::unordered_set excluded_eps = {kQnnExecutionProvider};
+ if constexpr (std::is_same::value) {
+ // TensorRT does not reject int8 input up front (as it does for uint8); instead it accepts the node
+ // and then fails at engine build time because int8 I/O without Q/DQ layers needs a calibrator or a
+ // set dynamic range. This op's 8-bit integer support targets the CPU EP, so exclude TensorRT here.
+ excluded_eps.insert(kTensorrtExecutionProvider);
+ }
+ test.Run(OpTester::ExpectResult::kExpectSuccess, "", excluded_eps);
}
TYPED_TEST(TensorOpTest, DepthToSpaceTest_4) {
@@ -383,12 +581,30 @@ TYPED_TEST(TensorOpTest, DepthToSpaceTest_4) {
ConvertFloatToUint8_t(result.data(), result_u8.data(), result.size());
test.AddInput("input", {N, C, H, W}, X_u8);
test.AddOutput("output", {2, 3, 6, 4}, result_u8);
+ } else if constexpr (std::is_same::value) {
+ // Note: this shared float data runs up to 143, outside int8_t's range [-128, 127], so the
+ // float -> int8_t conversion of those values is implementation-defined. That is intentional and
+ // harmless here: DepthToSpace is a pure permutation and the same conversion is applied to both the
+ // input and the expected output, so the values wrap identically on both sides and still match.
+ std::vector X_i8(X.size());
+ std::vector result_i8(result.size());
+ ConvertFloatToInt8_t(X.data(), X_i8.data(), X.size());
+ ConvertFloatToInt8_t(result.data(), result_i8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_i8);
+ test.AddOutput("output", {2, 3, 6, 4}, result_i8);
} else {
ORT_THROW("Type not supported");
}
// type not supported by QNN EP: MLFloat16 and unsigned char
- test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
+ std::unordered_set excluded_eps = {kQnnExecutionProvider};
+ if constexpr (std::is_same::value) {
+ // TensorRT does not reject int8 input up front (as it does for uint8); instead it accepts the node
+ // and then fails at engine build time because int8 I/O without Q/DQ layers needs a calibrator or a
+ // set dynamic range. This op's 8-bit integer support targets the CPU EP, so exclude TensorRT here.
+ excluded_eps.insert(kTensorrtExecutionProvider);
+ }
+ test.Run(OpTester::ExpectResult::kExpectSuccess, "", excluded_eps);
}
TYPED_TEST(TensorOpTest, DepthToSpaceTest_5) {
@@ -429,12 +645,26 @@ TYPED_TEST(TensorOpTest, DepthToSpaceTest_5) {
ConvertFloatToUint8_t(result.data(), result_u8.data(), result.size());
test.AddInput("input", {N, C, H, W}, X_u8);
test.AddOutput("output", {1, 1, 4, 6}, result_u8);
+ } else if constexpr (std::is_same::value) {
+ std::vector X_i8(X.size());
+ std::vector result_i8(result.size());
+ ConvertFloatToInt8_t(X.data(), X_i8.data(), X.size());
+ ConvertFloatToInt8_t(result.data(), result_i8.data(), result.size());
+ test.AddInput("input", {N, C, H, W}, X_i8);
+ test.AddOutput("output", {1, 1, 4, 6}, result_i8);
} else {
ORT_THROW("Type not supported");
}
// type not supported by QNN EP: MLFloat16 and unsigned char
- test.Run(OpTester::ExpectResult::kExpectSuccess, "", {kQnnExecutionProvider});
+ std::unordered_set excluded_eps = {kQnnExecutionProvider};
+ if constexpr (std::is_same::value) {
+ // TensorRT does not reject int8 input up front (as it does for uint8); instead it accepts the node
+ // and then fails at engine build time because int8 I/O without Q/DQ layers needs a calibrator or a
+ // set dynamic range. This op's 8-bit integer support targets the CPU EP, so exclude TensorRT here.
+ excluded_eps.insert(kTensorrtExecutionProvider);
+ }
+ test.Run(OpTester::ExpectResult::kExpectSuccess, "", excluded_eps);
}
TEST(TensorOpTest, DepthToSpaceTest_CRD_Batched) {
diff --git a/onnxruntime/test/unittest_util/conversion.h b/onnxruntime/test/unittest_util/conversion.h
index bb270e4c697d5..b0f0663f33dd7 100644
--- a/onnxruntime/test/unittest_util/conversion.h
+++ b/onnxruntime/test/unittest_util/conversion.h
@@ -22,6 +22,12 @@ inline void ConvertFloatToUint8_t(const float* f_datat, uint8_t* u8_data, size_t
output_vector = in_vector.template cast();
}
+inline void ConvertFloatToInt8_t(const float* f_datat, int8_t* i8_data, size_t input_size) {
+ auto in_vector = ConstEigenVectorMap(f_datat, input_size);
+ auto output_vector = EigenVectorMap(static_cast(static_cast(i8_data)), input_size);
+ output_vector = in_vector.template cast();
+}
+
inline void ConvertMLFloat16ToFloat(const MLFloat16* h_data, float* f_data, size_t input_size) {
auto in_vector =
ConstEigenVectorMap(static_cast(static_cast(h_data)), input_size);