| #include "test/gemm-microkernel-tester.h" |
| |
| #include <algorithm> |
| #include <cassert> |
| #include <cmath> |
| #include <cstddef> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <functional> |
| #include <limits> |
| #include <random> |
| |
| #include <gtest/gtest.h> |
| #include "include/xnnpack.h" |
| #include "src/xnnpack/buffer.h" |
| #include "src/xnnpack/common.h" |
| #include "src/xnnpack/config-types.h" |
| #include "src/xnnpack/config.h" |
| #include "src/xnnpack/math.h" |
| #include "src/xnnpack/microfnptr.h" |
| #include "src/xnnpack/microparams-init.h" |
| #include "src/xnnpack/microparams.h" |
| #include "src/xnnpack/pack.h" |
| #include "src/xnnpack/packq.h" |
| #include "src/xnnpack/quantization.h" |
| #include "src/xnnpack/requantization.h" |
| #include "test/replicable_random_device.h" |
| |
| // Dynamically quantized kernels do extra arithmetic, which accumulates more |
| // error, which we factor into our tolerances with this constant. |
| const float dynamic_quantization_ops = 2.0f; |
| |
| TEST_P(GemmTest, Test) { |
| const GemmTestParams& params = GetParam(); |
| GemmMicrokernelTester tester = params.tester; |
| size_t num_tests_invocations = 0; |
| |
| // Make sure that we can execute this test. |
| if (params.isa_check) { |
| params.isa_check(); |
| if (IsSkipped()) { |
| return; |
| } |
| } |
| |
| // Loop over the `k`, `m`, and `n` values, if required. |
| for (size_t k = params.loop_k_.from; k <= params.loop_k_.to; |
| k = params.loop_k_.next(k)) { |
| if (params.loop_k_.is_set) { |
| tester.k(k); |
| } |
| for (size_t m = params.loop_m_.from; m <= params.loop_m_.to; |
| m = params.loop_m_.next(m)) { |
| if (params.loop_m_.is_set) { |
| tester.m(m); |
| } |
| for (size_t n = params.loop_n_.from; n <= params.loop_n_.to; |
| n = params.loop_n_.next(n)) { |
| if (params.loop_n_.is_set) { |
| tester.n(n); |
| } |
| for (size_t zi = params.loop_zi_.from; zi <= params.loop_zi_.to; |
| zi = params.loop_zi_.next(zi)) { |
| if (params.loop_zi_.is_set) { |
| tester.zero_index(zi); |
| } |
| for (size_t bzp = params.loop_bzp_.from; bzp <= params.loop_bzp_.to; |
| bzp = params.loop_bzp_.next(bzp)) { |
| if (params.loop_bzp_.is_set) { |
| tester.b_zero_point(bzp); |
| } |
| for (size_t bl = params.loop_bl_.from; |
| bl <= std::max<size_t>(tester.k() / 2, params.loop_bl_.from); |
| bl = params.loop_bl_.next(bl)) { |
| if (params.loop_bl_.is_set) { |
| // Require block size to divide (padded) column size. |
| if (round_up_po2(k, params.loop_bl_.step) % bl != 0) { |
| continue; |
| } |
| tester.bl(bl); |
| } |
| |
| // Call the test function. |
| params.test_func(tester); |
| num_tests_invocations++; |
| |
| // Quit if anything has gone wrong. |
| if (HasFatalFailure()) { |
| return; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Ensure that the test was executed at least once. |
| ASSERT_GT(num_tests_invocations, 0) << "No tests were run."; |
| } |
| |
| static float compute_sum_tolerance(float max_value, int reduction_size, |
| float epsilon) { |
| // Each `reduction_size` add potentially grows the error by `epsilon`. |
| float tolerance = reduction_size * epsilon; |
| // The error is then scaled by the max value. |
| tolerance *= max_value; |
| // Also include some absolute error tolerance. |
| tolerance += epsilon; |
| |
| // TODO: This tolerance is very lax in some cases, but is required to be |
| // able to run the f16_qb4w tests. |
| EXPECT_LT(tolerance, max_value) |
| << "max_value=" << max_value << ", reduction_size=" << reduction_size |
| << ", epsilon=" << epsilon; |
| return tolerance; |
| } |
| |
| static int8_t sign_extend_int4(int8_t value) { |
| int8_t mask = 0x08; |
| if (value & mask) { |
| value = value | 0xF0; |
| } |
| return value; |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f16_qc8w_igemm_ukernel_fn igemm, |
| xnn_init_f16_minmax_params_fn init_params, |
| xnn_pack_qs8_igemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 256.0f * 2.0f; |
| |
| xnnpack::Buffer<float> input(mr() * k()); |
| xnnpack::Buffer<int8_t> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params( |
| 1 + XNN_EXTRA_QUANTIZATION_PARAMS); |
| xnnpack::Buffer<int8_t> b(n() * ks() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_n() * packed_k() + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<xnn_float16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| xnnpack::Buffer<int8_t> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const int8_t*> im2col(mr() * ks()); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| const auto minmax = |
| std::minmax_element(input.begin(), input.begin() + mr() * k()); |
| float inv_scale; |
| quantization_params[0] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t i = 0; i < mr(); ++i) { |
| const float* input_ptr = &input[i * k()]; |
| for (size_t j = 0; j < k(); ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[0].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[0].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[0].zero_point)); |
| } |
| } |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_packing_params packing_params = {/*input_zero_point=*/1}; |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), /*bias=*/nullptr, |
| /*scale=*/nullptr, packed_w.data(), 2 * sizeof(float) * nr(), |
| &packing_params); |
| // Fill in packed kernel scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| kernel_scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| // Fill in packed bias |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| bias.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 2 * sizeof(float)))); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| const size_t k_stride = round_up_po2(k(), kr() * sr()); |
| int32_t zp = quantization_params[0].zero_point; |
| if (unsigned_inputs()) { |
| zp += 128; |
| } |
| xnnpack::Buffer<int8_t> zero_points(k_stride, zp, xnnpack::XnnExtraBytes); |
| const int8_t* zero_sentinel = (const int8_t*)&packing_params; |
| const int8_t* zero_data = zero_points.data(); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = zero_sentinel; |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| if (im2col[ks_index * mr() + m_index] != zero_sentinel) { |
| c_ref[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index + a_offset()]) - |
| quantization_params[0].zero_point) * |
| static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| c_ref[m_index * n() + n_index] *= |
| quantization_params[0].inv_scale * kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f16_minmax_params params; |
| init_params(¶ms, static_cast<xnn_float16>(min()), |
| static_cast<xnn_float16>(max())); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| igemm(m(), n(), k(), ks() * mr() * sizeof(void*), im2col.data(), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(xnn_float16), nr() * sizeof(xnn_float16), |
| a_offset() * sizeof(uint8_t), zero_sentinel, zero_data, ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<xnn_float16>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << "), optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f32_qc8w_igemm_ukernel_fn igemm, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_qs8_igemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 256.0f * 2.0f; |
| |
| xnnpack::Buffer<float> input(mr() * k()); |
| xnnpack::Buffer<int8_t> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params( |
| 1 + XNN_EXTRA_QUANTIZATION_PARAMS); |
| xnnpack::Buffer<int8_t> b(n() * ks() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_n() * packed_k() + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| xnnpack::Buffer<int8_t> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const int8_t*> im2col(mr() * ks()); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| const auto minmax = |
| std::minmax_element(input.begin(), input.begin() + mr() * k()); |
| float inv_scale; |
| quantization_params[0] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t i = 0; i < mr(); ++i) { |
| const float* input_ptr = &input[i * k()]; |
| for (size_t j = 0; j < k(); ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[0].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[0].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[0].zero_point)); |
| } |
| } |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_packing_params packing_params = {/*input_zero_point=*/1}; |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), /*bias=*/nullptr, |
| /*scale=*/nullptr, packed_w.data(), 2 * sizeof(float) * nr(), |
| &packing_params); |
| // Fill in packed kernel scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| kernel_scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| // Fill in packed bias |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| bias.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 2 * sizeof(float)))); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| const size_t k_stride = round_up_po2(k(), kr() * sr()); |
| int32_t zp = quantization_params[0].zero_point; |
| if (unsigned_inputs()) { |
| zp += 128; |
| } |
| xnnpack::Buffer<int8_t> zero_points(k_stride, zp, xnnpack::XnnExtraBytes); |
| const int8_t* zero_sentinel = (const int8_t*)&packing_params; |
| const int8_t* zero_data = zero_points.data(); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = zero_sentinel; |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| if (im2col[ks_index * mr() + m_index] != zero_sentinel) { |
| c_ref[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index + a_offset()]) - |
| quantization_params[0].zero_point) * |
| static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| c_ref[m_index * n() + n_index] *= |
| quantization_params[0].inv_scale * kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| igemm(m(), n(), k(), ks() * mr() * sizeof(void*), im2col.data(), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), |
| a_offset() * sizeof(uint8_t), zero_sentinel, zero_data, ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<float>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << "), optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qu8_gemm_minmax_ukernel_fn gemm, |
| xnn_init_qu8_conv_minmax_params_fn init_params, |
| xnn_pack_qu8_gemm_fn pack, |
| xnn_qu8_requantize_fn requantize) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<uint8_t> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<uint8_t> b(n() * k()); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n() * sizeof(int32_t) / sizeof(uint8_t)); |
| xnnpack::Buffer<uint8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<uint8_t> c_ref(m() * n()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| xnnpack::fill_uniform_random_bits(b.data(), b.size(), rng); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), b_zero_point()); |
| const xnn_qu8_packing_params packing_params = {a_zero_point(), |
| b_zero_point()}; |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, &packing_params); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>(a[m_index * a_stride() + k_index]) - |
| static_cast<int32_t>(a_zero_point())) * |
| (static_cast<int32_t>(b[n_index * k() + k_index]) - |
| static_cast<int32_t>(b_zero_point())); |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int32_t accumulated_min = *std::min_element(acc.cbegin(), acc.cend()); |
| const int32_t accumulated_max = *std::max_element(acc.cbegin(), acc.cend()); |
| const double c_scale = |
| static_cast<uint32_t>(accumulated_max - accumulated_min) >= 256 |
| ? static_cast<double>( |
| static_cast<uint32_t>(accumulated_max - accumulated_min)) / |
| 255.0 |
| : 1.00001; |
| const uint8_t c_zero_point = static_cast<uint8_t>(std::max( |
| std::min( |
| lrint(127.5 - |
| 0.5 * static_cast<double>(accumulated_min + accumulated_max) / |
| c_scale), |
| static_cast<long>(std::numeric_limits<uint8_t>::max())), |
| static_cast<long>(std::numeric_limits<uint8_t>::min()))); |
| |
| const float requantization_scale = 1.0f / static_cast<float>(c_scale); |
| union xnn_qu8_conv_minmax_params quantization_params; |
| init_params(&quantization_params, b_zero_point(), requantization_scale, |
| c_zero_point, qmin(), qmax()); |
| |
| gemm(m(), n(), k(), a.data(), a_stride() * sizeof(uint8_t), packed_w.data(), |
| c.data(), cm_stride() * sizeof(uint8_t), nr() * sizeof(uint8_t), |
| &quantization_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], requantization_scale, |
| c_zero_point, qmin(), qmax()); |
| } |
| } |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<uint32_t>(c[i * cm_stride() + j]), |
| static_cast<uint32_t>(qmax())); |
| ASSERT_GE(static_cast<uint32_t>(c[i * cm_stride() + j]), |
| static_cast<uint32_t>(qmin())); |
| ASSERT_EQ(static_cast<uint32_t>(c[i * cm_stride() + j]), |
| static_cast<uint32_t>(c_ref[i * n() + j])) |
| << "at " << i << ", " << j |
| << ": reference = " << (uint32_t)c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << (uint32_t)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << requantization_scale |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qu8_igemm_minmax_ukernel_fn igemm, |
| xnn_init_qu8_conv_minmax_params_fn init_params, |
| xnn_pack_qu8_igemm_fn pack, |
| xnn_qu8_requantize_fn requantize) { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<uint8_t> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<uint8_t> b(n() * ks() * k()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_n() * packed_k() + |
| packed_n() * sizeof(int32_t) / sizeof(uint8_t)); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<uint8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<uint8_t> c_ref(m() * n()); |
| // The junk data needs to be initialized for some kernels because msan will |
| // assert in functions like `lrintf`. |
| xnnpack::Buffer<uint8_t> junk(k(), 0, xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const uint8_t*> im2col(mr() * ks()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| xnnpack::fill_uniform_random_bits(b.data(), b.size(), rng); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), b_zero_point()); |
| const xnn_qu8_packing_params packing_params = {a_zero_point(), |
| b_zero_point()}; |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, &packing_params); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index]) - |
| static_cast<int32_t>(a_zero_point())) * |
| (static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]) - |
| static_cast<int32_t>(b_zero_point())); |
| } else { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index + a_offset()]) - |
| static_cast<int32_t>(a_zero_point())) * |
| (static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]) - |
| static_cast<int32_t>(b_zero_point())); |
| } |
| } |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int32_t accumulated_min = *std::min_element(acc.cbegin(), acc.cend()); |
| const int32_t accumulated_max = *std::max_element(acc.cbegin(), acc.cend()); |
| const double c_scale = |
| static_cast<uint32_t>(accumulated_max - accumulated_min) >= 256 |
| ? static_cast<double>( |
| static_cast<uint32_t>(accumulated_max - accumulated_min)) / |
| 255.0 |
| : 1.00001; |
| const uint8_t c_zero_point = static_cast<uint8_t>(std::max( |
| std::min( |
| lrint(127.5 - |
| 0.5 * static_cast<double>(accumulated_min + accumulated_max) / |
| c_scale), |
| static_cast<long>(std::numeric_limits<uint8_t>::max())), |
| static_cast<long>(std::numeric_limits<uint8_t>::min()))); |
| |
| const float requantization_scale = 1.0f / static_cast<float>(c_scale); |
| union xnn_qu8_conv_minmax_params quantization_params; |
| init_params(&quantization_params, b_zero_point(), requantization_scale, |
| c_zero_point, qmin(), qmax()); |
| |
| const uint8_t* zero_pointer = (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm(m(), n(), k(), ks() * mr() * sizeof(void*), im2col.data(), |
| packed_w.data(), c.data(), cm_stride() * sizeof(uint8_t), |
| nr() * sizeof(uint8_t), a_offset() * sizeof(uint8_t), zero_pointer, |
| &quantization_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], requantization_scale, |
| c_zero_point, qmin(), qmax()); |
| } |
| } |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<uint32_t>(c[i * cm_stride() + j]), |
| static_cast<uint32_t>(qmax())); |
| ASSERT_GE(static_cast<uint32_t>(c[i * cm_stride() + j]), |
| static_cast<uint32_t>(qmin())); |
| ASSERT_EQ(static_cast<uint32_t>(c[i * cm_stride() + j]), |
| static_cast<uint32_t>(c_ref[i * n() + j])) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<uint32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << static_cast<uint32_t>(c[i * cm_stride() + j]) |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << requantization_scale |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_qs8_qc4w_gemm_minmax_ukernel_fn gemm, |
| xnn_init_qs8_qc8w_conv_minmax_params_fn init_params, |
| xnn_pack_qs8_qc4w_gemm_fn pack, xnn_qs8_requantize_fn requantize) { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10, 10), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| const size_t packed_k2 = |
| round_up_po2(k(), kr() * sr() * planes()); // 2 blocks for nibbles |
| const size_t packed_k_bytes = (packed_k2 + 1) / 2; |
| |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k2, |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k_bytes + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<int8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t> c_ref(m() * n()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| const xnn_qs8_qc4w_packing_params packing_params = {static_cast<int8_t>(a_zero_point() - 0x80), |
| b_zero_point()}; |
| void* const packed_data = packed_w.data(); |
| pack(/*g=*/1, n(), k2, nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), sizeof(float) * nr(), |
| &packing_params); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k2; k_index++) { |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| int8_t bv = |
| int8_t((k_index % 2 == 0) ? (b[nb_index] & 15) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| if (b_zero_point() == 0) { |
| bv = sign_extend_int4(bv); |
| } |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>(a[m_index * a_stride() + k_index]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * static_cast<int32_t>(bv); |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int8_t c_zero_point = -1; |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t accumulated_min = acc[n_index]; |
| int32_t accumulated_max = acc[n_index]; |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| accumulated_min = std::min(accumulated_min, acc[m_index * n() + n_index]); |
| accumulated_max = std::max(accumulated_max, acc[m_index * n() + n_index]); |
| } |
| const uint32_t accumulated_range = |
| static_cast<uint32_t>(accumulated_max - accumulated_min); |
| const float c_scale = accumulated_range >= 256 |
| ? static_cast<double>(accumulated_range) / 255.0 |
| : 1.00001; |
| scale[n_index] = 1.0f / c_scale; |
| } |
| |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), nr() * (ks() * packed_k_bytes + 2 * sizeof(float)), |
| scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k_bytes + sizeof(float)))); |
| |
| |
| union xnn_qs8_qc8w_conv_minmax_params minmax_params; |
| init_params(&minmax_params, c_zero_point, static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| |
| gemm(m(), n(), k2, a.data(), a_stride() * sizeof(int8_t), packed_data, |
| c.data(), cm_stride() * sizeof(int8_t), nr() * sizeof(int8_t), |
| &minmax_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], scale[n_index], c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| } |
| } |
| |
| #if XNN_ARCH_HEXAGON |
| // HVX losses 1 bit of accuracy due to qfloat for FP32 quantization on V73 |
| // V79 should be lossless. |
| const int32_t tolerance = 1; |
| #else |
| const int32_t tolerance = 0; |
| #endif |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmax()) - 0x80); |
| ASSERT_GE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmin()) - 0x80); |
| ASSERT_NEAR(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(c_ref[i * n() + j]), tolerance) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<int32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << static_cast<int32_t>(c[i * cm_stride() + j]) |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << scale[j] |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_qs8_qc8w_gemm_minmax_ukernel_fn gemm, |
| xnn_init_qs8_qc8w_conv_minmax_params_fn init_params, |
| xnn_pack_qs8_gemm_fn pack, xnn_qs8_requantize_fn requantize) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + |
| packed_n() * (sizeof(int32_t) + sizeof(float)) / sizeof(int8_t)); |
| xnnpack::Buffer<int8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t> c_ref(m() * n()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| const xnn_qs8_packing_params packing_params = { |
| static_cast<int8_t>(a_zero_point() - 0x80)}; |
| void* const packed_data = packed_w.data(); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_data, nr() * sizeof(float), &packing_params); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>(a[m_index * a_stride() + k_index]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>(b[n_index * k() + k_index]); |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int8_t c_zero_point = -1; |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t accumulated_min = acc[n_index]; |
| int32_t accumulated_max = acc[n_index]; |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| accumulated_min = std::min(accumulated_min, acc[m_index * n() + n_index]); |
| accumulated_max = std::max(accumulated_max, acc[m_index * n() + n_index]); |
| } |
| const uint32_t accumulated_range = |
| static_cast<uint32_t>(accumulated_max - accumulated_min); |
| const float c_scale = accumulated_range >= 256 |
| ? static_cast<double>(accumulated_range) / 255.0 |
| : 1.00001; |
| scale[n_index] = 1.0f / c_scale; |
| } |
| |
| const size_t type_size = sizeof(int8_t); |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (packed_k() * type_size + (sizeof(int32_t) + sizeof(float))), |
| scale.data(), |
| (void*)((uintptr_t)packed_data + |
| nr() * (packed_k() * type_size + sizeof(int32_t)))); |
| |
| union xnn_qs8_qc8w_conv_minmax_params minmax_params; |
| init_params(&minmax_params, c_zero_point, static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| |
| gemm(m(), n(), k(), a.data(), a_stride() * sizeof(int8_t), packed_data, |
| c.data(), cm_stride() * sizeof(int8_t), nr() * sizeof(int8_t), |
| &minmax_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], scale[n_index], c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| } |
| } |
| |
| #if XNN_ARCH_HEXAGON |
| // HVX losses 1 bit of accuracy due to qfloat for FP32 quantization on V73 |
| // V79 should be lossless. |
| const int32_t tolerance = 1; |
| #else |
| const int32_t tolerance = 0; |
| #endif |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmax()) - 0x80); |
| ASSERT_GE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmin()) - 0x80); |
| ASSERT_NEAR(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(c_ref[i * n() + j]), tolerance) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<int32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << static_cast<int32_t>(c[i * cm_stride() + j]) |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << scale[j] |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_qs8_qc8w_igemm_minmax_ukernel_fn igemm, |
| xnn_init_qs8_qc8w_conv_minmax_params_fn init_params, |
| xnn_pack_qs8_igemm_fn pack, xnn_qs8_requantize_fn requantize) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<int8_t> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * ks() * k()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_n() * packed_k() + |
| packed_n() * (sizeof(int32_t) + sizeof(float)) / sizeof(int8_t)); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<int8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t> c_ref(m() * n()); |
| // The junk data needs to be initialized for some kernels because msan will |
| // assert in functions like `lrintf`. |
| xnnpack::Buffer<int8_t> junk(k(), 0, xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const int8_t*> im2col(mr() * ks()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| const xnn_qs8_packing_params packing_params = { |
| static_cast<int8_t>(a_zero_point() - 0x80)}; |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), nr() * sizeof(float), |
| &packing_params); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]); |
| } else { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index + a_offset()]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int8_t c_zero_point = -1; |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t accumulated_min = acc[n_index]; |
| int32_t accumulated_max = acc[n_index]; |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| accumulated_min = std::min(accumulated_min, acc[m_index * n() + n_index]); |
| accumulated_max = std::max(accumulated_max, acc[m_index * n() + n_index]); |
| } |
| const uint32_t accumulated_range = |
| static_cast<uint32_t>(accumulated_max - accumulated_min); |
| const float c_scale = accumulated_range >= 256 |
| ? static_cast<double>(accumulated_range) / 255.0 |
| : 1.00001; |
| scale[n_index] = 1.0f / c_scale; |
| } |
| |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + |
| (sizeof(int32_t) + sizeof(float))), |
| scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(int32_t)))); |
| |
| union xnn_qs8_qc8w_conv_minmax_params minmax_params; |
| init_params(&minmax_params, c_zero_point, static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| |
| const int8_t* zero_pointer = (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm(m(), n(), k(), ks() * mr() * sizeof(void*), im2col.data(), |
| packed_w.data(), c.data(), cm_stride() * sizeof(int8_t), |
| nr() * sizeof(int8_t), a_offset() * sizeof(uint8_t), zero_pointer, |
| &minmax_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], scale[n_index], c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| } |
| } |
| |
| #if XNN_ARCH_HEXAGON |
| // HVX losses 1 bit of accuracy due to qfloat for FP32 quantization on V73 |
| // V79 should be lossless. |
| const int32_t tolerance = 1; |
| #else |
| const int32_t tolerance = 0; |
| #endif |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmax()) - 0x80); |
| ASSERT_GE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmin()) - 0x80); |
| ASSERT_NEAR(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(c_ref[i * n() + j]), tolerance) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<uint32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << (uint32_t)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << scale[j] |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f16_qc8w_gemm_ukernel_fn gemm, |
| xnn_init_f16_minmax_params_fn init_params, |
| xnn_pack_qs8_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 256.0f * 2.0f; |
| |
| xnnpack::Buffer<float> input(m() * k()); |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params(mr()); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<xnn_float16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| for (size_t i = 0; i < m(); ++i) { |
| const float* input_ptr = &input[i * k()]; |
| const auto minmax = std::minmax_element(input_ptr, input_ptr + k()); |
| float inv_scale; |
| quantization_params[i] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t j = 0; j < k(); ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[i].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[i].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[i].zero_point)); |
| } |
| } |
| for (size_t i = m(); i < mr(); ++i) { |
| quantization_params[i].zero_point = quantization_params[m() - 1].zero_point; |
| quantization_params[i].inv_scale = quantization_params[m() - 1].inv_scale; |
| } |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_packing_params packing_params = {/*input_zero_point=*/1}; |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), /*bias=*/nullptr, |
| /*scale=*/nullptr, packed_w.data(), 2 * sizeof(float) * nr(), |
| &packing_params); |
| // Fill in packed kernel scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| kernel_scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| // Fill in packed bias |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| bias.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 2 * sizeof(float)))); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t ksum = 0; |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ksum += b[n_index * k() + k_index]; |
| c_ref[m_index * n() + n_index] += |
| static_cast<int32_t>(a[m_index * a_stride() + k_index]) * |
| static_cast<int32_t>(b[n_index * k() + k_index]); |
| } |
| c_ref[m_index * n() + n_index] -= |
| (quantization_params[m_index].zero_point * ksum); |
| c_ref[m_index * n() + n_index] *= |
| quantization_params[m_index].inv_scale * kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f16_minmax_params params; |
| init_params(¶ms, static_cast<xnn_float16>(min()), |
| static_cast<xnn_float16>(max())); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| gemm(m(), n(), k(), a.data(), a_stride() * sizeof(int8_t), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(xnn_float16), nr() * sizeof(xnn_float16), ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<xnn_float16>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << "), optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f32_qc8w_gemm_ukernel_fn gemm, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_qs8_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 256.0f * 2.0f; |
| |
| xnnpack::Buffer<float> input(m() * k()); |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params(mr()); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| for (size_t i = 0; i < m(); ++i) { |
| const float* input_ptr = &input[i * k()]; |
| const auto minmax = std::minmax_element(input_ptr, input_ptr + k()); |
| float inv_scale; |
| quantization_params[i] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t j = 0; j < k(); ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[i].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[i].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[i].zero_point)); |
| } |
| } |
| for (size_t i = m(); i < mr(); ++i) { |
| quantization_params[i].zero_point = quantization_params[m() - 1].zero_point; |
| quantization_params[i].inv_scale = quantization_params[m() - 1].inv_scale; |
| } |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_packing_params packing_params = {/*input_zero_point=*/1}; |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), /*bias=*/nullptr, |
| /*scale=*/nullptr, packed_w.data(), 2 * sizeof(float) * nr(), |
| &packing_params); |
| // Fill in packed kernel scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| kernel_scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| // Fill in packed bias |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 3 * sizeof(float)), |
| bias.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + 2 * sizeof(float)))); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t ksum = 0; |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ksum += b[n_index * k() + k_index]; |
| c_ref[m_index * n() + n_index] += |
| static_cast<int32_t>(a[m_index * a_stride() + k_index]) * |
| static_cast<int32_t>(b[n_index * k() + k_index]); |
| } |
| c_ref[m_index * n() + n_index] -= |
| (quantization_params[m_index].zero_point * ksum); |
| c_ref[m_index * n() + n_index] *= |
| quantization_params[m_index].inv_scale * kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| gemm(m(), n(), k(), a.data(), a_stride() * sizeof(int8_t), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<float>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f16_qc4w_gemm_ukernel_fn gemm, |
| xnn_init_f16_qc4w_minmax_params_fn init_params, |
| xnn_pack_qs8_qc4w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 16.0f * 2.0f; |
| |
| const size_t planes = 2; // 4 bit is 2 planes - low nibbles and high nibbles |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| const size_t packed_k2 = |
| round_up_po2(k(), kr() * sr() * planes); // 2 blocks for nibbles |
| const size_t packed_k_bytes = (packed_k2 + 1) / 2; |
| |
| xnnpack::Buffer<float> input(m() * k2); |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k2, |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params(mr()); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k_bytes + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<xnn_float16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0.0f); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| for (size_t i = 0; i < m(); ++i) { |
| const float* input_ptr = &input[i * k2]; |
| const auto minmax = std::minmax_element(input_ptr, input_ptr + k2); |
| float inv_scale; |
| quantization_params[i] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t j = 0; j < k2; ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[i].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[i].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[i].zero_point)); |
| } |
| } |
| for (size_t i = m(); i < mr(); ++i) { |
| quantization_params[i].zero_point = quantization_params[m() - 1].zero_point; |
| quantization_params[i].inv_scale = quantization_params[m() - 1].inv_scale; |
| } |
| |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_qc4w_packing_params packing_params = {/*input_zero_point=*/1, |
| b_zero_point()}; |
| pack(/*g=*/1, n(), k2, nr(), kr(), sr(), b.data(), /*bias=*/nullptr, |
| /*scale=*/nullptr, packed_w.data(), 2 * sizeof(float) * nr(), |
| &packing_params); |
| // Fill in packed kernel scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), nr() * (ks() * packed_k_bytes + 3 * sizeof(float)), |
| kernel_scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k_bytes + sizeof(float)))); |
| |
| // Fill in packed bias |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), nr() * (ks() * packed_k_bytes + 3 * sizeof(float)), |
| bias.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k_bytes + 2 * sizeof(float)))); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t ksum = 0; |
| for (size_t k_index = 0; k_index < k2; k_index++) { |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| const int32_t bv = static_cast<int32_t>( |
| (k_index % 2 == 0) ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| ksum += bv; |
| c_ref[m_index * n() + n_index] += |
| static_cast<int32_t>(a[m_index * a_stride() + k_index]) * |
| static_cast<int32_t>(bv); |
| } |
| c_ref[m_index * n() + n_index] -= |
| (quantization_params[m_index].zero_point * ksum); |
| c_ref[m_index * n() + n_index] *= |
| quantization_params[m_index].inv_scale * kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f16_qc4w_minmax_params params; |
| init_params(¶ms, static_cast<xnn_float16>(min()), |
| static_cast<xnn_float16>(max()), 8); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| gemm(m(), n(), k2, a.data(), a_stride() * sizeof(int8_t), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(xnn_float16), nr() * sizeof(xnn_float16), ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<xnn_float16>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f16_qb4w_gemm_ukernel_fn gemm, |
| xnn_init_f16_qb4w_minmax_params_fn init_params, |
| xnn_pack_qs8_qb4w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| std::random_device random_device; |
| auto rng = std::mt19937(random_device()); |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 16.0f * 2.0f; |
| |
| const size_t planes = 2; // 4 bit is 2 planes - low nibbles and high nibbles |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| |
| const size_t packed_k2 = |
| round_up_po2(k(), kr() * sr() * planes); // 2 blocks for nibbles |
| const size_t packed_k_bytes = (packed_k2 + 1) / 2; |
| const size_t num_blocks = packed_k2 / bl(); |
| |
| xnnpack::Buffer<float> input(m() * k2); |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k2, |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params(mr()); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<xnn_bfloat16> kernel_scale2d(n() * k2 / bl()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k_bytes + |
| /* vksum */ packed_n() * sizeof(float) + |
| /* scales */ packed_n() * num_blocks * sizeof(xnn_bfloat16) + |
| /* bias */ packed_n() * sizeof(float)); |
| |
| xnnpack::Buffer<xnn_float16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| for (size_t i = 0; i < m(); ++i) { |
| const float* input_ptr = &input[i * k2]; |
| const auto minmax = std::minmax_element(input_ptr, input_ptr + k2); |
| float inv_scale; |
| quantization_params[i] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t j = 0; j < k2; ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[i].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[i].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[i].zero_point)); |
| } |
| } |
| for (size_t i = m(); i < mr(); ++i) { |
| quantization_params[i].zero_point = quantization_params[m() - 1].zero_point; |
| quantization_params[i].inv_scale = quantization_params[m() - 1].inv_scale; |
| } |
| |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale2d.begin(), kernel_scale2d.end(), |
| [&]() { return scalerng(); }); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_qc4w_packing_params packing_params = {/*input_zero_point=*/1, |
| b_zero_point()}; |
| |
| pack(/*g=*/1, n(), k2, nr(), kr(), sr(), bl(), b.data(), /*bias=*/nullptr, |
| /*scale=*/kernel_scale2d.data(), packed_w.data(), |
| sizeof(xnn_float16) * nr(), sizeof(float) * nr(), &packing_params); |
| |
| // Fill in packed kernel scale |
| size_t stride = |
| nr() * (packed_k_bytes + /* scales= */ num_blocks * sizeof(xnn_float16) + |
| /* ksum= */ sizeof(float) + /* bias= */ sizeof(float)); |
| size_t block_stride = (bl() / 2 + sizeof(xnn_float16)) * nr(); |
| size_t start_offset = nr() * (packed_k_bytes / num_blocks + sizeof(float)); |
| uintptr_t start = (uintptr_t)packed_w.data() + start_offset; |
| xnn_init_blockwise_scale_bf16_params(n(), nr(), stride, |
| /*num_blocks=*/num_blocks, |
| /*block_stride=*/block_stride, |
| kernel_scale2d.data(), (void*)start); |
| |
| start = (uintptr_t)packed_w.data() + stride - sizeof(float) * nr(); |
| xnn_init_qs8_qc8w_scale_fp32_params(n(), nr(), stride, bias.data(), |
| (void*)start); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| float kfsum = 0.0; |
| for (size_t bl_index = 0; bl_index < num_blocks; ++bl_index) { |
| int32_t ksum = 0; |
| int32_t c_ref_acc = 0; |
| for (size_t kr_index = 0; kr_index < bl(); kr_index++) { |
| const size_t k_index = bl_index * bl() + kr_index; |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| const int32_t bv = |
| static_cast<int32_t>((k_index % 2 == 0) |
| ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| ksum += bv; |
| c_ref_acc += static_cast<int32_t>(a[m_index * a_stride() + k_index]) * |
| static_cast<int32_t>(bv); |
| } |
| size_t scale_index = n_index * num_blocks + bl_index; |
| float scale = kernel_scale2d[scale_index]; |
| c_ref[m_index * n() + n_index] += c_ref_acc * scale; |
| kfsum += scale * ksum; |
| } |
| c_ref[m_index * n() + n_index] -= |
| (quantization_params[m_index].zero_point * kfsum); |
| c_ref[m_index * n() + n_index] *= quantization_params[m_index].inv_scale; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f16_qb4w_minmax_params params; |
| init_params(¶ms, static_cast<xnn_float16>(min()), |
| static_cast<xnn_float16>(max()), 8, bl()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm(m(), n(), k2, a.data(), a_stride() * sizeof(int8_t), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(xnn_float16), nr() * sizeof(xnn_float16), ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<xnn_float16>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k2; |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f32_qc4w_gemm_ukernel_fn gemm, |
| xnn_init_f32_qc4w_minmax_params_fn init_params, |
| xnn_pack_qs8_qc4w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 16.0f * 2.0f; |
| |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| const size_t packed_k2 = |
| round_up_po2(k(), kr() * sr() * planes()); // 2 blocks for nibbles |
| const size_t packed_k_bytes = (packed_k2 + 1) / 2; |
| |
| xnnpack::Buffer<float> input(m() * k2); |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k2, |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params(mr()); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k_bytes + |
| packed_n() * (sizeof(int32_t) + sizeof(float) * 2)); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| for (size_t i = 0; i < m(); ++i) { |
| const float* input_ptr = &input[i * k2]; |
| const auto minmax = std::minmax_element(input_ptr, input_ptr + k2); |
| float inv_scale; |
| quantization_params[i] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t j = 0; j < k2; ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[i].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[i].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[i].zero_point)); |
| } |
| } |
| for (size_t i = m(); i < mr(); ++i) { |
| quantization_params[i].zero_point = quantization_params[m() - 1].zero_point; |
| quantization_params[i].inv_scale = quantization_params[m() - 1].inv_scale; |
| } |
| |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_qc4w_packing_params packing_params = {/*input_zero_point=*/1, |
| b_zero_point()}; |
| pack(/*g=*/1, n(), k2, nr(), kr(), sr(), b.data(), /*bias=*/nullptr, |
| /*scale=*/nullptr, packed_w.data(), 2 * sizeof(float) * nr(), |
| &packing_params); |
| // Fill in packed kernel scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), nr() * (ks() * packed_k_bytes + 3 * sizeof(float)), |
| kernel_scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k_bytes + sizeof(float)))); |
| |
| // Fill in packed bias |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), nr() * (ks() * packed_k_bytes + 3 * sizeof(float)), |
| bias.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k_bytes + 2 * sizeof(float)))); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t ksum = 0; |
| for (size_t k_index = 0; k_index < k2; k_index++) { |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| const int32_t bv = static_cast<int32_t>( |
| (k_index % 2 == 0) ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| ksum += bv; |
| c_ref[m_index * n() + n_index] += |
| static_cast<int32_t>(a[m_index * a_stride() + k_index]) * |
| static_cast<int32_t>(bv); |
| } |
| c_ref[m_index * n() + n_index] -= |
| (quantization_params[m_index].zero_point * ksum); |
| c_ref[m_index * n() + n_index] *= |
| quantization_params[m_index].inv_scale * kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_qc4w_minmax_params params; |
| init_params(¶ms, min(), max(), 8); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| gemm(m(), n(), k2, a.data(), a_stride() * sizeof(int8_t), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<float>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k2; |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qd8_f32_qb4w_gemm_ukernel_fn gemm, |
| xnn_init_f32_qb4w_minmax_params_fn init_params, |
| xnn_pack_qs8_qb4w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| std::random_device random_device; |
| auto rng = std::mt19937(random_device()); |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 16.0f * 2.0f; |
| |
| const size_t planes = 2; // 4 bit is 2 planes - low nibbles and high nibbles |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| const size_t packed_k2 = |
| round_up_po2(k(), kr() * sr() * planes); // 2 blocks for nibbles |
| const size_t packed_k_bytes = (packed_k2 + 1) / 2; |
| const size_t num_blocks = packed_k2 / bl(); |
| |
| xnnpack::Buffer<float> input(m() * k2); |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k2, |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_qd8_quantization_params> quantization_params(mr()); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<xnn_bfloat16> kernel_scale2d(n() * k2 / bl()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k_bytes + |
| /* vksum */ packed_n() * sizeof(float) + |
| /* scales */ packed_n() * num_blocks * sizeof(float) + |
| /* bias */ packed_n() * sizeof(float)); |
| |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(input.begin(), input.end(), std::ref(f32rng)); |
| for (size_t i = 0; i < m(); ++i) { |
| const float* input_ptr = &input[i * k2]; |
| const auto minmax = std::minmax_element(input_ptr, input_ptr + k2); |
| float inv_scale; |
| quantization_params[i] = xnn_f32_qd8_asymmetric_quantization_params( |
| *minmax.first, *minmax.second, &inv_scale); |
| for (size_t j = 0; j < k2; ++j) { |
| float scaled_input = input_ptr[j] * inv_scale; |
| scaled_input = std::min<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::max() - |
| quantization_params[i].zero_point)); |
| scaled_input = std::max<float>( |
| scaled_input, static_cast<float>(std::numeric_limits<int8_t>::min() - |
| quantization_params[i].zero_point)); |
| a[i * a_stride() + j] = static_cast<int8_t>( |
| std::lrintf(scaled_input) + |
| static_cast<long>(quantization_params[i].zero_point)); |
| } |
| } |
| for (size_t i = m(); i < mr(); ++i) { |
| quantization_params[i].zero_point = quantization_params[m() - 1].zero_point; |
| quantization_params[i].inv_scale = quantization_params[m() - 1].inv_scale; |
| } |
| |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale2d.begin(), kernel_scale2d.end(), |
| [&]() { return scalerng(); }); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| // Row sums are multiplied by input zero point, since we don't know it |
| // until runtime, set it to 1. |
| const xnn_qs8_qc4w_packing_params packing_params = {/*input_zero_point=*/1, |
| b_zero_point()}; |
| pack(/*g=*/1, n(), k2, nr(), kr(), sr(), bl(), b.data(), /*bias=*/nullptr, |
| /*scale=*/kernel_scale2d.data(), packed_w.data(), |
| sizeof(xnn_float16) * nr(), sizeof(float) * nr(), &packing_params); |
| |
| // Fill in packed kernel scale |
| size_t stride = |
| nr() * (packed_k_bytes + /* scales= */ num_blocks * sizeof(xnn_float16) + |
| /* ksum= */ sizeof(float) + /* bias= */ sizeof(float)); |
| size_t block_stride = (bl() / 2 + sizeof(xnn_float16)) * nr(); |
| size_t start_offset = nr() * (packed_k_bytes / num_blocks + sizeof(float)); |
| uintptr_t start = (uintptr_t)packed_w.data() + start_offset; |
| xnn_init_blockwise_scale_bf16_params(n(), nr(), stride, |
| /*num_blocks=*/num_blocks, |
| /*block_stride=*/block_stride, |
| kernel_scale2d.data(), (void*)start); |
| |
| start = (uintptr_t)packed_w.data() + stride - sizeof(float) * nr(); |
| xnn_init_qs8_qc8w_scale_fp32_params(n(), nr(), stride, bias.data(), |
| (void*)start); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| float kfsum = 0.0; |
| for (size_t bl_index = 0; bl_index < num_blocks; ++bl_index) { |
| int32_t ksum = 0; |
| int32_t c_ref_acc = 0; |
| for (size_t kr_index = 0; kr_index < bl(); kr_index++) { |
| const size_t k_index = bl_index * bl() + kr_index; |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| const int32_t bv = |
| static_cast<int32_t>((k_index % 2 == 0) |
| ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| ksum += bv; |
| c_ref_acc += static_cast<int32_t>(a[m_index * a_stride() + k_index]) * |
| static_cast<int32_t>(bv); |
| } |
| size_t scale_index = n_index * num_blocks + bl_index; |
| float scale = kernel_scale2d[scale_index]; |
| c_ref[m_index * n() + n_index] += c_ref_acc * scale; |
| kfsum += scale * ksum; |
| } |
| c_ref[m_index * n() + n_index] -= |
| (quantization_params[m_index].zero_point * kfsum); |
| c_ref[m_index * n() + n_index] *= quantization_params[m_index].inv_scale; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_qb4w_minmax_params params; |
| init_params(¶ms, min(), max(), 8, bl()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| if (unsigned_inputs()) { |
| // Some architectures require that the input be unsigned. |
| // Adjust the zero point and flip the sign of the input to mimic adding |
| // 128 to the input with correct overflow behaviour. |
| for (int i = 0; i < quantization_params.size(); ++i) { |
| quantization_params[i].zero_point += 128; |
| } |
| for (int i = 0; i < a.size(); ++i) { |
| a[i] ^= 0x80; |
| } |
| } |
| gemm(m(), n(), k2, a.data(), a_stride() * sizeof(int8_t), |
| static_cast<const void*>(packed_w.data()), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms, |
| quantization_params.data()); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<float>::epsilon()) * |
| dynamic_quantization_ops; |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k2; |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_qp8_f32_qc4w_gemm_minmax_ukernel_fn gemm, |
| xnn_init_f32_minmax_params_fn init_minmax_params, |
| xnn_pack_weights_and_biases_fn pack, |
| xnn_packed_stride_weights_and_biases_fn packed_stride) { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 16.0f * 2.0f; |
| |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| |
| xnnpack::Buffer<float> input_f32(m() * k2); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<float> bias(n(), 0.0f); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| |
| // Create a fake `gemm_config` for the packing functions. |
| struct xnn_gemm_config gemm_config; |
| gemm_config.mr = static_cast<uint8_t>(mr()); |
| gemm_config.mr_packed = static_cast<uint8_t>(mr_packed()); |
| gemm_config.nr = static_cast<uint8_t>(nr()); |
| gemm_config.log2_kr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(kr())); |
| gemm_config.log2_sr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(sr())); |
| |
| const size_t packed_w_stride = |
| packed_stride(&gemm_config, k2, /*unused_block_size=*/0, /*k_stride=*/k2, |
| /*extra_bytes=*/0); |
| const size_t packed_w_size = packed_w_stride * round_up(n(), nr()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w(packed_w_size); |
| |
| std::generate(input_f32.begin(), input_f32.end(), std::ref(f32rng)); |
| |
| // Quantize the left-hand operand. |
| const size_t input_packed_size = |
| xnn_x8_packq_f32qp8_packed_size(m(), k2, mr_packed(), kr(), sr()); |
| xnnpack::Buffer<int8_t> input_qp8(input_packed_size); |
| xnn_x8_packq_f32qp8_ukernel__scalar_u1(m(), k2, mr_packed(), kr(), sr(), |
| /*m_idx_start=*/0, input_f32.data(), |
| /*lhs_stride=*/k2 * sizeof(float), |
| input_qp8.data()); |
| |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| |
| // RHS packing. |
| struct xnn_qs8_qc4w_packing_params params; |
| params.input_zero_point = 1; |
| params.kernel_zero_point = b_zero_point(); |
| pack(/*flags=*/0, &gemm_config, k2, n(), |
| /*groups=*/1, /*unused_block_size=*/0, |
| /*k_stride=*/k2, |
| /*accumulator_init=*/nullptr, |
| /*weights=*/b.data(), |
| /*int_extra_data0_fn=*/nullptr, |
| /*extra_data0=*/bias.data(), |
| /*extra_data0_size=*/sizeof(float), |
| /*init_extra_data1_fn=*/ |
| nullptr, |
| /*extra_data1=*/kernel_scale.data(), |
| /*extra_data1_size=*/sizeof(float), |
| /*packed_weights_ptr=*/packed_w.data(), ¶ms); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k2; k_index++) { |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| const int32_t bv = static_cast<int32_t>( |
| (k_index % 2 == 0) ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| c_ref[m_index * n() + n_index] += |
| xnn_x8_packq_f32qp8_get_dequantized(m_index, k_index, |
| input_qp8.data(), k2, |
| mr_packed(), kr(), sr()) * |
| static_cast<int32_t>(bv); |
| } |
| c_ref[m_index * n() + n_index] *= kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params minmax_params; |
| init_minmax_params(&minmax_params, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm(m(), n(), k2, input_qp8.data(), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), sizeof(float), &minmax_params); |
| |
| const float tolerance = compute_sum_tolerance( |
| max_abs_product, ks() * k(), xnnpack::NumericLimits<float>::epsilon()); |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k2 |
| << ", nr = " << nr() << ", cm_stride = " << cm_stride(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test_QP8F32QC8W( |
| xnn_qp8_f32_qc8w_gemm_minmax_ukernel_fn gemm, |
| xnn_init_f32_minmax_params_fn init_minmax_params, |
| xnn_pack_weights_and_biases_fn pack, |
| xnn_packed_stride_weights_and_biases_fn packed_stride) { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| std::numeric_limits<int8_t>::min(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 256.0f * 2.0f; |
| |
| xnnpack::Buffer<float> input_f32(m() * k()); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<float> bias(n(), 0.0f); |
| xnnpack::Buffer<float> kernel_scale(n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| // Create a fake `gemm_config` for the packing functions. |
| struct xnn_gemm_config gemm_config; |
| gemm_config.mr = static_cast<uint8_t>(mr()); |
| gemm_config.mr_packed = static_cast<uint8_t>(mr_packed()); |
| gemm_config.nr = static_cast<uint8_t>(nr()); |
| gemm_config.log2_kr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(kr())); |
| gemm_config.log2_sr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(sr())); |
| |
| const size_t packed_w_stride = |
| packed_stride(&gemm_config, k(), /*unused_block_size=*/0, |
| /*k_stride=*/k(), /*extra_bytes=*/0); |
| const size_t packed_w_size = packed_w_stride * round_up(n(), nr()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w(packed_w_size); |
| |
| std::generate(input_f32.begin(), input_f32.end(), std::ref(f32rng)); |
| |
| // Quantize the left-hand operand. |
| const size_t input_packed_size = |
| xnn_x8_packq_f32qp8_packed_size(m(), k(), mr_packed(), kr(), sr()); |
| xnnpack::Buffer<int8_t> input_qp8(input_packed_size); |
| xnn_x8_packq_f32qp8_ukernel__scalar_u1(m(), k(), mr_packed(), kr(), sr(), |
| /*m_idx_start=*/0, input_f32.data(), |
| /*lhs_stride=*/k() * sizeof(float), |
| input_qp8.data()); |
| |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale.begin(), kernel_scale.end(), std::ref(scalerng)); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| |
| // RHS packing. |
| struct xnn_qs8_qc8w_packing_params params; |
| params.input_zero_point = 1; |
| params.scale_multiplier = 1.0f; |
| pack(/*flags=*/0, &gemm_config, k(), n(), |
| /*groups=*/1, /*unused_block_size=*/0, |
| /*k_stride=*/k(), |
| /*accumulator_init=*/nullptr, |
| /*weights=*/b.data(), |
| /*int_extra_data0_fn=*/nullptr, |
| /*extra_data0=*/bias.data(), |
| /*extra_data0_size=*/sizeof(float), |
| /*init_extra_data1_fn=*/ |
| nullptr, |
| /*extra_data1=*/kernel_scale.data(), |
| /*extra_data1_size=*/sizeof(float), |
| /*packed_weights_ptr=*/packed_w.data(), ¶ms); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| const size_t nb_index = n_index * k() + k_index; |
| c_ref[m_index * n() + n_index] += |
| xnn_x8_packq_f32qp8_get_dequantized(m_index, k_index, |
| input_qp8.data(), k(), |
| mr_packed(), kr(), sr()) * |
| static_cast<int32_t>(b[nb_index]); |
| } |
| c_ref[m_index * n() + n_index] *= kernel_scale[n_index]; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params minmax_params; |
| init_minmax_params(&minmax_params, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm(m(), n(), k(), input_qp8.data(), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), sizeof(float), &minmax_params); |
| |
| const float tolerance = compute_sum_tolerance( |
| max_abs_product, ks() * k(), xnnpack::NumericLimits<float>::epsilon()); |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", nr = " << nr() << ", cm_stride = " << cm_stride(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test_PF32( |
| xnn_pf32_gemm_minmax_ukernel_fn gemm, |
| xnn_init_f32_minmax_params_fn init_minmax_params, |
| xnn_pack_weights_and_biases_fn pack, |
| xnn_packed_stride_weights_and_biases_fn packed_stride) { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.f, 1.f), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f; |
| |
| xnnpack::Buffer<float> input_f32(m() * k()); |
| xnnpack::Buffer<float> weights(n() * k()); |
| xnnpack::Buffer<float> bias(n(), 0.0f); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| |
| // Create a fake `gemm_config` for the packing functions. |
| struct xnn_gemm_config gemm_config; |
| gemm_config.mr = static_cast<uint8_t>(mr()); |
| gemm_config.mr_packed = static_cast<uint8_t>(mr_packed()); |
| gemm_config.nr = static_cast<uint8_t>(nr()); |
| gemm_config.log2_kr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(kr())); |
| gemm_config.log2_sr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(sr())); |
| |
| const size_t packed_w_stride = |
| packed_stride(&gemm_config, k(), /*unused_block_size=*/0, |
| /*k_stride=*/k(), /*extra_bytes=*/0); |
| const size_t packed_w_size = packed_w_stride * round_up(n(), nr()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w(packed_w_size); |
| |
| // Get the LHS packing config. |
| const struct xnn_pack_lh_config* pack_lh_config = |
| xnn_init_x32_pack_lh_config(); |
| ASSERT_NE(pack_lh_config, nullptr); |
| |
| // Loop over the iterations. |
| std::generate(input_f32.begin(), input_f32.end(), std::ref(f32rng)); |
| |
| // Pack the left-hand operand. |
| const size_t input_packed_size = |
| pack_lh_config->size_fn(m(), k(), mr_packed(), kr(), sr()); |
| xnnpack::Buffer<int8_t> input_packed(input_packed_size); |
| pack_lh_config->ukernel(m(), k(), mr_packed(), kr(), sr(), |
| /*m_idx_start=*/0, input_f32.data(), |
| /*lhs_stride=*/k() * sizeof(float), |
| input_packed.data()); |
| |
| std::generate(weights.begin(), weights.end(), std::ref(f32rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| |
| // RHS packing. |
| struct xnn_qs8_qc8w_packing_params params; |
| params.input_zero_point = 1; |
| params.scale_multiplier = 1.0f; |
| pack(/*flags=*/0, &gemm_config, k(), n(), |
| /*groups=*/1, /*unused_block_size=*/0, |
| /*k_stride=*/k(), |
| /*accumulator_init=*/bias.data(), |
| /*weights=*/weights.data(), |
| /*int_extra_data0_fn=*/nullptr, |
| /*extra_data0=*/nullptr, |
| /*extra_data0_size=*/0, |
| /*init_extra_data1_fn=*/ |
| nullptr, |
| /*extra_data1=*/nullptr, |
| /*extra_data1_size=*/0, |
| /*packed_weights_ptr=*/packed_w.data(), ¶ms); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| c_ref[m_index * n() + n_index] += input_f32[m_index * k() + k_index] * |
| weights[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params minmax_params; |
| init_minmax_params(&minmax_params, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm(m(), n(), k() * sizeof(float), input_packed.data(), packed_w.data(), |
| c.data(), cm_stride() * sizeof(float), sizeof(float), &minmax_params); |
| |
| const float tolerance = compute_sum_tolerance( |
| max_abs_product, ks() * k(), xnnpack::NumericLimits<float>::epsilon()); |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", nr = " << nr() << ", cm_stride = " << cm_stride(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test_PQS8( |
| xnn_pqs8_qc8w_gemm_minmax_ukernel_fn gemm, |
| xnn_init_qs8_qc8w_conv_minmax_params_fn init_minmax_params, |
| xnn_pack_weights_and_biases_fn pack, |
| xnn_packed_stride_weights_and_biases_fn packed_stride) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<int8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t> c_ref(m() * n()); |
| |
| // Create a fake `gemm_config` for the packing functions. |
| struct xnn_gemm_config gemm_config; |
| gemm_config.mr = static_cast<uint8_t>(mr()); |
| gemm_config.mr_packed = static_cast<uint8_t>(mr_packed()); |
| gemm_config.nr = static_cast<uint8_t>(nr()); |
| gemm_config.log2_kr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(kr())); |
| gemm_config.log2_sr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(sr())); |
| |
| const size_t packed_w_stride = |
| packed_stride(&gemm_config, k(), /*unused_block_size=*/0, |
| /*k_stride=*/k(), /*extra_bytes=*/0); |
| const size_t packed_w_size = packed_w_stride * round_up(n(), nr()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w(packed_w_size); |
| |
| // Get the LHS packing config. |
| const struct xnn_pack_lh_config* pack_lh_config = |
| xnn_init_x8_pack_lh_config(); |
| ASSERT_NE(pack_lh_config, nullptr); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| // Pack the left-hand operand. |
| const size_t input_packed_size = |
| pack_lh_config->size_fn(m(), k(), mr_packed(), kr(), sr()); |
| xnnpack::Buffer<int8_t> input_packed(input_packed_size); |
| pack_lh_config->ukernel(m(), k(), mr_packed(), kr(), sr(), |
| /*m_idx_start=*/0, a.data(), |
| /*lhs_stride=*/k() * sizeof(int8_t), |
| input_packed.data()); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>(a[m_index * a_stride() + k_index]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>(b[n_index * k() + k_index]); |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int8_t c_zero_point = -1; |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| int32_t accumulated_min = acc[n_index]; |
| int32_t accumulated_max = acc[n_index]; |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| accumulated_min = std::min(accumulated_min, acc[m_index * n() + n_index]); |
| accumulated_max = std::max(accumulated_max, acc[m_index * n() + n_index]); |
| } |
| const uint32_t accumulated_range = |
| static_cast<uint32_t>(accumulated_max - accumulated_min); |
| const float c_scale = accumulated_range >= 256 |
| ? static_cast<double>(accumulated_range) / 255.0 |
| : 256.0 / 255; |
| scale[n_index] = 1.0f / c_scale; |
| } |
| |
| // RHS packing. |
| struct xnn_qs8_packing_params params; |
| params.input_zero_point = static_cast<int8_t>(a_zero_point() - 0x80); |
| pack(/*flags=*/0, &gemm_config, k(), n(), |
| /*groups=*/1, /*unused_block_size=*/0, |
| /*k_stride=*/k(), |
| /*accumulator_init=*/bias.data(), |
| /*weights=*/b.data(), |
| /*int_extra_data0_fn=*/nullptr, |
| /*extra_data0=*/scale.data(), |
| /*extra_data0_size=*/sizeof(float), |
| /*init_extra_data1_fn=*/ |
| nullptr, |
| /*extra_data1=*/scale.data(), |
| /*extra_data1_size=*/sizeof(float), |
| /*packed_weights_ptr=*/packed_w.data(), ¶ms); |
| |
| union xnn_qs8_qc8w_conv_minmax_params minmax_params; |
| init_minmax_params(&minmax_params, c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| |
| gemm(m(), n(), k(), input_packed.data(), packed_w.data(), c.data(), |
| cm_stride() * sizeof(int8_t), sizeof(int8_t), &minmax_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = xnn_qs8_requantize_fp32( |
| acc[m_index * n() + n_index], scale[n_index], c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| } |
| } |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| const size_t idx = i * cm_stride() + j; |
| ASSERT_LE(static_cast<int32_t>(c[idx]), |
| static_cast<int32_t>(qmax()) - 0x80); |
| ASSERT_GE(static_cast<int32_t>(c[idx]), |
| static_cast<int32_t>(qmin()) - 0x80); |
| ASSERT_EQ(static_cast<int32_t>(c[idx]), |
| static_cast<int32_t>(c_ref[i * n() + j])) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<int32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << static_cast<int32_t>(c[idx]) |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << scale[j] |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_qp8_f32_qb4w_gemm_minmax_ukernel_fn gemm, |
| xnn_init_f32_qb4w_minmax_params_fn init_minmax_params, |
| xnn_pack_weights_and_biases_fn pack, |
| xnn_packed_stride_weights_and_biases_fn packed_stride) { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-5.f, 5.f), |
| std::ref(rng)); |
| auto scalerng = std::bind(std::uniform_real_distribution<float>(0.5f, 2.f), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| 0, std::numeric_limits<uint8_t>::max()), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f * 16.0f * 2.0f; |
| |
| const size_t k2 = round_up_po2(k(), 2); // tester assumes byte aligned rows |
| |
| const size_t packed_k2 = |
| round_up_po2(k(), kr() * sr()); // 2 blocks for nibbles |
| const size_t packed_k_bytes = (packed_k2 + 1) / 2; |
| const size_t num_blocks = packed_k2 / bl(); |
| |
| xnnpack::Buffer<float> input_f32(m() * k2); |
| xnnpack::Buffer<uint8_t> b(n() * k2 / 2); |
| xnnpack::Buffer<float> bias(n(), 0.0f); |
| xnnpack::Buffer<uint16_t> kernel_scale2d(n() * packed_k2 / bl()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<float> c_ref(m() * n(), 0); |
| |
| // Create a fake `gemm_config` for the packing functions. |
| struct xnn_gemm_config gemm_config; |
| gemm_config.mr = static_cast<uint8_t>(mr()); |
| gemm_config.mr_packed = static_cast<uint8_t>(mr_packed()); |
| gemm_config.nr = static_cast<uint8_t>(nr()); |
| gemm_config.log2_kr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(kr())); |
| gemm_config.log2_sr = static_cast<uint8_t>(31 - math_clz_nonzero_u32(sr())); |
| |
| const size_t packed_w_stride = |
| packed_stride(&gemm_config, k2, /*block_size=*/bl(), /*k_stride=*/k2, |
| /*extra_bytes=*/0); |
| const size_t packed_w_size = packed_w_stride * round_up(n(), nr()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w(packed_w_size); |
| |
| std::generate(input_f32.begin(), input_f32.end(), std::ref(f32rng)); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(f32rng)); |
| std::generate(kernel_scale2d.begin(), kernel_scale2d.end(), |
| [&]() { return math_cvt_bf16_fp32(scalerng()); }); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| |
| // Quantize the left-hand operand. |
| const size_t input_packed_size = |
| xnn_x8_packq_f32qp8_packed_size(m(), k2, mr_packed(), kr(), sr()); |
| xnnpack::Buffer<int8_t> input_qp8(input_packed_size); |
| xnn_x8_packq_f32qp8_ukernel__scalar_u1( |
| m(), k2, mr_packed(), kr(), sr(), |
| /*m_idx_start=*/0, reinterpret_cast<const float*>(input_f32.data()), |
| /*lhs_stride=*/k2 * sizeof(float), input_qp8.data()); |
| |
| // RHS packing. |
| struct xnn_qs8_qc4w_packing_params params; |
| params.input_zero_point = 1; |
| params.kernel_zero_point = b_zero_point(); |
| pack(/*flags=*/0, &gemm_config, k2, n(), |
| /*groups=*/1, /*block_size=*/bl(), |
| /*k_stride=*/k2, |
| /*accumulator_init=*/nullptr, |
| /*weights=*/b.data(), |
| /*int_extra_data0_fn=*/nullptr, |
| /*extra_data0=*/nullptr, |
| /*extra_data0_size=*/0, |
| /*init_extra_data1_fn=*/ |
| nullptr, |
| /*extra_data1=*/kernel_scale2d.data(), |
| /*extra_data1_size=*/sizeof(float), |
| /*packed_weights_ptr=*/packed_w.data(), ¶ms); |
| |
| size_t stride = |
| nr() * (packed_k_bytes + /* scales= */ num_blocks * sizeof(uint16_t) + |
| /* ksum= */ sizeof(float) + /* bias= */ sizeof(float)); |
| uintptr_t start = (uintptr_t)packed_w.data() + stride - sizeof(float) * nr(); |
| |
| xnn_init_qs8_qc8w_scale_fp32_params(n(), nr(), stride, bias.data(), |
| (void*)start); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(c_ref.begin(), c_ref.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| float kfsum = 0.0; |
| for (size_t bl_index = 0; bl_index < num_blocks; ++bl_index) { |
| int32_t ksum = 0; |
| int32_t c_ref_acc = 0; |
| for (size_t kr_index = 0; kr_index < bl(); kr_index++) { |
| const size_t k_index = bl_index * bl() + kr_index; |
| const size_t nb_index = (n_index * k2 + k_index) / 2; |
| const int32_t bv = |
| static_cast<int32_t>((k_index % 2 == 0) |
| ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4)) - |
| b_zero_point(); |
| ksum += bv; |
| c_ref_acc += static_cast<int32_t>(xnn_x8_packq_f32qp8_get_quantized( |
| m_index, k_index, input_qp8.data(), k2, mr_packed(), |
| kr(), sr())) * |
| static_cast<int32_t>(bv); |
| } |
| size_t scale_index = n_index * num_blocks + bl_index; |
| float scale = math_cvt_fp32_bf16(kernel_scale2d[scale_index]); |
| c_ref[m_index * n() + n_index] += c_ref_acc * scale; |
| kfsum += scale * ksum; |
| } |
| float inv_scale = xnn_x8_packq_f32qp8_get_recip_scale( |
| m_index, input_qp8.data(), k2, mr_packed(), kr(), sr()); |
| int32_t neg_nudged_zero_point = xnn_x8_packq_f32qp8_get_neg_nudged_zp( |
| m_index, input_qp8.data(), k2, mr_packed(), kr(), sr()); |
| c_ref[m_index * n() + n_index] += (neg_nudged_zero_point * kfsum); |
| c_ref[m_index * n() + n_index] *= inv_scale; |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_qb4w_minmax_params minmax_params; |
| init_minmax_params(&minmax_params, min(), max(), 8, bl()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm(m(), n(), k2, input_qp8.data(), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), sizeof(float), &minmax_params); |
| |
| const float tolerance = compute_sum_tolerance( |
| max_abs_product, ks() * k(), xnnpack::NumericLimits<float>::epsilon()); |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k2 |
| << ", nr = " << nr() << ", cm_stride = " << cm_stride(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qs8_gemm_minmax_ukernel_fn gemm, |
| xnn_init_qs8_conv_minmax_params_fn init_params, |
| xnn_pack_qs8_gemm_fn pack, |
| xnn_qs8_requantize_fn requantize) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<int8_t> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n() * sizeof(int32_t) / sizeof(int8_t)); |
| xnnpack::Buffer<int8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<int8_t> c_ref(m() * n()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| const xnn_qs8_packing_params packing_params = { |
| static_cast<int8_t>(a_zero_point() - 0x80)}; |
| void* const packed_data = packed_w.data(); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_data, /*extra_bytes=*/0, &packing_params); |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>(a[m_index * a_stride() + k_index]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>(b[n_index * k() + k_index]); |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int32_t accumulated_min = *std::min_element(acc.cbegin(), acc.cend()); |
| const int32_t accumulated_max = *std::max_element(acc.cbegin(), acc.cend()); |
| const double c_scale = |
| static_cast<uint32_t>(accumulated_max - accumulated_min) >= 256 |
| ? static_cast<double>( |
| static_cast<uint32_t>(accumulated_max - accumulated_min)) / |
| 255.0 |
| : 1.00001; |
| const int8_t c_zero_point = static_cast<int8_t>(std::max( |
| std::min( |
| lrint(-0.5 - |
| 0.5 * static_cast<double>(accumulated_min + accumulated_max) / |
| c_scale), |
| static_cast<long>(std::numeric_limits<int8_t>::max())), |
| static_cast<long>(std::numeric_limits<int8_t>::min()))); |
| |
| const float requantization_scale = 1.0f / static_cast<float>(c_scale); |
| union xnn_qs8_conv_minmax_params quantization_params; |
| init_params(&quantization_params, requantization_scale, c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| |
| gemm(m(), n(), k(), a.data(), a_stride() * sizeof(int8_t), packed_data, |
| c.data(), cm_stride() * sizeof(int8_t), nr() * sizeof(int8_t), |
| &quantization_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], requantization_scale, |
| c_zero_point, static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| } |
| } |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmax()) - 0x80); |
| ASSERT_GE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmin()) - 0x80); |
| ASSERT_EQ(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(c_ref[i * n() + j])) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<int32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << static_cast<int32_t>(c[i * cm_stride() + j]) |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << requantization_scale |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_qs8_igemm_minmax_ukernel_fn igemm, |
| xnn_init_qs8_conv_minmax_params_fn init_params, |
| xnn_pack_qs8_igemm_fn pack, |
| xnn_qs8_requantize_fn requantize) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto i32rng = std::bind(std::uniform_int_distribution<int32_t>(-10000, 10000), |
| std::ref(rng)); |
| auto w8rng = std::bind(std::uniform_int_distribution<int32_t>( |
| -std::numeric_limits<int8_t>::max(), |
| std::numeric_limits<int8_t>::max()), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<int8_t> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * ks() * k()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_n() * packed_k() + |
| packed_n() * sizeof(int32_t) / sizeof(int8_t)); |
| xnnpack::Buffer<int32_t> bias(n()); |
| xnnpack::Buffer<int8_t> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<int32_t> acc(m() * n()); |
| xnnpack::Buffer<int8_t> c_ref(m() * n()); |
| xnnpack::Buffer<int8_t> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const int8_t*> im2col(mr() * ks()); |
| |
| xnnpack::fill_uniform_random_bits(a.data(), a.size(), rng); |
| std::generate(b.begin(), b.end(), std::ref(w8rng)); |
| std::generate(bias.begin(), bias.end(), std::ref(i32rng)); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| const xnn_qs8_packing_params packing_params = { |
| static_cast<int8_t>(a_zero_point() - 0x80)}; |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, &packing_params); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| // Compute 32-bit results and output quantization arguments. |
| std::fill(acc.begin(), acc.end(), 0); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]); |
| } else { |
| acc[m_index * n() + n_index] += |
| (static_cast<int32_t>( |
| im2col[ks_index * mr() + m_index][k_index + a_offset()]) - |
| static_cast<int32_t>(a_zero_point() - 0x80)) * |
| static_cast<int32_t>( |
| b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| acc[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const int32_t accumulated_min = *std::min_element(acc.cbegin(), acc.cend()); |
| const int32_t accumulated_max = *std::max_element(acc.cbegin(), acc.cend()); |
| const double c_scale = |
| static_cast<uint32_t>(accumulated_max - accumulated_min) >= 256 |
| ? static_cast<double>( |
| static_cast<uint32_t>(accumulated_max - accumulated_min)) / |
| 255.0 |
| : 1.00001; |
| const uint8_t c_zero_point = static_cast<uint8_t>(std::max( |
| std::min( |
| lrint(-0.5 - |
| 0.5 * static_cast<double>(accumulated_min + accumulated_max) / |
| c_scale), |
| static_cast<long>(std::numeric_limits<int8_t>::max())), |
| static_cast<long>(std::numeric_limits<int8_t>::min()))); |
| |
| const float requantization_scale = 1.0f / static_cast<float>(c_scale); |
| union xnn_qs8_conv_minmax_params quantization_params; |
| init_params(&quantization_params, requantization_scale, c_zero_point, |
| static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| |
| const int8_t* zero_pointer = (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm(m(), n(), k(), ks() * mr() * sizeof(void*), im2col.data(), |
| packed_w.data(), c.data(), cm_stride() * sizeof(int8_t), |
| nr() * sizeof(int8_t), a_offset() * sizeof(uint8_t), zero_pointer, |
| &quantization_params); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| requantize(acc[m_index * n() + n_index], requantization_scale, |
| c_zero_point, static_cast<int8_t>(qmin() - 0x80), |
| static_cast<int8_t>(qmax() - 0x80)); |
| } |
| } |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmax()) - 0x80); |
| ASSERT_GE(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(qmin()) - 0x80); |
| ASSERT_EQ(static_cast<int32_t>(c[i * cm_stride() + j]), |
| static_cast<int32_t>(c_ref[i * n() + j])) |
| << "at " << i << ", " << j |
| << ": reference = " << static_cast<uint32_t>(c_ref[i * n() + j]) |
| << " (accumulator = " << acc[i * n() + j] |
| << "), optimized = " << (uint32_t)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k() |
| << ", requantization scale = " << requantization_scale |
| << ", output zero point = " << static_cast<int32_t>(c_zero_point); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_bf16_f32_gemm_minmax_ukernel_fn gemm_minmax, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_bf16_f32_gemm_fn pack) const { |
| if (a_stride() < k()) { |
| return; |
| } |
| if (m() > mr()) { |
| return; |
| } |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.0f, 1.0f), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<xnn_bfloat16> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_bfloat16> b(n() * k()); |
| xnnpack::Buffer<xnn_bfloat16, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n() * 2); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&] { return f32rng(rng); }); |
| std::generate(b.begin(), b.end(), [&] { return f32rng(rng); }); |
| std::generate(bias.begin(), bias.end(), [&] { return f32rng(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), static_cast<xnn_bfloat16>(0.0f)); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), |
| /*extra_bytes=*/0, /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = bias[n_index]; |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| ASSERT_LT(m_index * k() + k_index, a.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (float& c_value : c_ref) { |
| c_value = std::max(std::min(c_value, max()), min()); |
| } |
| |
| gemm_minmax(m(), n(), k() * sizeof(xnn_bfloat16), |
| reinterpret_cast<const uint16_t*>(a.data()), |
| a_stride() * sizeof(xnn_bfloat16), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), /*unused_cn_stride=*/0, ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-4f, std::abs(c_ref[i * n() + j]) * 3.0e-2f)) |
| << "at " << i << ", " << j << ": Mr x Nr x Kr = " << mr() << " x " |
| << nr() << " x " << kr() << ", M x N x K = " << m() << " x " << n() |
| << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_bf16_gemm_minmax_ukernel_fn gemm_minmax, |
| xnn_init_bf16_minmax_params_fn init_params, |
| xnn_pack_f16_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.0f, 1.0f), |
| std::ref(rng)); |
| |
| xnnpack::Buffer<xnn_bfloat16> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_bfloat16> b(n() * k()); |
| xnnpack::Buffer<xnn_bfloat16, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n()); |
| xnnpack::Buffer<xnn_bfloat16> bias(n()); |
| xnnpack::Buffer<xnn_bfloat16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&] { return f32rng(rng); }); |
| std::generate(b.begin(), b.end(), [&] { return f32rng(rng); }); |
| std::generate(bias.begin(), bias.end(), [&] { return f32rng(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), static_cast<xnn_bfloat16>(0.0f)); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), |
| reinterpret_cast<const uint16_t*>(b.data()), |
| reinterpret_cast<const uint16_t*>(bias.data()), /*scale=*/nullptr, |
| reinterpret_cast<uint16_t*>(packed_w.data()), |
| /*extra_bytes=*/0, /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = bias[n_index]; |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| ASSERT_LT(m_index * k() + k_index, a.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_bf16_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (float& c_value : c_ref) { |
| c_value = std::max(std::min(c_value, max()), min()); |
| } |
| |
| gemm_minmax(m(), n(), k() * sizeof(xnn_bfloat16), a.data(), |
| a_stride() * sizeof(xnn_bfloat16), packed_w.data(), c.data(), |
| cm_stride() * sizeof(xnn_bfloat16), nr() * sizeof(xnn_bfloat16), |
| ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-4f, std::abs(c_ref[i * n() + j]) * 3.0e-2f)) |
| << "at " << i << ", " << j << ": Mr x Nr x Kr = " << mr() << " x " |
| << nr() << " x " << kr() << ", M x N x K = " << m() << " x " << n() |
| << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f16_gemm_minmax_ukernel_fn gemm_minmax, |
| xnn_init_f16_minmax_params_fn init_params, |
| xnn_pack_f16_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = |
| std::bind(std::uniform_real_distribution<float>(), std::ref(rng)); |
| |
| xnnpack::Buffer<xnn_float16> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_float16> b(n() * k()); |
| xnnpack::Buffer<xnn_float16, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n()); |
| xnnpack::Buffer<xnn_float16> bias(n()); |
| xnnpack::Buffer<xnn_float16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), f32rng); |
| std::generate(b.begin(), b.end(), f32rng); |
| std::generate(bias.begin(), bias.end(), f32rng); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), static_cast<xnn_float16>(0.0f)); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), |
| reinterpret_cast<const uint16_t*>(b.data()), |
| reinterpret_cast<const uint16_t*>(bias.data()), |
| /*scale=*/nullptr, reinterpret_cast<uint16_t*>(packed_w.data()), |
| /*extra_bytes=*/0, /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| ASSERT_LT(m_index * k() + k_index, a.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f16_minmax_params params; |
| init_params(¶ms, static_cast<xnn_float16>(min()), |
| static_cast<xnn_float16>(max())); |
| |
| for (float& c_value : c_ref) { |
| c_value = std::max(std::min(c_value, max()), min()); |
| } |
| |
| gemm_minmax(m(), n(), k() * sizeof(xnn_float16), a.data(), |
| a_stride() * sizeof(xnn_float16), packed_w.data(), c.data(), |
| cm_stride() * sizeof(xnn_float16), nr() * sizeof(xnn_float16), |
| ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-4f, std::abs(c_ref[i * n() + j]) * 1.0e-2f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f16_igemm_minmax_ukernel_fn igemm_minmax, |
| xnn_init_f16_minmax_params_fn init_params, |
| xnn_pack_f16_igemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| auto f32rng = std::bind(std::uniform_real_distribution<float>(-1.0f, 1.0f), |
| std::ref(rng)); |
| const float max_abs_product = 1.0f; |
| |
| xnnpack::Buffer<xnn_float16> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<xnn_float16> b(n() * ks() * k()); |
| xnnpack::Buffer<xnn_float16, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_k() * packed_n() + packed_n()); |
| xnnpack::Buffer<xnn_float16> bias(n()); |
| xnnpack::Buffer<xnn_float16> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| xnnpack::Buffer<xnn_float16> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const xnn_float16*> im2col(mr() * ks()); |
| |
| std::generate(a.begin(), a.end(), f32rng); |
| std::generate(b.begin(), b.end(), f32rng); |
| std::generate(bias.begin(), bias.end(), f32rng); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), static_cast<xnn_float16>(0.0f)); |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), |
| reinterpret_cast<const uint16_t*>(b.data()), |
| reinterpret_cast<const uint16_t*>(bias.data()), /*scale=*/nullptr, |
| reinterpret_cast<uint16_t*>(packed_w.data()), |
| /*extra_bytes=*/0, /*params=*/nullptr); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LT(ks_index * mr() + m_index, im2col.size()); |
| ASSERT_LT(k_index, k()); |
| ASSERT_LT(k_index, a_stride()); |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| c_ref[m_index * n() + n_index] += |
| im2col[ks_index * mr() + m_index][k_index] * |
| b[(n_index * ks() + ks_index) * k() + k_index]; |
| } else { |
| c_ref[m_index * n() + n_index] += |
| im2col[ks_index * mr() + m_index][k_index + a_offset()] * |
| b[(n_index * ks() + ks_index) * k() + k_index]; |
| } |
| } |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f16_minmax_params params; |
| init_params(¶ms, static_cast<xnn_float16>(min()), |
| static_cast<xnn_float16>(max())); |
| |
| for (float& c_value : c_ref) { |
| c_value = std::max(std::min(c_value, max()), min()); |
| } |
| |
| const xnn_float16* zero_pointer = |
| (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm_minmax(m(), n(), k() * sizeof(xnn_float16), ks() * mr() * sizeof(void*), |
| reinterpret_cast<const xnn_float16**>(im2col.data()), |
| packed_w.data(), c.data(), cm_stride() * sizeof(xnn_float16), |
| nr() * sizeof(xnn_float16), a_offset() * sizeof(xnn_float16), |
| zero_pointer, ¶ms); |
| |
| const float tolerance = |
| compute_sum_tolerance(max_abs_product, ks() * k(), |
| xnnpack::NumericLimits<xnn_float16>::epsilon()); |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << (float)c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_ppmm_minmax_ukernel_fn ppmm_minmax, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_f32_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> a(packed_k() * mr()); |
| xnnpack::Buffer<float> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t i = m(); i < mr(); i++) { |
| for (size_t l = 0; l < k(); l++) { |
| a[l * mr() + i] = a[l * mr() + m() - 1]; |
| } |
| } |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| for (size_t l = 0; l < k(); l++) { |
| c_ref[i * n() + j] += a[l * mr() + i] * b[j * k() + l]; |
| } |
| c_ref[i * n() + j] += bias[j]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (float& c_value : c_ref) { |
| c_value = std::max(std::min(c_value, max()), min()); |
| } |
| |
| ppmm_minmax(m(), n(), k() * sizeof(float), a.data(), packed_w.data(), |
| c.data(), cm_stride() * sizeof(float), nr() * sizeof(float), |
| ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_gemm_ukernel_fn gemm, |
| xnn_pack_f32_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist; |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| gemm(m(), n(), k() * sizeof(float), a.data(), a_stride() * sizeof(float), |
| packed_w.data(), c.data(), cm_stride() * sizeof(float), |
| nr() * sizeof(float), nullptr); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_gemm_relu_ukernel_fn gemm_relu, |
| xnn_pack_f32_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist; |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] = |
| std::max(0.0f, c_ref[m_index * n() + n_index] + bias[n_index]); |
| } |
| } |
| |
| gemm_relu(m(), n(), k() * sizeof(float), a.data(), a_stride() * sizeof(float), |
| packed_w.data(), c.data(), cm_stride() * sizeof(float), |
| nr() * sizeof(float), nullptr); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_GE(c[i * cm_stride() + j], 0.0f) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_gemm_minmax_ukernel_fn gemm_minmax, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_f32_gemm_fn pack) const { |
| if (a_stride() < k()) { |
| return; |
| } |
| if (m() > mr()) { |
| return; |
| } |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm_minmax(m(), n(), k() * sizeof(float), a.data(), |
| a_stride() * sizeof(float), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_f32_gemm_goi_minmax_ukernel_fn gemm_minmax, |
| xnn_init_f32_minmax_params_fn init_params) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * k()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemm_minmax(m(), n(), k() * sizeof(float), a.data(), |
| a_stride() * sizeof(float), b.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_f32_qc4w_gemm_minmax_ukernel_fn gemm_minmax, |
| xnn_init_f32_qc4w_minmax_params_fn init_params, |
| xnn_pack_f32_qc4w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| std::uniform_int_distribution<int32_t> i8dist( |
| -1, std::numeric_limits<uint8_t>::max()); |
| |
| const size_t k_stride = (k() + 1) / 2; |
| const size_t packed_k_bytes = (packed_k() + 1) / 2; |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<uint8_t> b(n() * k_stride); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<uint8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k_bytes + packed_n() * sizeof(float) * 2); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<double> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::generate(scale.begin(), scale.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| |
| std::generate(b.begin(), b.end(), [&]() { return i8dist(rng); }); |
| // For odd k ensure last nibble is padded with 0 |
| if (k() & 1) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| const size_t nb_index = n_index * k_stride + k_stride - 1; |
| b[nb_index] &= 0xF; |
| } |
| } |
| |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), nr() * sizeof(float), |
| /*params=*/nullptr); |
| |
| // Fill in packed scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k_bytes + (sizeof(float) + sizeof(float))), |
| scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k_bytes + sizeof(float)))); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = static_cast<double>(bias[n_index]); |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| const size_t nb_index = n_index * k_stride + k_index / 2; |
| const int16_t bv = static_cast<int16_t>( |
| (k_index % 2 == 0) ? (b[nb_index] & UINT8_C(0xF)) |
| : (b[nb_index] >> 4u)) - |
| b_zero_point(); |
| |
| c_ref[m_index * n() + n_index] += |
| static_cast<double>(a[m_index * a_stride() + k_index]) * |
| static_cast<double>(bv); |
| } |
| c_ref[m_index * n() + n_index] *= static_cast<double>(scale[n_index]); |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_qc4w_minmax_params params; |
| init_params(¶ms, min(), max(), b_zero_point()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = std::max( |
| std::min(c_ref[m_index * n() + n_index], static_cast<double>(max())), |
| static_cast<double>(min())); |
| } |
| } |
| |
| gemm_minmax(m(), n(), |
| k() * sizeof(float), // Note KC measured in bytes of input |
| a.data(), a_stride() * sizeof(float), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5, std::abs(c_ref[i * n() + j]) * 1.0e-6)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_qc8w_gemm_ukernel_fn gemm, |
| xnn_pack_f32_qs8w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist; |
| std::uniform_int_distribution<int32_t> i8dist( |
| -1, std::numeric_limits<int8_t>::max()); |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n() * sizeof(float) * 2); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return i8dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::generate(scale.begin(), scale.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), nr() * sizeof(float), |
| /*params=*/nullptr); |
| |
| // Fill in packed scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + |
| (sizeof(float) + sizeof(float))), |
| scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += a[m_index * a_stride() + k_index] * |
| (float)b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| c_ref[m_index * n() + n_index] *= scale[n_index]; |
| } |
| } |
| |
| gemm(m(), n(), k() * sizeof(float), // Note KC measured in bytes of input |
| a.data(), a_stride() * sizeof(float), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), nullptr); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], 0.1f); |
| } |
| } |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_qc8w_gemm_relu_ukernel_fn gemm_relu, |
| xnn_pack_f32_qs8w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist; |
| std::uniform_int_distribution<int32_t> i8dist( |
| -1, std::numeric_limits<int8_t>::max()); |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n() * sizeof(float) * 2); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return i8dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::generate(scale.begin(), scale.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), nr() * sizeof(float), |
| /*params=*/nullptr); |
| |
| // Fill in packed scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + |
| (sizeof(float) + sizeof(float))), |
| scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += a[m_index * a_stride() + k_index] * |
| (float)b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] = |
| std::max(0.0f, c_ref[m_index * n() + n_index] + bias[n_index]); |
| c_ref[m_index * n() + n_index] *= scale[n_index]; |
| } |
| } |
| |
| gemm_relu(m(), n(), |
| k() * sizeof(float), // Note KC measured in bytes of input |
| a.data(), a_stride() * sizeof(float), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), nullptr); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_GE(c[i * cm_stride() + j], 0.0f) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test( |
| xnn_f32_qc8w_gemm_minmax_ukernel_fn gemm_minmax, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_f32_qs8w_gemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| std::uniform_int_distribution<int32_t> i8dist( |
| std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max()); |
| const float max_abs_product = 1.0f; // scale is 1/max b value below. |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<int8_t> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> scale(n()); |
| xnnpack::Buffer<int8_t, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k() + packed_n() * sizeof(float) * 2); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<double> c_ref(m() * n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return i8dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::generate(scale.begin(), scale.end(), [&]() { |
| return std::abs(f32dist(rng)) / std::numeric_limits<int8_t>::max(); |
| }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| std::fill(packed_w.begin(), packed_w.end(), 0); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), nr() * sizeof(float), |
| /*params=*/nullptr); |
| |
| // Fill in packed scale |
| xnn_init_qs8_qc8w_scale_fp32_params( |
| n(), nr(), |
| nr() * (ks() * packed_k() * sizeof(int8_t) + |
| (sizeof(float) + sizeof(float))), |
| scale.data(), |
| (void*)((uintptr_t)packed_w.data() + |
| nr() * (ks() * packed_k() * sizeof(int8_t) + sizeof(float)))); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = static_cast<double>(bias[n_index]); |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += |
| static_cast<double>(a[m_index * a_stride() + k_index]) * |
| static_cast<double>(b[n_index * k() + k_index]); |
| } |
| c_ref[m_index * n() + n_index] *= static_cast<double>(scale[n_index]); |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = std::max( |
| std::min(c_ref[m_index * n() + n_index], static_cast<double>(max())), |
| static_cast<double>(min())); |
| } |
| } |
| |
| gemm_minmax(m(), n(), |
| k() * sizeof(float), // Note KC measured in bytes of input |
| a.data(), a_stride() * sizeof(float), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), ¶ms); |
| |
| const float tolerance = compute_sum_tolerance( |
| max_abs_product, ks() * k(), xnnpack::NumericLimits<float>::epsilon()); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], tolerance) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_gemminc_minmax_ukernel_fn gemminc, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_f32_gemminc_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| ASSERT_GE(a_stride(), k()); |
| ASSERT_GE(cm_stride(), n()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float> a((m() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * k()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| packed_n() * packed_k()); // no packed_n() |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> acc(mr() * packed_n()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| std::generate(acc.begin(), acc.end(), [&]() { return f32dist(rng); }); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), k(), nr(), kr(), sr(), b.data(), packed_w.data(), |
| /*params=*/nullptr); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LE(n(), packed_n()); |
| ASSERT_LT(m_index * n() + n_index, c_ref.size()); |
| c_ref[m_index * n() + n_index] += |
| a[m_index * a_stride() + k_index] * b[n_index * k() + k_index]; |
| } |
| c_ref[m_index * n() + n_index] += |
| acc[n_index / nr() * nr() * mr() + m_index % mr() * nr() + |
| n_index % nr()]; |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::max(std::min(c_ref[m_index * n() + n_index], max()), min()); |
| } |
| } |
| |
| gemminc(m(), n(), k() * sizeof(float), a.data(), a_stride() * sizeof(float), |
| packed_w.data(), c.data(), cm_stride() * sizeof(float), |
| nr() * sizeof(float), acc.data(), ¶ms); |
| |
| // Compute an upper bound for the summation error of the inner products. |
| const float nu = k() * xnnpack::NumericLimits<float>::epsilon(); |
| ASSERT_LT(k() * nu, 1.0f) << "Unreasonable dimensions for `float` tolerance."; |
| float max_abs_err = k() * nu / (1.0f - nu); |
| |
| // Validate micro-kernel outputs. |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], max_abs_err) |
| << "at " << i << ", " << j << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x K = " << m() << " x " << n() << " x " << k(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_igemm_ukernel_fn igemm, |
| xnn_pack_f32_igemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * ks() * k()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_k() * packed_n() + packed_n()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| xnnpack::Buffer<float> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const float*> im2col(mr() * ks()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LT(ks_index * mr() + m_index, im2col.size()); |
| ASSERT_LT(k_index, k()); |
| ASSERT_LT(k_index, a_stride()); |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| c_ref[m_index * n() + n_index] += |
| (im2col[ks_index * mr() + m_index][k_index]) * |
| (b[(n_index * ks() + ks_index) * k() + k_index]); |
| } else { |
| c_ref[m_index * n() + n_index] += |
| (im2col[ks_index * mr() + m_index][k_index + a_offset()]) * |
| (b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| const float* zero_pointer = (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm(m(), n(), k() * sizeof(float), ks() * mr() * sizeof(void*), |
| im2col.data(), packed_w.data(), c.data(), cm_stride() * sizeof(float), |
| nr() * sizeof(float), a_offset() * sizeof(float), zero_pointer, |
| nullptr); |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_igemm_relu_ukernel_fn igemm_relu, |
| xnn_pack_f32_igemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * ks() * k()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_k() * packed_n() + packed_n()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| xnnpack::Buffer<float> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const float*> im2col(mr() * ks()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LT(ks_index * mr() + m_index, im2col.size()); |
| ASSERT_LT(k_index, k()); |
| ASSERT_LT(k_index, a_stride()); |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| c_ref[m_index * n() + n_index] += |
| (im2col[ks_index * mr() + m_index][k_index]) * |
| (b[(n_index * ks() + ks_index) * k() + k_index]); |
| } else { |
| c_ref[m_index * n() + n_index] += |
| (im2col[ks_index * mr() + m_index][k_index + a_offset()]) * |
| (b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| c_ref[m_index * n() + n_index] = |
| std::max(0.0f, bias[n_index] + c_ref[m_index * n() + n_index]); |
| } |
| } |
| |
| const float* zero_pointer = (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm_relu(m(), n(), k() * sizeof(float), ks() * mr() * sizeof(void*), |
| im2col.data(), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), |
| a_offset() * sizeof(float), zero_pointer, nullptr); |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_GE(c[i * cm_stride() + j], 0.0f) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| } |
| } |
| } |
| |
| void GemmMicrokernelTester::Test(xnn_f32_igemm_minmax_ukernel_fn igemm_minmax, |
| xnn_init_f32_minmax_params_fn init_params, |
| xnn_pack_f32_igemm_fn pack) const { |
| ASSERT_LE(m(), mr()); |
| |
| xnnpack::ReplicableRandomDevice rng; |
| std::uniform_real_distribution<float> f32dist(-1.0f, 1.0f); |
| |
| xnnpack::Buffer<float> a((mr() - 1) * a_stride() + k(), |
| xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<float> b(n() * ks() * k()); |
| xnnpack::Buffer<float, XNN_ALLOCATION_ALIGNMENT> packed_w( |
| ks() * packed_k() * packed_n() + packed_n()); |
| xnnpack::Buffer<float> bias(n()); |
| xnnpack::Buffer<float> c((m() - 1) * cm_stride() + n()); |
| xnnpack::Buffer<float> c_ref(m() * n()); |
| xnnpack::Buffer<float> junk(k(), xnnpack::XnnExtraBytes); |
| xnnpack::Buffer<const float*> im2col(mr() * ks()); |
| |
| std::generate(a.begin(), a.end(), [&]() { return f32dist(rng); }); |
| std::generate(b.begin(), b.end(), [&]() { return f32dist(rng); }); |
| std::generate(bias.begin(), bias.end(), [&]() { return f32dist(rng); }); |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| |
| std::fill(packed_w.begin(), packed_w.end(), 0.0f); |
| pack(/*g=*/1, n(), ks(), k(), nr(), kr(), sr(), b.data(), bias.data(), |
| /*scale=*/nullptr, packed_w.data(), /*extra_bytes=*/0, |
| /*params=*/nullptr); |
| |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = 0; m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = |
| a.data() + a_stride() * m_index - a_offset(); |
| } |
| } |
| std::shuffle(im2col.begin(), im2col.end(), rng); |
| if (zero_index() != SIZE_MAX) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| im2col[ks_index * mr() + zero_index()] = a.data(); |
| } |
| } |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t m_index = m(); m_index < mr(); m_index++) { |
| im2col[ks_index * mr() + m_index] = junk.data(); |
| } |
| } |
| |
| std::fill(c_ref.begin(), c_ref.end(), 0.0f); |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| for (size_t ks_index = 0; ks_index < ks(); ks_index++) { |
| for (size_t k_index = 0; k_index < k(); k_index++) { |
| ASSERT_LT(ks_index * mr() + m_index, im2col.size()); |
| ASSERT_LT(k_index, k()); |
| ASSERT_LT(k_index, a_stride()); |
| if (im2col[ks_index * mr() + m_index] == a.data()) { |
| c_ref[m_index * n() + n_index] += |
| (im2col[ks_index * mr() + m_index][k_index]) * |
| (b[(n_index * ks() + ks_index) * k() + k_index]); |
| } else { |
| c_ref[m_index * n() + n_index] += |
| (im2col[ks_index * mr() + m_index][k_index + a_offset()]) * |
| (b[(n_index * ks() + ks_index) * k() + k_index]); |
| } |
| } |
| } |
| c_ref[m_index * n() + n_index] += bias[n_index]; |
| } |
| } |
| |
| for (size_t m_index = 0; m_index < m(); m_index++) { |
| for (size_t n_index = 0; n_index < n(); n_index++) { |
| c_ref[m_index * n() + n_index] = |
| std::min(c_ref[m_index * n() + n_index], max()); |
| c_ref[m_index * n() + n_index] = |
| std::max(c_ref[m_index * n() + n_index], min()); |
| } |
| } |
| |
| // Prepare parameters. |
| xnn_f32_minmax_params params; |
| init_params(¶ms, min(), max()); |
| |
| const float* zero_pointer = (zero_index() != SIZE_MAX) ? a.data() : nullptr; |
| |
| igemm_minmax(m(), n(), k() * sizeof(float), ks() * mr() * sizeof(void*), |
| im2col.data(), packed_w.data(), c.data(), |
| cm_stride() * sizeof(float), nr() * sizeof(float), |
| a_offset() * sizeof(float), zero_pointer, ¶ms); |
| |
| for (size_t i = 0; i < m(); i++) { |
| for (size_t j = 0; j < n(); j++) { |
| ASSERT_LE(c[i * cm_stride() + j], max()) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| ASSERT_GE(c[i * cm_stride() + j], min()) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| ASSERT_NEAR(c[i * cm_stride() + j], c_ref[i * n() + j], |
| std::max(1.0e-5f, std::abs(c_ref[i * n() + j]) * 1.0e-6f)) |
| << "at " << i << ", " << i << ": reference = " << c_ref[i * n() + j] |
| << ", optimized = " << c[i * cm_stride() + j] |
| << ", Mr x Nr x Kr = " << mr() << " x " << nr() << " x " << kr() |
| << ", M x N x KC x KS = " << m() << " x " << n() << " x " << k() |
| << " x " << ks(); |
| } |
| } |
| } |