diff --git a/SerialPrograms/Source/Kernels/Kernels_Tests.cpp b/SerialPrograms/Source/Kernels/Kernels_Tests.cpp index 869e2ac50a..bcf950ab1e 100644 --- a/SerialPrograms/Source/Kernels/Kernels_Tests.cpp +++ b/SerialPrograms/Source/Kernels/Kernels_Tests.cpp @@ -15,6 +15,7 @@ #include "ImageFilters/Kernels_ImageFilter_Tests.h" #include "ImageScaleBrightness/Kernels_ImageScaleBrightness_Tests.h" #include "ImageStats/Kernels_ImageStats_Tests.h" +#include "ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.h" #include "SpikeConvolution/Kernels_SpikeConvolution.h" #include "Waterfill/Kernels_Waterfill_Tests.h" @@ -34,6 +35,7 @@ void add_tests(UnitTestDatabase& database){ add_tests_ImageFilters(database); add_tests_ImageScaleBrightness(database); add_tests_ImageStats(database); + ScaleInvariantMatrixMatch::add_tests(database); SpikeConvolution::add_tests(database); add_tests_Waterfill(database); } diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.cpp b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.cpp index e53d578e5b..0ffedea4fb 100644 --- a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.cpp +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.cpp @@ -4,8 +4,8 @@ * */ -#include "Common/Cpp/Exceptions.h" #include "Common/Cpp/CpuId/CpuId.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" namespace PokemonAutomation{ namespace Kernels{ @@ -81,6 +81,10 @@ float compute_error_min4_x86_SSE (size_t width, size_t height, float scale, f float compute_error_min8_x86_AVX2 (size_t width, size_t height, float scale, float const* const* A, float const* const* TW, float const* const* W); float compute_error_min16_x86_AVX512(size_t width, size_t height, float scale, float const* const* A, float const* const* TW, float const* const* W); +void add_tests_x86_SSE(UnitTestDatabase& database); +void add_tests_x86_AVX2(UnitTestDatabase& database); +void add_tests_x86_AVX512(UnitTestDatabase& database); + float compute_error( size_t width, size_t height, float scale, @@ -130,6 +134,30 @@ float compute_error( } +// +// Register the tests for the ISAs this binary can run. Each ISA checks its +// results against the Default path, which is the source of truth, so the +// Default path itself gets no tests. +// +void add_tests(UnitTestDatabase& database){ +#ifdef PA_AutoDispatch_x64_08_Nehalem + if (CPU_CAPABILITY_CURRENT.OK_08_Nehalem){ + add_tests_x86_SSE(database); + } +#endif +#ifdef PA_AutoDispatch_x64_13_Haswell + if (CPU_CAPABILITY_CURRENT.OK_13_Haswell){ + add_tests_x86_AVX2(database); + } +#endif +#ifdef PA_AutoDispatch_x64_17_Skylake + if (CPU_CAPABILITY_CURRENT.OK_17_Skylake){ + add_tests_x86_AVX512(database); + } +#endif +} + + diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.h b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.h index 4c3936f6b2..b68881b454 100644 --- a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.h +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch.h @@ -8,6 +8,9 @@ #define PokemonAutomation_Kernels_ScaleInvariantMatrixMatch_H namespace PokemonAutomation{ + +class UnitTestDatabase; + namespace Kernels{ namespace ScaleInvariantMatrixMatch{ @@ -54,6 +57,8 @@ float compute_error( +void add_tests(UnitTestDatabase& database); + } diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX2.cpp b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX2.cpp index 88a905f94b..ab431e6ccd 100644 --- a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX2.cpp +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX2.cpp @@ -10,6 +10,8 @@ #include "Kernels/Kernels_x64_AVX2.h" #include "Kernels/PartialWordAccess/Kernels_PartialWordAccess_x64_AVX2.h" #include "Kernels_ScaleInvariantMatrixMatch_Routines.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Kernels_ScaleInvariantMatrixMatch_TestInfra.h" namespace PokemonAutomation{ namespace Kernels{ @@ -126,6 +128,43 @@ float compute_error_min8_x86_AVX2( +float compute_scale_Default(size_t width, size_t height, float const* const* A, float const* const* T); +float compute_scale_Default(size_t width, size_t height, float const* const* A, float const* const* TW, float const* const* W); +float compute_error_Default(size_t width, size_t height, float scale, float const* const* A, float const* const* T); +float compute_error_Default(size_t width, size_t height, float scale, float const* const* A, float const* const* TW, float const* const* W); + +class Test_x86_AVX2 : public Test{ +public: + Test_x86_AVX2(size_t width, size_t height, bool use_weights) + : Test( + "x86_AVX2", + width, height, + use_weights, + compute_scale_Default, + compute_scale_min8_x86_AVX2, + compute_scale_Default, + compute_scale_min8_x86_AVX2, + compute_error_Default, + compute_error_min8_x86_AVX2, + compute_error_Default, + compute_error_min8_x86_AVX2 + ) + {} +}; +void add_tests_x86_AVX2(UnitTestDatabase& database){ + // The kernel requires a minimum row width (the partial-row loads span up to + // a full vector); sizes below that are not registered for this ISA. + for (size_t i = 0; i < sizeof(kTestSizes) / sizeof(kTestSizes[0]); i++){ + if (kTestSizes[i][0] < 8){ + continue; + } + database.add(kTestSizes[i][0], kTestSizes[i][1], false); + database.add(kTestSizes[i][0], kTestSizes[i][1], true); + } +} + + + } } } diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX512.cpp b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX512.cpp index 4cbeb2f19f..ecb9058c25 100644 --- a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX512.cpp +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX512.cpp @@ -8,6 +8,8 @@ #include #include "Kernels_ScaleInvariantMatrixMatch_Routines.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Kernels_ScaleInvariantMatrixMatch_TestInfra.h" namespace PokemonAutomation{ namespace Kernels{ @@ -118,6 +120,43 @@ float compute_error_min16_x86_AVX512( +float compute_scale_Default(size_t width, size_t height, float const* const* A, float const* const* T); +float compute_scale_Default(size_t width, size_t height, float const* const* A, float const* const* TW, float const* const* W); +float compute_error_Default(size_t width, size_t height, float scale, float const* const* A, float const* const* T); +float compute_error_Default(size_t width, size_t height, float scale, float const* const* A, float const* const* TW, float const* const* W); + +class Test_x86_AVX512 : public Test{ +public: + Test_x86_AVX512(size_t width, size_t height, bool use_weights) + : Test( + "x86_AVX512", + width, height, + use_weights, + compute_scale_Default, + compute_scale_min16_x86_AVX512, + compute_scale_Default, + compute_scale_min16_x86_AVX512, + compute_error_Default, + compute_error_min16_x86_AVX512, + compute_error_Default, + compute_error_min16_x86_AVX512 + ) + {} +}; +void add_tests_x86_AVX512(UnitTestDatabase& database){ + // The kernel requires a minimum row width (the partial-row loads span up to + // a full vector); sizes below that are not registered for this ISA. + for (size_t i = 0; i < sizeof(kTestSizes) / sizeof(kTestSizes[0]); i++){ + if (kTestSizes[i][0] < 16){ + continue; + } + database.add(kTestSizes[i][0], kTestSizes[i][1], false); + database.add(kTestSizes[i][0], kTestSizes[i][1], true); + } +} + + + } } } diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_SSE.cpp b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_SSE.cpp index 783cbd12a5..042dd0ca12 100644 --- a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_SSE.cpp +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_SSE.cpp @@ -10,6 +10,8 @@ #include "Kernels/Kernels_x64_SSE41.h" #include "Kernels/PartialWordAccess/Kernels_PartialWordAccess_x64_SSE41.h" #include "Kernels_ScaleInvariantMatrixMatch_Routines.h" +#include "Common/Cpp/TestRunners/UnitTestDatabase.h" +#include "Kernels_ScaleInvariantMatrixMatch_TestInfra.h" namespace PokemonAutomation{ namespace Kernels{ @@ -120,6 +122,43 @@ float compute_error_min4_x86_SSE( +float compute_scale_Default(size_t width, size_t height, float const* const* A, float const* const* T); +float compute_scale_Default(size_t width, size_t height, float const* const* A, float const* const* TW, float const* const* W); +float compute_error_Default(size_t width, size_t height, float scale, float const* const* A, float const* const* T); +float compute_error_Default(size_t width, size_t height, float scale, float const* const* A, float const* const* TW, float const* const* W); + +class Test_x86_SSE : public Test{ +public: + Test_x86_SSE(size_t width, size_t height, bool use_weights) + : Test( + "x86_SSE", + width, height, + use_weights, + compute_scale_Default, + compute_scale_min4_x86_SSE, + compute_scale_Default, + compute_scale_min4_x86_SSE, + compute_error_Default, + compute_error_min4_x86_SSE, + compute_error_Default, + compute_error_min4_x86_SSE + ) + {} +}; +void add_tests_x86_SSE(UnitTestDatabase& database){ + // The kernel requires a minimum row width (the partial-row loads span up to + // a full vector); sizes below that are not registered for this ISA. + for (size_t i = 0; i < sizeof(kTestSizes) / sizeof(kTestSizes[0]); i++){ + if (kTestSizes[i][0] < 4){ + continue; + } + database.add(kTestSizes[i][0], kTestSizes[i][1], false); + database.add(kTestSizes[i][0], kTestSizes[i][1], true); + } +} + + + } } } diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Routines.h b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Routines.h index 2ea1cd67e8..d02feab14c 100644 --- a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Routines.h +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Routines.h @@ -22,8 +22,18 @@ struct SumATA2{ using vtype = typename Context::vtype; static constexpr size_t VECTOR_LENGTH = sizeof(vtype) / sizeof(float); - vtype sum_AT = Context::vzero(); - vtype sum_A2 = Context::vzero(); + vtype sum_AT; + vtype sum_A2; + + // An explicit default constructor is required: with only non-static data + // member initializers, the implicit default constructor does not zero the + // accumulators at -O0 on x86, leaving stale stack contents in the lanes + // (observed with gcc and Apple clang; the user-defined constructor of + // SumError initializes correctly under the same conditions). + PA_FORCE_INLINE SumATA2() + : sum_AT(Context::vzero()) + , sum_A2(Context::vzero()) + {} PA_FORCE_INLINE float scale() const{ return Context::vreduce(sum_AT) / Context::vreduce(sum_A2); @@ -98,7 +108,7 @@ struct SumATA2{ if constexpr (VECTOR_LENGTH > 1){ if (length){ vtype a0, t0; - Context::load2_partial_front(length, a0, ptrT, t0, ptrA); + Context::load2_partial_front(length, a0, ptrA, t0, ptrT); sum_at0 = Context::vpma(a0, t0, sum_at0); sum_as0 = Context::vpma(a0, a0, sum_as0); } @@ -186,7 +196,7 @@ struct SumATA2{ } if (length){ vtype a0, t0, w0; - Context::load3_partial_front(length, a0, A, t0, TW, w0, W); + Context::load3_partial_front(length, a0, ptrA, t0, ptrT, w0, ptrW); a0 = Context::vmul(a0, w0); sum_as0 = Context::vpma(a0, a0, sum_as0); sum_at0 = Context::vpma(a0, t0, sum_at0); @@ -205,10 +215,11 @@ struct SumError{ static constexpr size_t VECTOR_LENGTH = sizeof(vtype) / sizeof(float); vtype scale; - vtype sum = Context::vzero(); + vtype sum; PA_FORCE_INLINE SumError(float p_scale) : scale(Context::vset(p_scale)) + , sum(Context::vzero()) {} PA_FORCE_INLINE float sum_sqr() const{ @@ -271,7 +282,7 @@ struct SumError{ } if (length){ vtype a0, t0; - Context::load2_partial_front(length, a0, ptrT, t0, ptrA); + Context::load2_partial_front(length, a0, ptrA, t0, ptrT); a0 = Context::vpms(scale, a0, t0); sum0 = Context::vpma(a0, a0, sum0); } @@ -345,7 +356,7 @@ struct SumError{ } if (length){ vtype a0, t0, w0; - Context::load3_partial_front(length, a0, A, t0, TW, w0, W); + Context::load3_partial_front(length, a0, ptrA, t0, ptrT, w0, ptrW); a0 = Context::vmul(scale, a0); a0 = Context::vpms(a0, w0, t0); sum0 = Context::vpma(a0, a0, sum0); diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.cpp b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.cpp new file mode 100644 index 0000000000..463141fd88 --- /dev/null +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.cpp @@ -0,0 +1,206 @@ +/* Scale Invariant Matrix Match Test Infra + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include +#include "Common/Compiler.h" +#include "Common/Cpp/CancellableScope.h" +#include "Common/Cpp/Containers/AlignedVector.tpp" +#include "Kernels_ScaleInvariantMatrixMatch_TestInfra.h" + +namespace PokemonAutomation{ +namespace Kernels{ +namespace ScaleInvariantMatrixMatch{ + + + +// +// Head padding (in floats) before the first row of the matrices. The +// current ISAs do not read before a row start, but the padding keeps the +// first row away from the very start of the allocation in case a future +// ISA does (e.g. an SSE4.1-style back-read of up to VL - 1 floats). +// +static constexpr size_t HEAD_PAD = 16; +// +// Tail padding after the last row. The SSE4.1 partial-tail load +// (PSHUFB-based `load_f32_no_read_past_end`) always reads a full 16-byte +// word, which extends up to VL - 1 floats past the end of the row even +// though only the first `length` lanes are used. 15 floats (VL - 1 for +// the widest supported ISA) keeps those reads inside the allocation for +// the last row. +// +static constexpr size_t TAIL_PAD = 15; + +// +// A result passes if it is within 4096 ULPs of the Default result, or +// within 1e-4 of the natural magnitude of the accumulated sum. The +// absolute part covers near-zero results, where ULPs are far smaller +// than the accumulated rounding of a multi-million-term sum. +// +static constexpr uint64_t MAX_ULP_DIFF = 4096; +static constexpr double ABS_TOLERANCE = 1e-4; + +static uint64_t ulp_distance(float a, float b){ + // Monotonic mapping of the float bits to a signed order. + auto U = [](float f) -> uint64_t{ + int32_t i = std::bit_cast(f); + if (i >= 0) + return (uint64_t)(uint32_t)i + 0x80000000ull; + return (uint64_t)(uint32_t)(-i); + }; + uint64_t x = U(a); + uint64_t y = U(b); + return x > y ? x - y : y - x; +} +static bool within_tolerance(float x, float y, double magnitude){ + if (x == y) + return true; + if (ulp_distance(x, y) <= MAX_ULP_DIFF) + return true; + return std::fabs((double)x - (double)y) <= ABS_TOLERANCE * std::fmax(1.0, magnitude); +} + + + +Test::Test( + const std::string& arch_str, + size_t width, size_t height, + bool use_weights, + SimmFnScale scale0, SimmFnScale scale1, + SimmFnScaleW scaleW0, SimmFnScaleW scaleW1, + SimmFnError error0, SimmFnError error1, + SimmFnErrorW errorW0, SimmFnErrorW errorW1 +) + : UnitTest( + "Kernels::ScaleInvariantMatrixMatch - " + arch_str + + "(w=" + std::to_string(width) + ", h=" + std::to_string(height) + + (use_weights ? ", weights)" : ")") + ) + , m_width(width) + , m_height(height) + , m_use_weights(use_weights) + , m_scale0(scale0) + , m_scale1(scale1) + , m_scaleW0(scaleW0) + , m_scaleW1(scaleW1) + , m_error0(error0) + , m_error1(error1) + , m_errorW0(errorW0) + , m_errorW1(errorW1) +{} + +UnitTestResult Test::run(Logger& logger, CancellableScope& scope) const{ + scope.throw_if_cancelled(); + const size_t n = m_width * m_height; + + // + // Random data with a deterministic seed: A and T in [-1, 1], W in + // [0, 1]. TW = T * W is precomputed with the same float rounding + // that production uses. + // + AlignedVector A(n + HEAD_PAD + TAIL_PAD); + AlignedVector T(n + HEAD_PAD + TAIL_PAD); + AlignedVector rowsA(m_height); + AlignedVector rowsT(m_height); + AlignedVector W; + AlignedVector TW; + AlignedVector rowsW; + AlignedVector rowsTW; + if (m_use_weights){ + W = AlignedVector(n + HEAD_PAD + TAIL_PAD); + TW = AlignedVector(n + HEAD_PAD + TAIL_PAD); + rowsW = AlignedVector(m_height); + rowsTW = AlignedVector(m_height); + } + + std::mt19937 generator((unsigned int)(m_width * 1000003u + m_height * 7919u + (m_use_weights ? 2654435761u : 0u))); + std::uniform_real_distribution data_distribution(-1.0f, 1.0f); + std::uniform_real_distribution weight_distribution(0.0f, 1.0f); + for (size_t c = HEAD_PAD; c < HEAD_PAD + n; c++){ + A[c] = data_distribution(generator); + T[c] = data_distribution(generator); + if (m_use_weights){ + W[c] = weight_distribution(generator); + TW[c] = T[c] * W[c]; + } + } + for (size_t r = 0; r < m_height; r++){ + rowsA[r] = A.data() + HEAD_PAD + r * m_width; + rowsT[r] = T.data() + HEAD_PAD + r * m_width; + if (m_use_weights){ + rowsW[r] = W.data() + HEAD_PAD + r * m_width; + rowsTW[r] = TW.data() + HEAD_PAD + r * m_width; + } + } + + char value0[32]; + char value1[32]; + auto mismatch = [&](const char* fn, float r0, float r1, double magnitude) -> UnitTestResult{ + std::snprintf(value0, sizeof(value0), "%.9g", (double)r0); + std::snprintf(value1, sizeof(value1), "%.9g", (double)r1); + return std::string(fn) + ": default=" + value0 + " isa=" + value1 + + " (" + std::to_string(ulp_distance(r0, r1)) + " ulps, abs " + + std::to_string(std::fabs((double)r0 - (double)r1)) + " vs tolerance " + + std::to_string(ABS_TOLERANCE * std::fmax(1.0, magnitude)) + ")"; + }; + + // + // compute_scale: s = sum(A * T) / sum(A * A). + // + float s0 = m_scale0(m_width, m_height, rowsA.data(), rowsT.data()); + float s1 = m_scale1(m_width, m_height, rowsA.data(), rowsT.data()); + double mag = std::fmax(std::fabs((double)s0), std::fabs((double)s1)); + if (!within_tolerance(s0, s1, mag)) + return mismatch("compute_scale", s0, s1, mag); + + // + // compute_error at the Default scale: sum((s * A - T)^2). + // + double magE = 0.0; + for (size_t c = HEAD_PAD; c < HEAD_PAD + n; c++){ + double m = std::fabs((double)s0 * (double)A[c]) + std::fabs((double)T[c]); + magE += m * m; + } + float e0 = m_error0(m_width, m_height, s0, rowsA.data(), rowsT.data()); + float e1 = m_error1(m_width, m_height, s0, rowsA.data(), rowsT.data()); + if (!within_tolerance(e0, e1, magE)) + return mismatch("compute_error", e0, e1, magE); + + if (m_use_weights){ + // + // Weighted compute_scale: s = sum((A * W) * (T * W)) / sum((A * W)^2). + // + float ws0 = m_scaleW0(m_width, m_height, rowsA.data(), rowsTW.data(), rowsW.data()); + float ws1 = m_scaleW1(m_width, m_height, rowsA.data(), rowsTW.data(), rowsW.data()); + double magW = std::fmax(std::fabs((double)ws0), std::fabs((double)ws1)); + if (!within_tolerance(ws0, ws1, magW)) + return mismatch("compute_scale (weighted)", ws0, ws1, magW); + + // + // Weighted compute_error at the Default weighted scale: + // sum(((s * A) * W - T * W)^2). + // + double magWE = 0.0; + for (size_t c = HEAD_PAD; c < HEAD_PAD + n; c++){ + double m = std::fabs((double)ws0 * (double)A[c]) + std::fabs((double)T[c]); + magWE += (double)W[c] * (double)W[c] * m * m; + } + float we0 = m_errorW0(m_width, m_height, ws0, rowsA.data(), rowsTW.data(), rowsW.data()); + float we1 = m_errorW1(m_width, m_height, ws0, rowsA.data(), rowsTW.data(), rowsW.data()); + if (!within_tolerance(we0, we1, magWE)) + return mismatch("compute_error (weighted)", we0, we1, magWE); + } + + return true; +} + + + +} +} +} diff --git a/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.h b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.h new file mode 100644 index 0000000000..c902eb242b --- /dev/null +++ b/SerialPrograms/Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.h @@ -0,0 +1,108 @@ +/* Scale Invariant Matrix Match Test Infra + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Compiler.h" +#include "Common/Cpp/TestRunners/UnitTest.h" + +#ifndef PokemonAutomation_Kernels_ScaleInvariantMatrixMatch_TestInfra_H +#define PokemonAutomation_Kernels_ScaleInvariantMatrixMatch_TestInfra_H + +namespace PokemonAutomation{ +namespace Kernels{ +namespace ScaleInvariantMatrixMatch{ + + + +// +// Signatures of compute_scale_Default(), compute_scale_min4_x86_SSE(), etc. +// +using SimmFnScale = float (*)(size_t width, size_t height, float const* const* A, float const* const* T); +using SimmFnScaleW = float (*)(size_t width, size_t height, float const* const* A, float const* const* TW, float const* const* W); +using SimmFnError = float (*)(size_t width, size_t height, float scale, float const* const* A, float const* const* T); +using SimmFnErrorW = float (*)(size_t width, size_t height, float scale, float const* const* A, float const* const* TW, float const* const* W); + + + +// +// The width/height combinations the tests cover: 1x1 up to 1000x1000, +// divisible and undivisible by the vector lengths (4, 8, 16), including +// the short-row cases that exercise the unaligned prefix and the partial +// tail of the vector loops. +// +static constexpr size_t kTestSizes[][2] = { + {1, 1}, {2, 1}, {3, 1}, {4, 1}, {5, 1}, {7, 1}, + {8, 1}, {15, 1}, {16, 1}, {17, 1}, + {1, 2}, {2, 3}, {3, 5}, {5, 7}, + {7, 16}, {8, 33}, + {31, 32}, {32, 33}, + {63, 64}, {64, 65}, + {127, 128},{128, 129}, + {255, 256},{256, 257}, + {511, 512},{512, 513}, + {999, 1000},{1000, 1000}, + {1000, 1},{999, 1}, + {1, 1000},{2, 1000},{3, 1000}, + {16, 1000},{17, 1000}, + {256, 1000},{257, 1000}, + {512, 1000},{513, 1000}, + {7, 1000},{8, 1000}, +}; + + + +// +// Runs all four functions (compute_scale / compute_error, plain and +// weighted) twice on identical random data, once with the Default +// implementation and once with the ISA-specific implementation, and +// requires the two results to agree within rounding. The Default path +// is the source of truth. +// +// The results are float sums over up to a million terms accumulated in +// different orders, so an exact comparison is not possible; a result +// passes if it is within 4096 ULPs of the Default result, or within +// 1e-4 of the natural magnitude of the accumulated sum (the latter +// covers near-zero results, where ULPs are far smaller than the +// accumulated rounding). +// +// Warning, this file is compiled in multiple ISA contexts. +// We cannot define the functions here since they will be compiled differently. +// If a higher (AVX512) version wins out in the linker, all versions will use +// it and crash on older CPUs. +// +class Test : public UnitTest{ +public: + Test( + const std::string& arch_str, + size_t width, size_t height, + bool use_weights, + SimmFnScale scale0, SimmFnScale scale1, + SimmFnScaleW scaleW0, SimmFnScaleW scaleW1, + SimmFnError error0, SimmFnError error1, + SimmFnErrorW errorW0, SimmFnErrorW errorW1 + ); + + virtual UnitTestResult run(Logger& logger, CancellableScope& scope) const override; + +private: + size_t m_width; + size_t m_height; + bool m_use_weights; + SimmFnScale m_scale0; + SimmFnScale m_scale1; + SimmFnScaleW m_scaleW0; + SimmFnScaleW m_scaleW1; + SimmFnError m_error0; + SimmFnError m_error1; + SimmFnErrorW m_errorW0; + SimmFnErrorW m_errorW1; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 715214a137..24ed728d73 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1186,6 +1186,8 @@ file(GLOB LIBRARY_SOURCES Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_AVX512.cpp Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Core_x86_SSE.cpp Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_Routines.h + Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.cpp + Source/Kernels/ScaleInvariantMatrixMatch/Kernels_ScaleInvariantMatrixMatch_TestInfra.h Source/Kernels/SpikeConvolution/Kernels_SpikeConvolution.cpp Source/Kernels/SpikeConvolution/Kernels_SpikeConvolution.h Source/Kernels/SpikeConvolution/Kernels_SpikeConvolution_Core_Default.cpp