diff options
| author | Scott Gasch <[email protected]> | 2026-10-15 16:26:09 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-10-15 16:26:09 -0700 |
| commit | 1ea81bae240912efb8cebabae35fe58bc5d12afb (patch) | |
| tree | e43b3bbb179601595a47f7c5c4661a297a8b4664 /src/main.c | |
| parent | 3d55dc94497661b1577109b42a94cc3dba1860fb (diff) | |
Wire NNUE eval into real search path; fix critical perf regression
Load-and-call infra: export_nnue_weights.py-produced weight files can
now be loaded at runtime (nnueeval.c's NnueLoadWeights, "nnue load"
command, --nnueweights CLI flag for scripted match-play), and the FC
head (NnueForward) runs the real forward pass against the live
accumulator.
Real integration: Eval() now short-circuits to NnueForward when
g_fUseNnueEval && g_fNnueWeightsLoaded, so ./typhoon can actually play
on the NNUE eval -- ./eval_tune/test_vs_head.sh works unmodified to
compare it against the old HCE. ReInitializeSearcherContext (root.c)
gained a defensive accumulator recompute hook covering setboard's
downstream effect on the next go/ponder; it only fires when both
kings are validly placed, since testrecogn.c's synthetic positions
don't always have one at the moment this is called.
Critical fix: the NNUE accumulator maintenance added to move.c's 8
primitives (and MakeMove/UnmakeMove's king-move backup/restore) was
running unconditionally, on the mistaken assumption this was cheap.
It cost ~50% of engine throughput even with NNUE off (673k nps
measured vs the expected 1.4-1.5M baseline). Gated all of it behind
g_fUseNnueEval; classic HCE is back to ~1.52M nps, NNUE eval runs at
a fair ~409k nps (expected for an unquantized float32 forward pass).
This gating also exposed that testnnue.c's TestNnueAccumulator never
set g_fUseNnueEval, so its own direct NnueAccumRecompute calls raced
ahead of now-gated-off incremental maintenance; fixed by having the
test set/restore the flag around its run.
board.c: added en passant legality check to IsBoardLegal (rank vs
side-to-move, origin-square occupancy) -- a real, previously-latent
bug surfaced while building the NNUE test fixtures, unrelated to
NNUE itself.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src/main.c')
| -rw-r--r-- | src/main.c | 26 |
1 files changed, 25 insertions, 1 deletions
@@ -50,7 +50,6 @@ Return value: Trace("Typhoon %s (built on %s %s, commit %s):\n", VERSION, __DATE__, __TIME__, GIT_COMMIT); Trace(" Copyright (C) 2000-2026, Scott Gasch ([email protected])\n"); - Trace(" " REVISION); Trace(" " COMPILER_STRING); Trace(" Make profile used: %s\n", PROFILE); #ifdef MP @@ -400,6 +399,31 @@ Return value: { g_Options.fNoInputThread = TRUE; } + else if ((!STRCMPI(pszArg, "--nnueweights")) && fHaveVal) + { + // Loads and enables NNUE eval at startup -- for scripted + // match-play (eval_tune/match_play.py et al) spawning a + // fresh engine process per game, where there's no + // reliable way to send an interactive "nnue load"/"nnue + // on" command pair before the first "go". Interactive use + // can still do the same thing via the 'nnue' command. + if (!NnueLoadWeights(pszVal)) + { + UtilPanic(INITIALIZATION_FAILURE, + (void *)"Bad NNUE weights file", + (void *)&(pszVal), + (void *)"load", + NULL, + __FILE__, __LINE__); + } + else + { + g_fUseNnueEval = TRUE; + Trace("Loaded NNUE weights \"%s\", NNUE eval enabled.\n", + pszVal); + } + if (fConsumedNextArg) i++; + } else if ((!STRCMPI(pszArg, "--dnafile")) && fHaveVal) { if (!ReadEvalDNA(pszVal)) |
