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);