From fda269a2997033a01ed49d83337a2e0405cec805 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Wed, 9 Apr 2025 16:09:47 -0700 Subject: [PATCH] Introduce double incremental accumulator updates when we need to update an accumulator by two moves and the second move captures the piece moved in the first move, we can skip computing the middle accumulator and cancel a feature add with a feature remove to save work. Passed STC https://tests.stockfishchess.org/tests/view/67f70b1c31d7cf8afdc45f51 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 72800 W: 18878 L: 18529 D: 35393 Ptnml(0-2): 160, 7711, 20374, 7930, 225 closes https://github.com/official-stockfish/Stockfish/pull/5988 No functional change --- src/nnue/nnue_accumulator.cpp | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 2bf76f530..5c1128539 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -46,6 +46,13 @@ namespace Stockfish::Eval::NNUE { namespace { +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed); + template void update_accumulator_incremental( const FeatureTransformer& featureTransformer, @@ -142,8 +149,27 @@ void AccumulatorStack::forward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = begin + 1; next < size; next++) + { + if (next + 1 < size) + { + auto& dp1 = accumulators[next].dirtyPiece; + auto& dp2 = accumulators[next + 1].dirtyPiece; + + if (dp2.dirty_num >= 2 && dp1.piece[0] == dp2.piece[1] && dp1.to[0] == dp2.from[1]) + { + const Square captureSq = dp1.to[0]; + dp1.to[0] = dp2.from[1] = SQ_NONE; + double_inc_update(featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); + dp1.to[0] = dp2.from[1] = captureSq; + + next++; + continue; + } + } update_accumulator_incremental( featureTransformer, ksq, accumulators[next], accumulators[next - 1]); + } assert((latest().acc()).computed[Perspective]); } @@ -240,6 +266,49 @@ auto make_accumulator_update_context(const FeatureTransformer& featu accumulatorTo}; } +template +void double_inc_update(const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& middle_state, + AccumulatorState& target_state, + const AccumulatorState& computed) { + + assert(computed.acc().computed[Perspective]); + assert(!middle_state.acc().computed[Perspective]); + assert(!target_state.acc().computed[Perspective]); + + FeatureSet::IndexList removed, added; + FeatureSet::append_changed_indices(ksq, middle_state.dirtyPiece, removed, added); + // you can't capture a piece that was just involved in castling since the rook ends up + // in a square that the king passed + assert(added.size() < 2); + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, added); + + assert(added.size() == 1); + assert(removed.size() == 2 || removed.size() == 3); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1); + sf_assume(removed.size() == 2 || removed.size() == 3); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if (removed.size() == 2) + { + updateContext.template apply(added[0], removed[0], removed[1]); + } + else + { + updateContext.template apply(added[0], removed[0], removed[1], + removed[2]); + } + + target_state.acc().computed[Perspective] = true; +} + template void update_accumulator_incremental( const FeatureTransformer& featureTransformer,