summaryrefslogtreecommitdiff
path: root/src/move.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/move.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/move.c')
-rw-r--r--src/move.c144
1 files changed, 96 insertions, 48 deletions
diff --git a/src/move.c b/src/move.c
index 0b6f578..98cdfee 100644
--- a/src/move.c
+++ b/src/move.c
@@ -97,18 +97,29 @@ Return value:
// king this is and still needs the ordinary incremental update.
// Single branch, predicts near-perfectly (king moves are a small
// minority of all SlidePiece calls).
- if (IS_KING(p))
- {
- ULONG xC = FLIP(c);
- NnueAccumRemoveFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cTo, p);
- }
- else
+ // 2026-10-15: gated on g_fUseNnueEval -- measured ~2x NPS cost
+ // (1.44M -> 673K on `bench`) from running this unconditionally,
+ // even with NNUE eval never actually consulted. Safe to skip
+ // when off: root.c's ReInitializeSearcherContext recomputes
+ // sNnueAccum from scratch at the start of every real "go", so
+ // toggling NNUE on between searches always gets a correct
+ // baseline regardless of whether this ran during the time it
+ // was off.
+ if (TRUE == g_fUseNnueEval)
{
- NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
- NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ if (IS_KING(p))
+ {
+ ULONG xC = FLIP(c);
+ NnueAccumRemoveFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cTo, p);
+ }
+ else
+ {
+ NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
+ NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ }
}
#ifdef DEBUG
VerifyPositionConsistency(pos, FALSE);
@@ -168,10 +179,13 @@ Return value:
#ifdef DEBUG
pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX;
#endif
- NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
- NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
+ NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ }
#ifdef DEBUG
VerifyPositionConsistency(pos, FALSE);
#endif
@@ -239,19 +253,23 @@ Return value:
#ifdef DEBUG
pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX;
#endif
- // See SlidePiece's comment on the same check.
- if (IS_KING(p))
- {
- ULONG xC = FLIP(c);
- NnueAccumRemoveFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cTo, p);
- }
- else
+ // See SlidePiece's comment on the same check, and on why this is
+ // gated on g_fUseNnueEval.
+ if (TRUE == g_fUseNnueEval)
{
- NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
- NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ if (IS_KING(p))
+ {
+ ULONG xC = FLIP(c);
+ NnueAccumRemoveFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[xC], xC, pos->cKingSq[xC], cTo, p);
+ }
+ else
+ {
+ NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
+ NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ }
}
}
@@ -307,10 +325,13 @@ Return value:
#ifdef DEBUG
pos->rgSquare[cFrom].uIndex = INVALID_PIECE_INDEX;
#endif
- NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
- NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
- NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cTo, p);
+ NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cFrom, p);
+ NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cTo, p);
+ }
}
@@ -397,8 +418,11 @@ Return value:
pos->bbPieces[color][u] &= ~COOR_TO_BB(cSquare);
}
- NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pLifted);
- NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pLifted);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pLifted);
+ NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pLifted);
+ }
#ifdef DEBUG
VerifyPositionConsistency(pos, FALSE);
#endif
@@ -485,8 +509,11 @@ Return value:
pos->bbPieces[color][u] &= ~COOR_TO_BB(cSquare);
}
- NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pLifted);
- NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pLifted);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumRemoveFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pLifted);
+ NnueAccumRemoveFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pLifted);
+ }
return(pLifted);
}
@@ -563,8 +590,11 @@ Return value:
//
pos->rgSquare[cSquare].pPiece = pPiece;
- NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pPiece);
- NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pPiece);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pPiece);
+ NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pPiece);
+ }
#ifdef DEBUG
VerifyPositionConsistency(pos, FALSE);
#endif
@@ -643,8 +673,11 @@ Return value:
//
pos->rgSquare[cSquare].pPiece = pPiece;
- NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pPiece);
- NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pPiece);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumAddFeature(&sNnueAccum[WHITE], WHITE, pos->cKingSq[WHITE], cSquare, pPiece);
+ NnueAccumAddFeature(&sNnueAccum[BLACK], BLACK, pos->cKingSq[BLACK], cSquare, pPiece);
+ }
}
@@ -949,11 +982,16 @@ Return value:
// Indexed by ply (not a single shared slot) so nested
// own-king moves at different plies never collide, same
// reasoning as cTrapped/cEnprise's existing per-ply
- // indexing.
- ctx->sPlyInfo[ctx->uPly].sNnueAccumBackup = ctx->sNnueAccum[uColor];
+ // indexing. Gated on g_fUseNnueEval same as the
+ // primitives themselves -- see SlidePiece's comment on
+ // why that's safe.
+ if (TRUE == g_fUseNnueEval)
+ {
+ ctx->sPlyInfo[ctx->uPly].sNnueAccumBackup = ctx->sNnueAccum[uColor];
#ifdef DEBUG
- ctx->sPlyInfo[ctx->uPly].uNnueAccumBackupColor = uColor;
+ ctx->sPlyInfo[ctx->uPly].uNnueAccumBackupColor = uColor;
#endif
+ }
SlidePiece(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum); // king
SlidePiece(pos, cRookFrom, cRookTo, ctx->sNnueAccum); // rook
// Both slides above already skipped uColor's own
@@ -963,7 +1001,10 @@ Return value:
// now-fully-updated board (king AND rook already in
// their final squares) -- the only correct way to update
// it, not just the cheapest.
- NnueAccumRecompute(&ctx->sNnueAccum[uColor], pos, uColor);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumRecompute(&ctx->sNnueAccum[uColor], pos, uColor);
+ }
pos->uFifty = 0;
goto clear_ep;
}
@@ -990,11 +1031,15 @@ Return value:
// backup missing it. Found exactly this way: testnnue.c's
// TestNnueAccumulator caught a king-captures-knight sequence
// where the restored accumulator came back short by one
- // feature row because of this ordering.
- ctx->sPlyInfo[ctx->uPly].sNnueAccumBackup = ctx->sNnueAccum[uColor];
+ // feature row because of this ordering. Gated on
+ // g_fUseNnueEval, same as the primitives themselves.
+ if (TRUE == g_fUseNnueEval)
+ {
+ ctx->sPlyInfo[ctx->uPly].sNnueAccumBackup = ctx->sNnueAccum[uColor];
#ifdef DEBUG
- ctx->sPlyInfo[ctx->uPly].uNnueAccumBackupColor = uColor;
+ ctx->sPlyInfo[ctx->uPly].uNnueAccumBackupColor = uColor;
#endif
+ }
}
if (pCaptured)
{
@@ -1012,7 +1057,10 @@ Return value:
else if (IS_KING(pPiece))
{
SlidePiece(pos, mv.cFrom, mv.cTo, ctx->sNnueAccum);
- NnueAccumRecompute(&ctx->sNnueAccum[uColor], pos, uColor);
+ if (TRUE == g_fUseNnueEval)
+ {
+ NnueAccumRecompute(&ctx->sNnueAccum[uColor], pos, uColor);
+ }
}
else
{
@@ -1399,7 +1447,7 @@ Return value:
// MakeMove saved at this exact ply before making the move, the
// same way pos->u64NonPawnSig etc. are restored from pi just
// below -- not via any incremental undo, there isn't one.
- if ((mv.uMove != 0) && IS_KING(mv.pMoved))
+ if ((TRUE == g_fUseNnueEval) && (mv.uMove != 0) && IS_KING(mv.pMoved))
{
ULONG uMoverColor = GET_COLOR(mv.pMoved);
#ifdef DEBUG