// Copyright (c) Facebook, Inc. and its affiliates. // All rights reserved. // // Copyright 2019 Google LLC // // This source code is licensed under the BSD-style license found in the // LICENSE file in the root directory of this source tree. #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class DWConv2DMicrokernelTester { public: enum class Variant { Native, Scalar, }; inline DWConv2DMicrokernelTester& padding_left(uint32_t padding_left) { this->padding_left_ = padding_left; return *this; } inline uint32_t padding_left() const { return this->padding_left_; } inline DWConv2DMicrokernelTester& padding_right(uint32_t padding_right) { this->padding_right_ = padding_right; return *this; } inline uint32_t padding_right() const { return this->padding_right_; } inline DWConv2DMicrokernelTester& padding_top(uint32_t padding_top) { this->padding_top_ = padding_top; return *this; } inline uint32_t padding_top() const { return this->padding_top_; } inline DWConv2DMicrokernelTester& padding_bottom(uint32_t padding_bottom) { this->padding_bottom_ = padding_bottom; return *this; } inline uint32_t padding_bottom() const { return this->padding_bottom_; } inline DWConv2DMicrokernelTester& input_height(uint32_t input_height) { assert(input_height >= 1); this->input_height_ = input_height; return *this; } inline uint32_t input_height() const { return this->input_height_; } inline DWConv2DMicrokernelTester& input_width(uint32_t input_width) { assert(input_width >= 1); this->input_width_ = input_width; return *this; } inline uint32_t input_width() const { return this->input_width_; } inline DWConv2DMicrokernelTester& subsampling(uint32_t subsampling) { assert(subsampling >= 1); this->subsampling_ = subsampling; return *this; } inline uint32_t subsampling() const { return this->subsampling_; } inline DWConv2DMicrokernelTester& kernel_height(uint32_t kernel_height) { assert(kernel_height != 0); this->kernel_height_ = kernel_height; return *this; } inline uint32_t kernel_height() const { return this->kernel_height_; } inline DWConv2DMicrokernelTester& kernel_width(uint32_t kernel_width) { assert(kernel_width != 0); this->kernel_width_ = kernel_width; return *this; } inline uint32_t kernel_width() const { return this->kernel_width_; } inline uint32_t kernel_size() const { return kernel_height() * kernel_width(); } inline uint32_t output_height() const { const uint32_t padded_input_height = padding_top() + input_height() + padding_bottom(); if (padded_input_height <= kernel_height()) { return 1; } else { return (padded_input_height - kernel_height()) / subsampling() + 1; } } inline uint32_t output_width() const { const uint32_t padded_input_width = padding_left() + input_width() + padding_right(); if (padded_input_width <= kernel_width()) { return 1; } else { return (padded_input_width - kernel_width()) / subsampling() + 1; } } inline DWConv2DMicrokernelTester& qmin(uint8_t qmin) { this->qmin_ = qmin; return *this; } inline uint8_t qmin() const { return this->qmin_; } inline DWConv2DMicrokernelTester& qmax(uint8_t qmax) { this->qmax_ = qmax; return *this; } inline uint8_t qmax() const { return this->qmax_; } inline DWConv2DMicrokernelTester& iterations(size_t iterations) { this->iterations_ = iterations; return *this; } inline size_t iterations() const { return this->iterations_; } void Test(xnn_f32_dwconv2d_chw_ukernel_function dwconv, Variant variant = Variant::Native) const { std::random_device random_device; auto rng = std::mt19937(random_device()); auto f32rng = std::bind(std::uniform_real_distribution(0.0f, 1.0f), rng); std::vector> input(input_height() * input_width() + 2 * XNN_EXTRA_BYTES); std::vector zero(input_width() + 2 * XNN_EXTRA_BYTES); std::vector packed_weights(kernel_size() + 1); std::vector> output(output_height() * output_width()); std::vector output_ref(output_height() * output_width()); for (size_t iteration = 0; iteration < iterations(); iteration++) { std::generate(input.begin(), input.end(), std::ref(f32rng)); std::generate(packed_weights.begin(), packed_weights.end(), std::ref(f32rng)); std::fill(output.begin(), output.end(), nanf("")); for (size_t oy = 0; oy < output_height(); oy++) { for (size_t ox = 0; ox < output_width(); ox++) { float acc = packed_weights[0]; for (size_t ky = 0; ky < kernel_height(); ky++) { const size_t iy = oy * subsampling() + ky - padding_top(); for (size_t kx = 0; kx < kernel_width(); kx++) { const size_t ix = ox * subsampling() + kx - padding_left(); if (ix < input_width() && iy < input_height()) { const float input_val = input[iy * input_width() + ix]; const float kernel_val = packed_weights[1 + ky * kernel_width() + kx]; acc += input_val * kernel_val; } } } output_ref[oy * output_width() + ox] = acc; } } // Compute clamping parameters. const float accumulated_min = *std::min_element(output_ref.cbegin(), output_ref.cend()); const float accumulated_max = *std::max_element(output_ref.cbegin(), output_ref.cend()); const float accumulated_range = accumulated_max - accumulated_min; const float output_min = accumulated_min + accumulated_range / 255.0f * float(qmin()); const float output_max = accumulated_max - accumulated_range / 255.0f * float(255 - qmax()); // Prepare parameters. xnn_f32_chw_params chw_params = { }; switch (variant) { case Variant::Native: chw_params = xnn_init_f32_chw_params(input_width(), output_min, output_max); break; case Variant::Scalar: chw_params = xnn_init_scalar_f32_chw_params(input_width(), output_min, output_max); break; } // Clamp reference results. for (float& output_val : output_ref) { output_val = std::max(std::min(output_val, output_max), output_min); } // Call optimized micro-kernel. dwconv( input_height(), input_width() * sizeof(float), input.data(), packed_weights.data(), zero.data(), output.data(), padding_top(), &chw_params); // Verify results. for (size_t y = 0; y < output_height(); y++) { for (size_t x = 0; x < output_width(); x++) { ASSERT_NEAR( output_ref[y * output_width() + x], output[y * output_width() + x], std::abs(output_ref[y * output_width() + x]) * 1.0e-5) << "x = " << x << ", y = " << y; } } } } private: uint32_t padding_left_{0}; uint32_t padding_right_{0}; uint32_t padding_top_{0}; uint32_t padding_bottom_{0}; uint32_t input_height_{1}; uint32_t input_width_{1}; uint32_t subsampling_{1}; uint32_t kernel_height_{1}; uint32_t kernel_width_{1}; uint8_t qmin_{0}; uint8_t qmax_{255}; size_t iterations_{1}; };