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/root.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/root.c')
| -rw-r--r-- | src/root.c | 60 |
1 files changed, 30 insertions, 30 deletions
@@ -360,10 +360,32 @@ Return value: **/ { ULONG u; +#ifdef FH_REGRESSION_COLLECTION + // ctx is frequently a function-static struct reused across many + // Think()/Ponder() calls within one process (root.c) -- the + // unconditional memset just below would otherwise silently close + // this thread's collection file and drop any buffered-but- + // unflushed records on every single search, not just at process + // startup. Flush (don't lose data) and preserve the open file + // across the wipe; uThreadNumber is NOT preserved here on purpose + // -- a helper thread's real uThreadNumber isn't known yet at this + // call site (split.c sets it on the line immediately after + // calling this), so FhCollectEmit opens the file lazily on first + // real use instead, by which point uThreadNumber is correct. See + // fhcollect.c's own comments. + FILE *pSavedFhFile = ctx->sFhCollectWriter.pFile; + if (NULL != pSavedFhFile) + { + FhCollectFlush(ctx); + } +#endif memset(ctx, 0, sizeof(SEARCHER_THREAD_CONTEXT)); ReInitializeSearcherContext(pos, ctx); ctx->sMoveStack.uUnblockedKeyValue[0] = 1; +#ifdef FH_REGRESSION_COLLECTION + ctx->sFhCollectWriter.pFile = pSavedFhFile; +#endif for (u = 1; u < MAX_PLY_PER_SEARCH; u++) @@ -1674,6 +1696,9 @@ Return value: g_Options.u64LeftoverTries = ctx->sCounters.tree.u64LeftoverTries; g_Options.u64LeftoverAlpha = ctx->sCounters.tree.u64LeftoverAlpha; g_Options.u64LeftoverFH = ctx->sCounters.tree.u64LeftoverFH; + g_Options.u64QuickNullMoves = ctx->sCounters.tree.u64QuickNullMoves; + g_Options.u64QuickNullMoveSuccess = + ctx->sCounters.tree.u64QuickNullMoveSuccess; #ifdef HASHCAP_COLLECTION for (u = 0; u < 2; u++) { @@ -1725,36 +1750,11 @@ Return value: sizeof(g_Options.u64NullMoveTriesByR)); memcpy(g_Options.u64NullMoveSuccessByR, ctx->sCounters.tree.u64NullMoveSuccessByR, sizeof(g_Options.u64NullMoveSuccessByR)); -#ifdef FH_TABLE_COLLECTION - memcpy(g_Options.u64Alpha5Tries, ctx->sFhCollection.u64Alpha5Tries, - sizeof(g_Options.u64Alpha5Tries)); - memcpy(g_Options.u64Alpha5FH, ctx->sFhCollection.u64Alpha5FH, - sizeof(g_Options.u64Alpha5FH)); - memcpy(g_Options.u64BetaTries, ctx->sFhCollection.u64BetaTries, - sizeof(g_Options.u64BetaTries)); - memcpy(g_Options.u64BetaFH, ctx->sFhCollection.u64BetaFH, - sizeof(g_Options.u64BetaFH)); - memcpy(g_Options.u64ImproveEstimateTries, ctx->sFhCollection.u64ImproveEstimateTries, - sizeof(g_Options.u64ImproveEstimateTries)); - memcpy(g_Options.u64ImproveEstimateFH, ctx->sFhCollection.u64ImproveEstimateFH, - sizeof(g_Options.u64ImproveEstimateFH)); - memcpy(g_Options.u64BetaDepthEstimateTries, ctx->sFhCollection.u64BetaDepthEstimateTries, - sizeof(g_Options.u64BetaDepthEstimateTries)); - memcpy(g_Options.u64BetaDepthEstimateFH, ctx->sFhCollection.u64BetaDepthEstimateFH, - sizeof(g_Options.u64BetaDepthEstimateFH)); - memcpy(g_Options.u64BetaGapEstimateTries, ctx->sFhCollection.u64BetaGapEstimateTries, - sizeof(g_Options.u64BetaGapEstimateTries)); - memcpy(g_Options.u64BetaGapEstimateFH, ctx->sFhCollection.u64BetaGapEstimateFH, - sizeof(g_Options.u64BetaGapEstimateFH)); - memcpy(g_Options.u64BetaOwnImpEstimateTries, ctx->sFhCollection.u64BetaOwnImpEstimateTries, - sizeof(g_Options.u64BetaOwnImpEstimateTries)); - memcpy(g_Options.u64BetaOwnImpEstimateFH, ctx->sFhCollection.u64BetaOwnImpEstimateFH, - sizeof(g_Options.u64BetaOwnImpEstimateFH)); - memcpy(g_Options.u64BetaEnemyImpEstimateTries, ctx->sFhCollection.u64BetaEnemyImpEstimateTries, - sizeof(g_Options.u64BetaEnemyImpEstimateTries)); - memcpy(g_Options.u64BetaEnemyImpEstimateFH, ctx->sFhCollection.u64BetaEnemyImpEstimateFH, - sizeof(g_Options.u64BetaEnemyImpEstimateFH)); -#endif // FH_TABLE_COLLECTION +// FH_REGRESSION_COLLECTION needs no per-search copy-out here, unlike +// the old FH_TABLE_COLLECTION histogram design -- FhCollectEmit +// (fhcollect.c) writes each record straight to this thread's own file +// as it's produced, during the search itself, so there's nothing left +// to accumulate into g_Options after the fact. #ifdef JOINT_EXTRA_VERIFY memcpy(g_Options.u64JointExtraVerifyTries, ctx->sCounters.tree.u64JointExtraVerifyTries, sizeof(g_Options.u64JointExtraVerifyTries)); |
