|
2 | 2 | // Licensed under the MIT License. |
3 | 3 |
|
4 | 4 | #include <exception> |
| 5 | +#include <limits> |
5 | 6 | #include "gtest/gtest.h" |
6 | 7 | #include "test/providers/provider_test_utils.h" |
7 | 8 | #include "test/util/include/default_providers.h" |
@@ -3187,6 +3188,209 @@ TEST(ResizeOpTest, Axes_OutOfRange_18) { |
3187 | 3188 | {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider}); |
3188 | 3189 | } |
3189 | 3190 |
|
| 3191 | +// Negative axis below the valid range must be rejected before being used as a scatter index. |
| 3192 | +TEST(ResizeOpTest, Axes_NegativeOutOfRange_18) { |
| 3193 | + std::vector<float> X(16 * 4); |
| 3194 | + std::iota(X.begin(), X.end(), 0.f); |
| 3195 | + std::vector<float> roi{}; |
| 3196 | + std::vector<float> scales{0.75f, 0.75f, 0.75f}; |
| 3197 | + std::vector<int64_t> axes{2, 3, -6}; |
| 3198 | + std::vector<float> Y(16 * 4, 0.0f); |
| 3199 | + |
| 3200 | + OpTester test("Resize", 18); |
| 3201 | + test.AddShapeToTensorData(false); |
| 3202 | + test.AddAttribute("mode", "linear"); |
| 3203 | + test.AddAttribute<std::vector<int64_t>>("axes", axes); |
| 3204 | + |
| 3205 | + test.AddInput<float>("X", {1, 1, 4, 4, 4}, X); |
| 3206 | + test.AddInput<float>("roi", {0}, roi); |
| 3207 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales); |
| 3208 | + test.AddOutput<float>("Y", {1, 1, 4, 4, 4}, Y); |
| 3209 | + |
| 3210 | + // TensorRT, QNN, and DML do not exercise the CPU axes-validation path. |
| 3211 | + test.Run(OpTester::ExpectResult::kExpectFailure, |
| 3212 | + "axis -6 is not in valid range [-5,4]", |
| 3213 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider}); |
| 3214 | +} |
| 3215 | + |
| 3216 | +// Valid negative axes (within [-rank, -1]) must still produce correct output. |
| 3217 | +TEST(ResizeOpTest, Axes_NegativeInRange_18) { |
| 3218 | + std::vector<float> X(16 * 4); |
| 3219 | + std::iota(X.begin(), X.end(), 0.f); |
| 3220 | + std::vector<float> Y = {3.5f, 4.8333335f, 6.1666665f, 8.833333f, 10.166667f, 11.5f, 14.166667f, |
| 3221 | + 15.5f, 16.833334f, 24.833334f, 26.166666f, 27.5f, 30.166666f, 31.5f, |
| 3222 | + 32.833332f, 35.5f, 36.833332f, 38.166668f, 46.166668f, 47.5f, 48.833332f, |
| 3223 | + 51.5f, 52.833332f, 54.166668f, 56.833332f, 58.166668f, 59.5f}; |
| 3224 | + std::vector<float> roi{}; |
| 3225 | + std::vector<float> scales{3 / 4.0f, 3 / 4.0f, 3 / 4.0f}; |
| 3226 | + std::vector<int64_t> output_shape{1, 1, 3, 3, 3}; |
| 3227 | + std::vector<int64_t> axes{-3, -2, -1}; |
| 3228 | + |
| 3229 | + OpTester test("Resize", 18); |
| 3230 | + test.AddAttribute<int64_t>("exclude_outside", 0LL); |
| 3231 | + test.AddAttribute<std::vector<int64_t>>("axes", axes); |
| 3232 | + test.AddAttribute<int64_t>("antialias", 0LL); |
| 3233 | + test.AddAttribute("mode", "linear"); |
| 3234 | + |
| 3235 | + test.AddInput<float>("X", {1, 1, 4, 4, 4}, X); |
| 3236 | + test.AddInput<float>("roi", {int64_t(roi.size())}, roi); |
| 3237 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales, true); |
| 3238 | + |
| 3239 | + test.AddOutput<float>("Y", output_shape, Y); |
| 3240 | + // OpenVINO EP's Resize importer does not normalize negative axes against the input rank, |
| 3241 | + // so it rejects models that the ONNX spec accepts. Tracked by GH issue #28788. |
| 3242 | + test.Run(OpTester::ExpectResult::kExpectSuccess, "", |
| 3243 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kOpenVINOExecutionProvider}); |
| 3244 | +} |
| 3245 | + |
| 3246 | +// When axes is provided, the sizes input length must match axes length so the scatter |
| 3247 | +// loop does not read past the end of sizes. |
| 3248 | +TEST(ResizeOpTest, Axes_and_Sizes_CountMismatch_18) { |
| 3249 | + std::vector<float> X(16 * 4); |
| 3250 | + std::iota(X.begin(), X.end(), 0.f); |
| 3251 | + std::vector<float> roi{}; |
| 3252 | + std::vector<float> scales{}; |
| 3253 | + std::vector<int64_t> sizes{3, 3}; |
| 3254 | + std::vector<int64_t> axes{2, 3, 4}; |
| 3255 | + std::vector<float> Y(16 * 4, 0.0f); |
| 3256 | + |
| 3257 | + OpTester test("Resize", 18); |
| 3258 | + test.AddShapeToTensorData(false); |
| 3259 | + test.AddAttribute("mode", "linear"); |
| 3260 | + test.AddAttribute<std::vector<int64_t>>("axes", axes); |
| 3261 | + |
| 3262 | + test.AddInput<float>("X", {1, 1, 4, 4, 4}, X); |
| 3263 | + test.AddInput<float>("roi", {0}, roi); |
| 3264 | + test.AddInput<float>("", {0}, scales); |
| 3265 | + test.AddInput<int64_t>("sizes", {int64_t(sizes.size())}, sizes); |
| 3266 | + test.AddOutput<float>("Y", {1, 1, 4, 4, 4}, Y); |
| 3267 | + |
| 3268 | + test.Run(OpTester::ExpectResult::kExpectFailure, |
| 3269 | + "Number of elements in sizes should be equal to number of axes.", |
| 3270 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider}); |
| 3271 | +} |
| 3272 | + |
| 3273 | +// Non-finite scale values must be rejected before being multiplied into output dimensions. |
| 3274 | +TEST(ResizeOpTest, Scales_NaN_Rejected_18) { |
| 3275 | + std::vector<float> X(16, 1.0f); |
| 3276 | + std::vector<float> roi{}; |
| 3277 | + std::vector<float> scales{1.0f, 1.0f, std::numeric_limits<float>::quiet_NaN(), 2.0f}; |
| 3278 | + std::vector<float> Y(32, 0.0f); |
| 3279 | + |
| 3280 | + OpTester test("Resize", 18); |
| 3281 | + test.AddShapeToTensorData(false); |
| 3282 | + test.AddAttribute("mode", "linear"); |
| 3283 | + |
| 3284 | + test.AddInput<float>("X", {1, 1, 4, 4}, X); |
| 3285 | + test.AddInput<float>("roi", {0}, roi); |
| 3286 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales); |
| 3287 | + test.AddOutput<float>("Y", {1, 1, 4, 8}, Y); |
| 3288 | + |
| 3289 | + // EPs that do their own validation or do not exercise the CPU ScalesValidation path. |
| 3290 | + test.Run(OpTester::ExpectResult::kExpectFailure, "Scale value must be finite.", |
| 3291 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider, |
| 3292 | + kOpenVINOExecutionProvider}); |
| 3293 | +} |
| 3294 | + |
| 3295 | +TEST(ResizeOpTest, Scales_PositiveInf_Rejected_18) { |
| 3296 | + std::vector<float> X(16, 1.0f); |
| 3297 | + std::vector<float> roi{}; |
| 3298 | + std::vector<float> scales{1.0f, 1.0f, 2.0f, std::numeric_limits<float>::infinity()}; |
| 3299 | + std::vector<float> Y(32, 0.0f); |
| 3300 | + |
| 3301 | + OpTester test("Resize", 18); |
| 3302 | + test.AddShapeToTensorData(false); |
| 3303 | + test.AddAttribute("mode", "linear"); |
| 3304 | + |
| 3305 | + test.AddInput<float>("X", {1, 1, 4, 4}, X); |
| 3306 | + test.AddInput<float>("roi", {0}, roi); |
| 3307 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales); |
| 3308 | + test.AddOutput<float>("Y", {1, 1, 8, 4}, Y); |
| 3309 | + |
| 3310 | + test.Run(OpTester::ExpectResult::kExpectFailure, "Scale value must be finite.", |
| 3311 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider, |
| 3312 | + kOpenVINOExecutionProvider}); |
| 3313 | +} |
| 3314 | + |
| 3315 | +// Duplicate values in the axes attribute violate the ONNX spec. The check runs after |
| 3316 | +// negative-axis normalization, so it covers both raw duplicates and pairs that collide |
| 3317 | +// only after canonicalization (e.g. {-1, rank-1}). |
| 3318 | +TEST(ResizeOpTest, Axes_Duplicate_Rejected_18) { |
| 3319 | + std::vector<float> X(16 * 4); |
| 3320 | + std::iota(X.begin(), X.end(), 0.f); |
| 3321 | + std::vector<float> roi{}; |
| 3322 | + std::vector<float> scales{0.75f, 0.75f, 0.75f}; |
| 3323 | + std::vector<int64_t> axes{2, 3, 2}; |
| 3324 | + std::vector<float> Y(16 * 4, 0.0f); |
| 3325 | + |
| 3326 | + OpTester test("Resize", 18); |
| 3327 | + test.AddShapeToTensorData(false); |
| 3328 | + test.AddAttribute("mode", "linear"); |
| 3329 | + test.AddAttribute<std::vector<int64_t>>("axes", axes); |
| 3330 | + |
| 3331 | + test.AddInput<float>("X", {1, 1, 4, 4, 4}, X); |
| 3332 | + test.AddInput<float>("roi", {0}, roi); |
| 3333 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales); |
| 3334 | + test.AddOutput<float>("Y", {1, 1, 4, 4, 4}, Y); |
| 3335 | + |
| 3336 | + test.Run(OpTester::ExpectResult::kExpectFailure, |
| 3337 | + "axes attribute contains duplicate axis 2", |
| 3338 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider}); |
| 3339 | +} |
| 3340 | + |
| 3341 | +// Negative and positive axis entries that resolve to the same canonical index must be |
| 3342 | +// rejected. For rank=5, axes={-1, 4} both map to 4. |
| 3343 | +TEST(ResizeOpTest, Axes_Duplicate_AfterNormalization_Rejected_18) { |
| 3344 | + std::vector<float> X(16 * 4); |
| 3345 | + std::iota(X.begin(), X.end(), 0.f); |
| 3346 | + std::vector<float> roi{}; |
| 3347 | + std::vector<float> scales{0.75f, 0.75f}; |
| 3348 | + std::vector<int64_t> axes{-1, 4}; |
| 3349 | + std::vector<float> Y(16 * 4, 0.0f); |
| 3350 | + |
| 3351 | + OpTester test("Resize", 18); |
| 3352 | + test.AddShapeToTensorData(false); |
| 3353 | + test.AddAttribute("mode", "linear"); |
| 3354 | + test.AddAttribute<std::vector<int64_t>>("axes", axes); |
| 3355 | + |
| 3356 | + test.AddInput<float>("X", {1, 1, 4, 4, 4}, X); |
| 3357 | + test.AddInput<float>("roi", {0}, roi); |
| 3358 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales); |
| 3359 | + test.AddOutput<float>("Y", {1, 1, 4, 4, 4}, Y); |
| 3360 | + |
| 3361 | + test.Run(OpTester::ExpectResult::kExpectFailure, |
| 3362 | + "axes attribute contains duplicate axis 4", |
| 3363 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider}); |
| 3364 | +} |
| 3365 | + |
| 3366 | +// When axes is provided in tf_crop_and_resize mode, the roi input must contain at least |
| 3367 | +// 2 * len(axes) entries so the per-axis start/end pairs can be read safely. |
| 3368 | +TEST(ResizeOpTest, Roi_TooShortForAxes_18) { |
| 3369 | + std::vector<float> X(16 * 4); |
| 3370 | + std::iota(X.begin(), X.end(), 0.f); |
| 3371 | + // roi length 4 (= 2 * 2), but axes has 3 entries, so 2 * len(axes) = 6 are required. |
| 3372 | + std::vector<float> roi{0.0f, 0.0f, 1.0f, 1.0f}; |
| 3373 | + std::vector<float> scales{0.75f, 0.75f, 0.75f}; |
| 3374 | + std::vector<int64_t> axes{2, 3, 4}; |
| 3375 | + std::vector<float> Y(16 * 4, 0.0f); |
| 3376 | + |
| 3377 | + OpTester test("Resize", 18); |
| 3378 | + test.AddShapeToTensorData(false); |
| 3379 | + test.AddAttribute("mode", "linear"); |
| 3380 | + test.AddAttribute("coordinate_transformation_mode", "tf_crop_and_resize"); |
| 3381 | + test.AddAttribute<std::vector<int64_t>>("axes", axes); |
| 3382 | + |
| 3383 | + test.AddInput<float>("X", {1, 1, 4, 4, 4}, X); |
| 3384 | + test.AddInput<float>("roi", {int64_t(roi.size())}, roi); |
| 3385 | + test.AddInput<float>("scales", {int64_t(scales.size())}, scales); |
| 3386 | + test.AddOutput<float>("Y", {1, 1, 4, 4, 4}, Y); |
| 3387 | + |
| 3388 | + test.Run(OpTester::ExpectResult::kExpectFailure, |
| 3389 | + "roi input length", |
| 3390 | + {kTensorrtExecutionProvider, kQnnExecutionProvider, kDmlExecutionProvider, |
| 3391 | + kOpenVINOExecutionProvider}); |
| 3392 | +} |
| 3393 | + |
3190 | 3394 | TEST(ResizeOpTest, Sizes_RankMismatch_13) { |
3191 | 3395 | OpTester test("Resize", 13); |
3192 | 3396 | test.AddShapeToTensorData(false); |
|
0 commit comments