1 // Copyright 2019 Google LLC 2 // 3 // This source code is licensed under the BSD-style license found in the 4 // LICENSE file in the root directory of this source tree. 5 6 #pragma once 7 8 #include <gtest/gtest.h> 9 10 #include <algorithm> 11 #include <cmath> 12 #include <cassert> 13 #include <cstddef> 14 #include <cstdlib> 15 #include <functional> 16 #include <random> 17 #include <vector> 18 19 #include <fp16.h> 20 21 #include <xnnpack.h> 22 23 24 class ResizeBilinearOperatorTester { 25 public: input_size(size_t input_height,size_t input_width)26 inline ResizeBilinearOperatorTester& input_size(size_t input_height, size_t input_width) { 27 assert(input_height >= 1); 28 assert(input_width >= 1); 29 this->input_height_ = input_height; 30 this->input_width_ = input_width; 31 return *this; 32 } 33 input_height(size_t input_height)34 inline ResizeBilinearOperatorTester& input_height(size_t input_height) { 35 assert(input_height >= 1); 36 this->input_height_ = input_height; 37 return *this; 38 } 39 input_height()40 inline size_t input_height() const { 41 return this->input_height_; 42 } 43 input_width(size_t input_width)44 inline ResizeBilinearOperatorTester& input_width(size_t input_width) { 45 assert(input_width >= 1); 46 this->input_width_ = input_width; 47 return *this; 48 } 49 input_width()50 inline size_t input_width() const { 51 return this->input_width_; 52 } 53 output_size(size_t output_height,size_t output_width)54 inline ResizeBilinearOperatorTester& output_size(size_t output_height, size_t output_width) { 55 assert(output_height >= 1); 56 assert(output_width >= 1); 57 this->output_height_ = output_height; 58 this->output_width_ = output_width; 59 return *this; 60 } 61 output_height(size_t output_height)62 inline ResizeBilinearOperatorTester& output_height(size_t output_height) { 63 assert(output_height >= 1); 64 this->output_height_ = output_height; 65 return *this; 66 } 67 output_height()68 inline size_t output_height() const { 69 return this->output_height_; 70 } 71 output_width(size_t output_width)72 inline ResizeBilinearOperatorTester& output_width(size_t output_width) { 73 assert(output_width >= 1); 74 this->output_width_ = output_width; 75 return *this; 76 } 77 output_width()78 inline size_t output_width() const { 79 return this->output_width_; 80 } 81 height_scale()82 inline float height_scale() const { 83 if (align_corners() && output_height() > 1) { 84 return float(input_height() - 1) / float(output_height() - 1); 85 } else { 86 return float(input_height()) / float(output_height()); 87 } 88 } 89 width_scale()90 inline float width_scale() const { 91 if (align_corners() && output_width() > 1) { 92 return float(input_width() - 1) / float(output_width() - 1); 93 } else { 94 return float(input_width()) / float(output_width()); 95 } 96 } 97 channels(size_t channels)98 inline ResizeBilinearOperatorTester& channels(size_t channels) { 99 assert(channels != 0); 100 this->channels_ = channels; 101 return *this; 102 } 103 channels()104 inline size_t channels() const { 105 return this->channels_; 106 } 107 batch_size(size_t batch_size)108 inline ResizeBilinearOperatorTester& batch_size(size_t batch_size) { 109 assert(batch_size != 0); 110 this->batch_size_ = batch_size; 111 return *this; 112 } 113 batch_size()114 inline size_t batch_size() const { 115 return this->batch_size_; 116 } 117 input_pixel_stride(size_t input_pixel_stride)118 inline ResizeBilinearOperatorTester& input_pixel_stride(size_t input_pixel_stride) { 119 assert(input_pixel_stride != 0); 120 this->input_pixel_stride_ = input_pixel_stride; 121 return *this; 122 } 123 input_pixel_stride()124 inline size_t input_pixel_stride() const { 125 if (this->input_pixel_stride_ == 0) { 126 return channels(); 127 } else { 128 assert(this->input_pixel_stride_ >= channels()); 129 return this->input_pixel_stride_; 130 } 131 } 132 output_pixel_stride(size_t output_pixel_stride)133 inline ResizeBilinearOperatorTester& output_pixel_stride(size_t output_pixel_stride) { 134 assert(output_pixel_stride != 0); 135 this->output_pixel_stride_ = output_pixel_stride; 136 return *this; 137 } 138 output_pixel_stride()139 inline size_t output_pixel_stride() const { 140 if (this->output_pixel_stride_ == 0) { 141 return channels(); 142 } else { 143 assert(this->output_pixel_stride_ >= channels()); 144 return this->output_pixel_stride_; 145 } 146 } 147 next_input_size(uint32_t next_input_height,uint32_t next_input_width)148 inline ResizeBilinearOperatorTester& next_input_size(uint32_t next_input_height, uint32_t next_input_width) { 149 assert(next_input_height >= 1); 150 assert(next_input_width >= 1); 151 this->next_input_height_ = next_input_height; 152 this->next_input_width_ = next_input_width; 153 return *this; 154 } 155 next_input_height(uint32_t next_input_height)156 inline ResizeBilinearOperatorTester& next_input_height(uint32_t next_input_height) { 157 assert(next_input_height >= 1); 158 this->next_input_height_ = next_input_height; 159 return *this; 160 } 161 next_input_height()162 inline uint32_t next_input_height() const { 163 if (this->next_input_height_ == 0) { 164 return input_height(); 165 } else { 166 return this->next_input_height_; 167 } 168 } 169 next_input_width(uint32_t next_input_width)170 inline ResizeBilinearOperatorTester& next_input_width(uint32_t next_input_width) { 171 assert(next_input_width >= 1); 172 this->next_input_width_ = next_input_width; 173 return *this; 174 } 175 next_input_width()176 inline uint32_t next_input_width() const { 177 if (this->next_input_width_ == 0) { 178 return input_width(); 179 } else { 180 return this->next_input_width_; 181 } 182 } 183 next_batch_size(size_t next_batch_size)184 inline ResizeBilinearOperatorTester& next_batch_size(size_t next_batch_size) { 185 assert(next_batch_size >= 1); 186 this->next_batch_size_ = next_batch_size; 187 return *this; 188 } 189 next_batch_size()190 inline size_t next_batch_size() const { 191 if (this->next_batch_size_ == 0) { 192 return batch_size(); 193 } else { 194 return this->next_batch_size_; 195 } 196 } 197 align_corners(bool align_corners)198 inline ResizeBilinearOperatorTester& align_corners(bool align_corners) { 199 this->align_corners_ = align_corners; 200 return *this; 201 } 202 align_corners()203 inline bool align_corners() const { 204 return this->align_corners_; 205 } 206 tf_legacy_mode(bool tf_legacy_mode)207 inline ResizeBilinearOperatorTester& tf_legacy_mode(bool tf_legacy_mode) { 208 this->tf_legacy_mode_ = tf_legacy_mode; 209 return *this; 210 } 211 tf_legacy_mode()212 inline bool tf_legacy_mode() const { 213 return this->tf_legacy_mode_; 214 } 215 iterations(size_t iterations)216 inline ResizeBilinearOperatorTester& iterations(size_t iterations) { 217 this->iterations_ = iterations; 218 return *this; 219 } 220 iterations()221 inline size_t iterations() const { 222 return this->iterations_; 223 } 224 TestNHWCxF16()225 void TestNHWCxF16() const { 226 if (align_corners()) { 227 ASSERT_FALSE(tf_legacy_mode()); 228 } 229 230 std::random_device random_device; 231 auto rng = std::mt19937(random_device()); 232 std::uniform_real_distribution<float> f32dist; 233 234 std::vector<uint16_t> input(XNN_EXTRA_BYTES / sizeof(uint16_t) + 235 (batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels()); 236 std::vector<uint16_t> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels()); 237 std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels()); 238 for (size_t iteration = 0; iteration < iterations(); iteration++) { 239 std::generate(input.begin(), input.end(), [&]() { return fp16_ieee_from_fp32_value(f32dist(rng)); }); 240 std::fill(output.begin(), output.end(), UINT16_C(0x7E00) /* NaN */); 241 242 // Compute reference results. 243 const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f; 244 for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) { 245 for (size_t output_y = 0; output_y < output_height(); output_y++) { 246 const float input_y = (float(output_y) + offset) * height_scale() - offset; 247 const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0); 248 const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1); 249 const float y_alpha = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(input_y - std::floor(input_y))); 250 for (size_t output_x = 0; output_x < output_width(); output_x++) { 251 const float input_x = (float(output_x) + offset) * width_scale() - offset; 252 const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0); 253 const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1); 254 const float x_alpha = fp16_ieee_to_fp32_value(fp16_ieee_from_fp32_value(input_x - std::floor(input_x))); 255 for (size_t c = 0; c < channels(); c++) { 256 output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] = 257 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c]) * (1.0f - y_alpha) * (1.0f - x_alpha) + 258 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c]) * (1.0f - y_alpha) * x_alpha + 259 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c]) * y_alpha * (1.0f - x_alpha) + 260 fp16_ieee_to_fp32_value(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c]) * y_alpha * x_alpha; 261 } 262 } 263 } 264 } 265 266 // Create, setup, run, and destroy Resize Bilinear operator. 267 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 268 xnn_operator_t resize_bilinear_op = nullptr; 269 270 const xnn_status status = xnn_create_resize_bilinear2d_nhwc_f16( 271 channels(), input_pixel_stride(), output_pixel_stride(), 272 (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0), 273 &resize_bilinear_op); 274 if (status == xnn_status_unsupported_hardware) { 275 GTEST_SKIP(); 276 } 277 ASSERT_EQ(xnn_status_success, status); 278 ASSERT_NE(nullptr, resize_bilinear_op); 279 280 // Smart pointer to automatically delete resize_bilinear_op. 281 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator); 282 283 ASSERT_EQ(xnn_status_success, 284 xnn_setup_resize_bilinear2d_nhwc_f16( 285 resize_bilinear_op, 286 batch_size(), input_height(), input_width(), 287 output_height(), output_width(), 288 input.data(), output.data(), 289 nullptr /* thread pool */)); 290 291 ASSERT_EQ(xnn_status_success, 292 xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */)); 293 294 // Verify results. 295 for (size_t i = 0; i < batch_size(); i++) { 296 for (size_t y = 0; y < output_height(); y++) { 297 for (size_t x = 0; x < output_width(); x++) { 298 for (size_t c = 0; c < channels(); c++) { 299 ASSERT_NEAR( 300 fp16_ieee_to_fp32_value(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c]), 301 output_ref[((i * output_height() + y) * output_width() + x) * channels() + c], 302 std::max(1.0e-4f, std::abs(output_ref[((i * output_height() + y) * output_width() + x) * channels() + c]) * 1.0e-2f)) << 303 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c; 304 } 305 } 306 } 307 } 308 } 309 } 310 TestNHWCxF32()311 void TestNHWCxF32() const { 312 if (align_corners()) { 313 ASSERT_FALSE(tf_legacy_mode()); 314 } 315 316 std::random_device random_device; 317 auto rng = std::mt19937(random_device()); 318 std::uniform_real_distribution<float> f32dist; 319 320 std::vector<float> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(float)); 321 std::vector<float> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels()); 322 std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels()); 323 for (size_t iteration = 0; iteration < iterations(); iteration++) { 324 std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); }); 325 std::fill(output.begin(), output.end(), std::nanf("")); 326 327 // Compute reference results. 328 const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f; 329 for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) { 330 for (size_t output_y = 0; output_y < output_height(); output_y++) { 331 const float input_y = (float(output_y) + offset) * height_scale() - offset; 332 const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0); 333 const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1); 334 const float y_alpha = input_y - std::floor(input_y); 335 for (size_t output_x = 0; output_x < output_width(); output_x++) { 336 const float input_x = (float(output_x) + offset) * width_scale() - offset; 337 const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0); 338 const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1); 339 const float x_alpha = input_x - std::floor(input_x); 340 for (size_t c = 0; c < channels(); c++) { 341 output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] = 342 input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c] * (1.0f - y_alpha) * (1.0f - x_alpha) + 343 input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c] * (1.0f - y_alpha) * x_alpha + 344 input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c] * y_alpha * (1.0f - x_alpha) + 345 input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c] * y_alpha * x_alpha; 346 } 347 } 348 } 349 } 350 351 // Create, setup, run, and destroy Resize Bilinear operator. 352 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 353 xnn_operator_t resize_bilinear_op = nullptr; 354 355 ASSERT_EQ(xnn_status_success, 356 xnn_create_resize_bilinear2d_nhwc_f32( 357 channels(), input_pixel_stride(), output_pixel_stride(), 358 (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0), 359 &resize_bilinear_op)); 360 ASSERT_NE(nullptr, resize_bilinear_op); 361 362 // Smart pointer to automatically delete resize_bilinear_op. 363 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator); 364 365 ASSERT_EQ(xnn_status_success, 366 xnn_setup_resize_bilinear2d_nhwc_f32( 367 resize_bilinear_op, 368 batch_size(), input_height(), input_width(), 369 output_height(), output_width(), 370 input.data(), output.data(), 371 nullptr /* thread pool */)); 372 373 ASSERT_EQ(xnn_status_success, 374 xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */)); 375 376 // Verify results. 377 for (size_t i = 0; i < batch_size(); i++) { 378 for (size_t y = 0; y < output_height(); y++) { 379 for (size_t x = 0; x < output_width(); x++) { 380 for (size_t c = 0; c < channels(); c++) { 381 ASSERT_NEAR(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c], 382 output_ref[((i * output_height() + y) * output_width() + x) * channels() + c], 383 std::abs(output_ref[((i * output_height() + y) * output_width() + x) * channels() + c]) * 1.0e-5f) << 384 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c; 385 } 386 } 387 } 388 } 389 } 390 } 391 TestNHWCxS8()392 void TestNHWCxS8() const { 393 if (align_corners()) { 394 ASSERT_FALSE(tf_legacy_mode()); 395 } 396 397 std::random_device random_device; 398 auto rng = std::mt19937(random_device()); 399 std::uniform_int_distribution<int32_t> i8dist( 400 std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max()); 401 402 std::vector<int8_t> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(int8_t)); 403 std::vector<int8_t> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels()); 404 std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels()); 405 for (size_t iteration = 0; iteration < iterations(); iteration++) { 406 std::generate(input.begin(), input.end(), [&]() { return i8dist(rng); }); 407 std::fill(output.begin(), output.end(), INT8_C(0xA5)); 408 409 // Compute reference results. 410 const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f; 411 for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) { 412 for (size_t output_y = 0; output_y < output_height(); output_y++) { 413 const float input_y = (float(output_y) + offset) * height_scale() - offset; 414 const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0); 415 const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1); 416 const float y_alpha = input_y - std::floor(input_y); 417 for (size_t output_x = 0; output_x < output_width(); output_x++) { 418 const float input_x = (float(output_x) + offset) * width_scale() - offset; 419 const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0); 420 const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1); 421 const float x_alpha = input_x - std::floor(input_x); 422 for (size_t c = 0; c < channels(); c++) { 423 output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] = 424 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c])) * (1.0f - y_alpha) * (1.0f - x_alpha) + 425 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c])) * (1.0f - y_alpha) * x_alpha + 426 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c])) * y_alpha * (1.0f - x_alpha) + 427 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c])) * y_alpha * x_alpha; 428 } 429 } 430 } 431 } 432 433 // Create, setup, run, and destroy Resize Bilinear operator. 434 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 435 xnn_operator_t resize_bilinear_op = nullptr; 436 437 ASSERT_EQ(xnn_status_success, 438 xnn_create_resize_bilinear2d_nhwc_s8( 439 channels(), input_pixel_stride(), output_pixel_stride(), 440 (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0), 441 &resize_bilinear_op)); 442 ASSERT_NE(nullptr, resize_bilinear_op); 443 444 // Smart pointer to automatically delete resize_bilinear_op. 445 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator); 446 447 ASSERT_EQ(xnn_status_success, 448 xnn_setup_resize_bilinear2d_nhwc_s8( 449 resize_bilinear_op, 450 batch_size(), input_height(), input_width(), 451 output_height(), output_width(), 452 input.data(), output.data(), 453 nullptr /* thread pool */)); 454 455 ASSERT_EQ(xnn_status_success, 456 xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */)); 457 458 // Verify results. 459 for (size_t i = 0; i < batch_size(); i++) { 460 for (size_t y = 0; y < output_height(); y++) { 461 for (size_t x = 0; x < output_width(); x++) { 462 for (size_t c = 0; c < channels(); c++) { 463 ASSERT_NEAR( 464 float(int32_t(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c])), 465 output_ref[((i * output_height() + y) * output_width() + x) * channels() + c], 466 0.6f) << 467 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c; 468 } 469 } 470 } 471 } 472 } 473 } 474 TestNHWCxU8()475 void TestNHWCxU8() const { 476 if (align_corners()) { 477 ASSERT_FALSE(tf_legacy_mode()); 478 } 479 480 std::random_device random_device; 481 auto rng = std::mt19937(random_device()); 482 std::uniform_int_distribution<int32_t> u8dist( 483 std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max()); 484 485 std::vector<uint8_t> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(uint8_t)); 486 std::vector<uint8_t> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels()); 487 std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels()); 488 for (size_t iteration = 0; iteration < iterations(); iteration++) { 489 std::generate(input.begin(), input.end(), [&]() { return u8dist(rng); }); 490 std::fill(output.begin(), output.end(), UINT8_C(0xA5)); 491 492 // Compute reference results. 493 const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f; 494 for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) { 495 for (size_t output_y = 0; output_y < output_height(); output_y++) { 496 const float input_y = (float(output_y) + offset) * height_scale() - offset; 497 const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0); 498 const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1); 499 const float y_alpha = input_y - std::floor(input_y); 500 for (size_t output_x = 0; output_x < output_width(); output_x++) { 501 const float input_x = (float(output_x) + offset) * width_scale() - offset; 502 const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0); 503 const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1); 504 const float x_alpha = input_x - std::floor(input_x); 505 for (size_t c = 0; c < channels(); c++) { 506 output_ref[((batch_index * output_height() + output_y) * output_width() + output_x) * channels() + c] = 507 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_left) * input_pixel_stride() + c])) * (1.0f - y_alpha) * (1.0f - x_alpha) + 508 float(int32_t(input[((batch_index * input_height() + input_y_top) * input_width() + input_x_right) * input_pixel_stride() + c])) * (1.0f - y_alpha) * x_alpha + 509 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_left) * input_pixel_stride() + c])) * y_alpha * (1.0f - x_alpha) + 510 float(int32_t(input[((batch_index * input_height() + input_y_bottom) * input_width() + input_x_right) * input_pixel_stride() + c])) * y_alpha * x_alpha; 511 } 512 } 513 } 514 } 515 516 // Create, setup, run, and destroy Resize Bilinear operator. 517 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 518 xnn_operator_t resize_bilinear_op = nullptr; 519 520 ASSERT_EQ(xnn_status_success, 521 xnn_create_resize_bilinear2d_nhwc_u8( 522 channels(), input_pixel_stride(), output_pixel_stride(), 523 (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0), 524 &resize_bilinear_op)); 525 ASSERT_NE(nullptr, resize_bilinear_op); 526 527 // Smart pointer to automatically delete resize_bilinear_op. 528 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator); 529 530 ASSERT_EQ(xnn_status_success, 531 xnn_setup_resize_bilinear2d_nhwc_u8( 532 resize_bilinear_op, 533 batch_size(), input_height(), input_width(), 534 output_height(), output_width(), 535 input.data(), output.data(), 536 nullptr /* thread pool */)); 537 538 ASSERT_EQ(xnn_status_success, 539 xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */)); 540 541 // Verify results. 542 for (size_t i = 0; i < batch_size(); i++) { 543 for (size_t y = 0; y < output_height(); y++) { 544 for (size_t x = 0; x < output_width(); x++) { 545 for (size_t c = 0; c < channels(); c++) { 546 ASSERT_NEAR( 547 float(int32_t(output[((i * output_height() + y) * output_width() + x) * output_pixel_stride() + c])), 548 output_ref[((i * output_height() + y) * output_width() + x) * channels() + c], 549 0.6f) << 550 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c; 551 } 552 } 553 } 554 } 555 } 556 } 557 TestNCHWxF32()558 void TestNCHWxF32() const { 559 if (align_corners()) { 560 ASSERT_FALSE(tf_legacy_mode()); 561 } 562 563 std::random_device random_device; 564 auto rng = std::mt19937(random_device()); 565 std::uniform_real_distribution<float> f32dist; 566 567 std::vector<float> input((batch_size() * input_height() * input_width() - 1) * input_pixel_stride() + channels() + XNN_EXTRA_BYTES / sizeof(float)); 568 std::vector<float> output((batch_size() * output_height() * output_width() - 1) * output_pixel_stride() + channels()); 569 std::vector<float> output_ref(batch_size() * output_height() * output_width() * channels()); 570 for (size_t iteration = 0; iteration < iterations(); iteration++) { 571 std::generate(input.begin(), input.end(), [&]() { return f32dist(rng); }); 572 std::fill(output.begin(), output.end(), std::nanf("")); 573 574 // Compute reference results. 575 const float offset = (tf_legacy_mode() || align_corners()) ? 0.0f : 0.5f; 576 const int64_t input_num_pixels = input_height() * input_width(); 577 const int64_t input_num_elements = input_num_pixels * input_pixel_stride(); 578 const int64_t output_num_pixels = output_height() * output_width(); 579 const int64_t output_num_elements = output_num_pixels * channels(); 580 for (size_t batch_index = 0; batch_index < batch_size(); batch_index++) { 581 for (size_t output_y = 0; output_y < output_height(); output_y++) { 582 const float input_y = (float(output_y) + offset) * height_scale() - offset; 583 const int64_t input_y_top = std::max<int64_t>(int64_t(std::floor(input_y)), 0); 584 const int64_t input_y_bottom = std::min<int64_t>(int64_t(std::ceil(input_y)), input_height() - 1); 585 const float y_alpha = input_y - std::floor(input_y); 586 for (size_t output_x = 0; output_x < output_width(); output_x++) { 587 const float input_x = (float(output_x) + offset) * width_scale() - offset; 588 const int64_t input_x_left = std::max<int64_t>(int64_t(std::floor(input_x)), 0); 589 const int64_t input_x_right = std::min<int64_t>(int64_t(std::ceil(input_x)), input_width() - 1); 590 const float x_alpha = input_x - std::floor(input_x); 591 for (size_t c = 0; c < channels(); c++) { 592 output_ref[batch_index * output_num_elements + c * output_num_pixels + output_y * output_width() + output_x] = 593 input[batch_index * input_num_elements + c * input_num_pixels + input_y_top * input_width() + input_x_left] * (1.0f - y_alpha) * (1.0f - x_alpha) + 594 input[batch_index * input_num_elements + c * input_num_pixels + input_y_top * input_width() + input_x_right] * (1.0f - y_alpha) * x_alpha + 595 input[batch_index * input_num_elements + c * input_num_pixels + input_y_bottom * input_width() + input_x_left] * y_alpha * (1.0f - x_alpha) + 596 input[batch_index * input_num_elements + c * input_num_pixels + input_y_bottom * input_width() + input_x_right] * y_alpha * x_alpha; 597 } 598 } 599 } 600 } 601 602 // Create, setup, run, and destroy Resize Bilinear operator. 603 ASSERT_EQ(xnn_status_success, xnn_initialize(nullptr /* allocator */)); 604 xnn_operator_t resize_bilinear_op = nullptr; 605 606 ASSERT_EQ(xnn_status_success, 607 xnn_create_resize_bilinear2d_nchw_f32( 608 channels(), input_pixel_stride(), output_pixel_stride(), 609 (align_corners() ? XNN_FLAG_ALIGN_CORNERS : 0) | (tf_legacy_mode() ? XNN_FLAG_TENSORFLOW_LEGACY_MODE : 0), 610 &resize_bilinear_op)); 611 ASSERT_NE(nullptr, resize_bilinear_op); 612 613 // Smart pointer to automatically delete resize_bilinear_op. 614 std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)> auto_resize_bilinear_op(resize_bilinear_op, xnn_delete_operator); 615 616 ASSERT_EQ(xnn_status_success, 617 xnn_setup_resize_bilinear2d_nchw_f32( 618 resize_bilinear_op, 619 batch_size(), input_height(), input_width(), 620 output_height(), output_width(), 621 input.data(), output.data(), 622 nullptr /* thread pool */)); 623 624 ASSERT_EQ(xnn_status_success, 625 xnn_run_operator(resize_bilinear_op, nullptr /* thread pool */)); 626 627 // Verify results. 628 for (size_t i = 0; i < batch_size(); i++) { 629 for (size_t y = 0; y < output_height(); y++) { 630 for (size_t x = 0; x < output_width(); x++) { 631 for (size_t c = 0; c < channels(); c++) { 632 ASSERT_NEAR(output[i * output_num_elements + c * output_num_pixels + y * output_width() + x], 633 output_ref[i * output_num_elements + c * output_num_pixels + y * output_width() + x], 634 1.0e-6f) << 635 "in batch index " << i << ", pixel (" << y << ", " << x << "), channel " << c; 636 } 637 } 638 } 639 } 640 } 641 } 642 643 private: 644 size_t input_height_{1}; 645 size_t input_width_{1}; 646 size_t output_height_{1}; 647 size_t output_width_{1}; 648 size_t channels_{1}; 649 size_t batch_size_{1}; 650 size_t input_pixel_stride_{0}; 651 size_t output_pixel_stride_{0}; 652 size_t next_input_height_{0}; 653 size_t next_input_width_{0}; 654 size_t next_batch_size_{0}; 655 bool align_corners_{false}; 656 bool tf_legacy_mode_{false}; 657 size_t iterations_{1}; 658 }; 659