summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-10-15 16:26:09 -0700
committerScott Gasch <[email protected]>2026-10-15 16:26:09 -0700
commit1ea81bae240912efb8cebabae35fe58bc5d12afb (patch)
treee43b3bbb179601595a47f7c5c4661a297a8b4664 /src/eval.c
parent3d55dc94497661b1577109b42a94cc3dba1860fb (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.c34
1 files changed, 31 insertions, 3 deletions
diff --git a/src/eval.c b/src/eval.c
index f0036bd..76b332c 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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