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/eval.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/eval.c')
| -rw-r--r-- | src/eval.c | 34 |
1 files changed, 31 insertions, 3 deletions
@@ -1019,23 +1019,27 @@ ExportEvalDNA() { ULONG uSize = DNABufferSizeBytes(); char *p = malloc(uSize); + char *pch; ULONG u, v; SCORE *q; FLAG fFirst = TRUE; memset(p, 0, uSize); + pch = p; for (u = 0; u < ARRAY_LENGTH(g_EvalDNA); u++) { q = g_EvalDNA[u].pBase; for (v = 0; v < g_EvalDNA[u].uCount; v++) { + size_t cbLeft = uSize - (size_t)(pch - p); if (!fFirst) { - sprintf(p, "%s,%d", p, *q); + pch += snprintf(pch, cbLeft, ",%d", *q); } else { - sprintf(p, "%s%d", p, *q); + pch += snprintf(pch, cbLeft, "%d", *q); fFirst = FALSE; } q++; } - strcat(p, "\n"); + *pch++ = '\n'; + *pch = '\0'; fFirst = TRUE; } return p; // caller must free @@ -7026,6 +7030,30 @@ Return value: EvalTraceClear(); #endif + // NNUE short-circuit (docs/NNUE.md's rollout plan: keep both + // evaluators coexisting behind a toggle, full bypass not partial + // mixing). ctx->sNnueAccum is assumed correct here -- maintained + // incrementally by every MakeMove/UnmakeMove since the last fresh + // NnueAccumRecompute (root.c's ReInitializeSearcherContext, run + // on every new root position). piPositional's contract ("always + // >= 0 on anything but a full eval") is satisfied with a neutral + // 0 -- none of the lazy/super-lazy margin-sizing machinery below + // has been recalibrated against NNUE's score distribution yet + // (a separate, not-yet-done project per NNUE.md), so this is + // intentionally the least-specific answer that's still safe, not + // an attempt to approximate what a real positional estimate would + // say. + if ((TRUE == g_fUseNnueEval) && (TRUE == g_fNnueWeightsLoaded)) + { + SCORE iNnueScore = NnueForward(ctx); + if (NULL != piPositional) + { + (*piPositional)[WHITE] = 0; + (*piPositional)[BLACK] = 0; + } + return iNnueScore; + } + // Initialize army scalers here (moved ahead of the super-lazy exit // point below, 2026-09-08 -- their only inputs, uNonPawnMaterial, // are already available this early, and the super-lazy margin |
