From 7ac8e6221979f14395bb63566e6ca43d803e2acb Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Mon, 3 Nov 2025 21:59:50 -0500 Subject: [PATCH 01/36] Refine constant in correction history update Passed STC LLR: 3.04 (-2.94,2.94) <0.00,2.00> Total: 250112 W: 65277 L: 64635 D: 120200 Ptnml(0-2): 841, 29326, 64134, 29860, 895 https://tests.stockfishchess.org/tests/view/69096c46ea4b268f1fac2a39 Passed LTC LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 142908 W: 37141 L: 36604 D: 69163 Ptnml(0-2): 100, 15478, 39742, 16053, 81 https://tests.stockfishchess.org/tests/view/690e7dfbec1d00d2c195c351 closes https://github.com/official-stockfish/Stockfish/pull/6405 bench 2613869 --- src/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index ce7d41008..afd862273 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1452,9 +1452,9 @@ moves_loop: // When in check, search starts here if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) && (bestValue < ss->staticEval) == !bestMove) { - auto bonus = - std::clamp(int(bestValue - ss->staticEval) * depth / (8 + (bestValue > ss->staticEval)), - -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth + / (8 + 2 * (bestValue > ss->staticEval)), + -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } From 3ae76847145b31553a958ff87c52c280e4f784be Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Wed, 12 Nov 2025 13:08:23 +0000 Subject: [PATCH 02/36] Improve Threats Speed Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 23168 W: 6132 L: 5845 D: 11191 Ptnml(0-2): 77, 2405, 6325, 2708, 69 https://tests.stockfishchess.org/tests/view/69148c3c7ca8781852331831 ``` Result of 50 runs ================== base (./stockfish.master ) = 985641 +/- 4249 test (./stockfish.patch ) = 1038567 +/- 5679 diff = +52926 +/- 4473 speedup = +0.0537 P(speedup > 0) = 1.0000 CPU: 16 x AMD Ryzen 9 3950X 16-Core Processor Hyperthreading: on ``` closes https://github.com/official-stockfish/Stockfish/pull/6415 No functional change --- src/nnue/features/full_threats.cpp | 81 +++++++++++++++++------------- src/nnue/nnue_accumulator.cpp | 18 ++++++- src/position.cpp | 71 ++++++++++++++++---------- 3 files changed, 107 insertions(+), 63 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 6fc6b00ec..994d2160c 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -58,6 +58,35 @@ constexpr std::array AllPieces = { PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] +namespace { + +template +IndexType make_index_with_orientation( + Piece attacker, Square from, Square to, Piece attacked, int orientation) { + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); + + if constexpr (Perspective == BLACK) + { + attacker = ~attacker; + attacked = ~attacked; + } + + const auto piecePairData = index_lut1[attacker][attacked]; + + const bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return FullThreats::Dimensions; + + const IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; + + sf_assume(index != FullThreats::Dimensions); + return index; +} + +} // namespace + static void init_index_luts() { for (Piece attacker : AllPieces) { @@ -129,32 +158,8 @@ void init_threat_offsets() { template IndexType FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - from = (Square) (int(from) ^ OrientTBL[Perspective][ksq]); - to = (Square) (int(to) ^ OrientTBL[Perspective][ksq]); - - if (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } - - auto piecePairData = index_lut1[attacker][attacked]; - - // Some threats imply the existence of the corresponding ones in the opposite - // direction. We filter them here to ensure only one such threat is active. - - // In the below addition, the 2nd lsb gets set iff either the pair is always excluded, - // or the pair is semi-excluded and from < to. By using an unsigned compare, the following - // sequence can use an add-with-carry instruction. - bool less_than = static_cast(from) < static_cast(to); - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return Dimensions; - - IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != Dimensions); - return index; + return make_index_with_orientation(attacker, from, to, attacked, + OrientTBL[Perspective][ksq]); } // Get a list of indices for active features in ascending order @@ -162,8 +167,9 @@ template void FullThreats::append_active_indices(const Position& pos, IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); - Bitboard occupied = pos.pieces(); + Square ksq = pos.square(Perspective); + const int orientation = OrientTBL[Perspective][ksq]; + Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { @@ -187,7 +193,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -198,7 +205,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -215,8 +223,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = - make_index(attacker, from, to, attacked, ksq); + IndexType index = make_index_with_orientation( + attacker, from, to, attacked, orientation); if (index < Dimensions) active.push_back(index); @@ -243,7 +251,9 @@ void FullThreats::append_changed_indices(Square ksq, IndexList& added, FusedUpdateData* fusedData, bool first) { - for (const auto dirty : diff.list) + const int orientation = OrientTBL[Perspective][ksq]; + + for (const auto& dirty : diff.list) { auto attacker = dirty.pc(); auto attacked = dirty.threatened_pc(); @@ -282,9 +292,10 @@ void FullThreats::append_changed_indices(Square ksq, } } - IndexType index = make_index(attacker, from, to, attacked, ksq); + const IndexType index = + make_index_with_orientation(attacker, from, to, attacked, orientation); - if (index != Dimensions) + if (index < Dimensions) (add ? added : removed).push_back(index); } } diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0444b6e40..7f723c61f 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -336,7 +336,8 @@ struct AccumulatorUpdateContext { to_psqt_weight_vector(indices)...); } - void apply(typename FeatureSet::IndexList added, typename FeatureSet::IndexList removed) { + void apply(const typename FeatureSet::IndexList& added, + const typename FeatureSet::IndexList& removed) { const auto fromAcc = from.template acc().accumulation[Perspective]; const auto toAcc = to.template acc().accumulation[Perspective]; @@ -573,6 +574,21 @@ void update_accumulator_incremental( FeatureSet::template append_changed_indices(ksq, computed.diff, added, removed); + if (!added.size() && !removed.size()) + { + auto& targetAcc = target_state.template acc(); + const auto& sourceAcc = computed.template acc(); + + std::memcpy(targetAcc.accumulation[Perspective], sourceAcc.accumulation[Perspective], + sizeof(targetAcc.accumulation[Perspective])); + std::memcpy(targetAcc.psqtAccumulation[Perspective], + sourceAcc.psqtAccumulation[Perspective], + sizeof(targetAcc.psqtAccumulation[Perspective])); + + targetAcc.computed[Perspective] = true; + return; + } + auto updateContext = make_accumulator_update_context(featureTransformer, computed, target_state); diff --git a/src/position.cpp b/src/position.cpp index a515876b6..58edbcc33 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1051,12 +1051,22 @@ inline void add_dirty_threat( template void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { - // Add newly threatened pieces - Bitboard occupied = pieces(); + const Bitboard occupied = pieces(); + const Bitboard rookQueens = pieces(ROOK, QUEEN); + const Bitboard bishopQueens = pieces(BISHOP, QUEEN); + const Bitboard knights = pieces(KNIGHT); + const Bitboard kings = pieces(KING); + const Bitboard whitePawns = pieces(WHITE, PAWN); + const Bitboard blackPawns = pieces(BLACK, PAWN); - Bitboard rAttacks = attacks_bb(s, occupied); - Bitboard bAttacks = attacks_bb(s, occupied); - Bitboard qAttacks = rAttacks | bAttacks; + const Bitboard rAttacks = attacks_bb(s, occupied); + const Bitboard bAttacks = attacks_bb(s, occupied); + + Bitboard qAttacks = Bitboard(0); + if constexpr (ComputeRay) + qAttacks = rAttacks | bAttacks; + else if (type_of(pc) == QUEEN) + qAttacks = rAttacks | bAttacks; Bitboard threatened; @@ -1092,35 +1102,42 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); } - Bitboard sliders = (pieces(ROOK, QUEEN) & rAttacks) | (pieces(BISHOP, QUEEN) & bAttacks); + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); - Bitboard incoming_threats = (attacks_bb(s, occupied) & pieces(KNIGHT)) - | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) - | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) - | (attacks_bb(s, occupied) & pieces(KING)); - - while (sliders) + if constexpr (ComputeRay) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); - - Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; - threatened = ray & qAttacks & occupied; - - assert(!more_than_one(threatened)); - if (ComputeRay && threatened) + while (sliders) { - Square threatened_sq = lsb(threatened); + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); - Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + const Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + const Bitboard discovered = ray & qAttacks & occupied; + + assert(!more_than_one(discovered)); + if (discovered) + { + const Square threatened_sq = lsb(discovered); + const Piece threatened_pc = piece_on(threatened_sq); + add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + } + + add_dirty_threat(dts, slider, pc, slider_sq, s); + } + } + else + { + while (sliders) + { + Square slider_sq = pop_lsb(sliders); + Piece slider = piece_on(slider_sq); + add_dirty_threat(dts, slider, pc, slider_sq, s); } - - add_dirty_threat(dts, slider, pc, slider_sq, s); } - // Add threats of sliders that were already threatening s, - // sliders are already handled in the loop above + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); while (incoming_threats) { From fa4f05d3ef2dc393da73b0709e6c2c59a09652c5 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 9 Nov 2025 03:50:15 -0800 Subject: [PATCH 03/36] Don't copy around DirtyThreats The contents of DirtyThreats gets memcpyed twice for each call to do_move. (Return value optimization doesn't apply to the do_move function itself because it constructs a std::pair, so it gets copied; and the calls to reset also require a copy.) This patch inserts the dirty info in place. Sometimes the caller of do_move ignores the DirtyThreats info, so we pass in scratch objects. I found that stack-allocating these scratch objects was bad on Fishtest, so I begrudgingly put them in the Position struct. Both templating the do_move function on whether dirty threats are needed and putting a null-check branch for each use of dirty threats were slowdowns locally. Of course, nothing prevents a future attempt at cleaning this up. passed STC LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 68448 W: 17770 L: 17418 D: 33260 Ptnml(0-2): 198, 7425, 18630, 7769, 202 https://tests.stockfishchess.org/tests/view/6914c01a7ca87818523318ba closes https://github.com/official-stockfish/Stockfish/pull/6414 No functional change --- src/nnue/nnue_accumulator.cpp | 9 ++++++--- src/nnue/nnue_accumulator.h | 14 ++++++++++--- src/position.cpp | 24 +++++++++++------------ src/position.h | 37 ++++++++++++++++++++++------------- src/search.cpp | 8 ++++---- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 7f723c61f..47f09afce 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "../bitboard.h" @@ -122,11 +123,13 @@ void AccumulatorStack::reset() noexcept { size = 1; } -void AccumulatorStack::push(const DirtyBoardData& dirtyBoardData) noexcept { +std::pair AccumulatorStack::push() noexcept { assert(size < MaxSize); - psq_accumulators[size].reset(dirtyBoardData.dp); - threat_accumulators[size].reset(dirtyBoardData.dts); + auto& dp = psq_accumulators[size].reset(); + auto& dts = threat_accumulators[size].reset(); + new (&dts) DirtyThreats; size++; + return {dp, dts}; } void AccumulatorStack::pop() noexcept { diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 4c907c7e3..341960b30 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "../types.h" #include "nnue_architecture.h" @@ -141,6 +142,12 @@ struct AccumulatorState { accumulatorBig.computed.fill(false); accumulatorSmall.computed.fill(false); } + + typename FeatureSet::DiffType& reset() noexcept { + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); + return diff; + } }; class AccumulatorStack { @@ -150,9 +157,10 @@ class AccumulatorStack { template [[nodiscard]] const AccumulatorState& latest() const noexcept; - void reset() noexcept; - void push(const DirtyBoardData& dirtyBoardData) noexcept; - void pop() noexcept; + void reset() noexcept; + void push(const DirtyBoardData& dirtyBoardData) noexcept; + std::pair push() noexcept; + void pop() noexcept; template void evaluate(const Position& pos, diff --git a/src/position.cpp b/src/position.cpp index 58edbcc33..347bbcfc8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -201,7 +201,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { Square sq = SQ_A8; std::istringstream ss(fenStr); - std::memset(this, 0, sizeof(Position)); + std::memset(reinterpret_cast(this), 0, sizeof(Position)); std::memset(si, 0, sizeof(StateInfo)); st = si; @@ -688,10 +688,12 @@ bool Position::gives_check(Move m) const { // moves should be filtered out before this function is called. // If a pointer to the TT table is passed, the entry for the new position // will be prefetched -DirtyBoardData Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +void Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -720,12 +722,10 @@ DirtyBoardData Position::do_move(Move m, bool checkEP = false; - DirtyPiece dp; - dp.pc = pc; - dp.from = from; - dp.to = to; - dp.add_sq = SQ_NONE; - DirtyThreats dts; + dp.pc = pc; + dp.from = from; + dp.to = to; + dp.add_sq = SQ_NONE; dts.us = us; dts.prevKsq = square(us); dts.threatenedSqs = dts.threateningSqs = 0; @@ -970,8 +970,6 @@ DirtyBoardData Position::do_move(Move m, assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE)); assert(dp.from != SQ_NONE); assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING)); - - return {dp, dts}; } diff --git a/src/position.h b/src/position.h index 9afdb17f9..c95697d19 100644 --- a/src/position.h +++ b/src/position.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "bitboard.h" @@ -133,11 +134,16 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - DirtyBoardData do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt); - void undo_move(Move m); - void do_null_move(StateInfo& newSt, const TranspositionTable& tt); - void undo_null_move(); + void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); + void do_move(Move m, + StateInfo& newSt, + bool givesCheck, + DirtyPiece& dp, + DirtyThreats& dts, + const TranspositionTable* tt); + void undo_move(Move m); + void do_null_move(StateInfo& newSt, const TranspositionTable& tt); + void undo_null_move(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -196,14 +202,16 @@ class Position { std::array byTypeBB; std::array byColorBB; - int pieceCount[PIECE_NB]; - int castlingRightsMask[SQUARE_NB]; - Square castlingRookSquare[CASTLING_RIGHT_NB]; - Bitboard castlingPath[CASTLING_RIGHT_NB]; - StateInfo* st; - int gamePly; - Color sideToMove; - bool chess960; + int pieceCount[PIECE_NB]; + int castlingRightsMask[SQUARE_NB]; + Square castlingRookSquare[CASTLING_RIGHT_NB]; + Bitboard castlingPath[CASTLING_RIGHT_NB]; + StateInfo* st; + int gamePly; + Color sideToMove; + bool chess960; + DirtyPiece scratch_dp; + DirtyThreats scratch_dts; }; std::ostream& operator<<(std::ostream& os, const Position& pos); @@ -389,7 +397,8 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { } inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) { - do_move(m, newSt, gives_check(m), tt); + new (&scratch_dts) DirtyThreats; + do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt); } inline StateInfo* Position::state() const { return st; } diff --git a/src/search.cpp b/src/search.cpp index afd862273..43d96bf24 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -531,16 +531,16 @@ void Search::Worker::do_move( bool capture = pos.capture_stage(move); nodes.fetch_add(1, std::memory_order_relaxed); - DirtyBoardData dirtyBoardData = pos.do_move(move, st, givesCheck, &tt); - accumulatorStack.push(dirtyBoardData); + auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); + pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); if (ss != nullptr) { ss->currentMove = move; ss->continuationHistory = - &continuationHistory[ss->inCheck][capture][dirtyBoardData.dp.pc][move.to_sq()]; + &continuationHistory[ss->inCheck][capture][dirtyPiece.pc][move.to_sq()]; ss->continuationCorrectionHistory = - &continuationCorrectionHistory[dirtyBoardData.dp.pc][move.to_sq()]; + &continuationCorrectionHistory[dirtyPiece.pc][move.to_sq()]; } } From 84148586e53f4bd4f3177a98c7f201f63245fd99 Mon Sep 17 00:00:00 2001 From: mstembera <5421953+mstembera@users.noreply.github.com> Date: Tue, 4 Nov 2025 21:27:33 -0800 Subject: [PATCH 04/36] Fix MSVC compile broken after Shared Memory patch. closes https://github.com/official-stockfish/Stockfish/pull/6397 No functional change --- src/memory.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/memory.h b/src/memory.h index b9be6f170..dad07df1d 100644 --- a/src/memory.h +++ b/src/memory.h @@ -41,6 +41,13 @@ #endif #include + // Some Windows headers (RPC/old headers) define short macros such + // as 'small' expanding to 'char', which breaks identifiers in the code. + // Undefine those macros immediately after including . + #ifdef small + #undef small + #endif + #include extern "C" { From 4784ff2b3be97a0364b77a677e21a644403f7b3a Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Mon, 3 Nov 2025 08:55:26 +0100 Subject: [PATCH 05/36] Unify do_move & do_null_move workload While being there also remove and unused assignment. Tested for non-regression at STC: https://tests.stockfishchess.org/tests/view/69060d81ea4b268f1fac1f36 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 94496 W: 24570 L: 24421 D: 45505 Ptnml(0-2): 264, 10145, 26275, 10306, 258 closes https://github.com/official-stockfish/Stockfish/pull/6383 no functional change --- src/search.cpp | 17 ++++++++--------- src/search.h | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 43d96bf24..d28ac5f27 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -544,7 +544,12 @@ void Search::Worker::do_move( } } -void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } +void Search::Worker::do_null_move(Position& pos, StateInfo& st, Stack* const ss) { + pos.do_null_move(st, tt); + ss->currentMove = Move::null(); + ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; + ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; +} void Search::Worker::undo_move(Position& pos, const Move move) { pos.undo_move(move); @@ -868,12 +873,7 @@ Value Search::Worker::search( // Null move dynamic reduction based on depth Depth R = 6 + depth / 3 + improving; - - ss->currentMove = Move::null(); - ss->continuationHistory = &continuationHistory[0][0][NO_PIECE][0]; - ss->continuationCorrectionHistory = &continuationCorrectionHistory[NO_PIECE][0]; - - do_null_move(pos, st); + do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); @@ -1580,8 +1580,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) futilityBase = ss->staticEval + 352; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, - (ss - 2)->continuationHistory}; + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; diff --git a/src/search.h b/src/search.h index 4c4357d3e..f3d99d415 100644 --- a/src/search.h +++ b/src/search.h @@ -299,7 +299,7 @@ class Worker { void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss); void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss); - void do_null_move(Position& pos, StateInfo& st); + void do_null_move(Position& pos, StateInfo& st, Stack* const ss); void undo_move(Position& pos, const Move move); void undo_null_move(Position& pos); From 8551f86efce1d55c3cf5bb639247212a3c290bdf Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 25 Oct 2025 03:23:06 -0400 Subject: [PATCH 06/36] Remove check term in capture movepick Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 248448 W: 64697 L: 64708 D: 119043 Ptnml(0-2): 784, 29393, 63971, 29202, 874 https://tests.stockfishchess.org/tests/view/68fc7afb637acd2a11e72d86 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 193626 W: 49808 L: 49764 D: 94054 Ptnml(0-2): 162, 21415, 53621, 21447, 168 https://tests.stockfishchess.org/tests/view/6901ad09637acd2a11e73828 closes https://github.com/official-stockfish/Stockfish/pull/6395 bench 2920273 --- src/movepick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index 2eec3556b..b5b02609e 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -153,7 +153,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if constexpr (Type == CAPTURES) m.value = (*captureHistory)[pc][to][type_of(capturedPiece)] - + 7 * int(PieceValue[capturedPiece]) + 1024 * bool(pos.check_squares(pt) & to); + + 7 * int(PieceValue[capturedPiece]); else if constexpr (Type == QUIETS) { From 55643baa3f6be3265598fba7c7e8b308fe6ccbcf Mon Sep 17 00:00:00 2001 From: Didier Durand Date: Fri, 7 Nov 2025 17:23:26 +0100 Subject: [PATCH 07/36] Fix some doc typos closes https://github.com/official-stockfish/Stockfish/pull/6400 No functional change --- src/history.h | 2 +- src/numa.h | 2 +- tests/instrumented.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/history.h b/src/history.h index 940e98991..445d54181 100644 --- a/src/history.h +++ b/src/history.h @@ -104,7 +104,7 @@ using Stats = MultiArray, Sizes...>; // see https://www.chessprogramming.org/Butterfly_Boards using ButterflyHistory = Stats; -// LowPlyHistory is adressed by play and move's from and to squares, used +// LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root using LowPlyHistory = Stats; diff --git a/src/numa.h b/src/numa.h index 261b6005d..76d265af2 100644 --- a/src/numa.h +++ b/src/numa.h @@ -1346,7 +1346,7 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase { std::size_t get_discriminator(NumaIndex idx) const { const NumaConfig& cfg = get_numa_config(); const NumaConfig& cfg_sys = NumaConfig::from_system(false); - // as a descriminator, locate the hardware/system numadomain this cpuindex belongs to + // as a discriminator, locate the hardware/system numadomain this cpuindex belongs to CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0; std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx); diff --git a/tests/instrumented.py b/tests/instrumented.py index db5ec8e08..23de446ef 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -508,7 +508,7 @@ if __name__ == "__main__": framework = MiniTestFramework() - # Each test suite will be ran inside a temporary directory + # Each test suite will be run inside a temporary directory framework.run([TestCLI, TestInteractive, TestSyzygy]) EPD.delete_bench_epd() From 9e38023a8c2c67204f9a5d03eac5d5f00ea93d3f Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Fri, 31 Oct 2025 02:18:22 -0400 Subject: [PATCH 08/36] Simplify threat term in movepick Passed simplification STC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 71296 W: 18605 L: 18419 D: 34272 Ptnml(0-2): 250, 8374, 18183, 8622, 219 https://tests.stockfishchess.org/tests/view/690454c7ea4b268f1fac1bbe Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 240552 W: 61870 L: 61874 D: 116808 Ptnml(0-2): 113, 26300, 67460, 26284, 119 https://tests.stockfishchess.org/tests/view/69063956ea4b268f1fac1f66 closes https://github.com/official-stockfish/Stockfish/pull/6401 bench 2697501 --- src/movepick.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/movepick.cpp b/src/movepick.cpp index b5b02609e..0b18cf565 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -171,9 +171,8 @@ ExtMove* MovePicker::score(MoveList& ml) { // penalty for moving to a square threatened by a lesser piece // or bonus for escaping an attack by a lesser piece. - static constexpr int bonus[KING + 1] = {0, 0, 144, 144, 256, 517, 10000}; - int v = threatByLesser[pt] & to ? -95 : 100 * bool(threatByLesser[pt] & from); - m.value += bonus[pt] * v; + int v = threatByLesser[pt] & to ? -19 : 20 * bool(threatByLesser[pt] & from); + m.value += PieceValue[pt] * v; if (ply < LOW_PLY_HISTORY_SIZE) From 3f2405bf4e4c87d67e3939f2469419bcccb8a707 Mon Sep 17 00:00:00 2001 From: Torsten Hellwig Date: Sun, 9 Nov 2025 15:54:20 +0100 Subject: [PATCH 09/36] Print NEON before POPCNT in compilation settings Normally, Stockfish outputs the compilation settings from advanced to less advanced, e.g.: `AVX512ICL VNNI AVX512 BMI2 AVX2 SSE41 SSSE3 SSE2 POPCNT` With NEON, however, POPCNT is printed first, followed by the more advanced NEON options: `POPCNT NEON_DOTPROD` This PR places POPCNT behind the NEON options as well. closes https://github.com/official-stockfish/Stockfish/pull/6402 no functional change --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 3bdde000f..d21497280 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -259,12 +259,12 @@ std::string compiler_info() { #if defined(USE_SSE2) compiler += " SSE2"; #endif - compiler += (HasPopCnt ? " POPCNT" : ""); #if defined(USE_NEON_DOTPROD) compiler += " NEON_DOTPROD"; #elif defined(USE_NEON) compiler += " NEON"; #endif + compiler += (HasPopCnt ? " POPCNT" : ""); #if !defined(NDEBUG) compiler += " DEBUG"; From 229bd1e2e350b4b67ed5a962ae99d939c0a27d29 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Tue, 11 Nov 2025 06:09:26 -0800 Subject: [PATCH 10/36] check for material key validity in tbprobe During recent work on threat inputs, there was a hard-to-debug crash in tbprobe caused by incorrectly computing st->materialKey. This PR adds correctness checking to pos_is_ok and a faster check in tbprobe that will actually run in CI (because pos_is_ok checking is disabled even in debug mode, for performance purposes). closes https://github.com/official-stockfish/Stockfish/pull/6410 No functional change --- src/position.cpp | 16 +++++++++++++--- src/position.h | 2 ++ src/syzygy/tbprobe.cpp | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 347bbcfc8..c34ceb400 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -336,8 +336,8 @@ void Position::set_check_info() const { // The function is only used when a new position is set up void Position::set_state() const { - st->key = st->materialKey = 0; - st->minorPieceKey = 0; + st->key = 0; + st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -375,10 +375,15 @@ void Position::set_state() const { st->key ^= Zobrist::side; st->key ^= Zobrist::castling[st->castlingRights]; + st->materialKey = compute_material_key(); +} +Key Position::compute_material_key() const { + Key k = 0; for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][8 + cnt]; + k ^= Zobrist::psq[pc][8 + cnt]; + return k; } @@ -1447,6 +1452,9 @@ void Position::flip() { } +bool Position::material_key_is_ok() const { return compute_material_key() == st->materialKey; } + + // Performs some consistency checks for the position object // and raise an assert if something wrong is detected. // This is meant to be helpful when debugging. @@ -1496,6 +1504,8 @@ bool Position::pos_is_ok() const { assert(0 && "pos_is_ok: Castling"); } + assert(material_key_is_ok() && "pos_is_ok: materialKey"); + return true; } diff --git a/src/position.h b/src/position.h index c95697d19..711c4e444 100644 --- a/src/position.h +++ b/src/position.h @@ -169,6 +169,7 @@ class Position { // Position consistency check, for debugging bool pos_is_ok() const; + bool material_key_is_ok() const; void flip(); StateInfo* state() const; @@ -180,6 +181,7 @@ class Position { private: // Initialization helpers (used while setting up a position) void set_castling_right(Color c, Square rfrom); + Key compute_material_key() const; void set_state() const; void set_check_info() const; diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index cbf8dce5e..657866ba4 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1214,6 +1214,8 @@ template void* mapped(TBTable& e, const Position& pos) { static std::mutex mutex; + // Because TB is the only usage of materialKey, check it here in debug mode + assert(pos.material_key_is_ok()); // Use 'acquire' to avoid a thread reading 'ready' == true while // another is still working. (compiler reordering may cause this). From df88db16c611516c494facadae2ec9ff0d0b6c06 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 14 Nov 2025 01:02:06 +0300 Subject: [PATCH 11/36] Simplify NMP reduction formula Passed STC: LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 178912 W: 46625 L: 46559 D: 85728 Ptnml(0-2): 540, 21167, 45975, 21235, 539 https://tests.stockfishchess.org/tests/view/69060677ea4b268f1fac1f25 Passed LTC: LLR: 2.99 (-2.94,2.94) <-1.75,0.25> Total: 140988 W: 36278 L: 36176 D: 68534 Ptnml(0-2): 82, 15520, 39215, 15568, 109 https://tests.stockfishchess.org/tests/view/6908bc32ea4b268f1fac288f closes https://github.com/official-stockfish/Stockfish/pull/6416 bench: 2959834 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d28ac5f27..b88c428e2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -872,7 +872,7 @@ Value Search::Worker::search( assert((ss - 1)->currentMove != Move::null()); // Null move dynamic reduction based on depth - Depth R = 6 + depth / 3 + improving; + Depth R = 7 + depth / 3; do_null_move(pos, st, ss); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); From 7b7a9485d6d36b246b8eb3f278570e52d929aa51 Mon Sep 17 00:00:00 2001 From: AliceRoselia <63040919+AliceRoselia@users.noreply.github.com> Date: Fri, 14 Nov 2025 17:18:29 +0100 Subject: [PATCH 12/36] Simplify indexing Removes 1 function (from_to) from the Move type, change them to simpler raw. Remove the "and" use in the correction history structure calculation. Adjust the indexing. Passed simplification STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42880 W: 11327 L: 11113 D: 20440 Ptnml(0-2): 161, 4852, 11212, 5042, 173 https://tests.stockfishchess.org/tests/view/690593b8ea4b268f1fac1e8a Passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 43560 W: 11276 L: 11079 D: 21205 Ptnml(0-2): 17, 4679, 12197, 4864, 23 https://tests.stockfishchess.org/tests/view/6906f819ea4b268f1fac216b closes https://github.com/official-stockfish/Stockfish/pull/6391 Bench: 2523092 --- src/history.h | 27 +++++++++++---------------- src/movepick.cpp | 8 ++++---- src/search.cpp | 12 ++++++------ src/types.h | 2 -- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/history.h b/src/history.h index 445d54181..a605ae417 100644 --- a/src/history.h +++ b/src/history.h @@ -33,32 +33,28 @@ namespace Stockfish { -constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 -constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2 +constexpr int PAWN_HISTORY_SIZE = 8192; // has to be a power of 2 +constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits::max() + 1; constexpr int CORRECTION_HISTORY_LIMIT = 1024; constexpr int LOW_PLY_HISTORY_SIZE = 5; static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0, "PAWN_HISTORY_SIZE has to be a power of 2"); -static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0, +static_assert((UINT_16_HISTORY_SIZE & (UINT_16_HISTORY_SIZE - 1)) == 0, "CORRECTION_HISTORY_SIZE has to be a power of 2"); inline int pawn_history_index(const Position& pos) { return pos.pawn_key() & (PAWN_HISTORY_SIZE - 1); } -inline int pawn_correction_history_index(const Position& pos) { - return pos.pawn_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t pawn_correction_history_index(const Position& pos) { return pos.pawn_key(); } -inline int minor_piece_index(const Position& pos) { - return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} +inline uint16_t minor_piece_index(const Position& pos) { return pos.minor_piece_key(); } template -inline int non_pawn_index(const Position& pos) { - return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1); +inline uint16_t non_pawn_index(const Position& pos) { + return pos.non_pawn_key(c); } // StatsEntry is the container of various numerical statistics. We use a class @@ -102,12 +98,11 @@ using Stats = MultiArray, Sizes...>; // during the current search, and is used for reduction and move ordering decisions. // It uses 2 tables (one for each color) indexed by the move's from and to squares, // see https://www.chessprogramming.org/Butterfly_Boards -using ButterflyHistory = Stats; +using ButterflyHistory = Stats; // LowPlyHistory is addressed by play and move's from and to squares, used // to improve move ordering near the root -using LowPlyHistory = - Stats; +using LowPlyHistory = Stats; // CapturePieceToHistory is addressed by a move's [piece][to][captured piece type] using CapturePieceToHistory = Stats; @@ -139,7 +134,7 @@ namespace Detail { template struct CorrHistTypedef { - using type = Stats; + using type = Stats; }; template<> @@ -155,7 +150,7 @@ struct CorrHistTypedef { template<> struct CorrHistTypedef { using type = - Stats; + Stats; }; } diff --git a/src/movepick.cpp b/src/movepick.cpp index 0b18cf565..7de11fa1f 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -158,7 +158,7 @@ ExtMove* MovePicker::score(MoveList& ml) { else if constexpr (Type == QUIETS) { // histories - m.value = 2 * (*mainHistory)[us][m.from_to()]; + m.value = 2 * (*mainHistory)[us][m.raw()]; m.value += 2 * (*pawnHistory)[pawn_history_index(pos)][pc][to]; m.value += (*continuationHistory[0])[pc][to]; m.value += (*continuationHistory[1])[pc][to]; @@ -176,7 +176,7 @@ ExtMove* MovePicker::score(MoveList& ml) { if (ply < LOW_PLY_HISTORY_SIZE) - m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + ply); + m.value += 8 * (*lowPlyHistory)[ply][m.raw()] / (1 + ply); } else // Type == EVASIONS @@ -185,9 +185,9 @@ ExtMove* MovePicker::score(MoveList& ml) { m.value = PieceValue[capturedPiece] + (1 << 28); else { - m.value = (*mainHistory)[us][m.from_to()] + (*continuationHistory[0])[pc][to]; + m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to]; if (ply < LOW_PLY_HISTORY_SIZE) - m.value += (*lowPlyHistory)[ply][m.from_to()]; + m.value += (*lowPlyHistory)[ply][m.raw()]; } } } diff --git a/src/search.cpp b/src/search.cpp index b88c428e2..7404b939c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -824,7 +824,7 @@ Value Search::Worker::search( if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; - mainHistory[~us][((ss - 1)->currentMove).from_to()] << evalDiff * 9; + mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; @@ -1066,7 +1066,7 @@ moves_loop: // When in check, search starts here if (history < -4312 * depth) continue; - history += 76 * mainHistory[us][move.from_to()] / 32; + history += 76 * mainHistory[us][move.raw()] / 32; // (*Scaler): Generally, a lower divisor scales well lmrDepth += history / 3220; @@ -1196,7 +1196,7 @@ moves_loop: // When in check, search starts here ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else - ss->statScore = 2 * mainHistory[us][move.from_to()] + ss->statScore = 2 * mainHistory[us][move.raw()] + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()]; @@ -1414,7 +1414,7 @@ moves_loop: // When in check, search starts here update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, scaledBonus * 400 / 32768); - mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 220 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] @@ -1862,10 +1862,10 @@ void update_quiet_histories( const Position& pos, Stack* ss, Search::Worker& workerThread, Move move, int bonus) { Color us = pos.side_to_move(); - workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort + workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 761 / 1024; update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); diff --git a/src/types.h b/src/types.h index 46aa16a03..a2171b638 100644 --- a/src/types.h +++ b/src/types.h @@ -448,8 +448,6 @@ class Move { return Square(data & 0x3F); } - constexpr int from_to() const { return data & 0xFFF; } - constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); } constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); } From bd82b9e01f9e5280b5e3771891e4c471d16cfc08 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:19:15 -0800 Subject: [PATCH 13/36] Cleanup, fix style issues use naming convention for variables, functions, constants closes https://github.com/official-stockfish/Stockfish/pull/6416 no functional change --- src/nnue/nnue_accumulator.cpp | 37 ++++++++++++++++---------------- src/position.cpp | 40 +++++++++++++++++------------------ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 47f09afce..fa059463e 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -636,25 +636,26 @@ void update_accumulator_incremental( (target_state.template acc()).computed[Perspective] = true; } -Bitboard get_changed_pieces(const Piece old[SQUARE_NB], const Piece new_[SQUARE_NB]) { +Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); - Bitboard same_bb = 0; + Bitboard sameBB = 0; + for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(old + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(new_ + i)); - const __m256i cmp_equal = _mm256_cmpeq_epi8(old_v, new_v); - const std::uint32_t equal_mask = _mm256_movemask_epi8(cmp_equal); - same_bb |= static_cast(equal_mask) << i; + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(oldPieces + i)); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(newPieces + i)); + const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); + const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); + sameBB |= static_cast(equalMask) << i; } - return ~same_bb; + return ~sameBB; #else Bitboard changed = 0; + for (Square sq = SQUARE_ZERO; sq < SQUARE_NB; ++sq) - { - changed |= static_cast(old[sq] != new_[sq]) << sq; - } + changed |= static_cast(oldPieces[sq] != newPieces[sq]) << sq; + return changed; #endif } @@ -671,18 +672,18 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat auto& entry = cache[ksq][Perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changed_bb = get_changed_pieces(entry.pieces, pos.piece_array().data()); - Bitboard removed_bb = changed_bb & entry.pieceBB; - Bitboard added_bb = changed_bb & pos.pieces(); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); + Bitboard removedBB = changedBB & entry.pieceBB; + Bitboard addedBB = changedBB & pos.pieces(); - while (removed_bb) + while (removedBB) { - Square sq = pop_lsb(removed_bb); + Square sq = pop_lsb(removedBB); removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); } - while (added_bb) + while (addedBB) { - Square sq = pop_lsb(added_bb); + Square sq = pop_lsb(addedBB); added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); } diff --git a/src/position.cpp b/src/position.cpp index c34ceb400..8993c2406 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1096,13 +1096,13 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) while (threatened) { - Square threatened_sq = pop_lsb(threatened); - Piece threatened_pc = piece_on(threatened_sq); + Square threatenedSq = pop_lsb(threatened); + Piece threatenedPc = piece_on(threatenedSq); - assert(threatened_sq != s); - assert(threatened_pc); + assert(threatenedSq != s); + assert(threatenedPc); - add_dirty_threat(dts, pc, threatened_pc, s, threatened_sq); + add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); @@ -1111,30 +1111,30 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { while (sliders) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); - const Bitboard ray = RayPassBB[slider_sq][s] & ~BetweenBB[slider_sq][s]; + const Bitboard ray = RayPassBB[sliderSq][s] & ~BetweenBB[sliderSq][s]; const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); if (discovered) { - const Square threatened_sq = lsb(discovered); - const Piece threatened_pc = piece_on(threatened_sq); - add_dirty_threat(dts, slider, threatened_pc, slider_sq, threatened_sq); + const Square threatenedSq = lsb(discovered); + const Piece threatenedPc = piece_on(threatenedSq); + add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } - add_dirty_threat(dts, slider, pc, slider_sq, s); + add_dirty_threat(dts, slider, pc, sliderSq, s); } } else { while (sliders) { - Square slider_sq = pop_lsb(sliders); - Piece slider = piece_on(slider_sq); - add_dirty_threat(dts, slider, pc, slider_sq, s); + Square sliderSq = pop_lsb(sliders); + Piece slider = piece_on(sliderSq); + add_dirty_threat(dts, slider, pc, sliderSq, s); } } @@ -1144,13 +1144,13 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) while (incoming_threats) { - Square src_sq = pop_lsb(incoming_threats); - Piece src_pc = piece_on(src_sq); + Square srcSq = pop_lsb(incoming_threats); + Piece srcPc = piece_on(srcSq); - assert(src_sq != s); - assert(src_pc != NO_PIECE); + assert(srcSq != s); + assert(srcPc != NO_PIECE); - add_dirty_threat(dts, src_pc, pc, src_sq, s); + add_dirty_threat(dts, srcPc, pc, srcSq, s); } } From a191791f46735817e76d1ba29ec81a5304e16430 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 13 Nov 2025 22:53:48 -0500 Subject: [PATCH 14/36] Clean up code and comments closes https://github.com/official-stockfish/Stockfish/pull/6416 No functional change --- src/search.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7404b939c..e4ef2d42e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -834,9 +834,12 @@ Value Search::Worker::search( // bigger than the previous static evaluation at our turn (if we were in // check at our previous move we go back until we weren't in check) and is // false otherwise. The improving flag is used in various pruning heuristics. + // Similarly, opponentWorsening is true if our static evaluation is better + // for us than at the last ply. improving = ss->staticEval > (ss - 2)->staticEval; opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; + // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) @@ -856,7 +859,7 @@ Value Search::Worker::search( return futilityMult * d // - 2094 * improving * futilityMult / 1024 // - - 1324 * opponentWorsening * futilityMult / 4096 // + - 331 * opponentWorsening * futilityMult / 1024 // + std::abs(correctionValue) / 158105; }; @@ -904,7 +907,7 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // At sufficient depth, reduce depth for PV/Cut nodes without a TTMove. - // (*Scaler) Especially if they make IIR less aggressive. + // (*Scaler) Making IIR more aggressive scales poorly. if (!allNode && depth >= 6 && !ttData.move && priorReduction <= 3) depth--; @@ -1018,12 +1021,11 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); // Increase reduction for ttPv nodes (*Scaler) - // Smaller or even negative value is better for short time controls - // Bigger value is better for long time controls + // Larger values scale well if (ss->ttPv) r += 946; - // Step 14. Pruning at shallow depth. + // Step 14. Pruning at shallow depths. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { @@ -1068,7 +1070,7 @@ moves_loop: // When in check, search starts here history += 76 * mainHistory[us][move.raw()] / 32; - // (*Scaler): Generally, a lower divisor scales well + // (*Scaler): Generally, lower divisors scales well lmrDepth += history / 3220; Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth @@ -1076,7 +1078,7 @@ moves_loop: // When in check, search starts here // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning - // scales well with respect to time and threads + // scales well if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) @@ -1218,8 +1220,7 @@ moves_loop: // When in check, search starts here ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high - // (*Scaler) Usually doing more shallower searches - // doesn't scale well to longer TCs + // (*Scaler) Shallower searches here don't scale well if (value > alpha) { // Adjust full-depth search based on LMR results - if the result was @@ -1347,7 +1348,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - // (*Scaler) Especially if they make cutoffCnt increment more often. + // (*Scaler) Infrequent and small updates scale well ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; @@ -1450,10 +1451,9 @@ moves_loop: // When in check, search starts here // Adjust correction history if the best move is not a capture // and the error direction matches whether we are above/below bounds. if (!ss->inCheck && !(bestMove && pos.capture(bestMove)) - && (bestValue < ss->staticEval) == !bestMove) + && (bestValue > ss->staticEval) == bool(bestMove)) { - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth - / (8 + 2 * (bestValue > ss->staticEval)), + auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / (bestMove ? 10 : 8), -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); update_correction_history(pos, ss, *this, bonus); } From db824e26bebd08c70d1ccfb03f2c4ef06af82fef Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:46:47 -0800 Subject: [PATCH 15/36] Pass accumulator caches by reference closes https://github.com/official-stockfish/Stockfish/pull/6416 No functional change --- src/evaluate.cpp | 8 ++++---- src/nnue/network.cpp | 4 ++-- src/nnue/network.h | 4 ++-- src/nnue/nnue_feature_transformer.h | 4 ++-- src/nnue/nnue_misc.cpp | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 23bc70d0e..4bbbcaac6 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -59,15 +59,15 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small) - : networks.big.evaluate(pos, accumulators, &caches.big); + auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small) + : networks.big.evaluate(pos, accumulators, caches.big); Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent if (smallNet && (std::abs(nnue) < 236)) { - std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } @@ -107,7 +107,7 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15); - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches->big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big); Value v = psqt + positional; v = pos.side_to_move() == WHITE ? v : -v; ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n"; diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b91763f06..a4d464df0 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -172,7 +172,7 @@ template NetworkOutput Network::evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; @@ -234,7 +234,7 @@ template NnueEvalTrace Network::trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const { + AccumulatorCaches::Cache& cache) const { constexpr uint64_t alignment = CacheLineSize; diff --git a/src/nnue/network.h b/src/nnue/network.h index c701ac6f5..ba8f469f7 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -76,13 +76,13 @@ class Network { NetworkOutput evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache) const; + AccumulatorCaches::Cache& cache) const; private: void load_user_net(const std::string&, const std::string&); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 5ad2d3371..1f328fff4 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -264,12 +264,12 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, AccumulatorStack& accumulatorStack, - AccumulatorCaches::Cache* cache, + AccumulatorCaches::Cache& cache, OutputType* output, int bucket) const { using namespace SIMD; - accumulatorStack.evaluate(pos, *this, *cache); + accumulatorStack.evaluate(pos, *this, cache); const auto& accumulatorState = accumulatorStack.latest(); const auto& threatAccumulatorState = accumulatorStack.latest(); diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 957e3453f..220140e5e 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -124,7 +124,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat // We estimate the value of each piece by doing a differential evaluation from // the current base eval, simulating the removal of the piece from its square. - auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -140,7 +140,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat pos.remove_piece(sq); accumulators->reset(); - std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big); Value eval = psqt + positional; eval = pos.side_to_move() == WHITE ? eval : -eval; v = base - eval; @@ -157,7 +157,7 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << '\n'; accumulators->reset(); - auto t = networks.big.trace_evaluate(pos, *accumulators, &caches.big); + auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big); ss << " NNUE network contributions " << (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl From 4b71d8e20293bdc5db5dbb07ae75f3ef65497642 Mon Sep 17 00:00:00 2001 From: Robert Nurnberg Date: Fri, 14 Nov 2025 10:07:14 +0100 Subject: [PATCH 16/36] Allow time checking after each DTZ probe This PR allows for time checking within Tablebases::rank_root_moves(). The principal application for now is syzygy_extend_pv(). In the past, Stockfish suffered some time losses at e.g. TCEC when the HDD with the DTZ tables was too slow for the chosen move overhead setting, see #5894. ``` > ./fastchess -engine name="patch" cmd=./stockfish.patch -engine name="master" cmd=./stockfish.master -each tc=10+0.1 option.SyzygyPath=/disk1/syzygy/3-4-5-6/WDL:/disk2/syzygy/3-4-5-6/DTZ -draw movenumber=34 movecount=8 score=20 -openings file=UHO_Lichess_4852_v1.epd format=epd -rounds 50 -repeat -concurrency 16 ... Results of patch vs master (10+0.1, 1t, 16MB, UHO_Lichess_4852_v1.epd): Elo: 59.64 +/- 35.35, nElo: 117.61 +/- 68.10 LOS: 99.96 %, DrawRatio: 56.00 %, PairsRatio: 4.50 Games: 100, Wins: 34, Losses: 17, Draws: 49, Points: 58.5 (58.50 %) Ptnml(0-2): [0, 4, 28, 15, 3], WL/DD Ratio: 0.87 -------------------------------------------------- Player: master Timeouts: 19 Crashed: 0 Finished match ``` closes https://github.com/official-stockfish/Stockfish/pull/6422 No functional change --- src/search.cpp | 12 +++++++----- src/syzygy/tbprobe.cpp | 27 +++++++++++++++------------ src/syzygy/tbprobe.h | 17 ++++++++++++----- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e4ef2d42e..9fb7b702f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -857,8 +857,8 @@ Value Search::Worker::search( auto futility_margin = [&](Depth d) { Value futilityMult = 91 - 21 * !ss->ttHit; - return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // + return futilityMult * d // + - 2094 * improving * futilityMult / 1024 // - 331 * opponentWorsening * futilityMult / 1024 // + std::abs(correctionValue) / 158105; }; @@ -1980,8 +1980,9 @@ void syzygy_extend_pv(const OptionsMap& options, for (const auto& m : MoveList(pos)) legalMoves.emplace_back(m); - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves); - RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, false, time_abort); + RootMove& rm = *std::find(legalMoves.begin(), legalMoves.end(), pvMove); if (legalMoves[0].tbRank != rm.tbRank) break; @@ -2040,7 +2041,8 @@ void syzygy_extend_pv(const OptionsMap& options, [](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; }); // The winning side tries to minimize DTZ, the losing side maximizes it - Tablebases::Config config = Tablebases::rank_root_moves(options, pos, legalMoves, true); + Tablebases::Config config = + Tablebases::rank_root_moves(options, pos, legalMoves, true, time_abort); // If DTZ is not available we might not find a mate, so we bail out if (!config.rootInTB || config.cardinality > 0) diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 657866ba4..c8ff60739 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1594,10 +1594,11 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) { // Use the DTZ tables to rank root moves. // // A return value false indicates that not all probes were successful. -bool Tablebases::root_probe(Position& pos, - Search::RootMoves& rootMoves, - bool rule50, - bool rankDTZ) { +bool Tablebases::root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort) { ProbeState result = OK; StateInfo st; @@ -1642,7 +1643,7 @@ bool Tablebases::root_probe(Position& pos, pos.undo_move(m.pv[0]); - if (result == FAIL) + if (time_abort() || result == FAIL) return false; // Better moves are ranked higher. Certain wins are ranked equally. @@ -1707,10 +1708,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo return true; } -Config Tablebases::rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ) { +Config Tablebases::rank_root_moves(const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ, + const std::function& time_abort) { Config config; if (rootMoves.empty()) @@ -1733,10 +1735,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options, if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { - // Rank moves using DTZ tables - config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ); + // Rank moves using DTZ tables, bail out if time_abort flags zeitnot + config.rootInTB = + root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort); - if (!config.rootInTB) + if (!config.rootInTB && !time_abort()) { // DTZ tables are missing; try to rank moves using WDL tables dtz_available = false; diff --git a/src/syzygy/tbprobe.h b/src/syzygy/tbprobe.h index c34338fe3..4a6c3b763 100644 --- a/src/syzygy/tbprobe.h +++ b/src/syzygy/tbprobe.h @@ -19,6 +19,7 @@ #ifndef TBPROBE_H #define TBPROBE_H +#include #include #include @@ -66,12 +67,18 @@ extern int MaxCardinality; void init(const std::string& paths); WDLScore probe_wdl(Position& pos, ProbeState* result); int probe_dtz(Position& pos, ProbeState* result); -bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ); +bool root_probe(Position& pos, + Search::RootMoves& rootMoves, + bool rule50, + bool rankDTZ, + const std::function& time_abort); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50); -Config rank_root_moves(const OptionsMap& options, - Position& pos, - Search::RootMoves& rootMoves, - bool rankDTZ = false); +Config rank_root_moves( + const OptionsMap& options, + Position& pos, + Search::RootMoves& rootMoves, + bool rankDTZ = false, + const std::function& time_abort = []() { return false; }); } // namespace Stockfish::Tablebases From 1d504b927fde7c3227a0c32f26fd49810c0fcd55 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:57:58 -0800 Subject: [PATCH 17/36] Fix undefined behavior C++ standard does not define `uint8_t` as an allowed pointer alias type. Despite `uint8_t` being `unsigned char` on most platforms, this is not enforced by the standard. https://eel.is/c++draft/basic.lval#11 https://stackoverflow.com/a/16138470 closes https://github.com/official-stockfish/Stockfish/pull/6420 No functional change --- src/nnue/nnue_accumulator.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 341960b30..c0a912f58 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -77,10 +77,9 @@ struct AccumulatorCaches { // To initialize a refresh entry, we set all its bitboards empty, // so we put the biases in the accumulation, without any weights on top void clear(const std::array& biases) { - accumulation = biases; - std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0, - sizeof(Entry) - offsetof(Entry, psqtAccumulation)); + std::memset(reinterpret_cast(this) + offsetof(Entry, psqtAccumulation), + 0, sizeof(Entry) - offsetof(Entry, psqtAccumulation)); } }; From b083049fe0c3671d20d195aa8dea72687cd6e44c Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 17 Nov 2025 13:24:05 +0100 Subject: [PATCH 18/36] Use non-locking operations for nodes count and tbHits fetch_add and friends must produce a locking instruction because they guarantee consistency in the presence of multiple writers. Because only one thread ever writes to nodes and tbHits, we may use relaxed load and store to emit regular loads and stores (on both x86-64 and arm64), while avoiding undefined behavior and miscounting nodes. Credit must go to Arseniy Surkov of Reckless (codedeliveryservice) for uncovering this performance gotcha. failed yellow as a gainer on fishtest: LLR: -2.95 (-2.94,2.94) <0.00,2.00> Total: 219616 W: 57165 L: 57105 D: 105346 Ptnml(0-2): 732, 24277, 59720, 24357, 722 https://tests.stockfishchess.org/tests/view/69086abfea4b268f1fac2809 potential small speedup on large core system: ``` ==== master ==== 1 Nodes/second : 294269736 2 Nodes/second : 295217629 Average (over 2): 294743682 ==== patch ==== 1 Nodes/second : 299003603 2 Nodes/second : 298111320 Average (over 2): 298557461 ``` closes https://github.com/official-stockfish/Stockfish/pull/6392 No functional change --- src/search.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9fb7b702f..53ff9f5d9 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -529,7 +529,8 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { bool capture = pos.capture_stage(move); - nodes.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt); @@ -749,7 +750,8 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - tbHits.fetch_add(1, std::memory_order_relaxed); + // Preferable over fetch_add to avoid locking instructions + tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); int drawScore = tbConfig.useRule50 ? 1 : 0; From 2084d94266f76f1ab5631f32708916a4d5cca246 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Thu, 13 Nov 2025 11:45:45 -0800 Subject: [PATCH 19/36] inline make_index() and avoid templating perspective combination of two patches. pass perspective as argument passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 52832 W: 13725 L: 13528 D: 25579 Ptnml(0-2): 154, 5756, 14412, 5927, 167 https://tests.stockfishchess.org/tests/view/69162e307ca8781852331c6a inline make_index passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 68768 W: 17786 L: 17607 D: 33375 Ptnml(0-2): 187, 7591, 18694, 7680, 232 https://tests.stockfishchess.org/tests/view/6916859e7ca8781852331d36 closes https://github.com/official-stockfish/Stockfish/pull/6429 No functional change --- src/misc.h | 9 + src/nnue/features/full_threats.cpp | 113 +++++-------- src/nnue/features/full_threats.h | 15 +- src/nnue/features/half_ka_v2_hm.cpp | 47 ++--- src/nnue/features/half_ka_v2_hm.h | 18 +- src/nnue/nnue_accumulator.cpp | 254 +++++++++++++++------------- src/nnue/nnue_accumulator.h | 19 ++- 7 files changed, 231 insertions(+), 244 deletions(-) diff --git a/src/misc.h b/src/misc.h index fce6f17df..66c03b806 100644 --- a/src/misc.h +++ b/src/misc.h @@ -412,6 +412,15 @@ void move_to_front(std::vector& vec, Predicate pred) { } } +#if defined(__GNUC__) + #define sf_always_inline __attribute__((always_inline)) +#elif defined(__MSVC) + #define sf_always_inline __forceinline +#else + // do nothign for other compilers + #define sf_always_inline +#endif + #if defined(__GNUC__) && !defined(__clang__) #if __GNUC__ >= 13 #define sf_assume(cond) __attribute__((assume(cond))) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 994d2160c..122478dc6 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -58,35 +58,6 @@ constexpr std::array AllPieces = { PiecePairData index_lut1[PIECE_NB][PIECE_NB]; // [attacker][attacked] uint8_t index_lut2[PIECE_NB][SQUARE_NB][SQUARE_NB]; // [attacker][from][to] -namespace { - -template -IndexType make_index_with_orientation( - Piece attacker, Square from, Square to, Piece attacked, int orientation) { - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); - - if constexpr (Perspective == BLACK) - { - attacker = ~attacker; - attacked = ~attacked; - } - - const auto piecePairData = index_lut1[attacker][attacked]; - - const bool less_than = static_cast(from) < static_cast(to); - if ((piecePairData.excluded_pair_info() + less_than) & 2) - return FullThreats::Dimensions; - - const IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != FullThreats::Dimensions); - return index; -} - -} // namespace - static void init_index_luts() { for (Piece attacker : AllPieces) { @@ -155,27 +126,49 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -template -IndexType -FullThreats::make_index(Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - return make_index_with_orientation(attacker, from, to, attacked, - OrientTBL[Perspective][ksq]); +inline sf_always_inline +IndexType FullThreats::make_index(Color perspective, + Piece attacker, + Square from, + Square to, + Piece attacked, + Square ksq) { + const int orientation = OrientTBL[perspective][ksq]; + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); + + std::int8_t swap = 8 * perspective; + attacker = Piece(attacker ^ swap); + attacked = Piece(attacked ^ swap); + + const auto piecePairData = index_lut1[attacker][attacked]; + + const bool less_than = static_cast(from) < static_cast(to); + if ((piecePairData.excluded_pair_info() + less_than) & 2) + return FullThreats::Dimensions; + + const IndexType index = piecePairData.feature_index_base() + offsets[attacker][from] + + index_lut2[attacker][from][to]; + + sf_assume(index != FullThreats::Dimensions); + return index; } // Get a list of indices for active features in ascending order -template -void FullThreats::append_active_indices(const Position& pos, IndexList& active) { + +void FullThreats::append_active_indices(Color perspective, + const Position& pos, + IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(Perspective); - const int orientation = OrientTBL[Perspective][ksq]; + Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[Perspective][color]; + Color c = order[perspective][color]; Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -193,8 +186,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -205,8 +197,7 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -223,8 +214,8 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = make_index_with_orientation( - attacker, from, to, attacked, orientation); + IndexType index = make_index( + perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -235,23 +226,15 @@ void FullThreats::append_active_indices(const Position& pos, IndexList& active) } } -// Explicit template instantiations -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template void FullThreats::append_active_indices(const Position& pos, IndexList& active); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); -template IndexType -FullThreats::make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); - // Get a list of indices for recently changed features -template -void FullThreats::append_changed_indices(Square ksq, + +void FullThreats::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, FusedUpdateData* fusedData, bool first) { - const int orientation = OrientTBL[Perspective][ksq]; for (const auto& dirty : diff.list) { @@ -292,28 +275,14 @@ void FullThreats::append_changed_indices(Square ksq, } } - const IndexType index = - make_index_with_orientation(attacker, from, to, attacked, orientation); + const IndexType index = make_index(perspective, + attacker, from, to, attacked, ksq); if (index < Dimensions) (add ? added : removed).push_back(index); } } -// Explicit template instantiations -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); -template void FullThreats::append_changed_indices(Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added, - FusedUpdateData* fd, - bool first); - bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { return perspective == diff.us && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index 458b04dd1..d5c91d8c2 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -89,16 +89,19 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - template - static IndexType make_index(Piece attkr, Square from, Square to, Piece attkd, Square ksq); + static IndexType make_index(Color perspective, + Piece attkr, + Square from, + Square to, + Piece attkd, + Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - template - static void append_changed_indices(Square ksq, + static void append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added, diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index f652ba0b8..5a6f610cf 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -28,58 +28,45 @@ namespace Stockfish::Eval::NNUE::Features { // Index of a feature for a given king position and another piece on some square -template -IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) { - const IndexType flip = 56 * Perspective; - return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[Perspective][pc] + +IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) { + const IndexType flip = 56 * perspective; + return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[perspective][pc] + KingBuckets[int(ksq) ^ flip]; } // Get a list of indices for active features -template -void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) { - Square ksq = pos.square(Perspective); + +void HalfKAv2_hm::append_active_indices(Color perspective, + const Position& pos, + IndexList& active) { + Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) { Square s = pop_lsb(bb); - active.push_back(make_index(s, pos.piece_on(s), ksq)); + active.push_back(make_index(perspective, s, pos.piece_on(s), ksq)); } } -// Explicit template instantiations -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); -template IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq); - // Get a list of indices for recently changed features -template -void HalfKAv2_hm::append_changed_indices(Square ksq, + +void HalfKAv2_hm::append_changed_indices(Color perspective, + Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { - removed.push_back(make_index(diff.from, diff.pc, ksq)); + removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) - added.push_back(make_index(diff.to, diff.pc, ksq)); + added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); if (diff.remove_sq != SQ_NONE) - removed.push_back(make_index(diff.remove_sq, diff.remove_pc, ksq)); + removed.push_back(make_index(perspective, diff.remove_sq, diff.remove_pc, ksq)); if (diff.add_sq != SQ_NONE) - added.push_back(make_index(diff.add_sq, diff.add_pc, ksq)); + added.push_back(make_index(perspective, diff.add_sq, diff.add_pc, ksq)); } -// Explicit template instantiations -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); -template void HalfKAv2_hm::append_changed_indices(Square ksq, - const DiffType& dp, - IndexList& removed, - IndexList& added); - bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) { return diff.pc == make_piece(perspective, KING); } diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e695b273a..e9448f838 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -107,17 +107,21 @@ class HalfKAv2_hm { using DiffType = DirtyPiece; // Index of a feature for a given king position and another piece on some square - template - static IndexType make_index(Square s, Piece pc, Square ksq); + + static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq); // Get a list of indices for active features - template - static void append_active_indices(const Position& pos, IndexList& active); + + static void append_active_indices(Color perspective, + const Position& pos, + IndexList& active); // Get a list of indices for recently changed features - template - static void - append_changed_indices(Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); + static void append_changed_indices(Color perspective, + Square ksq, + const DiffType& diff, + IndexList& removed, + IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index fa059463e..3d99b8063 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -40,39 +40,43 @@ using namespace SIMD; namespace { -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed); -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2); -template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed); -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); } @@ -143,71 +147,73 @@ void AccumulatorStack::evaluate(const Position& pos, AccumulatorCaches::Cache& cache) noexcept { constexpr bool UseThreats = (Dimensions == TransformedFeatureDimensionsBig); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(WHITE, pos, featureTransformer, cache); - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); if (UseThreats) - evaluate_side(pos, featureTransformer, cache); + evaluate_side(BLACK, pos, featureTransformer, cache); } -template -void AccumulatorStack::evaluate_side(const Position& pos, +template +void AccumulatorStack::evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept { const auto last_usable_accum = - find_last_usable_accumulator(); + find_last_usable_accumulator(perspective); if ((accumulators()[last_usable_accum].template acc()) - .computed[Perspective]) - forward_update_incremental(pos, featureTransformer, - last_usable_accum); + .computed[perspective]) + forward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); else { if constexpr (std::is_same_v) - update_accumulator_refresh_cache(featureTransformer, pos, - mut_latest(), cache); + update_accumulator_refresh_cache(perspective, featureTransformer, pos, + mut_latest(), cache); else - update_threats_accumulator_full(featureTransformer, pos, - mut_latest()); + update_threats_accumulator_full(perspective, featureTransformer, pos, + mut_latest()); - backward_update_incremental(pos, featureTransformer, - last_usable_accum); + backward_update_incremental(perspective, pos, featureTransformer, + last_usable_accum); } } // Find the earliest usable accumulator, this can either be a computed accumulator or the accumulator // state just before a change that requires full refresh. -template -std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { +template +std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) const noexcept { for (std::size_t curr_idx = size - 1; curr_idx > 0; curr_idx--) { - if ((accumulators()[curr_idx].template acc()).computed[Perspective]) + if ((accumulators()[curr_idx].template acc()).computed[perspective]) return curr_idx; - if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, Perspective)) + if (FeatureSet::requires_refresh(accumulators()[curr_idx].diff, perspective)) return curr_idx; } return 0; } -template +template void AccumulatorStack::forward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { assert(begin < accumulators().size()); - assert((accumulators()[begin].template acc()).computed[Perspective]); + assert((accumulators()[begin].template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::size_t next = begin + 1; next < size; next++) { @@ -223,9 +229,8 @@ void AccumulatorStack::forward_update_incremental( if (dp2.remove_sq != SQ_NONE && (accumulators[next].diff.threateningSqs & square_bb(dp2.remove_sq))) { - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1], - dp2); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1], dp2); next++; continue; } @@ -237,8 +242,8 @@ void AccumulatorStack::forward_update_incremental( { const Square captureSq = dp1.to; dp1.to = dp2.remove_sq = SQ_NONE; - double_inc_update(featureTransformer, ksq, accumulators[next], - accumulators[next + 1], accumulators[next - 1]); + double_inc_update(perspective, featureTransformer, ksq, accumulators[next], + accumulators[next + 1], accumulators[next - 1]); dp1.to = dp2.remove_sq = captureSq; next++; continue; @@ -246,32 +251,33 @@ void AccumulatorStack::forward_update_incremental( } } - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next - 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next - 1]); } - assert((latest().acc()).computed[Perspective]); + assert((latest().acc()).computed[perspective]); } -template -void AccumulatorStack::backward_update_incremental( +template +void AccumulatorStack::backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept { assert(end < accumulators().size()); assert(end < size); - assert((latest().template acc()).computed[Perspective]); + assert((latest().template acc()).computed[perspective]); - const Square ksq = pos.square(Perspective); + const Square ksq = pos.square(perspective); for (std::int64_t next = std::int64_t(size) - 2; next >= std::int64_t(end); next--) - update_accumulator_incremental(featureTransformer, ksq, - mut_accumulators()[next], - accumulators()[next + 1]); + update_accumulator_incremental(perspective, featureTransformer, ksq, + mut_accumulators()[next], + accumulators()[next + 1]); - assert((accumulators()[end].template acc()).computed[Perspective]); + assert((accumulators()[end].template acc()).computed[perspective]); } // Explicit template instantiations @@ -304,15 +310,18 @@ void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const.. vecIn[i], reinterpret_cast(rows)[i]...); } -template +template struct AccumulatorUpdateContext { + Color perspective; const FeatureTransformer& featureTransformer; const AccumulatorState& from; AccumulatorState& to; - AccumulatorUpdateContext(const FeatureTransformer& ft, + AccumulatorUpdateContext(Color persp, + const FeatureTransformer& ft, const AccumulatorState& accF, AccumulatorState& accT) noexcept : + perspective{persp}, featureTransformer{ft}, from{accF}, to{accT} {} @@ -330,22 +339,22 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[Perspective], - (to.template acc()).accumulation[Perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective], + (to.template acc()).accumulation[perspective], to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[Perspective], - (to.template acc()).psqtAccumulation[Perspective], + (from.template acc()).psqtAccumulation[perspective], + (to.template acc()).psqtAccumulation[perspective], to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto fromAcc = from.template acc().accumulation[Perspective]; - const auto toAcc = to.template acc().accumulation[Perspective]; + const auto fromAcc = from.template acc().accumulation[perspective]; + const auto toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[Perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[Perspective]; + const auto fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + const auto toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -469,31 +478,33 @@ struct AccumulatorUpdateContext { } }; -template -auto make_accumulator_update_context(const FeatureTransformer& featureTransformer, +template +auto make_accumulator_update_context(Color perspective, + const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{ + perspective, featureTransformer, accumulatorFrom, accumulatorTo}; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, + 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]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); PSQFeatureSet::IndexList removed, added; - PSQFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, 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); - PSQFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added); + PSQFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); assert(added.size() == 1); assert(removed.size() == 2 || removed.size() == 3); @@ -505,7 +516,7 @@ void double_inc_update(const FeatureTransformer& f sf_assume(removed.size() == 2 || removed.size() == 3); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if (removed.size() == 2) { @@ -517,51 +528,52 @@ void double_inc_update(const FeatureTransformer& f removed[2]); } - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template -void double_inc_update(const FeatureTransformer& featureTransformer, +template +void double_inc_update(Color perspective, +const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, const AccumulatorState& computed, const DirtyPiece& dp2) { - assert(computed.acc().computed[Perspective]); - assert(!middle_state.acc().computed[Perspective]); - assert(!target_state.acc().computed[Perspective]); + assert(computed.acc().computed[perspective]); + assert(!middle_state.acc().computed[perspective]); + assert(!target_state.acc().computed[perspective]); ThreatFeatureSet::FusedUpdateData fusedData; fusedData.dp2removed = dp2.remove_sq; ThreatFeatureSet::IndexList removed, added; - ThreatFeatureSet::append_changed_indices(ksq, middle_state.diff, removed, added, - &fusedData, true); - ThreatFeatureSet::append_changed_indices(ksq, target_state.diff, removed, added, - &fusedData, false); + ThreatFeatureSet::append_changed_indices(perspective, ksq, middle_state.diff, removed, added, + &fusedData, true); + ThreatFeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added, + &fusedData, false); auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); updateContext.apply(added, removed); - target_state.acc().computed[Perspective] = true; + target_state.acc().computed[perspective] = true; } -template void update_accumulator_incremental( + Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& target_state, const AccumulatorState& computed) { - assert((computed.template acc()).computed[Perspective]); - assert(!(target_state.template acc()).computed[Perspective]); + assert((computed.template acc()).computed[perspective]); + assert(!(target_state.template acc()).computed[perspective]); // The size must be enough to contain the largest possible update. // That might depend on the feature set and generally relies on the @@ -571,29 +583,27 @@ void update_accumulator_incremental( // is 2, since we are incrementally updating one move at a time. typename FeatureSet::IndexList removed, added; if constexpr (Forward) - FeatureSet::template append_changed_indices(ksq, target_state.diff, removed, - added); + FeatureSet::append_changed_indices(perspective, ksq, target_state.diff, removed, added); else - FeatureSet::template append_changed_indices(ksq, computed.diff, added, - removed); + FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); if (!added.size() && !removed.size()) { auto& targetAcc = target_state.template acc(); const auto& sourceAcc = computed.template acc(); - std::memcpy(targetAcc.accumulation[Perspective], sourceAcc.accumulation[Perspective], - sizeof(targetAcc.accumulation[Perspective])); - std::memcpy(targetAcc.psqtAccumulation[Perspective], - sourceAcc.psqtAccumulation[Perspective], - sizeof(targetAcc.psqtAccumulation[Perspective])); + std::memcpy(targetAcc.accumulation[perspective], sourceAcc.accumulation[perspective], + sizeof(targetAcc.accumulation[perspective])); + std::memcpy(targetAcc.psqtAccumulation[perspective], + sourceAcc.psqtAccumulation[perspective], + sizeof(targetAcc.psqtAccumulation[perspective])); - targetAcc.computed[Perspective] = true; + targetAcc.computed[perspective] = true; return; } auto updateContext = - make_accumulator_update_context(featureTransformer, computed, target_state); + make_accumulator_update_context(perspective, featureTransformer, computed, target_state); if constexpr (std::is_same_v) updateContext.apply(added, removed); @@ -633,7 +643,7 @@ void update_accumulator_incremental( } } - (target_state.template acc()).computed[Perspective] = true; + (target_state.template acc()).computed[perspective] = true; } Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { @@ -660,16 +670,17 @@ Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPie #endif } -template -void update_accumulator_refresh_cache(const FeatureTransformer& featureTransformer, +template +void update_accumulator_refresh_cache(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache) { using Tiling [[maybe_unused]] = SIMDTiling; - const Square ksq = pos.square(Perspective); - auto& entry = cache[ksq][Perspective]; + const Square ksq = pos.square(perspective); + auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); @@ -679,19 +690,19 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat while (removedBB) { Square sq = pop_lsb(removedBB); - removed.push_back(PSQFeatureSet::make_index(sq, entry.pieces[sq], ksq)); + removed.push_back(PSQFeatureSet::make_index(perspective, sq, entry.pieces[sq], ksq)); } while (addedBB) { Square sq = pop_lsb(addedBB); - added.push_back(PSQFeatureSet::make_index(sq, pos.piece_on(sq), ksq)); + added.push_back(PSQFeatureSet::make_index(perspective, sq, pos.piece_on(sq), ksq)); } entry.pieceBB = pos.pieces(); std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -700,7 +711,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); auto* entryTile = reinterpret_cast(&entry.accumulation[j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) @@ -747,7 +758,7 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); auto* entryTilePsqt = reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); @@ -805,25 +816,26 @@ void update_accumulator_refresh_cache(const FeatureTransformer& feat // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - std::memcpy(accumulator.accumulation[Perspective], entry.accumulation.data(), + std::memcpy(accumulator.accumulation[perspective], entry.accumulation.data(), sizeof(BiasType) * Dimensions); - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation.data(), + std::memcpy(accumulator.psqtAccumulation[perspective], entry.psqtAccumulation.data(), sizeof(int32_t) * PSQTBuckets); #endif } -template -void update_threats_accumulator_full(const FeatureTransformer& featureTransformer, +template +void update_threats_accumulator_full(Color perspective, + const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { using Tiling [[maybe_unused]] = SIMDTiling; ThreatFeatureSet::IndexList active; - ThreatFeatureSet::append_active_indices(pos, active); + ThreatFeatureSet::append_active_indices(perspective, pos, active); auto& accumulator = accumulatorState.acc(); - accumulator.computed[Perspective] = true; + accumulator.computed[perspective] = true; #ifdef VECTOR vec_t acc[Tiling::NumRegs]; @@ -832,7 +844,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) { auto* accTile = - reinterpret_cast(&accumulator.accumulation[Perspective][j * Tiling::TileHeight]); + reinterpret_cast(&accumulator.accumulation[perspective][j * Tiling::TileHeight]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_zero(); @@ -865,7 +877,7 @@ void update_threats_accumulator_full(const FeatureTransformer& featu for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) { auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + &accumulator.psqtAccumulation[perspective][j * Tiling::PsqtTileHeight]); for (IndexType k = 0; k < Tiling::NumPsqtRegs; ++k) psqt[k] = vec_zero_psqt(); @@ -888,21 +900,21 @@ void update_threats_accumulator_full(const FeatureTransformer& featu #else for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] = 0; + accumulator.accumulation[perspective][j] = 0; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] = 0; + accumulator.psqtAccumulation[perspective][k] = 0; for (const auto index : active) { const IndexType offset = Dimensions * index; for (IndexType j = 0; j < Dimensions; ++j) - accumulator.accumulation[Perspective][j] += + accumulator.accumulation[perspective][j] += featureTransformer.threatWeights[offset + j]; for (std::size_t k = 0; k < PSQTBuckets; ++k) - accumulator.psqtAccumulation[Perspective][k] += + accumulator.psqtAccumulation[perspective][k] += featureTransformer.threatPsqtWeights[index * PSQTBuckets + k]; } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index c0a912f58..181412b43 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -176,21 +176,24 @@ class AccumulatorStack { template [[nodiscard]] std::array, MaxSize>& mut_accumulators() noexcept; - template - void evaluate_side(const Position& pos, + template + void evaluate_side(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, AccumulatorCaches::Cache& cache) noexcept; - template - [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + template + [[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept; - template - void forward_update_incremental(const Position& pos, + template + void forward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept; - template - void backward_update_incremental(const Position& pos, + template + void backward_update_incremental(Color perspective, + const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t end) noexcept; From 4b8fffe3b3cde2f351aff0afb0a0a5c33f376b40 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 15 Nov 2025 09:44:08 +0100 Subject: [PATCH 20/36] Silence warning for systems without shared memory support Fixes https://github.com/official-stockfish/Stockfish/issues/6425 closes https://github.com/official-stockfish/Stockfish/pull/6426 No functional change --- src/shm.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/shm.h b/src/shm.h index ae5429676..b870afc24 100644 --- a/src/shm.h +++ b/src/shm.h @@ -455,7 +455,8 @@ class SharedMemoryBackend { public: SharedMemoryBackend() = default; - SharedMemoryBackend(const std::string& shm_name, const T& value) {} + SharedMemoryBackend([[maybe_unused]] const std::string& shm_name, + [[maybe_unused]] const T& value) {} void* get() const { return nullptr; } From 563b4a9c4d649a78c3c4ee45cc779fb5caaed385 Mon Sep 17 00:00:00 2001 From: Andreas Matthies Date: Sat, 15 Nov 2025 16:46:55 +0100 Subject: [PATCH 21/36] Cleanup benchmark Avoid unnecessary consecutive ucinewgame commands in benchmark setup. Remove measuring time and nodes in speedtest warmup. closes https://github.com/official-stockfish/Stockfish/pull/6427 No functional change --- src/benchmark.cpp | 1 - src/uci.cpp | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index 38cafaec7..136b4031e 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -483,7 +483,6 @@ BenchmarkSetup setup_benchmark(std::istream& is) { float totalTime = 0; for (const auto& game : BenchmarkPositions) { - setup.commands.emplace_back("ucinewgame"); int ply = 1; for (int i = 0; i < static_cast(game.size()); ++i) { diff --git a/src/uci.cpp b/src/uci.cpp index 9b1dd865c..5bd235823 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -339,16 +339,9 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); - TimePoint elapsed = now(); - // Run with silenced network verification engine.go(limits); engine.wait_for_search_finished(); - - totalTime += now() - elapsed; - - nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); @@ -396,6 +389,7 @@ void UCIEngine::benchmark(std::istream& args) { Search::LimitsType limits = parse_limits(is); + nodesSearched = 0; TimePoint elapsed = now(); // Run with silenced network verification @@ -407,7 +401,6 @@ void UCIEngine::benchmark(std::istream& args) { updateHashfullReadings(); nodes += nodesSearched; - nodesSearched = 0; } else if (token == "position") position(is); From a27fcd6274a8538bf6d07844c7f799d2a0401db6 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Mon, 17 Nov 2025 13:57:53 +0100 Subject: [PATCH 22/36] Fix format No functional change --- src/nnue/features/full_threats.cpp | 40 ++++++++++++----------------- src/nnue/features/full_threats.h | 8 ++---- src/nnue/features/half_ka_v2_hm.cpp | 11 +++----- src/nnue/features/half_ka_v2_hm.h | 11 +++----- src/nnue/nnue_accumulator.cpp | 35 ++++++++++++------------- 5 files changed, 40 insertions(+), 65 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 122478dc6..9c818a8e0 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -126,20 +126,15 @@ void init_threat_offsets() { } // Index of a feature for a given king position and another piece on some square -inline sf_always_inline -IndexType FullThreats::make_index(Color perspective, - Piece attacker, - Square from, - Square to, - Piece attacked, - Square ksq) { +inline sf_always_inline IndexType FullThreats::make_index( + Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { const int orientation = OrientTBL[perspective][ksq]; - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); + from = Square(int(from) ^ orientation); + to = Square(int(to) ^ orientation); std::int8_t swap = 8 * perspective; - attacker = Piece(attacker ^ swap); - attacked = Piece(attacked ^ swap); + attacker = Piece(attacker ^ swap); + attacked = Piece(attacked ^ swap); const auto piecePairData = index_lut1[attacker][attacked]; @@ -147,8 +142,8 @@ IndexType FullThreats::make_index(Color perspective, if ((piecePairData.excluded_pair_info() + less_than) & 2) return FullThreats::Dimensions; - const IndexType index = piecePairData.feature_index_base() + offsets[attacker][from] - + index_lut2[attacker][from][to]; + const IndexType index = + piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; sf_assume(index != FullThreats::Dimensions); return index; @@ -156,13 +151,11 @@ IndexType FullThreats::make_index(Color perspective, // Get a list of indices for active features in ascending order -void FullThreats::append_active_indices(Color perspective, - const Position& pos, - IndexList& active) { +void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(perspective); - Bitboard occupied = pos.pieces(); + Square ksq = pos.square(perspective); + Bitboard occupied = pos.pieces(); for (Color color : {WHITE, BLACK}) { @@ -186,7 +179,7 @@ void FullThreats::append_active_indices(Color perspective, Square to = pop_lsb(attacks_left); Square from = to - right; Piece attacked = pos.piece_on(to); - IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -197,7 +190,7 @@ void FullThreats::append_active_indices(Color perspective, Square to = pop_lsb(attacks_right); Square from = to - left; Piece attacked = pos.piece_on(to); - IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -214,8 +207,8 @@ void FullThreats::append_active_indices(Color perspective, { Square to = pop_lsb(attacks); Piece attacked = pos.piece_on(to); - IndexType index = make_index( - perspective, attacker, from, to, attacked, ksq); + IndexType index = + make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) active.push_back(index); @@ -275,8 +268,7 @@ void FullThreats::append_changed_indices(Color perspective, } } - const IndexType index = make_index(perspective, - attacker, from, to, attacked, ksq); + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) (add ? added : removed).push_back(index); diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index d5c91d8c2..bc8a1ce32 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -89,12 +89,8 @@ class FullThreats { using IndexList = ValueList; using DiffType = DirtyThreats; - static IndexType make_index(Color perspective, - Piece attkr, - Square from, - Square to, - Piece attkd, - Square ksq); + static IndexType + make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq); // Get a list of indices for active features static void append_active_indices(Color perspective, const Position& pos, IndexList& active); diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5a6f610cf..56779ddce 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -37,9 +37,7 @@ IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square // Get a list of indices for active features -void HalfKAv2_hm::append_active_indices(Color perspective, - const Position& pos, - IndexList& active) { +void HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) { Square ksq = pos.square(perspective); Bitboard bb = pos.pieces(); while (bb) @@ -51,11 +49,8 @@ void HalfKAv2_hm::append_active_indices(Color perspective, // Get a list of indices for recently changed features -void HalfKAv2_hm::append_changed_indices(Color perspective, - Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added) { +void HalfKAv2_hm::append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) { removed.push_back(make_index(perspective, diff.from, diff.pc, ksq)); if (diff.to != SQ_NONE) added.push_back(make_index(perspective, diff.to, diff.pc, ksq)); diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index e9448f838..c58a3246b 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -112,16 +112,11 @@ class HalfKAv2_hm { // Get a list of indices for active features - static void append_active_indices(Color perspective, - const Position& pos, - IndexList& active); + static void append_active_indices(Color perspective, const Position& pos, IndexList& active); // Get a list of indices for recently changed features - static void append_changed_indices(Color perspective, - Square ksq, - const DiffType& diff, - IndexList& removed, - IndexList& added); + static void append_changed_indices( + Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added); // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3d99b8063..3ed729ffb 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -41,7 +41,7 @@ using namespace SIMD; namespace { template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -49,7 +49,7 @@ void double_inc_update(Color p const AccumulatorState& computed); template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -57,9 +57,7 @@ void double_inc_update(Color p const AccumulatorState& computed, const DirtyPiece& dp2); -template +template void update_accumulator_incremental( Color perspective, const FeatureTransformer& featureTransformer, @@ -68,14 +66,14 @@ void update_accumulator_incremental( const AccumulatorState& computed); template -void update_accumulator_refresh_cache(Color perspective, +void update_accumulator_refresh_cache(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState, AccumulatorCaches::Cache& cache); template -void update_threats_accumulator_full(Color perspective, +void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState); @@ -205,7 +203,7 @@ std::size_t AccumulatorStack::find_last_usable_accumulator(Color perspective) co template void AccumulatorStack::forward_update_incremental( - Color perspective, + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, const std::size_t begin) noexcept { @@ -260,7 +258,8 @@ void AccumulatorStack::forward_update_incremental( } template -void AccumulatorStack::backward_update_incremental(Color perspective, +void AccumulatorStack::backward_update_incremental( + Color perspective, const Position& pos, const FeatureTransformer& featureTransformer, @@ -479,16 +478,16 @@ struct AccumulatorUpdateContext { }; template -auto make_accumulator_update_context(Color perspective, +auto make_accumulator_update_context(Color perspective, const FeatureTransformer& featureTransformer, const AccumulatorState& accumulatorFrom, AccumulatorState& accumulatorTo) noexcept { - return AccumulatorUpdateContext{ - perspective, featureTransformer, accumulatorFrom, accumulatorTo}; + return AccumulatorUpdateContext{perspective, featureTransformer, + accumulatorFrom, accumulatorTo}; } template -void double_inc_update(Color perspective, +void double_inc_update(Color perspective, const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, @@ -532,8 +531,8 @@ void double_inc_update(Color p } template -void double_inc_update(Color perspective, -const FeatureTransformer& featureTransformer, +void double_inc_update(Color perspective, + const FeatureTransformer& featureTransformer, const Square ksq, AccumulatorState& middle_state, AccumulatorState& target_state, @@ -562,9 +561,7 @@ const FeatureTransformer& featureTransformer, target_state.acc().computed[perspective] = true; } -template +template void update_accumulator_incremental( Color perspective, const FeatureTransformer& featureTransformer, @@ -825,7 +822,7 @@ void update_accumulator_refresh_cache(Color pers } template -void update_threats_accumulator_full(Color perspective, +void update_threats_accumulator_full(Color perspective, const FeatureTransformer& featureTransformer, const Position& pos, AccumulatorState& accumulatorState) { From 61149ac9ee59b1d2ea96f9a5a36fa5e74bcf5359 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Wed, 12 Nov 2025 17:38:27 +0100 Subject: [PATCH 23/36] Update main net to nn-c0ae49f08b40.nnue Trained using the recipe https://github.com/vondele/nettest/blob/d39f72420504e474a716b9977c1380541a7b482e/threats.yaml fix CI check for negative evals. fix format passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 84032 W: 21876 L: 21490 D: 40666 Ptnml(0-2): 239, 9821, 21553, 10121, 282 https://tests.stockfishchess.org/tests/view/6914b81e7ca87818523318aa passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 126420 W: 32429 L: 31927 D: 62064 Ptnml(0-2): 56, 13748, 35118, 14214, 74 https://tests.stockfishchess.org/tests/view/6916c1277ca8781852331dd8 closes https://github.com/official-stockfish/Stockfish/pull/6431 Bench: 2513286 --- src/evaluate.h | 2 +- tests/instrumented.py | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/evaluate.h b/src/evaluate.h index c8dc64ace..fc7784364 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-49c1193b131c.nnue" +#define EvalFileDefaultNameBig "nn-c0ae49f08b40.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { diff --git a/tests/instrumented.py b/tests/instrumented.py index 23de446ef..80831ce3f 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -67,7 +67,6 @@ def Stockfish(*args, **kwargs): class TestCLI(metaclass=OrderedClassMembers): - def beforeAll(self): pass @@ -141,7 +140,7 @@ class TestCLI(metaclass=OrderedClassMembers): def test_bench_128_threads_3_bench_tmp_epd_depth(self): self.stockfish = Stockfish( - f"bench 128 {get_threads()} 3 {os.path.join(PATH,'bench_tmp.epd')} depth".split( + f"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth".split( " " ), True, @@ -167,7 +166,7 @@ class TestCLI(metaclass=OrderedClassMembers): def test_export_net_verify_nnue(self): current_path = os.path.abspath(os.getcwd()) self.stockfish = Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) assert self.stockfish.process.returncode == 0 @@ -254,7 +253,7 @@ class TestInteractive(metaclass=OrderedClassMembers): self.stockfish.send_command("go depth 5") def callback(output): - regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp -?\d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth") and not re.match(regex, output): assert False if output.startswith("bestmove"): @@ -274,7 +273,7 @@ class TestInteractive(metaclass=OrderedClassMembers): def callback(output): nonlocal depth - regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" + regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp -?\d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv" if output.startswith("info depth"): if not re.match(regex, output): @@ -390,7 +389,7 @@ class TestInteractive(metaclass=OrderedClassMembers): def test_verify_nnue_network(self): current_path = os.path.abspath(os.getcwd()) Stockfish( - f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True + f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True ) self.stockfish.send_command("setoption name EvalFile value verify.nnue") @@ -469,7 +468,7 @@ class TestSyzygy(metaclass=OrderedClassMembers): self.stockfish.send_command("go depth 5") def check_output(output): - if "score cp -20000" in output or "score mate" in output: + if "score cp -20000" in output or "score mate -" in output: return True self.stockfish.check_output(check_output) From 035cb146d430b601e94c13e3f2fefcc7688ec1d6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 20:30:59 -0800 Subject: [PATCH 24/36] Do more futility pruning closes https://github.com/official-stockfish/Stockfish/pull/6433 Bench: 2469519 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 53ff9f5d9..1c3875cdc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -857,7 +857,7 @@ Value Search::Worker::search( // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 91 - 21 * !ss->ttHit; + Value futilityMult = 81 - 21 * !ss->ttHit; return futilityMult * d // - 2094 * improving * futilityMult / 1024 // From d9fd516547849bd5ca2a05c491aadc66fc750a39 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 22 Nov 2025 00:17:02 -0500 Subject: [PATCH 25/36] Post-NNUEv10 tune Tune search parameters after the switch to NNUEv10. The change is neutral at STC but increases with the TC. The main changes are more aggressive corrections, futility pruning, and extensions. Failed STC LLR: -2.96 (-2.94,2.94) <0.00,2.00> Total: 108320 W: 27616 L: 27719 D: 52985 Ptnml(0-2): 332, 12833, 27884, 12828, 283 https://tests.stockfishchess.org/tests/view/69212e623b03dd3a060e6114 Passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 43272 W: 11117 L: 10788 D: 21367 Ptnml(0-2): 20, 4543, 12180, 4874, 19 https://tests.stockfishchess.org/tests/view/692130813b03dd3a060e6123 Passed VLTC LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 22714 W: 5840 L: 5581 D: 11293 Ptnml(0-2): 2, 2152, 6795, 2401, 7 https://tests.stockfishchess.org/tests/view/6921387b3b03dd3a060e6191 Passed VLTC SMP LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 11868 W: 3142 L: 2896 D: 5830 Ptnml(0-2): 0, 1007, 3676, 1249, 2 https://tests.stockfishchess.org/tests/view/69212e953b03dd3a060e611b closes https://github.com/official-stockfish/Stockfish/pull/6444 bench 2907929 --- src/evaluate.cpp | 12 ++-- src/search.cpp | 184 ++++++++++++++++++++++++----------------------- 2 files changed, 101 insertions(+), 95 deletions(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4bbbcaac6..d20843e85 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -65,7 +65,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, Value nnue = (125 * psqt + 131 * positional) / 128; // Re-evaluate the position when higher eval accuracy is worth the time spent - if (smallNet && (std::abs(nnue) < 236)) + if (smallNet && (std::abs(nnue) < 277)) { std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big); nnue = (125 * psqt + 131 * positional) / 128; @@ -74,14 +74,14 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); - optimism += optimism * nnueComplexity / 468; - nnue -= nnue * nnueComplexity / 18000; + optimism += optimism * nnueComplexity / 476; + nnue -= nnue * nnueComplexity / 18236; - int material = 535 * pos.count() + pos.non_pawn_material(); - int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; + int material = 534 * pos.count() + pos.non_pawn_material(); + int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871; // Damp down the evaluation linearly when shuffling - v -= v * pos.rule50_count() / 212; + v -= v * pos.rule50_count() / 199; // Guarantee evaluation does not hit the tablebase range v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); diff --git a/src/search.cpp b/src/search.cpp index 1c3875cdc..d2a90283c 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -88,7 +88,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss + (*(ss - 4)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 8; - return 9536 * pcv + 8494 * micv + 10132 * (wnpcv + bnpcv) + 7156 * cntcv; + return 10347 * pcv + 8821 * micv + 11168 * (wnpcv + bnpcv) + 7841 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -104,10 +104,10 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - constexpr int nonPawnWeight = 165; + constexpr int nonPawnWeight = 178; workerThread.pawnCorrectionHistory[pawn_correction_history_index(pos)][us] << bonus; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 156 / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] @@ -117,8 +117,8 @@ void update_correction_history(const Position& pos, { const Square to = m.to_sq(); const Piece pc = pos.piece_on(m.to_sq()); - (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 137 / 128; - (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 64 / 128; + (*(ss - 2)->continuationCorrectionHistory)[pc][to] << bonus * 127 / 128; + (*(ss - 4)->continuationCorrectionHistory)[pc][to] << bonus * 59 / 128; } } @@ -338,7 +338,7 @@ void Search::Worker::iterative_deepening() { beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 137 * avg / (std::abs(avg) + 91); + optimism[us] = 142 * avg / (std::abs(avg) + 91); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -467,20 +467,23 @@ void Search::Worker::iterative_deepening() { uint64_t nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); - double fallingEval = - (11.325 + 2.115 * (mainThread->bestPreviousAverageScore - bestValue) - + 0.987 * (mainThread->iterValue[iterIdx] - bestValue)) - / 100.0; - fallingEval = std::clamp(fallingEval, 0.5688, 1.5698); + double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) + / 100.0; + + fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly - double k = 0.5189; - double center = lastBestMoveDepth + 11.57; - timeReduction = 0.723 + 0.79 / (1.104 + std::exp(-k * (completedDepth - center))); - double reduction = - (1.455 + mainThread->previousTimeReduction) / (2.2375 * timeReduction); - double bestMoveInstability = 1.04 + 1.8956 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 92425 ? 0.666 : 1.0; + double k = 0.51; + double center = lastBestMoveDepth + 12.15; + + timeReduction = 0.66 + 0.85 / (0.98 + std::exp(-k * (completedDepth - center))); + + double reduction = (1.43 + mainThread->previousTimeReduction) / (2.28 * timeReduction); + + double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); + + double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 93337 ? 0.75 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; @@ -502,7 +505,7 @@ void Search::Worker::iterative_deepening() { threads.stop = true; } else - threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.503; + threads.increaseDepth = mainThread->ponder || elapsedTime <= totalTime * 0.50; } mainThread->iterValue[iterIdx] = bestValue; @@ -582,7 +585,7 @@ void Search::Worker::clear() { h.fill(-529); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2809 / 128.0 * std::log(i)); + reductions[i] = int(2747 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -702,11 +705,12 @@ Value Search::Worker::search( // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, - std::min(130 * depth - 71, 1043)); + std::min(132 * depth - 72, 985)); + // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2142); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); } // Partial workaround for the graph history interaction problem @@ -825,11 +829,11 @@ Value Search::Worker::search( // Use static evaluation difference to improve quiet move ordering if (((ss - 1)->currentMove).is_ok() && !(ss - 1)->inCheck && !priorCapture) { - int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -200, 156) + 58; + int evalDiff = std::clamp(-int((ss - 1)->staticEval + ss->staticEval), -209, 167) + 59; mainHistory[~us][((ss - 1)->currentMove).raw()] << evalDiff * 9; if (!ttHit && type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) - pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 14; + pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] << evalDiff * 13; } // Set up the improving flag, which is true if current static evaluation is @@ -844,25 +848,25 @@ Value Search::Worker::search( // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 173) + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. // For PvNodes, we must have a guard against mates being returned. - if (!PvNode && eval < alpha - 514 - 294 * depth * depth) + if (!PvNode && eval < alpha - 485 - 281 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. { auto futility_margin = [&](Depth d) { - Value futilityMult = 81 - 21 * !ss->ttHit; + Value futilityMult = 76 - 23 * !ss->ttHit; return futilityMult * d // - - 2094 * improving * futilityMult / 1024 // + - 2474 * improving * futilityMult / 1024 // - 331 * opponentWorsening * futilityMult / 1024 // - + std::abs(correctionValue) / 158105; + + std::abs(correctionValue) / 174665; }; if (!ss->ttPv && depth < 14 && eval - futility_margin(depth) >= beta && eval >= beta @@ -871,7 +875,7 @@ Value Search::Worker::search( } // Step 9. Null move search with verification search - if (cutNode && ss->staticEval >= beta - 18 * depth + 390 && !excludedMove + if (cutNode && ss->staticEval >= beta - 18 * depth + 350 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= nmpMinPly && !is_loss(beta)) { assert((ss - 1)->currentMove != Move::null()); @@ -905,6 +909,7 @@ Value Search::Worker::search( } } + improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions @@ -916,7 +921,7 @@ Value Search::Worker::search( // Step 11. ProbCut // If we have a good enough capture (or queen promotion) and a reduced search // returns a value much above beta, we can (almost) safely prune the previous move. - probCutBeta = beta + 224 - 64 * improving; + probCutBeta = beta + 235 - 63 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -926,7 +931,7 @@ Value Search::Worker::search( assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); MovePicker mp(pos, ttData.move, probCutBeta - ss->staticEval, &captureHistory); - Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 306, 0, depth); + Depth probCutDepth = std::clamp(depth - 5 - (ss->staticEval - beta) / 315, 0, depth); while ((move = mp.next_move()) != Move::none()) { @@ -1046,8 +1051,8 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7) { - Value futilityValue = ss->staticEval + 231 + 211 * lmrDepth - + PieceValue[capturedPiece] + 130 * captHist / 1024; + Value futilityValue = ss->staticEval + 232 + 217 * lmrDepth + + PieceValue[capturedPiece] + 131 * captHist / 1024; if (futilityValue <= alpha) continue; @@ -1055,7 +1060,7 @@ moves_loop: // When in check, search starts here // SEE based pruning for captures and checks // Avoid pruning sacrifices of our last piece for stalemate - int margin = std::max(157 * depth + captHist / 29, 0); + int margin = std::max(166 * depth + captHist / 29, 0); if ((alpha >= VALUE_DRAW || pos.non_pawn_material(us) != PieceValue[movedPiece]) && !pos.see_ge(move, -margin)) continue; @@ -1067,21 +1072,21 @@ moves_loop: // When in check, search starts here + pawnHistory[pawn_history_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4312 * depth) + if (history < -4083 * depth) continue; - history += 76 * mainHistory[us][move.raw()] / 32; + history += 69 * mainHistory[us][move.raw()] / 32; // (*Scaler): Generally, lower divisors scales well - lmrDepth += history / 3220; + lmrDepth += history / 3208; - Value futilityValue = ss->staticEval + 47 + 171 * !bestMove + 134 * lmrDepth - + 90 * (ss->staticEval > alpha); + Value futilityValue = ss->staticEval + 42 + 161 * !bestMove + 127 * lmrDepth + + 85 * (ss->staticEval > alpha); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning // scales well - if (!ss->inCheck && lmrDepth < 11 && futilityValue <= alpha) + if (!ss->inCheck && lmrDepth < 13 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) && !is_win(futilityValue)) @@ -1092,7 +1097,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } @@ -1107,12 +1112,11 @@ moves_loop: // When in check, search starts here // (*Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. - if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (56 + 81 * (ss->ttPv && !PvNode)) * depth / 60; + Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1121,11 +1125,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 229958; - int doubleMargin = -4 + 198 * PvNode - 212 * !ttCapture - corrValAdj - - 921 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 45; - int tripleMargin = 76 + 308 * PvNode - 250 * !ttCapture + 92 * ss->ttPv - corrValAdj - - (ss->ply * 2 > rootDepth * 3) * 52; + int corrValAdj = std::abs(correctionValue) / 230673; + int doubleMargin = -4 + 199 * PvNode - 201 * !ttCapture - corrValAdj + - 897 * ttMoveHistory / 127649 - (ss->ply > rootDepth) * 42; + int tripleMargin = 73 + 302 * PvNode - 248 * !ttCapture + 90 * ss->ttPv - corrValAdj + - (ss->ply * 2 > rootDepth * 3) * 50; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1171,33 +1175,32 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2618 + PvNode * 991 + (ttData.value > alpha) * 903 - + (ttData.depth >= depth) * (978 + cutNode * 1051); - + r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + + (ttData.depth >= depth) * (934 + cutNode * 1011); // These reduction adjustments have no proven non-linear scaling - r += 843; // Base reduction offset to compensate for other tweaks - r -= moveCount * 66; - r -= std::abs(correctionValue) / 30450; + r += 714; // Base reduction offset to compensate for other tweaks + r -= moveCount * 73; + r -= std::abs(correctionValue) / 30370; // Increase reduction for cut nodes if (cutNode) - r += 3094 + 1056 * !ttData.move; + r += 3372 + 997 * !ttData.move; // Increase reduction if ttMove is a capture if (ttCapture) - r += 1415; + r += 1119; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 2) - r += 1051 + allNode * 814; + r += 991 + allNode * 923; // For first picked move (ttMove) reduce reduction if (move == ttData.move) - r -= 2018; + r -= 2151; if (capture) - ss->statScore = 803 * int(PieceValue[pos.captured_piece()]) / 128 + ss->statScore = 868 * int(PieceValue[pos.captured_piece()]) / 128 + captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())]; else ss->statScore = 2 * mainHistory[us][move.raw()] @@ -1205,7 +1208,8 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()]; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 794 / 8192; + r -= ss->statScore * 850 / 8192; + // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1245,13 +1249,12 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1118; + r += 1140; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3212) - (r > 4784 && newDepth > 2), !cutNode); + newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } - // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. if (PvNode && (moveCount == 1 || value > alpha)) @@ -1403,25 +1406,25 @@ moves_loop: // When in check, search starts here // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = -228; - bonusScale -= (ss - 1)->statScore / 104; - bonusScale += std::min(63 * depth, 508); + int bonusScale = -215; + bonusScale -= (ss - 1)->statScore / 100; + bonusScale += std::min(56 * depth, 489); bonusScale += 184 * ((ss - 1)->moveCount > 8); - bonusScale += 143 * (!ss->inCheck && bestValue <= ss->staticEval - 92); - bonusScale += 149 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 70); + bonusScale += 147 * (!ss->inCheck && bestValue <= ss->staticEval - 107); + bonusScale += 156 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 65); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(144 * depth - 92, 1365) * bonusScale; + const int scaledBonus = std::min(141 * depth - 87, 1351) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 400 / 32768); + scaledBonus * 406 / 32768); - mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 220 / 32768; + mainHistory[~us][((ss - 1)->currentMove).raw()] << scaledBonus * 243 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) pawnHistory[pawn_history_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1164 / 32768; + << scaledBonus * 1160 / 32768; } // Bonus for prior capture countermove that caused the fail low @@ -1429,9 +1432,10 @@ moves_loop: // When in check, search starts here { Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 964; + captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } + if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1579,9 +1583,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 352; + futilityBase = ss->staticEval + 351; } + const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1640,7 +1645,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -78)) + if (!pos.see_ge(move, -80)) continue; } @@ -1713,9 +1718,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { int reductionScale = reductions[d] * reductions[mn]; - return reductionScale - delta * 757 / rootDelta + !i * reductionScale * 218 / 512 + 1200; + return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } + // elapsed() returns the time elapsed since the search started. If the // 'nodestime' option is enabled, it will return the count of nodes searched // instead. This function is called to check whether the search should be @@ -1809,35 +1815,35 @@ void update_all_stats(const Position& pos, Piece movedPiece = pos.moved_piece(bestMove); PieceType capturedPiece; - int bonus = std::min(121 * depth - 77, 1633) + 375 * (bestMove == ttMove); - int malus = std::min(825 * depth - 196, 2159) - 16 * moveCount; + int bonus = std::min(116 * depth - 81, 1515) + 347 * (bestMove == ttMove); + int malus = std::min(848 * depth - 207, 2446) - 17 * moveCount; if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 881 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 910 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1083 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1085 / 1024); } else { // Increase stats for the best move in case it was a capture move capturedPiece = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1482 / 1024; + captureHistory[movedPiece][bestMove.to_sq()][capturedPiece] << bonus * 1395 / 1024; } // Extra penalty for a quiet early move that was not a TT move in // previous ply when it gets refuted. if (prevSq != SQ_NONE && ((ss - 1)->moveCount == 1 + (ss - 1)->ttHit) && !pos.captured_piece()) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 614 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 602 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { movedPiece = pos.moved_piece(move); capturedPiece = type_of(pos.piece_on(move.to_sq())); - captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1397 / 1024; + captureHistory[movedPiece][move.to_sq()][capturedPiece] << -malus * 1448 / 1024; } } @@ -1845,8 +1851,8 @@ void update_all_stats(const Position& pos, // Updates histories of the move pairs formed by moves // at ply -1, -2, -3, -4, and -6 with current move. void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { - static constexpr std::array conthist_bonuses = { - {{1, 1157}, {2, 648}, {3, 288}, {4, 576}, {5, 140}, {6, 441}}}; + static std::array conthist_bonuses = { + {{1, 1133}, {2, 683}, {3, 312}, {4, 582}, {5, 149}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1867,13 +1873,13 @@ void update_quiet_histories( workerThread.mainHistory[us][move.raw()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 761 / 1024; + workerThread.lowPlyHistory[ss->ply][move.raw()] << bonus * 805 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 955 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 896 / 1024); int pIndex = pawn_history_index(pos); workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] - << bonus * (bonus > 0 ? 850 : 550) / 1024; + << bonus * (bonus > 0 ? 905 : 505) / 1024; } } From 1132d893e09d6dae78a452b841375545aca9e03e Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:57:08 -0800 Subject: [PATCH 26/36] Simplify Accumulator Updates Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 146848 W: 37915 L: 37820 D: 71113 Ptnml(0-2): 415, 16159, 40186, 16244, 420 https://tests.stockfishchess.org/tests/view/691772217ca878185233202b closes https://github.com/official-stockfish/Stockfish/pull/6421 No functional change --- src/nnue/nnue_accumulator.cpp | 50 +++++++++++++++-------------------- src/nnue/nnue_accumulator.h | 8 +++--- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 3ed729ffb..e63a68cb7 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -18,7 +18,6 @@ #include "nnue_accumulator.h" -#include #include #include #include @@ -338,22 +337,23 @@ struct AccumulatorUpdateContext { }; fused_row_reduce( - (from.template acc()).accumulation[perspective], - (to.template acc()).accumulation[perspective], to_weight_vector(indices)...); + (from.template acc()).accumulation[perspective].data(), + (to.template acc()).accumulation[perspective].data(), + to_weight_vector(indices)...); fused_row_reduce( - (from.template acc()).psqtAccumulation[perspective], - (to.template acc()).psqtAccumulation[perspective], + (from.template acc()).psqtAccumulation[perspective].data(), + (to.template acc()).psqtAccumulation[perspective].data(), to_psqt_weight_vector(indices)...); } void apply(const typename FeatureSet::IndexList& added, const typename FeatureSet::IndexList& removed) { - const auto fromAcc = from.template acc().accumulation[perspective]; - const auto toAcc = to.template acc().accumulation[perspective]; + const auto& fromAcc = from.template acc().accumulation[perspective]; + auto& toAcc = to.template acc().accumulation[perspective]; - const auto fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; - const auto toPsqtAcc = to.template acc().psqtAccumulation[perspective]; + const auto& fromPsqtAcc = from.template acc().psqtAccumulation[perspective]; + auto& toPsqtAcc = to.template acc().psqtAccumulation[perspective]; #ifdef VECTOR using Tiling = SIMDTiling; @@ -448,8 +448,8 @@ struct AccumulatorUpdateContext { #else - std::copy_n(fromAcc, Dimensions, toAcc); - std::copy_n(fromPsqtAcc, PSQTBuckets, toPsqtAcc); + toAcc = fromAcc; + toPsqtAcc = fromPsqtAcc; for (const auto index : removed) { @@ -589,13 +589,10 @@ void update_accumulator_incremental( auto& targetAcc = target_state.template acc(); const auto& sourceAcc = computed.template acc(); - std::memcpy(targetAcc.accumulation[perspective], sourceAcc.accumulation[perspective], - sizeof(targetAcc.accumulation[perspective])); - std::memcpy(targetAcc.psqtAccumulation[perspective], - sourceAcc.psqtAccumulation[perspective], - sizeof(targetAcc.psqtAccumulation[perspective])); + targetAcc.accumulation[perspective] = sourceAcc.accumulation[perspective]; + targetAcc.psqtAccumulation[perspective] = sourceAcc.psqtAccumulation[perspective]; + targetAcc.computed[perspective] = true; - targetAcc.computed[perspective] = true; return; } @@ -643,15 +640,16 @@ void update_accumulator_incremental( (target_state.template acc()).computed[perspective] = true; } -Bitboard get_changed_pieces(const Piece oldPieces[SQUARE_NB], const Piece newPieces[SQUARE_NB]) { +Bitboard get_changed_pieces(const std::array& oldPieces, + const std::array& newPieces) { #if defined(USE_AVX512) || defined(USE_AVX2) static_assert(sizeof(Piece) == 1); Bitboard sameBB = 0; for (int i = 0; i < 64; i += 32) { - const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(oldPieces + i)); - const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(newPieces + i)); + const __m256i old_v = _mm256_loadu_si256(reinterpret_cast(&oldPieces[i])); + const __m256i new_v = _mm256_loadu_si256(reinterpret_cast(&newPieces[i])); const __m256i cmpEqual = _mm256_cmpeq_epi8(old_v, new_v); const std::uint32_t equalMask = _mm256_movemask_epi8(cmpEqual); sameBB |= static_cast(equalMask) << i; @@ -680,7 +678,7 @@ void update_accumulator_refresh_cache(Color pers auto& entry = cache[ksq][perspective]; PSQFeatureSet::IndexList removed, added; - const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array().data()); + const Bitboard changedBB = get_changed_pieces(entry.pieces, pos.piece_array()); Bitboard removedBB = changedBB & entry.pieceBB; Bitboard addedBB = changedBB & pos.pieces(); @@ -696,7 +694,7 @@ void update_accumulator_refresh_cache(Color pers } entry.pieceBB = pos.pieces(); - std::copy_n(pos.piece_array().begin(), SQUARE_NB, entry.pieces); + entry.pieces = pos.piece_array(); auto& accumulator = accumulatorState.acc(); accumulator.computed[perspective] = true; @@ -812,12 +810,8 @@ void update_accumulator_refresh_cache(Color pers // The accumulator of the refresh entry has been updated. // Now copy its content to the actual accumulator we were refreshing. - - std::memcpy(accumulator.accumulation[perspective], entry.accumulation.data(), - sizeof(BiasType) * Dimensions); - - std::memcpy(accumulator.psqtAccumulation[perspective], entry.psqtAccumulation.data(), - sizeof(int32_t) * PSQTBuckets); + accumulator.accumulation[perspective] = entry.accumulation; + accumulator.psqtAccumulation[perspective] = entry.psqtAccumulation; #endif } diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 181412b43..69ab49632 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -46,9 +46,9 @@ class FeatureTransformer; // Class that holds the result of affine transformation of input features template struct alignas(CacheLineSize) Accumulator { - std::int16_t accumulation[COLOR_NB][Size]; - std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; - std::array computed = {}; + std::array, COLOR_NB> accumulation; + std::array, COLOR_NB> psqtAccumulation; + std::array computed = {}; }; @@ -71,7 +71,7 @@ struct AccumulatorCaches { struct alignas(CacheLineSize) Entry { std::array accumulation; std::array psqtAccumulation; - Piece pieces[SQUARE_NB]; + std::array pieces; Bitboard pieceBB; // To initialize a refresh entry, we set all its bitboards empty, From 93f2d14d95ca72ec2ec96c6e3de0b9a6121fe118 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Thu, 13 Nov 2025 19:09:06 -0800 Subject: [PATCH 27/36] Simplify Incremental Updates Passed Non-regression STC: LLR: 3.03 (-2.94,2.94) <-1.75,0.25> Total: 277856 W: 71575 L: 71611 D: 134670 Ptnml(0-2): 842, 30719, 75836, 30695, 836 https://tests.stockfishchess.org/tests/view/69169dc17ca8781852331d76 Supersedes #6430 closes https://github.com/official-stockfish/Stockfish/pull/6434 No functional change --- src/nnue/nnue_accumulator.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index e63a68cb7..358a4b278 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -584,18 +584,6 @@ void update_accumulator_incremental( else FeatureSet::append_changed_indices(perspective, ksq, computed.diff, added, removed); - if (!added.size() && !removed.size()) - { - auto& targetAcc = target_state.template acc(); - const auto& sourceAcc = computed.template acc(); - - targetAcc.accumulation[perspective] = sourceAcc.accumulation[perspective]; - targetAcc.psqtAccumulation[perspective] = sourceAcc.psqtAccumulation[perspective]; - targetAcc.computed[perspective] = true; - - return; - } - auto updateContext = make_accumulator_update_context(perspective, featureTransformer, computed, target_state); From f4244e13e41b936f73076c51eac38511f406d8e4 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sat, 15 Nov 2025 20:46:14 -0800 Subject: [PATCH 28/36] Some more work on FullThreats::make_index [Passed STC](https://tests.stockfishchess.org/tests/live_elo/691fc321acb6dbdf23d07e4b): ``` LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 215360 W: 55651 L: 55108 D: 104601 Ptnml(0-2): 520, 22399, 61290, 22960, 511 ``` This PR is on top of ces42's work so I'll rebase if that PR changes. Essentially, it comprises my adjustments to `make_index` that remained unmerged before threat inputs was finalized. The unsigned intermediate variables let us skip a bunch of useless sign extensions (because Square and Piece inherit from `int8_t`, and C/C++ semantics require narrow integers to be promoted to `int` before doing arithmetic on them). Additionally, by removing the special usage of `offsets[][64]` and `[65]` the indexing becomes more efficient. (This usage was a temporary hack from sscg anyway, so I think he'll like that it's gone.) Finally, the `sf_assume` condition was fixed so that it actually makes a difference to the compiler. The speedup is fairly nice locally (combining both ces42's and these changes): ``` Result of 100 runs ================== base (...kfish.master) = 1691982 +/- 1907 test (./stockfish ) = 1714349 +/- 1789 diff = +22367 +/- 2465 speedup = +0.0132 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/6445 No functional change --- src/nnue/features/full_threats.cpp | 50 ++++++++++++++++-------------- src/nnue/features/full_threats.h | 15 ++------- src/types.h | 8 ++--- 3 files changed, 31 insertions(+), 42 deletions(-) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 9c818a8e0..645bb7090 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -32,7 +32,12 @@ namespace Stockfish::Eval::NNUE::Features { // Lookup array for indexing threats -IndexType offsets[PIECE_NB][SQUARE_NB + 2]; +IndexType offsets[PIECE_NB][SQUARE_NB]; + +struct HelperOffsets { + int cumulativePieceOffset, cumulativeOffset; +}; +std::array helper_offsets; // Information on a particular pair of pieces and whether they should be excluded struct PiecePairData { @@ -69,9 +74,9 @@ static void init_index_luts() { int map = FullThreats::map[attackerType - 1][attackedType - 1]; bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN); - IndexType feature = offsets[attacker][65] + IndexType feature = helper_offsets[attacker].cumulativeOffset + (color_of(attacked) * (numValidTargets[attacker] / 2) + map) - * offsets[attacker][64]; + * helper_offsets[attacker].cumulativePieceOffset; bool excluded = map < 0; index_lut1[attacker][attacked] = PiecePairData(excluded, semi_excluded, feature); @@ -116,8 +121,7 @@ void init_threat_offsets() { } } - offsets[pieceIdx][64] = cumulativePieceOffset; - offsets[pieceIdx][65] = cumulativeOffset; + helper_offsets[pieceIdx] = {cumulativePieceOffset, cumulativeOffset}; cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset; } @@ -128,32 +132,30 @@ void init_threat_offsets() { // Index of a feature for a given king position and another piece on some square inline sf_always_inline IndexType FullThreats::make_index( Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) { - const int orientation = OrientTBL[perspective][ksq]; - from = Square(int(from) ^ orientation); - to = Square(int(to) ^ orientation); + const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective); + unsigned from_oriented = uint8_t(from) ^ orientation; + unsigned to_oriented = uint8_t(to) ^ orientation; - std::int8_t swap = 8 * perspective; - attacker = Piece(attacker ^ swap); - attacked = Piece(attacked ^ swap); + std::int8_t swap = 8 * perspective; + unsigned attacker_oriented = attacker ^ swap; + unsigned attacked_oriented = attacked ^ swap; - const auto piecePairData = index_lut1[attacker][attacked]; + const auto piecePairData = index_lut1[attacker_oriented][attacked_oriented]; - const bool less_than = static_cast(from) < static_cast(to); + const bool less_than = from_oriented < to_oriented; if ((piecePairData.excluded_pair_info() + less_than) & 2) return FullThreats::Dimensions; - const IndexType index = - piecePairData.feature_index_base() + offsets[attacker][from] + index_lut2[attacker][from][to]; - - sf_assume(index != FullThreats::Dimensions); + const IndexType index = piecePairData.feature_index_base() + + offsets[attacker_oriented][from_oriented] + + index_lut2[attacker_oriented][from_oriented][to_oriented]; + sf_assume(index < Dimensions); return index; } // Get a list of indices for active features in ascending order void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) { - static constexpr Color order[2][2] = {{WHITE, BLACK}, {BLACK, WHITE}}; - Square ksq = pos.square(perspective); Bitboard occupied = pos.pieces(); @@ -161,7 +163,7 @@ void FullThreats::append_active_indices(Color perspective, const Position& pos, { for (PieceType pt = PAWN; pt <= KING; ++pt) { - Color c = order[perspective][color]; + Color c = Color(perspective ^ color); Piece attacker = make_piece(c, pt); Bitboard bb = pos.pieces(c, pt); @@ -268,16 +270,16 @@ void FullThreats::append_changed_indices(Color perspective, } } - const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); + auto& insert = add ? added : removed; + const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq); if (index < Dimensions) - (add ? added : removed).push_back(index); + insert.push_back(index); } } bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) { - return perspective == diff.us - && OrientTBL[diff.us][diff.ksq] != OrientTBL[diff.us][diff.prevKsq]; + return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/full_threats.h b/src/nnue/features/full_threats.h index bc8a1ce32..177e9fabe 100644 --- a/src/nnue/features/full_threats.h +++ b/src/nnue/features/full_threats.h @@ -32,7 +32,6 @@ namespace Stockfish::Eval::NNUE::Features { static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0, 0, 6, 12, 10, 10, 12, 8, 0}; -extern IndexType offsets[PIECE_NB][SQUARE_NB + 2]; void init_threat_offsets(); class FullThreats { @@ -48,23 +47,15 @@ class FullThreats { // clang-format off // Orient a square according to perspective (rotates by 180 for black) - static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = { - { SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + static constexpr std::int8_t OrientTBL[SQUARE_NB] = { + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, + SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1, - SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1 }, - { SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8, - SQ_A8, SQ_A8, SQ_A8, SQ_A8, SQ_H8, SQ_H8, SQ_H8, SQ_H8 } }; static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = { diff --git a/src/types.h b/src/types.h index a2171b638..3aeb2bcc4 100644 --- a/src/types.h +++ b/src/types.h @@ -295,18 +295,14 @@ struct DirtyPiece { struct DirtyThreat { DirtyThreat() { /* don't initialize data */ } DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 28) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); } Piece pc() const { return static_cast(data >> 20 & 0xf); } Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { - uint32_t b = data >> 28; - sf_assume(b == 0 || b == 1); - return b; - } + bool add() const { return data >> 31; } private: uint32_t data; From c73f21df97e98c960773d0eb0fcf765582c06fed Mon Sep 17 00:00:00 2001 From: pb00067 Date: Sat, 22 Nov 2025 13:24:03 +0100 Subject: [PATCH 29/36] Fix for SF getting stuck during search (issue #5023) Among several passed similar versions this is the simpliest one. Passed STC non-regression https://tests.stockfishchess.org/tests/view/691f3b4aacb6dbdf23d07d11 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 126368 W: 32607 L: 32489 D: 61272 Ptnml(0-2): 408, 13974, 34298, 14100, 404 Passed LTC non-regression https://tests.stockfishchess.org/tests/view/69209712acb6dbdf23d0836a LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 114786 W: 29097 L: 28976 D: 56713 Ptnml(0-2): 53, 11841, 33488, 11954, 57 closes https://github.com/official-stockfish/Stockfish/pull/6447 Bench: 2941767 --- src/search.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index d2a90283c..9924b0cec 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -141,6 +141,16 @@ void update_all_stats(const Position& pos, Move TTMove, int moveCount); +bool isShuffling(Move move, Stack* const ss, const Position& pos) { + if (type_of(pos.moved_piece(move)) == PAWN || pos.capture_stage(move) + || pos.rule50_count() < 10) + return false; + if (pos.state()->pliesFromNull <= 6 || ss->ply < 20) + return false; + return move.from_sq() == (ss - 2)->currentMove.to_sq() + && (ss - 2)->currentMove.from_sq() == (ss - 4)->currentMove.to_sq(); +} + } // namespace Search::Worker::Worker(SharedState& sharedState, @@ -1114,7 +1124,7 @@ moves_loop: // When in check, search starts here // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove && depth >= 6 + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) - && ttData.depth >= depth - 3) + && ttData.depth >= depth - 3 && !isShuffling(move, ss, pos)) { Value singularBeta = ttData.value - (53 + 75 * (ss->ttPv && !PvNode)) * depth / 60; Depth singularDepth = newDepth / 2; From 45d034fa504c0262c9be958e6139cabcc65aeaf6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Nov 2025 23:11:16 -0800 Subject: [PATCH 30/36] Formatting fixups closes https://github.com/official-stockfish/Stockfish/pull/6446 No functional change --- src/search.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 9924b0cec..f5133905e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -480,7 +480,6 @@ void Search::Worker::iterative_deepening() { double fallingEval = (11.85 + 2.24 * (mainThread->bestPreviousAverageScore - bestValue) + 0.93 * (mainThread->iterValue[iterIdx] - bestValue)) / 100.0; - fallingEval = std::clamp(fallingEval, 0.57, 1.70); // If the bestMove is stable over several iterations, reduce time accordingly @@ -717,7 +716,6 @@ Value Search::Worker::search( update_quiet_histories(pos, ss, *this, ttData.move, std::min(132 * depth - 72, 985)); - // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount < 4 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2060); @@ -738,6 +736,7 @@ Value Search::Worker::search( // Check that the ttValue after the tt move would also trigger a cutoff if (!is_valid(ttDataNext.value)) return ttData.value; + if ((ttData.value >= beta) == (-ttDataNext.value >= beta)) return ttData.value; } @@ -858,6 +857,7 @@ Value Search::Worker::search( // Hindsight adjustment of reductions based on static evaluation difference. if (priorReduction >= 3 && !opponentWorsening) depth++; + if (priorReduction >= 2 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 169) depth--; @@ -919,7 +919,6 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta; // Step 10. Internal iterative reductions @@ -1187,7 +1186,6 @@ moves_loop: // When in check, search starts here if (ss->ttPv) r -= 2719 + PvNode * 983 + (ttData.value > alpha) * 922 + (ttData.depth >= depth) * (934 + cutNode * 1011); - // These reduction adjustments have no proven non-linear scaling r += 714; // Base reduction offset to compensate for other tweaks r -= moveCount * 73; @@ -1220,7 +1218,6 @@ moves_loop: // When in check, search starts here // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 850 / 8192; - // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) { @@ -1265,6 +1262,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3957) - (r > 5654 && newDepth > 2), !cutNode); } + // For PV nodes only, do a full PV search on the first move or after a fail high, // otherwise let the parent node fail low with value <= alpha and try another move. if (PvNode && (moveCount == 1 || value > alpha)) @@ -1445,7 +1443,6 @@ moves_loop: // When in check, search starts here captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1012; } - if (PvNode) bestValue = std::min(bestValue, maxValue); @@ -1560,8 +1557,10 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { // Never assume anything about values stored in TT unadjustedStaticEval = ttData.eval; + if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); @@ -1573,8 +1572,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) else { unadjustedStaticEval = evaluate(pos); - - ss->staticEval = bestValue = + ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); } @@ -1583,6 +1581,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) { if (!is_decisive(bestValue)) bestValue = (bestValue + beta) / 2; + if (!ss->ttHit) ttWriter.write(posKey, value_to_tt(bestValue, ss->ply), false, BOUND_LOWER, DEPTH_UNSEARCHED, Move::none(), unadjustedStaticEval, @@ -1596,7 +1595,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) futilityBase = ss->staticEval + 351; } - const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory}; Square prevSq = ((ss - 1)->currentMove).is_ok() ? ((ss - 1)->currentMove).to_sq() : SQ_NONE; @@ -1699,7 +1697,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (!is_decisive(bestValue) && bestValue > beta) bestValue = (bestValue + beta) / 2; - Color us = pos.side_to_move(); if (!ss->inCheck && !moveCount && !pos.non_pawn_material(us) && type_of(pos.captured_piece()) >= ROOK) @@ -1731,7 +1728,6 @@ Depth Search::Worker::reduction(bool i, Depth d, int mn, int delta) const { return reductionScale - delta * 608 / rootDelta + !i * reductionScale * 238 / 512 + 1182; } - // elapsed() returns the time elapsed since the search started. If the // 'nodestime' option is enabled, it will return the count of nodes searched // instead. This function is called to check whether the search should be @@ -1869,6 +1865,7 @@ void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { // Only update the first 2 continuation histories if we are in check if (ss->inCheck && i > 2) break; + if (((ss - i)->currentMove).is_ok()) (*(ss - i)->continuationHistory)[pc][to] << (bonus * weight / 1024) + 88 * (i < 2); } @@ -1925,7 +1922,6 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { return best; } - // Used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. void SearchManager::check_time(Search::Worker& worker) { From 7c7c574e6b572ff7e23388d9f767471e597cb872 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sat, 22 Nov 2025 14:15:50 -0800 Subject: [PATCH 31/36] Fix msvc macro closes https://github.com/official-stockfish/Stockfish/pull/6449 No functional change --- src/misc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.h b/src/misc.h index 66c03b806..54598cd0e 100644 --- a/src/misc.h +++ b/src/misc.h @@ -414,7 +414,7 @@ void move_to_front(std::vector& vec, Predicate pred) { #if defined(__GNUC__) #define sf_always_inline __attribute__((always_inline)) -#elif defined(__MSVC) +#elif defined(_MSC_VER) #define sf_always_inline __forceinline #else // do nothign for other compilers From c95386256ef79c2a415eeb4ab83ae5ec9e5b1532 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 26 Nov 2025 17:43:38 +0300 Subject: [PATCH 32/36] Simplify the highBestMoveEffort formula in Time management Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 22272 W: 5885 L: 5655 D: 10732 Ptnml(0-2): 76, 2329, 6108, 2535, 88 https://tests.stockfishchess.org/tests/view/69244d07ba083df4ca63e02b Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 48690 W: 12405 L: 12221 D: 24064 Ptnml(0-2): 29, 4755, 14597, 4931, 33 https://tests.stockfishchess.org/tests/view/6924de9aba083df4ca63e327 closes https://github.com/official-stockfish/Stockfish/pull/6451 No functional change --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f5133905e..8e30f3dc3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -492,7 +492,7 @@ void Search::Worker::iterative_deepening() { double bestMoveInstability = 1.02 + 2.14 * totBestMoveChanges / threads.size(); - double highBestMoveEffort = completedDepth >= 10 && nodesEffort >= 93337 ? 0.75 : 1.0; + double highBestMoveEffort = nodesEffort >= 93340 ? 0.76 : 1.0; double totalTime = mainThread->tm.optimum() * fallingEval * reduction * bestMoveInstability * highBestMoveEffort; From 4fe04a2cc6efdd3ce1a859cd4d0bd61ca875b4e2 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sun, 23 Nov 2025 17:57:33 +0100 Subject: [PATCH 33/36] Update main network to nn-87a9d7857d88.nnue trained with https://github.com/vondele/nettest/blob/842d95177f882c0e9b5262247c38d8fcb3e0f3db/threats.yaml passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 114880 W: 30291 L: 29851 D: 54738 Ptnml(0-2): 499, 13547, 28947, 13909, 538 https://tests.stockfishchess.org/tests/view/6923ec4bba083df4ca63dd39 passed LTC: LLR: 2.97 (-2.94,2.94) <0.50,2.50> Total: 113268 W: 29115 L: 28634 D: 55519 Ptnml(0-2): 114, 12295, 31340, 12766, 119 https://tests.stockfishchess.org/tests/view/6925cd01b23dfeae38cfe134 closes https://github.com/official-stockfish/Stockfish/pull/6452 Bench: 3028457 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index fc7784364..9eff46709 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-c0ae49f08b40.nnue" +#define EvalFileDefaultNameBig "nn-87a9d7857d88.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From 74303ca7f9ccba952c1937e2b9e09a341c6c0c34 Mon Sep 17 00:00:00 2001 From: KazApps Date: Sat, 29 Nov 2025 18:11:46 +0900 Subject: [PATCH 34/36] Update AUTHORS (KazApps) closes https://github.com/official-stockfish/Stockfish/pull/6454 No functional change --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index db552a3a5..fac1e7d8d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -131,6 +131,7 @@ Jörg Oster (joergoster) Julian Willemer (NightlyKing) jundery Justin Blanchard (UncombedCoconut) +Kazuki Yamashita (KazApps) Kelly Wilson Ken Takusagawa Kenneth Lee (kennethlee33) From 9e2ee13e775274ff5faab3fa12a7c0fd0370df3a Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Fri, 28 Nov 2025 07:42:26 +0100 Subject: [PATCH 35/36] Update main network to nn-2962dca31855.nnue trained with https://github.com/vondele/nettest/blob/dd921672bd4ad8eb09fa45248078450857e81884/threats.yaml on top off and tested against https://github.com/official-stockfish/Stockfish/pull/6452 passed STC LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 130720 W: 34028 L: 33570 D: 63122 Ptnml(0-2): 435, 15348, 33384, 15710, 483 https://tests.stockfishchess.org/tests/view/69294539b23dfeae38cff3ad passed LTC LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 51144 W: 13140 L: 12794 D: 25210 Ptnml(0-2): 38, 5370, 14408, 5720, 36 https://tests.stockfishchess.org/tests/view/692a9c37b23dfeae38cffa4f closes https://github.com/official-stockfish/Stockfish/pull/6457 Bench: 2823033 --- src/evaluate.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.h b/src/evaluate.h index 9eff46709..853aeb5b2 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -33,7 +33,7 @@ namespace Eval { // for the build process (profile-build and fishtest) to work. Do not change the // name of the macro or the location where this macro is defined, as it is used // in the Makefile/Fishtest. -#define EvalFileDefaultNameBig "nn-87a9d7857d88.nnue" +#define EvalFileDefaultNameBig "nn-2962dca31855.nnue" #define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue" namespace NNUE { From abd835dcbc3a28481224f6253b00b7420d062513 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 30 Nov 2025 13:17:39 -0800 Subject: [PATCH 36/36] Improve update_piece_threats passed on avx512ICL: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 30240 W: 8026 L: 7726 D: 14488 Ptnml(0-2): 95, 3235, 8171, 3513, 106 https://tests.stockfishchess.org/tests/view/69281d9ab23dfeae38cfeeb8 passed on generic architectures: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 73184 W: 19183 L: 18821 D: 35180 Ptnml(0-2): 258, 7988, 19744, 8338, 264 https://tests.stockfishchess.org/tests/view/6928ba11b23dfeae38cff276 subsequent cleanups tested as: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 72480 W: 18678 L: 18502 D: 35300 Ptnml(0-2): 242, 7925, 19718, 8125, 230 https://tests.stockfishchess.org/tests/view/692a26adb23dfeae38cff566 We add an argument noRaysContaining, which skips all discoveries which contain all bits in the argument; if the argument is from | to, then this will eliminate the discovery. Separately, on AVX512ICL we can speed up the computation of DirtyThreats by moving from a pop_lsb loop to a vector extraction with vpcompressb. See PR for details. closes https://github.com/official-stockfish/Stockfish/pull/6453 No functional change --- src/misc.h | 7 ++++ src/position.cpp | 94 ++++++++++++++++++++++++++++++++++++++++-------- src/position.h | 9 +++-- src/types.h | 23 ++++++++---- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/misc.h b/src/misc.h index 54598cd0e..db5f701e9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -142,6 +142,13 @@ class ValueList { const T* end() const { return values_ + size_; } const T& operator[](int index) const { return values_[index]; } + T* make_space(size_t count) { + T* result = &values_[size_]; + size_ += count; + assert(size_ <= MaxSize); + return result; + } + private: T values_[MaxSize]; std::size_t size_ = 0; diff --git a/src/position.cpp b/src/position.cpp index 8993c2406..049c72682 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1052,8 +1052,51 @@ inline void add_dirty_threat( dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); } +#ifdef USE_AVX512ICL +// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats +// present at the given bitboard. +template +void write_multiple_dirties(const Position& p, + Bitboard mask, + DirtyThreat dt_template, + DirtyThreats* dts) { + static_assert(sizeof(DirtyThreat) == 4); + + const __m512i board = _mm512_loadu_si512(p.piece_array().data()); + const __m512i AllSquares = _mm512_set_epi8( + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + const int dt_count = popcount(mask); + assert(dt_count <= 16); + + const __m512i template_v = _mm512_set1_epi32(dt_template.raw()); + auto* write = dts->list.make_space(dt_count); + + // Extract the list of squares and upconvert to 32 bits. There are never more than 16 + // incoming threats so this is sufficient. + __m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares); + threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares)); + + __m512i threat_pieces = + _mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board); + + // Shift the piece and square into place + threat_squares = _mm512_slli_epi32(threat_squares, SqShift); + threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift); + + const __m512i dirties = + _mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */); + _mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties); +} +#endif + template -void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { +void Position::update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining) { const Bitboard occupied = pieces(); const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard bishopQueens = pieces(BISHOP, QUEEN); @@ -1093,7 +1136,36 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) } threatened &= occupied; + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); +#ifdef USE_AVX512ICL + if (threatened) + { + if constexpr (PutPiece) + { + dts->threatenedSqs |= threatened; + dts->threateningSqs |= square_bb(s); + } + + DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; + write_multiple_dirties( + *this, threatened, dt_template, dts); + } + + Bitboard all_attackers = sliders | incoming_threats; + if (!all_attackers) + return; // Square s is threatened iff there's at least one attacker + + dts->threatenedSqs |= square_bb(s); + dts->threateningSqs |= all_attackers; + + DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; + write_multiple_dirties(*this, all_attackers, + dt_template, dts); +#else while (threatened) { Square threatenedSq = pop_lsb(threatened); @@ -1104,8 +1176,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } - - Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); +#endif if constexpr (ComputeRay) { @@ -1118,30 +1189,24 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); - if (discovered) + if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) { const Square threatenedSq = lsb(discovered); const Piece threatenedPc = piece_on(threatenedSq); add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } +#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers) add_dirty_threat(dts, slider, pc, sliderSq, s); +#endif } } else { - while (sliders) - { - Square sliderSq = pop_lsb(sliders); - Piece slider = piece_on(sliderSq); - add_dirty_threat(dts, slider, pc, sliderSq, s); - } + incoming_threats |= sliders; } - Bitboard incoming_threats = - (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) - | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); - +#ifndef USE_AVX512ICL while (incoming_threats) { Square srcSq = pop_lsb(incoming_threats); @@ -1152,6 +1217,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, srcPc, pc, srcSq, s); } +#endif } // Helper used to do/undo a castling move. This is a bit diff --git a/src/position.h b/src/position.h index 711c4e444..7a029ce18 100644 --- a/src/position.h +++ b/src/position.h @@ -187,7 +187,10 @@ class Position { // Other helpers template - void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); + void update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining = -1ULL); void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template void do_castling(Color us, @@ -372,7 +375,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts Bitboard fromTo = from | to; if (dts) - update_piece_threats(pc, from, dts); + update_piece_threats(pc, from, dts, fromTo); byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; @@ -381,7 +384,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts board[to] = pc; if (dts) - update_piece_threats(pc, to, dts); + update_piece_threats(pc, to, dts, fromTo); } inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { diff --git a/src/types.h b/src/types.h index 3aeb2bcc4..c6613f2f2 100644 --- a/src/types.h +++ b/src/types.h @@ -293,22 +293,31 @@ struct DirtyPiece { // Keep track of what threats change on the board (used by NNUE) struct DirtyThreat { + static constexpr int PcSqOffset = 0; + static constexpr int ThreatenedSqOffset = 8; + static constexpr int ThreatenedPcOffset = 16; + static constexpr int PcOffset = 20; + DirtyThreat() { /* don't initialize data */ } + DirtyThreat(uint32_t raw) : + data(raw) {} DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset) + | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset); } - Piece pc() const { return static_cast(data >> 20 & 0xf); } - Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } - Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } - Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { return data >> 31; } + Piece pc() const { return static_cast(data >> 20 & 0xf); } + Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } + Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } + Square pc_sq() const { return static_cast(data & 0xff); } + bool add() const { return data >> 31; } + uint32_t raw() const { return data; } private: uint32_t data; }; -using DirtyThreatList = ValueList; +using DirtyThreatList = ValueList; // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // Moving a piece also can reveal at most 8 discovered attacks.