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/move.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/move.c')
| -rw-r--r-- | src/move.c | 144 |
1 files changed, 96 insertions, 48 deletions
@@ -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 |
