diff options
| author | Scott Gasch <[email protected]> | 2026-10-25 09:55:30 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-10-25 09:55:30 -0700 |
| commit | 874ef0478078ef4eb436af7d2b68a353f61f4d70 (patch) | |
| tree | e9954c289393c64d88402beff04637b52732ca73 /src/script.c | |
| parent | 5f901d737b17c5f99b6e456d6c0c7d5e3c093dc4 (diff) | |
Learned FH% logistic-regression model (alpha full, beta whole-node-only)
Replaces the data.c histogram tables (g_uJointFHThousandths/
g_uBetaFHThousandths and their _NNUE counterparts) with two quantized
logistic regressions as the default FH%-prediction mechanism under
FH_USE_REGRESSION_MODEL=1, trained against real collected search data
(fhcollect.c) per docs/REGRESSION.md.
Alpha side is a full per-move regression, validated against the old
table (0.119pp weighted mean deviation across 10,684 matched cells).
Beta side is whole-node-only (matching the old table's own scope --
no functionality lost), refit on the NO_MOVE population after an
earlier mixed-tier fit saturated and over-pruned; its skip threshold
(FH_BETA_SKIP_THRESHOLD=99000) was confirmed by direct self-play
sweeps, not just ECM solve-count intuition (see docs/REGRESSION.md's
"Category A vs B" discussion).
Gated by an overnight SPRT match (regression vs HEAD tables, same
NNUE eval, sd8): at 4120 games, llr=+2.53 (never dipped negative,
peaked +2.8), score=0.517, candidate reaching comparable depth at
higher nps/ebf than head. Did not cross the H1=+2.94 SPRT bound, but
the trend across the full run is unambiguous and the marginal value of
waiting out the remaining games was judged not worth it.
The data.c tables are left in place as the inactive #else branch of
FH_USE_REGRESSION_MODEL for now; a follow-up commit will flip the
build default and remove them once this commit's head_reference
baseline and ECM numbers are recorded.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/script.c')
| -rw-r--r-- | src/script.c | 269 |
1 files changed, 29 insertions, 240 deletions
diff --git a/src/script.c b/src/script.c index 848546b..a5af8e9 100644 --- a/src/script.c +++ b/src/script.c @@ -97,33 +97,23 @@ typedef struct _SUITE_COUNTERS UINT64 u64TotalJointExtraTries[JOINT_EXTRA_TIER_COUNT]; UINT64 u64TotalJointExtraResearch[JOINT_EXTRA_TIER_COUNT]; UINT64 u64TotalLMPPruned; + // Static-nullmove/reverse-futility shortcut firing rate -- diagnostic, + // added to compare HOW OFTEN the shortcut fires (not just its + // per-decision wrong rate, already measured offline against + // FH_REGRESSION_COLLECTION data) between the table and regression + // builds on the same suite. Only meaningful single-threaded + // (--cpus 1), same caveat as the hang/gap/depth axes above. + UINT64 u64TotalQuickNullMoves; + UINT64 u64TotalQuickNullMoveSuccess; #ifdef HASHCAP_COLLECTION UINT64 u64TotalHashCapTries[2][2]; UINT64 u64TotalHashCapFH[2][2]; #endif -#ifdef FH_TABLE_COLLECTION - // Full dual-table (5D alpha + 4D beta) collection -- see chess.h's - // SEARCHER_THREAD_CONTEXT.sCounters.tree comment for the full - // design. Dumped as ALPHARAW/BETARAW (5/4-axis raw cells) and - // ESTRAW (1D marginals) below, never printed as a human-readable - // table here -- same reasoning as u64TotalJointTries/FH above, - // just a much bigger shape (the 5D alpha array alone is - // 25*11*11*33*11 = 1,208,625 raw cells). - UINT64 u64TotalAlpha5Tries[SEARCH_DEPTH_BUCKETS][HANG_CP_BUCKETS][GAP_CP_BUCKETS][MOVE_NUM_BUCKETS][IMPROVE_BUCKETS]; - UINT64 u64TotalAlpha5FH[SEARCH_DEPTH_BUCKETS][HANG_CP_BUCKETS][GAP_CP_BUCKETS][MOVE_NUM_BUCKETS][IMPROVE_BUCKETS]; - UINT64 u64TotalBetaTries[SEARCH_DEPTH_BUCKETS][GAP_BETA_BUCKETS][IMPROVE_BUCKETS][IMPROVE_BUCKETS]; - UINT64 u64TotalBetaFH[SEARCH_DEPTH_BUCKETS][GAP_BETA_BUCKETS][IMPROVE_BUCKETS][IMPROVE_BUCKETS]; - UINT64 u64TotalImproveEstimateTries[IMPROVE_BUCKETS]; - UINT64 u64TotalImproveEstimateFH[IMPROVE_BUCKETS]; - UINT64 u64TotalBetaDepthEstimateTries[SEARCH_DEPTH_BUCKETS]; - UINT64 u64TotalBetaDepthEstimateFH[SEARCH_DEPTH_BUCKETS]; - UINT64 u64TotalBetaGapEstimateTries[GAP_BETA_BUCKETS]; - UINT64 u64TotalBetaGapEstimateFH[GAP_BETA_BUCKETS]; - UINT64 u64TotalBetaOwnImpEstimateTries[IMPROVE_BUCKETS]; - UINT64 u64TotalBetaOwnImpEstimateFH[IMPROVE_BUCKETS]; - UINT64 u64TotalBetaEnemyImpEstimateTries[IMPROVE_BUCKETS]; - UINT64 u64TotalBetaEnemyImpEstimateFH[IMPROVE_BUCKETS]; -#endif // FH_TABLE_COLLECTION +// FH_REGRESSION_COLLECTION needs no g_SuiteCounters fields at all -- +// unlike the old FH_TABLE_COLLECTION histogram design, each record is +// written straight to its own thread's file as it's produced +// (fhcollect.c), so there's no in-process total to accumulate or dump +// here. #ifdef JOINT_EXTRA_VERIFY UINT64 u64TotalJointExtraVerifyTries[JOINT_EXTRA_TIER_COUNT][POST_REDUCE_DEPTH_BUCKETS]; UINT64 u64TotalJointExtraVerifySilentMiss[JOINT_EXTRA_TIER_COUNT][POST_REDUCE_DEPTH_BUCKETS]; @@ -464,6 +454,10 @@ _ProcessSuiteLine(char *szLine, POSITION *pos) g_Options.u64LeftoverAlpha; g_SuiteCounters.u64TotalLeftoverFH += g_Options.u64LeftoverFH; + g_SuiteCounters.u64TotalQuickNullMoves += + g_Options.u64QuickNullMoves; + g_SuiteCounters.u64TotalQuickNullMoveSuccess += + g_Options.u64QuickNullMoveSuccess; #ifdef HASHCAP_COLLECTION { ULONG u2, v2; @@ -548,75 +542,8 @@ _ProcessSuiteLine(char *szLine, POSITION *pos) } g_SuiteCounters.u64TotalLMPPruned += g_Options.u64LMPPruned; -#ifdef FH_TABLE_COLLECTION - { - ULONG a, b, c, d; - for (a = 0; a < SEARCH_DEPTH_BUCKETS; a++) - { - for (b = 0; b < HANG_CP_BUCKETS; b++) - { - for (c = 0; c < GAP_CP_BUCKETS; c++) - { - for (d = 0; d < MOVE_NUM_BUCKETS; d++) - { - for (v = 0; v < IMPROVE_BUCKETS; v++) - { - g_SuiteCounters.u64TotalAlpha5Tries[a][b][c][d][v] += - g_Options.u64Alpha5Tries[a][b][c][d][v]; - g_SuiteCounters.u64TotalAlpha5FH[a][b][c][d][v] += - g_Options.u64Alpha5FH[a][b][c][d][v]; - } - } - } - } - } - for (a = 0; a < SEARCH_DEPTH_BUCKETS; a++) - { - for (b = 0; b < GAP_BETA_BUCKETS; b++) - { - for (c = 0; c < IMPROVE_BUCKETS; c++) - { - for (d = 0; d < IMPROVE_BUCKETS; d++) - { - g_SuiteCounters.u64TotalBetaTries[a][b][c][d] += - g_Options.u64BetaTries[a][b][c][d]; - g_SuiteCounters.u64TotalBetaFH[a][b][c][d] += - g_Options.u64BetaFH[a][b][c][d]; - } - } - } - } - for (v = 0; v < IMPROVE_BUCKETS; v++) - { - g_SuiteCounters.u64TotalImproveEstimateTries[v] += - g_Options.u64ImproveEstimateTries[v]; - g_SuiteCounters.u64TotalImproveEstimateFH[v] += - g_Options.u64ImproveEstimateFH[v]; - g_SuiteCounters.u64TotalBetaOwnImpEstimateTries[v] += - g_Options.u64BetaOwnImpEstimateTries[v]; - g_SuiteCounters.u64TotalBetaOwnImpEstimateFH[v] += - g_Options.u64BetaOwnImpEstimateFH[v]; - g_SuiteCounters.u64TotalBetaEnemyImpEstimateTries[v] += - g_Options.u64BetaEnemyImpEstimateTries[v]; - g_SuiteCounters.u64TotalBetaEnemyImpEstimateFH[v] += - g_Options.u64BetaEnemyImpEstimateFH[v]; - } - for (v = 0; v < SEARCH_DEPTH_BUCKETS; v++) - { - g_SuiteCounters.u64TotalBetaDepthEstimateTries[v] += - g_Options.u64BetaDepthEstimateTries[v]; - g_SuiteCounters.u64TotalBetaDepthEstimateFH[v] += - g_Options.u64BetaDepthEstimateFH[v]; - } - for (v = 0; v < GAP_BETA_BUCKETS; v++) - { - g_SuiteCounters.u64TotalBetaGapEstimateTries[v] += - g_Options.u64BetaGapEstimateTries[v]; - g_SuiteCounters.u64TotalBetaGapEstimateFH[v] += - g_Options.u64BetaGapEstimateFH[v]; - } - } -#endif // FH_TABLE_COLLECTION +// FH_REGRESSION_COLLECTION needs no accumulation here -- see the +// g_SuiteCounters struct comment above. #ifdef JOINT_EXTRA_VERIFY { ULONG w; @@ -695,6 +622,9 @@ _PrintSuiteReport(double dSuiteStart) COMPILER_LONGLONG_UNSIGNED_FORMAT " tries)\n" " leftover : %5.2f%% alpha, %5.2f%% FH (%" COMPILER_LONGLONG_UNSIGNED_FORMAT " tries)\n" + " quick nullmove fire : %5.2f%% (%" + COMPILER_LONGLONG_UNSIGNED_FORMAT " of %" + COMPILER_LONGLONG_UNSIGNED_FORMAT " eligible)\n" " script time : %6.1f sec\n\n", g_SuiteCounters.uCorrect, g_SuiteCounters.uIncorrect, @@ -719,6 +649,10 @@ _PrintSuiteReport(double dSuiteStart) (100.0 * (double)g_SuiteCounters.u64TotalLeftoverFH / ((double)g_SuiteCounters.u64TotalLeftoverTries + 1.0)), g_SuiteCounters.u64TotalLeftoverTries, + (100.0 * (double)g_SuiteCounters.u64TotalQuickNullMoveSuccess / + ((double)g_SuiteCounters.u64TotalQuickNullMoves + 1.0)), + g_SuiteCounters.u64TotalQuickNullMoveSuccess, + g_SuiteCounters.u64TotalQuickNullMoves, (SystemTimeStamp() - dSuiteStart)); // Countermove evidence calibration -- FH% per evidence bucket, @@ -1035,154 +969,9 @@ _PrintSuiteReport(double dSuiteStart) } } -#ifdef FH_TABLE_COLLECTION - // Full dual-table raw dump for offline reassembly/baking (dual_table_run*/ - // aggregate.py et al): ALPHARAW is the 5D alpha joint (depth, dumb, - // gap, late, improve -- late==0 is the "whole node" sample, see - // chess.h), BETARAW is the 4D beta joint (depth, gap, own-improve, - // enemy-improve), ESTRAW is every independent 1D marginal (used - // only by marginal_crosscheck.py, never for baking itself). Same - // "last checkpoint per shard is authoritative" convention as the - // FOURAXISRAW dump above. - { - ULONG a, b, c, d; - for (a = 0; a < SEARCH_DEPTH_BUCKETS; a++) - { - for (b = 0; b < HANG_CP_BUCKETS; b++) - { - for (c = 0; c < GAP_CP_BUCKETS; c++) - { - for (d = 0; d < MOVE_NUM_BUCKETS; d++) - { - for (v = 0; v < IMPROVE_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalAlpha5Tries[a][b][c][d][v] > 0) - { - Trace("ALPHARAW %lu %lu %lu %lu %lu %" - COMPILER_LONGLONG_UNSIGNED_FORMAT " %" - COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", - a, b, c, d, v, - g_SuiteCounters.u64TotalAlpha5FH[a][b][c][d][v], - g_SuiteCounters.u64TotalAlpha5Tries[a][b][c][d][v]); - } - } - } - } - } - } - for (a = 0; a < SEARCH_DEPTH_BUCKETS; a++) - { - for (b = 0; b < GAP_BETA_BUCKETS; b++) - { - for (c = 0; c < IMPROVE_BUCKETS; c++) - { - for (d = 0; d < IMPROVE_BUCKETS; d++) - { - if (g_SuiteCounters.u64TotalBetaTries[a][b][c][d] > 0) - { - Trace("BETARAW %lu %lu %lu %lu %" - COMPILER_LONGLONG_UNSIGNED_FORMAT " %" - COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", - a, b, c, d, - g_SuiteCounters.u64TotalBetaFH[a][b][c][d], - g_SuiteCounters.u64TotalBetaTries[a][b][c][d]); - } - } - } - } - } - for (v = 0; v < SEARCH_DEPTH_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalDepthEstimateTries[v] > 0) - { - Trace("ESTRAW depth %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalDepthEstimateFH[v], - g_SuiteCounters.u64TotalDepthEstimateTries[v]); - } - } - for (v = 0; v < HANG_CP_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalHangEstimateTries[v] > 0) - { - Trace("ESTRAW dumb %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalHangEstimateFH[v], - g_SuiteCounters.u64TotalHangEstimateTries[v]); - } - } - for (v = 0; v < GAP_CP_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalGapEstimateTries[v] > 0) - { - Trace("ESTRAW gap %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalGapEstimateFH[v], - g_SuiteCounters.u64TotalGapEstimateTries[v]); - } - } - for (v = 0; v < MOVE_NUM_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalMoveNumEstimateTries[v] > 0) - { - Trace("ESTRAW late %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalMoveNumEstimateFH[v], - g_SuiteCounters.u64TotalMoveNumEstimateTries[v]); - } - } - for (v = 0; v < IMPROVE_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalImproveEstimateTries[v] > 0) - { - Trace("ESTRAW improve %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalImproveEstimateFH[v], - g_SuiteCounters.u64TotalImproveEstimateTries[v]); - } - } - for (v = 0; v < SEARCH_DEPTH_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalBetaDepthEstimateTries[v] > 0) - { - Trace("ESTRAW betadepth %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalBetaDepthEstimateFH[v], - g_SuiteCounters.u64TotalBetaDepthEstimateTries[v]); - } - } - for (v = 0; v < GAP_BETA_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalBetaGapEstimateTries[v] > 0) - { - Trace("ESTRAW betagap %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalBetaGapEstimateFH[v], - g_SuiteCounters.u64TotalBetaGapEstimateTries[v]); - } - } - for (v = 0; v < IMPROVE_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalBetaOwnImpEstimateTries[v] > 0) - { - Trace("ESTRAW betaown %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalBetaOwnImpEstimateFH[v], - g_SuiteCounters.u64TotalBetaOwnImpEstimateTries[v]); - } - } - for (v = 0; v < IMPROVE_BUCKETS; v++) - { - if (g_SuiteCounters.u64TotalBetaEnemyImpEstimateTries[v] > 0) - { - Trace("ESTRAW betaopp %lu %" COMPILER_LONGLONG_UNSIGNED_FORMAT - " %" COMPILER_LONGLONG_UNSIGNED_FORMAT "\n", v, - g_SuiteCounters.u64TotalBetaEnemyImpEstimateFH[v], - g_SuiteCounters.u64TotalBetaEnemyImpEstimateTries[v]); - } - } - } -#endif // FH_TABLE_COLLECTION +// FH_REGRESSION_COLLECTION needs no end-of-script dump here -- +// each record was already written to its own thread's binary file +// as it was produced (fhcollect.c). See docs/REGRESSION.md. #ifdef TEST_NULL // Null-move precision/recall calibration, summed across the whole |
