From 57f0fe08c0a65ffc8e2e366de85985428e6e6ba5 Mon Sep 17 00:00:00 2001 From: Myself <63040919+AliceRoselia@users.noreply.github.com> Date: Fri, 21 Feb 2025 19:45:35 +0700 Subject: [PATCH 01/39] Add risk tolerance calculation https://tests.stockfishchess.org/tests/view/67b1db2188b11e2400eb06ae Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 123552 W: 32388 L: 31938 D: 59226 Ptnml(0-2): 487, 14520, 31345, 14904, 520 Passed LTC: https://tests.stockfishchess.org/tests/view/67b3d53f154c4df4fc4b1f43 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 206928 W: 52916 L: 52246 D: 101766 Ptnml(0-2): 159, 22546, 57394, 23196, 169 closes https://github.com/official-stockfish/Stockfish/pull/5893 Bench: 2705449 --- src/search.cpp | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 7736c4121..5ec0def6a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -96,6 +96,32 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; } +int risk_tolerance(const Position& pos, Value v) { + // Returns (some constant of) second derivative of sigmoid. + static constexpr auto sigmoid_d2 = [](int x, int y) { + return -345600 * x / (x * x + 3 * y * y); + }; + + int material = pos.count() + 3 * pos.count() + 3 * pos.count() + + 5 * pos.count() + 9 * pos.count(); + + int m = std::clamp(material, 17, 78); + + // a and b are the crude approximation of the wdl model. + // The win rate is: 1/(1+exp((a-v)/b)) + // The loss rate is 1/(1+exp((v+a)/b)) + int a = ((-m * 3220 / 256 + 2361) * m / 256 - 586) * m / 256 + 421; + int b = ((m * 7761 / 256 - 2674) * m / 256 + 314) * m / 256 + 51; + + + // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) + // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) + int winning_risk = sigmoid_d2(v - a, b); + int losing_risk = -sigmoid_d2(-v - a, b); + + return (winning_risk + losing_risk) * 60 / b; +} + // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. Value to_corrected_static_eval(const Value v, const int cv) { @@ -1166,6 +1192,9 @@ moves_loop: // When in check, search starts here r -= std::abs(correctionValue) / 31568; + if (PvNode && !is_decisive(bestValue)) + r -= risk_tolerance(pos, bestValue); + // Increase reduction for cut nodes if (cutNode) r += 2608 + 1024 * !ttData.move; From c19a6ea53cd715af97717ba687c3ad4c9c2a98c8 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 23 Feb 2025 19:52:37 +0300 Subject: [PATCH 02/39] Make Pv search shallower in some cases Conditions are the same as they are for doShallowerSearch, just that they also apply for cases where LMR wasn't reducing anything - if result is bad enough reduce search depth of PV search by 1. Passed STC: https://tests.stockfishchess.org/tests/view/67b9d2aca49c651c2caac818 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 29216 W: 7731 L: 7424 D: 14061 Ptnml(0-2): 87, 3345, 7473, 3580, 123 Passed LTC: https://tests.stockfishchess.org/tests/view/67ba538c01f3463ae1d35e69 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 33168 W: 8529 L: 8219 D: 16420 Ptnml(0-2): 12, 3505, 9262, 3771, 34 closes https://github.com/official-stockfish/Stockfish/pull/5895 Bench: 2290732 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 5ec0def6a..6a2f8f2d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1260,6 +1260,8 @@ moves_loop: // When in check, search starts here int bonus = (value >= beta) * 2010; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } + else if (value > alpha && value < bestValue + 9) + newDepth--; } // Step 18. Full-depth search when LMR is skipped From 0f9ae0d11cd034288a49ef3892c580dfed025091 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Feb 2025 10:50:41 +0100 Subject: [PATCH 03/39] Check maximum time every iteration This fixes a TCEC timeloss, where slow DTZ TB7 access, in combination with syzygy PV extension, led to a timeloss. While the extension code correctly aborted after spending moveOverhead/2 time, the mainThread did not search sufficient nodes (512 in > 1s) to trigger the stop in check_time. At the same time, totalTime exceeded tm.maximum() due to the factors multiplying tm.optimum(). This corner case is fixed by checking also against the tm.maximum() at each iteration. Even though this problem can't be triggered on fishtest, the patch was verified there. Passed STC: https://tests.stockfishchess.org/tests/view/67b99e1be99f8640b318810d LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 136832 W: 35625 L: 35518 D: 65689 Ptnml(0-2): 499, 14963, 37431, 14978, 545 closes https://github.com/official-stockfish/Stockfish/pull/5896 No functional change --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 6a2f8f2d2..edd8d9eb5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -520,8 +520,8 @@ void Search::Worker::iterative_deepening() { && !mainThread->ponder) threads.stop = true; - // Stop the search if we have exceeded the totalTime - if (elapsedTime > totalTime) + // Stop the search if we have exceeded the totalTime or maximum + if (elapsedTime > std::min(totalTime, double(mainThread->tm.maximum()))) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". From 93b966829bd7d2d9d9dce49f11e20125f48d0cfd Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 24 Feb 2025 14:13:24 +0300 Subject: [PATCH 04/39] Simplify bestvalue update formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 124960 W: 32598 L: 32472 D: 59890 Ptnml(0-2): 480, 14852, 31694, 14970, 484 https://tests.stockfishchess.org/tests/view/67b348bae00eea114cdba37d Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 150306 W: 38220 L: 38132 D: 73954 Ptnml(0-2): 98, 16430, 42005, 16526, 94 https://tests.stockfishchess.org/tests/view/67b5e37918a66624a7a3f751 closes https://github.com/official-stockfish/Stockfish/pull/5898 Bench: 2146010 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index edd8d9eb5..21b328fbe 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1720,8 +1720,8 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return mated_in(ss->ply); // Plies to mate from the root } - if (!is_decisive(bestValue) && bestValue >= beta) - bestValue = (3 * bestValue + beta) / 4; + if (!is_decisive(bestValue) && bestValue > beta) + bestValue = (bestValue + beta) / 2; // Save gathered info in transposition table. The static evaluation // is saved as it was before adjustment by correction history. From d330b48e21f688e80b44001a912168097646671d Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Feb 2025 11:32:27 -0800 Subject: [PATCH 05/39] Handle updating the small accumulator the same way as the big one https://tests.stockfishchess.org/tests/view/67abfe1ca04df5eb8dbebf0b LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 153088 W: 40072 L: 39979 D: 73037 Ptnml(0-2): 619, 16728, 41764, 16807, 626 closes https://github.com/official-stockfish/Stockfish/pull/5901 No functional change --- src/nnue/features/half_ka_v2_hm.cpp | 4 ---- src/nnue/features/half_ka_v2_hm.h | 5 ----- src/nnue/nnue_feature_transformer.h | 15 ++++----------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 5bb0296e2..81eddb060 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -77,10 +77,6 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& removed, IndexList& added); -int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; } - -int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count(); } - bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { return st->dirtyPiece.piece[0] == 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 ca940c54e..0a420cd1e 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -135,11 +135,6 @@ class HalfKAv2_hm { static void append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - // Returns the cost of updating one perspective, the most costly one. - // Assumes no refresh needed. - static int update_cost(const StateInfo* st); - static int refresh_cost(const Position& pos); - // Returns whether the change stored in this StateInfo means // that a full accumulator refresh is required. static bool requires_refresh(const StateInfo* st, Color perspective); diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 931d9aed5..60a044158 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -254,7 +254,6 @@ class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig; private: using Tiling = SIMDTiling; @@ -836,23 +835,17 @@ class FeatureTransformer { if ((st->*accPtr).computed[Perspective]) return; // nothing to do - [[maybe_unused]] // only used when !Big - int gain = FeatureSet::refresh_cost(pos); // Look for a usable already computed accumulator of an earlier position. - // When computing the small accumulator, we keep track of the estimated gain in - // terms of features to be added/subtracted. - // When computing the big accumulator, we expect to be able to reuse any - // accumulators, so we always try to do an incremental update. + // Always try to do an incremental update as most accumulators will be reusable. do { - if (FeatureSet::requires_refresh(st, Perspective) - || (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !st->previous + if (FeatureSet::requires_refresh(st, Perspective) || !st->previous || st->previous->next != st) { // compute accumulator from scratch for this position update_accumulator_refresh_cache(pos, cache); - if (Big && st != pos.state()) - // when computing a big accumulator from scratch we can use it to + if (st != pos.state()) + // when computing an accumulator from scratch we can use it to // efficiently compute the accumulator backwards, until we get to a king // move. We expect that we will need these accumulators later anyway, so // computing them now will save some work. From e4d7136042f46d0002b91ac5a49602369dbe3d05 Mon Sep 17 00:00:00 2001 From: mstembera Date: Mon, 24 Feb 2025 11:42:03 -0800 Subject: [PATCH 06/39] Combine last 3 add/remove operations https://tests.stockfishchess.org/tests/view/67ad587052879dfd14d7e8a5 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 45856 W: 12177 L: 11855 D: 21824 Ptnml(0-2): 176, 4845, 12588, 5119, 200 The two most common cases are when added and removed counts are equal and when they are off by 1. When they are off by 1 we currently do a pass combining 2 and then an extra pass for the last 1. This patch does a single combined pass on the final 3 instead. Tested on top of the simplification in https://github.com/official-stockfish/Stockfish/pull/5901 closes https://github.com/official-stockfish/Stockfish/pull/5902 No functional change --- src/nnue/nnue_feature_transformer.h | 61 +++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 60a044158..7e4c669ae 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -704,6 +704,8 @@ class FeatureTransformer { accumulator.computed[Perspective] = true; #ifdef VECTOR + const bool combineLast3 = std::abs((int) removed.size() - (int) added.size()) == 1 + && removed.size() + added.size() > 2; vec_t acc[Tiling::NumRegs]; psqt_vec_t psqt[Tiling::NumPsqtRegs]; @@ -717,7 +719,7 @@ class FeatureTransformer { acc[k] = entryTile[k]; std::size_t i = 0; - for (; i < std::min(removed.size(), added.size()); ++i) + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) { IndexType indexR = removed[i]; const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; @@ -729,23 +731,56 @@ class FeatureTransformer { for (IndexType k = 0; k < Tiling::NumRegs; ++k) acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); } - for (; i < removed.size(); ++i) + if (combineLast3) { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); + IndexType indexR = removed[i]; + const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&weights[offsetA]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = HalfDimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = reinterpret_cast(&weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = HalfDimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = reinterpret_cast(&weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } } - for (; i < added.size(); ++i) + else { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } } for (IndexType k = 0; k < Tiling::NumRegs; k++) From 09faa626210e8f72cacc35887823c4929c36a86b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 23 Feb 2025 00:40:06 -0800 Subject: [PATCH 07/39] Simplify NMP Conditions Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 142400 W: 36883 L: 36779 D: 68738 Ptnml(0-2): 467, 16804, 36571, 16874, 484 https://tests.stockfishchess.org/tests/view/67bd1898e4a8d7152b974ef1 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 251868 W: 63905 L: 63920 D: 124043 Ptnml(0-2): 133, 27480, 70708, 27495, 118 https://tests.stockfishchess.org/tests/view/67bd1898e4a8d7152b974ef1 closes https://github.com/official-stockfish/Stockfish/pull/5906 Bench: 2188400 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 21b328fbe..89a44b930 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -846,8 +846,8 @@ Value Search::Worker::search( // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 455 - 60 * improving && !excludedMove - && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + && ss->staticEval >= beta - 21 * depth + 395 && !excludedMove && pos.non_pawn_material(us) + && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); From a730b4d08b6429b5ec345f7bc607ec9df0988b71 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Wed, 26 Feb 2025 03:06:47 +0300 Subject: [PATCH 08/39] Remove two unnecessary divisions Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 280768 W: 72187 L: 72236 D: 136345 Ptnml(0-2): 815, 33131, 72550, 33064, 824 https://tests.stockfishchess.org/tests/view/67bcf7afe670525923b8a101 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 108684 W: 27666 L: 27536 D: 53482 Ptnml(0-2): 40, 11768, 30606, 11878, 50 https://tests.stockfishchess.org/tests/view/67be472ed8d5c2c657c52cb8 closes https://github.com/official-stockfish/Stockfish/pull/5908 Bench: 2400689 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 89a44b930..7e5cf5f92 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -699,13 +699,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(117600 * depth - 71344, 1244992) / 1024); + std::min(115 * depth - 70, 1216)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(779788 * (depth + 1) - 271806, 2958308) - / 1024); + -std::min(762 * (depth + 1) - 266, 2889)); } // Partial workaround for the graph history interaction problem From 6d9c6f99b9439cf083175d8c4c36ccb77fd31789 Mon Sep 17 00:00:00 2001 From: Jake Senne Date: Thu, 20 Feb 2025 22:53:16 -0600 Subject: [PATCH 09/39] Replace aligned() function with line_bb() and simplify king piece detection From https://discord.com/channels/435943710472011776/813919248455827515/1342267241168900228 Saves 6 instructions closes https://github.com/official-stockfish/Stockfish/pull/5909 No functional change --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37e9a2eb5..1e19e3c8a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; + return !(line_bb(from, to) & pieces(~sideToMove, KING) || m.type_of() == CASTLING); switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From 5c617e579cb44215a8904b879b24d3e7bbd78412 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 27 Feb 2025 18:31:40 +0300 Subject: [PATCH 10/39] VVLTC Search Tune Passed VVLTC with STC bounds: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 15788 W: 4106 L: 3868 D: 7814 Ptnml(0-2): 0, 1324, 5009, 1560, 1 https://tests.stockfishchess.org/tests/view/67bf2ddd6e569f6234102ade Passed VVLTC with LTC bounds: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 13622 W: 3620 L: 3368 D: 6634 Ptnml(0-2): 3, 1190, 4170, 1448, 0 https://tests.stockfishchess.org/tests/view/67c04308c8f7c4c0632d8055 closes https://github.com/official-stockfish/Stockfish/pull/5910 Bench: 1823605 --- src/search.cpp | 189 +++++++++++++++++++++++++------------------------ 1 file changed, 95 insertions(+), 94 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7e5cf5f92..bbd43ed69 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -71,7 +71,7 @@ namespace { // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { - Value futilityMult = 112 - 26 * noTtCutNode; + Value futilityMult = 110 - 25 * noTtCutNode; Value improvingDeduction = improving * futilityMult * 2; Value worseningDeduction = oppWorsening * futilityMult / 3; @@ -93,25 +93,26 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; + return 7685 * pcv + 7495 * micv + 9144 * (wnpcv + bnpcv) + 6469 * cntcv; } int risk_tolerance(const Position& pos, Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return -345600 * x / (x * x + 3 * y * y); + return -355752 * x / (x * x + 3 * y * y); }; - int material = pos.count() + 3 * pos.count() + 3 * pos.count() - + 5 * pos.count() + 9 * pos.count(); + int material = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; int m = std::clamp(material, 17, 78); // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = ((-m * 3220 / 256 + 2361) * m / 256 - 586) * m / 256 + 421; - int b = ((m * 7761 / 256 - 2674) * m / 256 + 314) * m / 256 + 51; + int a = ((-m * 3037 / 256 + 2270) * m / 256 - 637) * m / 256 + 413; + int b = ((m * 7936 / 256 - 2255) * m / 256 + 319) * m / 256 + 83; // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) @@ -119,7 +120,7 @@ int risk_tolerance(const Position& pos, Value v) { int winning_risk = sigmoid_d2(v - a, b); int losing_risk = -sigmoid_d2(-v - a, b); - return (winning_risk + losing_risk) * 60 / b; + return (winning_risk + losing_risk) * 58 / b; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -135,11 +136,11 @@ void update_correction_history(const Position& pos, const Move m = (ss - 1)->currentMove; const Color us = pos.side_to_move(); - static constexpr int nonPawnWeight = 165; + static constexpr int nonPawnWeight = 162; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 109 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 141 / 128; + << bonus * 111 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] @@ -147,7 +148,7 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 138 / 128; + << bonus * 143 / 128; } // Add a small random component to draw evaluations to avoid 3-fold blindness @@ -326,7 +327,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(95); + lowPlyHistory.fill(92); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -362,13 +363,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13000; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 11834; Value avg = rootMoves[pvIdx].averageScore; alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); // Adjust optimism based on root move's averageScore - optimism[us] = 138 * avg / (std::abs(avg) + 81); + optimism[us] = 138 * avg / (std::abs(avg) + 84); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -552,27 +553,27 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(65); - lowPlyHistory.fill(107); - captureHistory.fill(-655); - pawnHistory.fill(-1215); - pawnCorrectionHistory.fill(4); + mainHistory.fill(66); + lowPlyHistory.fill(105); + captureHistory.fill(-646); + pawnHistory.fill(-1262); + pawnCorrectionHistory.fill(6); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); for (auto& to : continuationCorrectionHistory) for (auto& h : to) - h.fill(0); + h.fill(5); for (bool inCheck : {false, true}) for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-493); + h.fill(-468); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2937 / 128.0 * std::log(i)); + reductions[i] = int(2954 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -699,12 +700,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(115 * depth - 70, 1216)); + std::min(120 * depth - 75, 1241)); // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(762 * (depth + 1) - 266, 2889)); + -std::min(809 * (depth + 1) - 249, 3052)); } // Partial workaround for the graph history interaction problem @@ -808,11 +809,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 bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1906, 1450) + 638; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1136 / 1024; + int bonus = std::clamp(-10 * int((ss - 1)->staticEval + ss->staticEval), -1950, 1416) + 655; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1124 / 1024; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << bonus * 1195 / 1024; + << bonus * 1196 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -825,33 +826,33 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 200) + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 188) 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 - 446 - 303 * depth * depth) + if (!PvNode && eval < alpha - 461 - 315 * depth * depth) return qsearch(pos, ss, alpha, beta); // Step 8. Futility pruning: child node // The depth condition is important for mate finding. if (!ss->ttPv && depth < 14 && eval - futility_margin(depth, cutNode && !ss->ttHit, improving, opponentWorsening) - - (ss - 1)->statScore / 326 + 37 - std::abs(correctionValue) / 132821 + - (ss - 1)->statScore / 301 + 37 - std::abs(correctionValue) / 139878 >= beta && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 21 * depth + 395 && !excludedMove && pos.non_pawn_material(us) + && ss->staticEval >= beta - 19 * depth + 418 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and eval - Depth R = std::min(int(eval - beta) / 237, 6) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 232, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -884,7 +885,7 @@ Value Search::Worker::search( } } - improving |= ss->staticEval >= beta + 97; + improving |= ss->staticEval >= beta + 94; // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. @@ -895,7 +896,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 + 187 - 55 * improving; + probCutBeta = beta + 185 - 58 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -958,7 +959,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 413; + probCutBeta = beta + 415; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) return probCutBeta; @@ -1024,7 +1025,7 @@ moves_loop: // When in check, search starts here // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls if (ss->ttPv) - r += 1031; + r += 979; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1046,15 +1047,15 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 242 + 238 * lmrDepth - + PieceValue[capturedPiece] + 95 * captHist / 700; + Value futilityValue = ss->staticEval + 242 + 230 * lmrDepth + + PieceValue[capturedPiece] + 133 * captHist / 1024; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 36, -153 * depth, 134 * depth); - if (!pos.see_ge(move, -157 * depth - seeHist)) + int seeHist = std::clamp(captHist / 32, -138 * depth, 135 * depth); + if (!pos.see_ge(move, -154 * depth - seeHist)) continue; } else @@ -1065,17 +1066,17 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -4107 * depth) + if (history < -4348 * depth) continue; history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3576; + lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 49 : 143) + 116 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth; - if (bestValue < ss->staticEval - 150 && lmrDepth < 7) - futilityValue += 108; + if (bestValue < ss->staticEval - 128 && lmrDepth < 8) + futilityValue += 103; // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning @@ -1091,7 +1092,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -27 * lmrDepth * lmrDepth)) continue; } } @@ -1111,11 +1112,11 @@ moves_loop: // When in check, search starts here // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove - && depth >= 5 - (thisThread->completedDepth > 32) + ss->ttPv + && depth >= 6 - (thisThread->completedDepth > 29) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (55 + 81 * (ss->ttPv && !PvNode)) * depth / 58; + Value singularBeta = ttData.value - (59 + 77 * (ss->ttPv && !PvNode)) * depth / 54; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1125,11 +1126,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj1 = std::abs(correctionValue) / 265083; - int corrValAdj2 = std::abs(correctionValue) / 253680; - int doubleMargin = 267 * PvNode - 181 * !ttCapture - corrValAdj1; + int corrValAdj1 = std::abs(correctionValue) / 248873; + int corrValAdj2 = std::abs(correctionValue) / 255331; + int doubleMargin = 262 * PvNode - 188 * !ttCapture - corrValAdj1; int tripleMargin = - 96 + 282 * PvNode - 250 * !ttCapture + 103 * ss->ttPv - corrValAdj2; + 88 + 265 * PvNode - 256 * !ttCapture + 93 * ss->ttPv - corrValAdj2; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1182,46 +1183,46 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2230 + PvNode * 1013 + (ttData.value > alpha) * 925 - + (ttData.depth >= depth) * (971 + cutNode * 1159); + r -= 2381 + PvNode * 1008 + (ttData.value > alpha) * 880 + + (ttData.depth >= depth) * (1022 + cutNode * 1140); // These reduction adjustments have no proven non-linear scaling - r += 316 - moveCount * 32; + r += 306 - moveCount * 34; - r -= std::abs(correctionValue) / 31568; + r -= std::abs(correctionValue) / 29696; if (PvNode && !is_decisive(bestValue)) r -= risk_tolerance(pos, bestValue); // Increase reduction for cut nodes if (cutNode) - r += 2608 + 1024 * !ttData.move; + r += 2784 + 1038 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) - r += 1123 + (depth < 8) * 982; + r += 1171 + (depth < 8) * 985; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) - r += 981 + allNode * 833; + r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction else if (move == ttData.move) - r -= 1982; + r -= 1937; if (capture) ss->statScore = - 688 * int(PieceValue[pos.captured_piece()]) / 100 + 846 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 4653; + - 4822; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3591; + + (*contHist[1])[movedPiece][move.to_sq()] - 3271; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 1407 / 16384; + r -= ss->statScore * 1582 / 16384; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1247,7 +1248,7 @@ moves_loop: // When in check, search starts here { // Adjust full-depth search based on LMR results - if the result was // good enough search deeper, if it was bad enough search shallower. - const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); + const bool doDeeperSearch = value > (bestValue + 43 + 2 * newDepth); const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1256,7 +1257,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 2010; + int bonus = (value >= beta) * 1800; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } else if (value > alpha && value < bestValue + 9) @@ -1268,11 +1269,11 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 1111; + r += 1156; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3554) - (r > 5373 && newDepth > 2), !cutNode); + newDepth - (r > 3495) - (r > 5510 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1379,7 +1380,7 @@ moves_loop: // When in check, search starts here else { // Reduce other moves if we have found at least one score improvement - if (depth > 2 && depth < 15 && !is_decisive(value)) + if (depth > 2 && depth < 16 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1423,25 +1424,25 @@ moves_loop: // When in check, search starts here // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { - int bonusScale = (118 * (depth > 5) + 36 * !allNode + 161 * ((ss - 1)->moveCount > 8) - + 133 * (!ss->inCheck && bestValue <= ss->staticEval - 107) - + 120 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 84) - + 81 * ((ss - 1)->isTTMove) + 100 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 108, 320)); + int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = std::min(160 * depth - 106, 1523) * bonusScale; + const int scaledBonus = std::min(160 * depth - 99, 1492) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 416 / 32768); + scaledBonus * 388 / 32768); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 219 / 32768; + << scaledBonus * 212 / 32768; if (type_of(pos.piece_on(prevSq)) != PAWN && ((ss - 1)->currentMove).type_of() != PROMOTION) thisThread->pawnHistory[pawn_structure_index(pos)][pos.piece_on(prevSq)][prevSq] - << scaledBonus * 1103 / 32768; + << scaledBonus * 1055 / 32768; } else if (priorCapture && prevSq != SQ_NONE) @@ -1450,7 +1451,7 @@ moves_loop: // When in check, search starts here Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << std::min(330 * depth - 198, 3320); + << std::min(300 * depth - 182, 2995); } if (PvNode) @@ -1601,7 +1602,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 325; + futilityBase = ss->staticEval + 359; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1664,7 +1665,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5389) + <= 5923) continue; // Do not search moves with bad enough SEE values @@ -1735,7 +1736,7 @@ 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 * 735 / rootDelta + !i * reductionScale * 191 / 512 + 1132; + return reductionScale - delta * 764 / rootDelta + !i * reductionScale * 191 / 512 + 1087; } // elapsed() returns the time elapsed since the search started. If the @@ -1831,35 +1832,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = std::min(162 * depth - 92, 1587) + 298 * isTTMove; - int malus = std::min(694 * depth - 230, 2503) - 32 * (moveCount - 1); + int bonus = std::min(141 * depth - 89, 1613) + 311 * isTTMove; + int malus = std::min(695 * depth - 215, 2808) - 31 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1202 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1129 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1152 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1246 / 1024); } else { // Increase stats for the best move in case it was a capture move captured = type_of(pos.piece_on(bestMove.to_sq())); - captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1236 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1187 / 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 * 976 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 987 / 1024); // Decrease stats for all non-best capture moves for (Move move : capturesSearched) { moved_piece = pos.moved_piece(move); captured = type_of(pos.piece_on(move.to_sq())); - captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1224 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1377 / 1024; } } @@ -1868,7 +1869,7 @@ void update_all_stats(const Position& pos, // 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, 1029}, {2, 656}, {3, 326}, {4, 536}, {5, 120}, {6, 537}}}; + {{1, 1103}, {2, 659}, {3, 323}, {4, 533}, {5, 121}, {6, 474}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1889,12 +1890,12 @@ void update_quiet_histories( workerThread.mainHistory[us][move.from_to()] << bonus; // Untuned to prevent duplicate effort if (ss->ply < LOW_PLY_HISTORY_SIZE) - workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 844 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 829 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 964 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 1004 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 615 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 587 / 1024; } } From f3bfce353168b03e4fedce515de1898c691f81ec Mon Sep 17 00:00:00 2001 From: xu-shawn <50402888+xu-shawn@users.noreply.github.com> Date: Fri, 28 Feb 2025 00:50:59 -0800 Subject: [PATCH 11/39] Revert "Replace aligned() function with line_bb() and simplify king piece detection" (#5915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/official-stockfish/Stockfish/pull/5915 No functional change Co-authored-by: Robert Nürnberg --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 1e19e3c8a..37e9a2eb5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); + return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !(line_bb(from, to) & pieces(~sideToMove, KING) || m.type_of() == CASTLING); + return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From 99d32e395ee0509578553d75580234e90de89d66 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 28 Feb 2025 00:55:29 -0800 Subject: [PATCH 12/39] Reapply #5909 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This version fixes the logic of `gives_check`, which was identified to be the cause of illegal moves. closes https://github.com/official-stockfish/Stockfish/pull/5914 No functional change Co-authored-by: Robert Nürnberg Co-authored-by: gab8192 --- src/position.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37e9a2eb5..14599a76d 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -560,7 +560,7 @@ bool Position::legal(Move m) const { // A non-king move is legal if and only if it is not pinned or it // is moving along the ray towards or away from the king. - return !(blockers_for_king(us) & from) || aligned(from, to, square(us)); + return !(blockers_for_king(us) & from) || line_bb(from, to) & pieces(us, KING); } @@ -648,7 +648,7 @@ bool Position::gives_check(Move m) const { // Is there a discovered check? if (blockers_for_king(~sideToMove) & from) - return !aligned(from, to, square(~sideToMove)) || m.type_of() == CASTLING; + return !(line_bb(from, to) & pieces(~sideToMove, KING)) || m.type_of() == CASTLING; switch (m.type_of()) { @@ -656,7 +656,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION : - return attacks_bb(m.promotion_type(), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(m.promotion_type(), to, pieces() ^ from) & pieces(~sideToMove, KING); // En passant capture with check? We have already handled the case of direct // checks and ordinary discovered check, so the only case we need to handle From b825ea6e57f224d7f468b1562a71fa4c22fe2fd8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 28 Feb 2025 09:30:43 -0800 Subject: [PATCH 13/39] Improve Perft Testing Added #5909 problematic position and chess-library DFRC positions to perft testing. Additional work done by @Disservin to clean up and improve error reporting. closes https://github.com/official-stockfish/Stockfish/pull/5917 No functional change Co-authored-by: disservin --- tests/perft.sh | 93 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 16 deletions(-) diff --git a/tests/perft.sh b/tests/perft.sh index c1532c20c..97c462c57 100755 --- a/tests/perft.sh +++ b/tests/perft.sh @@ -1,6 +1,8 @@ #!/bin/bash # verify perft numbers (positions from https://www.chessprogramming.org/Perft_Results) +TESTS_FAILED=0 + error() { echo "perft testing failed on line $1" @@ -10,23 +12,82 @@ trap 'error ${LINENO}' ERR echo "perft testing started" -cat << EOF > perft.exp - set timeout 10 - lassign \$argv pos depth result - spawn ./stockfish - send "position \$pos\\ngo perft \$depth\\n" - expect "Nodes searched? \$result" {} timeout {exit 1} - send "quit\\n" - expect eof +EXPECT_SCRIPT=$(mktemp) + +cat << 'EOF' > $EXPECT_SCRIPT +#!/usr/bin/expect -f +set timeout 30 +lassign [lrange $argv 0 4] pos depth result chess960 logfile +log_file -noappend $logfile +spawn ./stockfish +if {$chess960 == "true"} { + send "setoption name UCI_Chess960 value true\n" +} +send "position $pos\ngo perft $depth\n" +expect { + "Nodes searched: $result" {} + timeout {puts "TIMEOUT: Expected $result nodes"; exit 1} + eof {puts "EOF: Stockfish crashed"; exit 2} +} +send "quit\n" +expect eof EOF -expect perft.exp startpos 5 4865609 > /dev/null -expect perft.exp "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 > /dev/null -expect perft.exp "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 6 11030083 > /dev/null -expect perft.exp "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 5 15833292 > /dev/null -expect perft.exp "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 > /dev/null -expect perft.exp "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 > /dev/null +chmod +x $EXPECT_SCRIPT -rm perft.exp +run_test() { + local pos="$1" + local depth="$2" + local expected="$3" + local chess960="$4" + local tmp_file=$(mktemp) -echo "perft testing OK" + echo -n "Testing depth $depth: ${pos:0:40}... " + + if $EXPECT_SCRIPT "$pos" "$depth" "$expected" "$chess960" "$tmp_file" > /dev/null 2>&1; then + echo "OK" + rm -f "$tmp_file" + else + local exit_code=$? + echo "FAILED (exit code: $exit_code)" + echo "===== Output for failed test =====" + cat "$tmp_file" + echo "==================================" + rm -f "$tmp_file" + TESTS_FAILED=1 + fi +} + +# standard positions + +run_test "startpos" 7 3195901860 "false" +run_test "fen r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -" 5 193690690 "false" +run_test "fen 8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -" 7 178633661 "false" +run_test "fen r3k2r/Pppp1ppp/1b3nbN/nP6/BBP1P3/q4N2/Pp1P2PP/R2Q1RK1 w kq - 0 1" 6 706045033 "false" +run_test "fen rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8" 5 89941194 "false" +run_test "fen r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10" 5 164075551 "false" +run_test "fen r7/4p3/5p1q/3P4/4pQ2/4pP2/6pp/R3K1kr w Q - 1 3" 5 11609488 "false" + +# chess960 positions + +run_test "fen rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1" 6 119060324 "true" +run_test "fen 1rqbkrbn/1ppppp1p/1n6/p1N3p1/8/2P4P/PP1PPPP1/1RQBKRBN w FBfb - 0 9" 6 191762235 "true" +run_test "fen rbbqn1kr/pp2p1pp/6n1/2pp1p2/2P4P/P7/BP1PPPP1/R1BQNNKR w HAha - 0 9" 6 924181432 "true" +run_test "fen rqbbknr1/1ppp2pp/p5n1/4pp2/P7/1PP5/1Q1PPPPP/R1BBKNRN w GAga - 0 9" 6 308553169 "true" +run_test "fen 4rrb1/1kp3b1/1p1p4/pP1Pn2p/5p2/1PR2P2/2P1NB1P/2KR1B2 w D - 0 21" 6 872323796 "true" +run_test "fen 1rkr3b/1ppn3p/3pB1n1/6q1/R2P4/4N1P1/1P5P/2KRQ1B1 b Dbd - 0 14" 6 2678022813 "true" +run_test "fen qbbnrkr1/p1pppppp/1p4n1/8/2P5/6N1/PPNPPPPP/1BRKBRQ1 b FCge - 1 3" 6 521301336 "true" +run_test "fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2R2R b E - 1 20" 2 1438 "true" +run_test "fen rr6/2kpp3/1ppn2p1/p2b1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 w E - 0 20" 3 37340 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p2Q1q1p/P4P1P/1PNN2P1/2PP4/1K2RR2 b E - 2 19" 4 2237725 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 4 2098209 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 5 79014522 "true" +run_test "fen rr6/2kpp3/1ppnb1p1/p4q1p/P4P1P/1PNN2P1/2PP2Q1/1K2RR2 w E - 1 19" 6 2998685421 "true" + +rm -f $EXPECT_SCRIPT +echo "perft testing completed" + +if [ $TESTS_FAILED -ne 0 ]; then + echo "Some tests failed" + exit 1 +fi From e407a4f269ba4389f31e9bb71fd8b944e3056ced Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Fri, 28 Feb 2025 17:54:46 -0800 Subject: [PATCH 14/39] Simplify risk_tolerance + avoid overflow passed simplification STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 73984 W: 19058 L: 18879 D: 36047 Ptnml(0-2): 232, 8735, 18866, 8940, 219 https://tests.stockfishchess.org/tests/view/67c269a38200cf1034c9baf9 passed simplification LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 39288 W: 10033 L: 9833 D: 19422 Ptnml(0-2): 14, 4168, 11086, 4356, 20 https://tests.stockfishchess.org/tests/view/67c34f8c8200cf1034c9bda1 closes https://github.com/official-stockfish/Stockfish/pull/5919 Bench: 2050046 --- src/search.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bbd43ed69..bacd63c95 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -99,28 +100,28 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss int risk_tolerance(const Position& pos, Value v) { // Returns (some constant of) second derivative of sigmoid. static constexpr auto sigmoid_d2 = [](int x, int y) { - return -355752 * x / (x * x + 3 * y * y); + return 644800 * x / ((x * x + 3 * y * y) * y); }; - int material = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() - + 337 * pos.count() + 553 * pos.count()) - / 64; - - int m = std::clamp(material, 17, 78); + int m = (67 * pos.count() + 182 * pos.count() + 182 * pos.count() + + 337 * pos.count() + 553 * pos.count()) + / 64; // a and b are the crude approximation of the wdl model. // The win rate is: 1/(1+exp((a-v)/b)) // The loss rate is 1/(1+exp((v+a)/b)) - int a = ((-m * 3037 / 256 + 2270) * m / 256 - 637) * m / 256 + 413; - int b = ((m * 7936 / 256 - 2255) * m / 256 + 319) * m / 256 + 83; + int a = 356; + int b = ((65 * m - 3172) * m + 240578) / 2048; + // guard against overflow + assert(abs(v) + a <= std::numeric_limits::max() / 644800); // The risk utility is therefore d/dv^2 (1/(1+exp(-(v-a)/b)) -1/(1+exp(-(-v-a)/b))) // -115200x/(x^2+3) = -345600(ab) / (a^2+3b^2) (multiplied by some constant) (second degree pade approximant) int winning_risk = sigmoid_d2(v - a, b); - int losing_risk = -sigmoid_d2(-v - a, b); + int losing_risk = sigmoid_d2(v + a, b); - return (winning_risk + losing_risk) * 58 / b; + return -(winning_risk + losing_risk) * 32; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -1192,7 +1193,7 @@ moves_loop: // When in check, search starts here r -= std::abs(correctionValue) / 29696; - if (PvNode && !is_decisive(bestValue)) + if (PvNode && std::abs(bestValue) <= 2000) r -= risk_tolerance(pos, bestValue); // Increase reduction for cut nodes From e3660b47bdd8249c3e647b11f506058a99167a69 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 2 Mar 2025 21:38:11 -0800 Subject: [PATCH 15/39] Add dbg_clear helper function closes https://github.com/official-stockfish/Stockfish/pull/5921 No functional change --- src/misc.cpp | 15 ++++++++++++++- src/misc.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index 06a8c624a..f85356c59 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -287,12 +287,18 @@ namespace { template struct DebugInfo { - std::atomic data[N] = {0}; + std::array, N> data = {0}; [[nodiscard]] constexpr std::atomic& operator[](size_t index) { assert(index < N); return data[index]; } + + constexpr DebugInfo& operator=(const DebugInfo& other) { + for (size_t i = 0; i < N; i++) + data[i].store(other.data[i].load()); + return *this; + } }; struct DebugExtremes: public DebugInfo<3> { @@ -393,6 +399,13 @@ void dbg_print() { } } +void dbg_clear() { + hit.fill({}); + mean.fill({}); + stdev.fill({}); + correl.fill({}); + extremes.fill({}); +} // Used to serialize access to std::cout // to avoid multiple threads writing at the same time. diff --git a/src/misc.h b/src/misc.h index d2cbb699d..84f11d6de 100644 --- a/src/misc.h +++ b/src/misc.h @@ -73,6 +73,7 @@ void dbg_stdev_of(int64_t value, int slot = 0); void dbg_extremes_of(int64_t value, int slot = 0); void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0); void dbg_print(); +void dbg_clear(); using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits"); From f9a6d4328654f31cca5414be988b1158e555e09b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 6 Mar 2025 19:04:56 -0800 Subject: [PATCH 16/39] Simplify condition in futility pruning Passed STC LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 427040 W: 111061 L: 111271 D: 204708 Ptnml(0-2): 1709, 48524, 113171, 48500, 1616 https://tests.stockfishchess.org/tests/view/67af01d01a4c73ae1f930ea4 Passed rebased LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 28704 W: 7330 L: 7120 D: 14254 Ptnml(0-2): 8, 3000, 8138, 3186, 20 https://tests.stockfishchess.org/tests/view/67ca629a45214989aa0a123e closes https://github.com/official-stockfish/Stockfish/pull/5924 Bench: 2050046 --- src/search.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index bacd63c95..440cdc8e3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1074,10 +1074,8 @@ moves_loop: // When in check, search starts here lmrDepth += history / 3593; - Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth; - - if (bestValue < ss->staticEval - 128 && lmrDepth < 8) - futilityValue += 103; + Value futilityValue = ss->staticEval + (bestMove ? 48 : 146) + 116 * lmrDepth + + 103 * (bestValue < ss->staticEval - 128); // Futility pruning: parent node // (*Scaler): Generally, more frequent futility pruning From 66aee01bb1430ee25ba4df96e0c4c4a931759e4c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 2 Mar 2025 01:29:38 -0800 Subject: [PATCH 17/39] Simplify Return Value Adjustment Condition Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 82112 W: 21281 L: 21110 D: 39721 Ptnml(0-2): 258, 9630, 21112, 9795, 261 https://tests.stockfishchess.org/tests/view/67c42528b7226b5d8a2dd3a0 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 182652 W: 46295 L: 46240 D: 90117 Ptnml(0-2): 103, 20025, 51003, 20104, 91 https://tests.stockfishchess.org/tests/view/67c4d56b685e87e15e7c43d8 closes https://github.com/official-stockfish/Stockfish/pull/5925 Bench: 1711791 --- src/search.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 440cdc8e3..baf99c0c6 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1406,9 +1406,8 @@ moves_loop: // When in check, search starts here assert(moveCount || !ss->inCheck || excludedMove || !MoveList(pos).size()); - // Adjust best value for fail high cases at non-pv nodes - if (!PvNode && bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) - && !is_decisive(alpha)) + // Adjust best value for fail high cases + if (bestValue >= beta && !is_decisive(bestValue) && !is_decisive(beta) && !is_decisive(alpha)) bestValue = (bestValue * depth + beta) / (depth + 1); if (!moveCount) From fc0e0a44d407dfa440c83e86de9b338b7e2d092d Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 9 Mar 2025 19:33:30 -0700 Subject: [PATCH 18/39] Refactor accumulator storage/updates Passed Non-regression STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 115840 W: 29983 L: 29854 D: 56003 Ptnml(0-2): 338, 12990, 31149, 13091, 352 https://tests.stockfishchess.org/tests/view/67d0a044166a3e8781d84223 closes https://github.com/official-stockfish/Stockfish/pull/5927 No functional change --- src/Makefile | 3 +- src/evaluate.cpp | 16 +- src/evaluate.h | 2 + src/nnue/features/half_ka_v2_hm.cpp | 4 +- src/nnue/features/half_ka_v2_hm.h | 5 +- src/nnue/network.cpp | 15 +- src/nnue/network.h | 13 +- src/nnue/nnue_accumulator.cpp | 601 ++++++++++++++++++++++++++++ src/nnue/nnue_accumulator.h | 89 +++- src/nnue/nnue_common.h | 5 + src/nnue/nnue_feature_transformer.h | 449 +-------------------- src/nnue/nnue_misc.cpp | 15 +- src/perft.h | 1 - src/position.cpp | 47 +-- src/position.h | 24 +- src/search.cpp | 44 +- src/search.h | 7 + 17 files changed, 813 insertions(+), 527 deletions(-) create mode 100644 src/nnue/nnue_accumulator.cpp diff --git a/src/Makefile b/src/Makefile index 39cfce8bf..76b94785e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -55,7 +55,8 @@ PGOBENCH = $(WINE_PATH) ./$(EXE) bench SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ - nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp memory.cpp + nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \ + engine.cpp score.cpp memory.cpp HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \ diff --git a/src/evaluate.cpp b/src/evaluate.cpp index dddb56860..ccb089d97 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -54,21 +54,22 @@ bool Eval::use_smallnet(const Position& pos) { // of the position from the point of view of the side to move. Value Eval::evaluate(const Eval::NNUE::Networks& networks, const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, int optimism) { assert(!pos.checkers()); bool smallNet = use_smallnet(pos); - auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, &caches.small) - : networks.big.evaluate(pos, &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, &caches.big); + std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big); nnue = (125 * psqt + 131 * positional) / 128; smallNet = false; } @@ -99,7 +100,10 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) { if (pos.checkers()) return "Final evaluation: none (in check)"; - auto caches = std::make_unique(networks); + Eval::NNUE::AccumulatorStack accumulators; + auto caches = std::make_unique(networks); + + accumulators.reset(pos, networks, *caches); std::stringstream ss; ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2); @@ -107,12 +111,12 @@ 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, &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"; - v = evaluate(networks, pos, *caches, VALUE_ZERO); + v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO); v = pos.side_to_move() == WHITE ? v : -v; ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)"; ss << " [with scaled NNUE, ...]"; diff --git a/src/evaluate.h b/src/evaluate.h index aad358321..07b914007 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -39,6 +39,7 @@ namespace Eval { namespace NNUE { struct Networks; struct AccumulatorCaches; +class AccumulatorStack; } std::string trace(Position& pos, const Eval::NNUE::Networks& networks); @@ -47,6 +48,7 @@ int simple_eval(const Position& pos, Color c); bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, + Eval::NNUE::AccumulatorStack& accumulators, Eval::NNUE::AccumulatorCaches& caches, int optimism); } // namespace Eval diff --git a/src/nnue/features/half_ka_v2_hm.cpp b/src/nnue/features/half_ka_v2_hm.cpp index 81eddb060..eb3c7e6a7 100644 --- a/src/nnue/features/half_ka_v2_hm.cpp +++ b/src/nnue/features/half_ka_v2_hm.cpp @@ -77,8 +77,8 @@ template void HalfKAv2_hm::append_changed_indices(Square ksq, IndexList& removed, IndexList& added); -bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) { - return st->dirtyPiece.piece[0] == make_piece(perspective, KING); +bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) { + return dirtyPiece.piece[0] == make_piece(perspective, KING); } } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/features/half_ka_v2_hm.h b/src/nnue/features/half_ka_v2_hm.h index 0a420cd1e..ba122adc8 100644 --- a/src/nnue/features/half_ka_v2_hm.h +++ b/src/nnue/features/half_ka_v2_hm.h @@ -28,7 +28,6 @@ #include "../nnue_common.h" namespace Stockfish { -struct StateInfo; class Position; } @@ -135,9 +134,9 @@ class HalfKAv2_hm { static void append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added); - // Returns whether the change stored in this StateInfo means + // Returns whether the change stored in this DirtyPiece means // that a full accumulator refresh is required. - static bool requires_refresh(const StateInfo* st, Color perspective); + static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective); }; } // namespace Stockfish::Eval::NNUE::Features diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index fe312fcb8..cba3abc63 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -210,6 +210,7 @@ bool Network::save(const std::optional& filename template NetworkOutput Network::evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -229,8 +230,9 @@ Network::evaluate(const Position& pos ASSERT_ALIGNED(transformedFeatures, alignment); - const int bucket = (pos.count() - 1) / 4; - const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket); + const int bucket = (pos.count() - 1) / 4; + const auto psqt = + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); return {static_cast(psqt / OutputScale), static_cast(positional / OutputScale)}; } @@ -280,6 +282,7 @@ void Network::verify(std::string template NnueEvalTrace Network::trace_evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache) const { // We manually align the arrays on the stack because with gcc < 9.3 // overaligning stack variables with alignas() doesn't work correctly. @@ -303,7 +306,7 @@ Network::trace_evaluate(const Position& for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) { const auto materialist = - featureTransformer->transform(pos, cache, transformedFeatures, bucket); + featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket); const auto positional = network[bucket].propagate(transformedFeatures); t.psqt[bucket] = static_cast(materialist / OutputScale); @@ -447,14 +450,14 @@ bool Network::write_parameters(std::ostream& stream, return bool(stream); } -// Explicit template instantiation +// Explicit template instantiations template class Network< NetworkArchitecture, - FeatureTransformer>; + FeatureTransformer>; template class Network< NetworkArchitecture, - FeatureTransformer>; + FeatureTransformer>; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/network.h b/src/nnue/network.h index 764481d94..21df4b0a1 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -29,13 +29,16 @@ #include #include "../memory.h" -#include "../position.h" #include "../types.h" #include "nnue_accumulator.h" #include "nnue_architecture.h" #include "nnue_feature_transformer.h" #include "nnue_misc.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Eval::NNUE { enum class EmbeddedNNUEType { @@ -64,11 +67,13 @@ class Network { bool save(const std::optional& filename) const; NetworkOutput evaluate(const Position& pos, + AccumulatorStack& accumulatorStack, 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; private: @@ -100,16 +105,18 @@ class Network { template friend struct AccumulatorCaches::Cache; + + friend class AccumulatorStack; }; // Definitions of the network types using SmallFeatureTransformer = - FeatureTransformer; + FeatureTransformer; using SmallNetworkArchitecture = NetworkArchitecture; using BigFeatureTransformer = - FeatureTransformer; + FeatureTransformer; using BigNetworkArchitecture = NetworkArchitecture; using NetworkBig = Network; diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp new file mode 100644 index 000000000..0a3d95ad4 --- /dev/null +++ b/src/nnue/nnue_accumulator.cpp @@ -0,0 +1,601 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "nnue_accumulator.h" + +#include +#include +#include + +#include "../bitboard.h" +#include "../position.h" +#include "../types.h" +#include "nnue_architecture.h" +#include "network.h" +#include "nnue_common.h" +#include "nnue_feature_transformer.h" + +namespace Stockfish::Eval::NNUE { + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed); + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + const FeatureTransformer& featureTransformer, + const Position& pos, + AccumulatorState& accumulatorState, + AccumulatorCaches::Cache& cache); + +} + +void AccumulatorState::reset(const DirtyPiece& dp) noexcept { + dirtyPiece = dp; + accumulatorBig.computed.fill(false); + accumulatorSmall.computed.fill(false); +} + +const AccumulatorState& AccumulatorStack::latest() const noexcept { + return m_accumulators[m_current_idx - 1]; +} + +AccumulatorState& AccumulatorStack::mut_latest() noexcept { + return m_accumulators[m_current_idx - 1]; +} + +void AccumulatorStack::reset(const Position& rootPos, + const Networks& networks, + AccumulatorCaches& caches) noexcept { + m_current_idx = 1; + + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + update_accumulator_refresh_cache( + *networks.big.featureTransformer, rootPos, m_accumulators[0], caches.big); + + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); + update_accumulator_refresh_cache( + *networks.small.featureTransformer, rootPos, m_accumulators[0], caches.small); +} + +void AccumulatorStack::push(const DirtyPiece& dirtyPiece) noexcept { + assert(m_current_idx + 1 < m_accumulators.size()); + m_accumulators[m_current_idx].reset(dirtyPiece); + m_current_idx++; +} + +void AccumulatorStack::pop() noexcept { + assert(m_current_idx > 1); + m_current_idx--; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + evaluate_side(pos, featureTransformer, cache); + evaluate_side(pos, featureTransformer, cache); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::evaluate_side( + const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept { + + const auto last_usable_accum = find_last_usable_accumulator(); + + if ((m_accumulators[last_usable_accum].*accPtr).computed[Perspective]) + forward_update_incremental(pos, featureTransformer, last_usable_accum); + + else + { + update_accumulator_refresh_cache(featureTransformer, pos, mut_latest(), cache); + backward_update_incremental(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 AccumulatorState::*accPtr> +std::size_t AccumulatorStack::find_last_usable_accumulator() const noexcept { + + for (std::size_t curr_idx = m_current_idx - 1; curr_idx > 0; curr_idx--) + { + if ((m_accumulators[curr_idx].*accPtr).computed[Perspective]) + return curr_idx; + + if (FeatureSet::requires_refresh(m_accumulators[curr_idx].dirtyPiece, Perspective)) + return curr_idx; + } + + return 0; +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::forward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept { + + assert(begin < m_accumulators.size()); + assert((m_accumulators[begin].*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = begin + 1; next < m_current_idx; next++) + update_accumulator_incremental(featureTransformer, ksq, m_accumulators[next], + m_accumulators[next - 1]); + + assert((latest().*accPtr).computed[Perspective]); +} + +template AccumulatorState::*accPtr> +void AccumulatorStack::backward_update_incremental( + const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept { + + assert(end < m_accumulators.size()); + assert(end < m_current_idx); + assert((latest().*accPtr).computed[Perspective]); + + const Square ksq = pos.square(Perspective); + + for (std::size_t next = m_current_idx - 2; next >= end; next--) + update_accumulator_incremental( + featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); + + assert((m_accumulators[end].*accPtr).computed[Perspective]); +} + +// Explicit template instantiations +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; +template void +AccumulatorStack::evaluate( + const Position& pos, + const FeatureTransformer& + featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + +namespace { + +template AccumulatorState::*accPtr> +void update_accumulator_incremental( + const FeatureTransformer& featureTransformer, + const Square ksq, + AccumulatorState& target_state, + const AccumulatorState& computed) { + [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; + [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; + + assert(Forward != Backwards); + + assert((computed.*accPtr).computed[Perspective]); + assert(!(target_state.*accPtr).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 + // feature set's update cost calculation to be correct and never allow + // updates with more added/removed features than MaxActiveDimensions. + // In this case, the maximum size of both feature addition and removal + // is 2, since we are incrementally updating one move at a time. + FeatureSet::IndexList removed, added; + if constexpr (Forward) + FeatureSet::append_changed_indices(ksq, target_state.dirtyPiece, removed, + added); + else + FeatureSet::append_changed_indices(ksq, computed.dirtyPiece, added, removed); + + if (removed.size() == 0 && added.size() == 0) + { + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + } + else + { + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); + +#ifdef VECTOR + auto* accIn = + reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); + auto* accOut = + reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); + + const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; + auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); + const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; + auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); + + if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) + { + assert(added.size() == 1 && removed.size() == 1); + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), + vec_add_16(columnR0[i], columnR1[i])); + } + else if (Backwards && removed.size() == 1) + { + assert(added.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), + vec_sub_16(columnA1[i], columnR0[i])); + } + else + { + assert(added.size() == 2 && removed.size() == 2); + const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; + auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); + const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; + auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); + + for (IndexType i = 0; + i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) + accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), + vec_add_16(columnR0[i], columnR1[i]))); + } + + auto* accPsqtIn = + reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); + auto* accPsqtOut = + reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); + + const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; + auto* columnPsqtA0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); + const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; + auto* columnPsqtR0 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); + + if ((Forward && removed.size() == 1) + || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 + { + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = + vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); + } + else if (Forward && added.size() == 1) + { + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); + } + else if (Backwards && removed.size() == 1) + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), + vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); + } + else + { + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); + + for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); + ++i) + accPsqtOut[i] = vec_add_psqt_32( + accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), + vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); + } +#else + std::memcpy((target_state.*accPtr).accumulation[Perspective], + (computed.*accPtr).accumulation[Perspective], + TransformedFeatureDimensions * sizeof(BiasType)); + std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], + (computed.*accPtr).psqtAccumulation[Perspective], + PSQTBuckets * sizeof(PSQTWeightType)); + + // Difference calculation for the deactivated features + for (const auto index : removed) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] -= + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] -= + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } + + // Difference calculation for the activated features + for (const auto index : added) + { + const IndexType offset = TransformedFeatureDimensions * index; + for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) + (target_state.*accPtr).accumulation[Perspective][i] += + featureTransformer.weights[offset + i]; + + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (target_state.*accPtr).psqtAccumulation[Perspective][i] += + featureTransformer.psqtWeights[index * PSQTBuckets + i]; + } +#endif + } + + (target_state.*accPtr).computed[Perspective] = true; +} + +template AccumulatorState::*accPtr> +void update_accumulator_refresh_cache( + 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]; + FeatureSet::IndexList removed, added; + + for (Color c : {WHITE, BLACK}) + { + for (PieceType pt = PAWN; pt <= KING; ++pt) + { + const Piece piece = make_piece(c, pt); + const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; + const Bitboard newBB = pos.pieces(c, pt); + Bitboard toRemove = oldBB & ~newBB; + Bitboard toAdd = newBB & ~oldBB; + + while (toRemove) + { + Square sq = pop_lsb(toRemove); + removed.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + while (toAdd) + { + Square sq = pop_lsb(toAdd); + added.push_back(FeatureSet::make_index(sq, piece, ksq)); + } + } + } + + auto& accumulator = accumulatorState.*accPtr; + accumulator.computed[Perspective] = true; + +#ifdef VECTOR + const bool combineLast3 = + std::abs((int) removed.size() - (int) added.size()) == 1 && removed.size() + added.size() > 2; + vec_t acc[Tiling::NumRegs]; + psqt_vec_t psqt[Tiling::NumPsqtRegs]; + + for (IndexType j = 0; j < Dimensions / Tiling::TileHeight; ++j) + { + auto* accTile = + 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) + acc[k] = entryTile[k]; + + std::size_t i = 0; + for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + } + if (combineLast3) + { + IndexType indexR = removed[i]; + const IndexType offsetR = Dimensions * indexR + j * Tiling::TileHeight; + auto* columnR = reinterpret_cast(&featureTransformer.weights[offsetR]); + IndexType indexA = added[i]; + const IndexType offsetA = Dimensions * indexA + j * Tiling::TileHeight; + auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); + + if (removed.size() > added.size()) + { + IndexType indexR2 = removed[i + 1]; + const IndexType offsetR2 = Dimensions * indexR2 + j * Tiling::TileHeight; + auto* columnR2 = + reinterpret_cast(&featureTransformer.weights[offsetR2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), + vec_add_16(columnR[k], columnR2[k])); + } + else + { + IndexType indexA2 = added[i + 1]; + const IndexType offsetA2 = Dimensions * indexA2 + j * Tiling::TileHeight; + auto* columnA2 = + reinterpret_cast(&featureTransformer.weights[offsetA2]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), + vec_add_16(columnA[k], columnA2[k])); + } + } + else + { + for (; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_sub_16(acc[k], column[k]); + } + for (; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = Dimensions * index + j * Tiling::TileHeight; + auto* column = reinterpret_cast(&featureTransformer.weights[offset]); + + for (IndexType k = 0; k < Tiling::NumRegs; ++k) + acc[k] = vec_add_16(acc[k], column[k]); + } + } + + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&entryTile[k], acc[k]); + for (IndexType k = 0; k < Tiling::NumRegs; k++) + vec_store(&accTile[k], acc[k]); + } + + for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) + { + auto* accTilePsqt = reinterpret_cast( + &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); + auto* entryTilePsqt = + reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = entryTilePsqt[k]; + + for (std::size_t i = 0; i < removed.size(); ++i) + { + IndexType index = removed[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); + } + for (std::size_t i = 0; i < added.size(); ++i) + { + IndexType index = added[i]; + const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; + auto* columnPsqt = + reinterpret_cast(&featureTransformer.psqtWeights[offset]); + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); + } + + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&entryTilePsqt[k], psqt[k]); + for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) + vec_store_psqt(&accTilePsqt[k], psqt[k]); + } + +#else + + for (const auto index : removed) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] -= featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] -= featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + for (const auto index : added) + { + const IndexType offset = Dimensions * index; + for (IndexType j = 0; j < Dimensions; ++j) + entry.accumulation[j] += featureTransformer.weights[offset + j]; + + for (std::size_t k = 0; k < PSQTBuckets; ++k) + entry.psqtAccumulation[k] += featureTransformer.psqtWeights[index * PSQTBuckets + k]; + } + + // 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, + sizeof(BiasType) * Dimensions); + + std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, + sizeof(int32_t) * PSQTBuckets); +#endif + + for (Color c : {WHITE, BLACK}) + entry.byColorBB[c] = pos.pieces(c); + + for (PieceType pt = PAWN; pt <= KING; ++pt) + entry.byTypeBB[pt] = pos.pieces(pt); +} + +} + +} diff --git a/src/nnue/nnue_accumulator.h b/src/nnue/nnue_accumulator.h index 0d3d94135..362ea83e3 100644 --- a/src/nnue/nnue_accumulator.h +++ b/src/nnue/nnue_accumulator.h @@ -21,23 +21,43 @@ #ifndef NNUE_ACCUMULATOR_H_INCLUDED #define NNUE_ACCUMULATOR_H_INCLUDED +#include +#include #include +#include +#include +#include "../types.h" #include "nnue_architecture.h" #include "nnue_common.h" +namespace Stockfish { +class Position; +} + namespace Stockfish::Eval::NNUE { using BiasType = std::int16_t; using PSQTWeightType = std::int32_t; using IndexType = std::uint32_t; +struct Networks; + +template +struct alignas(CacheLineSize) Accumulator; + +struct AccumulatorState; + +template AccumulatorState::*accPtr> +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]; - bool computed[COLOR_NB]; + std::int16_t accumulation[COLOR_NB][Size]; + std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets]; + std::array computed; }; @@ -95,6 +115,69 @@ struct AccumulatorCaches { Cache small; }; + +struct AccumulatorState { + Accumulator accumulatorBig; + Accumulator accumulatorSmall; + DirtyPiece dirtyPiece; + + void reset(const DirtyPiece& dp) noexcept; +}; + + +class AccumulatorStack { + public: + AccumulatorStack() : + m_accumulators(MAX_PLY + 1), + m_current_idx{} {} + + [[nodiscard]] const AccumulatorState& latest() const noexcept; + + void + reset(const Position& rootPos, const Networks& networks, AccumulatorCaches& caches) noexcept; + void push(const DirtyPiece& dirtyPiece) noexcept; + void pop() noexcept; + + template AccumulatorState::*accPtr> + void evaluate(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + private: + [[nodiscard]] AccumulatorState& mut_latest() noexcept; + + template AccumulatorState::*accPtr> + void evaluate_side(const Position& pos, + const FeatureTransformer& featureTransformer, + AccumulatorCaches::Cache& cache) noexcept; + + template AccumulatorState::*accPtr> + [[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept; + + template AccumulatorState::*accPtr> + void + forward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t begin) noexcept; + + template AccumulatorState::*accPtr> + void + backward_update_incremental(const Position& pos, + const FeatureTransformer& featureTransformer, + const std::size_t end) noexcept; + + std::vector m_accumulators; + std::size_t m_current_idx; +}; + } // namespace Stockfish::Eval::NNUE #endif // NNUE_ACCUMULATOR_H_INCLUDED diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index f21a8dec7..b217c3583 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -279,6 +279,11 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size flush(); } +enum IncUpdateDirection { + FORWARD, + BACKWARDS +}; + } // namespace Stockfish::Eval::NNUE #endif // #ifndef NNUE_COMMON_H_INCLUDED diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7e4c669ae..20e85be3c 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -22,12 +22,9 @@ #define NNUE_FEATURE_TRANSFORMER_H_INCLUDED #include -#include #include #include #include -#include -#include #include "../position.h" #include "../types.h" @@ -41,11 +38,6 @@ using BiasType = std::int16_t; using WeightType = std::int16_t; using PSQTWeightType = std::int32_t; -enum IncUpdateDirection { - FORWARD, - BACKWARDS -}; - // If vector instructions are enabled, we update and refresh the // accumulator tile by tile such that each tile fits in the CPU's // vector registers. @@ -249,15 +241,12 @@ class SIMDTiling { // Input feature converter template StateInfo::*accPtr> + Accumulator AccumulatorState::*accPtr> class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; - private: - using Tiling = SIMDTiling; - public: // Output type using OutputType = TransformedFeatureType; @@ -348,19 +337,21 @@ class FeatureTransformer { // Convert input features std::int32_t transform(const Position& pos, + AccumulatorStack& accumulatorStack, AccumulatorCaches::Cache* cache, OutputType* output, int bucket) const { - update_accumulator(pos, cache); - update_accumulator(pos, cache); + + accumulatorStack.evaluate(pos, *this, *cache); + const auto& accumulatorState = accumulatorStack.latest(); const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()}; - const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation; + const auto& psqtAccumulation = (accumulatorState.*accPtr).psqtAccumulation; const auto psqt = (psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]) / 2; - const auto& accumulation = (pos.state()->*accPtr).accumulation; + const auto& accumulation = (accumulatorState.*accPtr).accumulation; for (IndexType p = 0; p < 2; ++p) { @@ -473,432 +464,6 @@ class FeatureTransformer { return psqt; } // end of function transform() - private: - // Given a computed accumulator, computes the accumulator of another position. - template - void update_accumulator_incremental(const Square ksq, - StateInfo* target_state, - const StateInfo* computed) const { - [[maybe_unused]] constexpr bool Forward = Direction == FORWARD; - [[maybe_unused]] constexpr bool Backwards = Direction == BACKWARDS; - assert((computed->*accPtr).computed[Perspective]); - - StateInfo* next = Forward ? computed->next : computed->previous; - - assert(next != nullptr); - assert(!(next->*accPtr).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 - // feature set's update cost calculation to be correct and never allow - // updates with more added/removed features than MaxActiveDimensions. - // In this case, the maximum size of both feature addition and removal - // is 2, since we are incrementally updating one move at a time. - FeatureSet::IndexList removed, added; - if constexpr (Forward) - FeatureSet::append_changed_indices(ksq, next->dirtyPiece, removed, added); - else - FeatureSet::append_changed_indices(ksq, computed->dirtyPiece, added, - removed); - - if (removed.size() == 0 && added.size() == 0) - { - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - } - else - { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); - -#ifdef VECTOR - auto* accIn = - reinterpret_cast(&(computed->*accPtr).accumulation[Perspective][0]); - auto* accOut = reinterpret_cast(&(next->*accPtr).accumulation[Perspective][0]); - - const IndexType offsetA0 = HalfDimensions * added[0]; - auto* columnA0 = reinterpret_cast(&weights[offsetA0]); - const IndexType offsetR0 = HalfDimensions * removed[0]; - auto* columnR0 = reinterpret_cast(&weights[offsetR0]); - - if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - const IndexType offsetR1 = HalfDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), - vec_add_16(columnR0[i], columnR1[i])); - } - else if (Backwards && removed.size() == 1) - { - assert(added.size() == 2); - const IndexType offsetA1 = HalfDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&weights[offsetA1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), - vec_sub_16(columnA1[i], columnR0[i])); - } - else - { - assert(added.size() == 2 && removed.size() == 2); - const IndexType offsetA1 = HalfDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&weights[offsetA1]); - const IndexType offsetR1 = HalfDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&weights[offsetR1]); - - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = - vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), - vec_add_16(columnR0[i], columnR1[i]))); - } - - auto* accPsqtIn = reinterpret_cast( - &(computed->*accPtr).psqtAccumulation[Perspective][0]); - auto* accPsqtOut = - reinterpret_cast(&(next->*accPtr).psqtAccumulation[Perspective][0]); - - const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; - auto* columnPsqtA0 = reinterpret_cast(&psqtWeights[offsetPsqtA0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - - if ((Forward && removed.size() == 1) - || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 - { - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), - columnPsqtA0[i]); - } - else if (Forward && added.size() == 1) - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = - vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); - } - else if (Backwards && removed.size() == 1) - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&psqtWeights[offsetPsqtA1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = - vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); - } - else - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&psqtWeights[offsetPsqtA1]); - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; - i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); ++i) - accPsqtOut[i] = vec_add_psqt_32( - accPsqtIn[i], - vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); - } -#else - std::memcpy((next->*accPtr).accumulation[Perspective], - (computed->*accPtr).accumulation[Perspective], - HalfDimensions * sizeof(BiasType)); - std::memcpy((next->*accPtr).psqtAccumulation[Perspective], - (computed->*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] -= weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] -= - psqtWeights[index * PSQTBuckets + i]; - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType i = 0; i < HalfDimensions; ++i) - (next->*accPtr).accumulation[Perspective][i] += weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (next->*accPtr).psqtAccumulation[Perspective][i] += - psqtWeights[index * PSQTBuckets + i]; - } -#endif - } - - (next->*accPtr).computed[Perspective] = true; - - if (next != target_state) - update_accumulator_incremental(ksq, target_state, next); - } - - - template - void update_accumulator_refresh_cache(const Position& pos, - AccumulatorCaches::Cache* cache) const { - assert(cache != nullptr); - - Square ksq = pos.square(Perspective); - auto& entry = (*cache)[ksq][Perspective]; - FeatureSet::IndexList removed, added; - - for (Color c : {WHITE, BLACK}) - { - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - const Piece piece = make_piece(c, pt); - const Bitboard oldBB = entry.byColorBB[c] & entry.byTypeBB[pt]; - const Bitboard newBB = pos.pieces(c, pt); - Bitboard toRemove = oldBB & ~newBB; - Bitboard toAdd = newBB & ~oldBB; - - while (toRemove) - { - Square sq = pop_lsb(toRemove); - removed.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - while (toAdd) - { - Square sq = pop_lsb(toAdd); - added.push_back(FeatureSet::make_index(sq, piece, ksq)); - } - } - } - - auto& accumulator = pos.state()->*accPtr; - accumulator.computed[Perspective] = true; - -#ifdef VECTOR - const bool combineLast3 = std::abs((int) removed.size() - (int) added.size()) == 1 - && removed.size() + added.size() > 2; - vec_t acc[Tiling::NumRegs]; - psqt_vec_t psqt[Tiling::NumPsqtRegs]; - - for (IndexType j = 0; j < HalfDimensions / Tiling::TileHeight; ++j) - { - auto* accTile = 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) - acc[k] = entryTile[k]; - - std::size_t i = 0; - for (; i < std::min(removed.size(), added.size()) - combineLast3; ++i) - { - IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&weights[offsetA]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); - } - if (combineLast3) - { - IndexType indexR = removed[i]; - const IndexType offsetR = HalfDimensions * indexR + j * Tiling::TileHeight; - auto* columnR = reinterpret_cast(&weights[offsetR]); - IndexType indexA = added[i]; - const IndexType offsetA = HalfDimensions * indexA + j * Tiling::TileHeight; - auto* columnA = reinterpret_cast(&weights[offsetA]); - - if (removed.size() > added.size()) - { - IndexType indexR2 = removed[i + 1]; - const IndexType offsetR2 = HalfDimensions * indexR2 + j * Tiling::TileHeight; - auto* columnR2 = reinterpret_cast(&weights[offsetR2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), - vec_add_16(columnR[k], columnR2[k])); - } - else - { - IndexType indexA2 = added[i + 1]; - const IndexType offsetA2 = HalfDimensions * indexA2 + j * Tiling::TileHeight; - auto* columnA2 = reinterpret_cast(&weights[offsetA2]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), - vec_add_16(columnA[k], columnA2[k])); - } - } - else - { - for (; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(acc[k], column[k]); - } - for (; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = HalfDimensions * index + j * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - - for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], column[k]); - } - } - - for (IndexType k = 0; k < Tiling::NumRegs; k++) - vec_store(&entryTile[k], acc[k]); - for (IndexType k = 0; k < Tiling::NumRegs; k++) - vec_store(&accTile[k], acc[k]); - } - - for (IndexType j = 0; j < PSQTBuckets / Tiling::PsqtTileHeight; ++j) - { - auto* accTilePsqt = reinterpret_cast( - &accumulator.psqtAccumulation[Perspective][j * Tiling::PsqtTileHeight]); - auto* entryTilePsqt = - reinterpret_cast(&entry.psqtAccumulation[j * Tiling::PsqtTileHeight]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = entryTilePsqt[k]; - - for (std::size_t i = 0; i < removed.size(); ++i) - { - IndexType index = removed[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = vec_sub_psqt_32(psqt[k], columnPsqt[k]); - } - for (std::size_t i = 0; i < added.size(); ++i) - { - IndexType index = added[i]; - const IndexType offset = PSQTBuckets * index + j * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - psqt[k] = vec_add_psqt_32(psqt[k], columnPsqt[k]); - } - - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - vec_store_psqt(&entryTilePsqt[k], psqt[k]); - for (std::size_t k = 0; k < Tiling::NumPsqtRegs; ++k) - vec_store_psqt(&accTilePsqt[k], psqt[k]); - } - -#else - - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] -= weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[k] -= psqtWeights[index * PSQTBuckets + k]; - } - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index; - for (IndexType j = 0; j < HalfDimensions; ++j) - entry.accumulation[j] += weights[offset + j]; - - for (std::size_t k = 0; k < PSQTBuckets; ++k) - entry.psqtAccumulation[k] += psqtWeights[index * PSQTBuckets + k]; - } - - // 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, - sizeof(BiasType) * HalfDimensions); - - std::memcpy(accumulator.psqtAccumulation[Perspective], entry.psqtAccumulation, - sizeof(int32_t) * PSQTBuckets); -#endif - - for (Color c : {WHITE, BLACK}) - entry.byColorBB[c] = pos.pieces(c); - - for (PieceType pt = PAWN; pt <= KING; ++pt) - entry.byTypeBB[pt] = pos.pieces(pt); - } - - - template - void update_accumulator(const Position& pos, - AccumulatorCaches::Cache* cache) const { - StateInfo* st = pos.state(); - if ((st->*accPtr).computed[Perspective]) - return; // nothing to do - - // Look for a usable already computed accumulator of an earlier position. - // Always try to do an incremental update as most accumulators will be reusable. - do - { - if (FeatureSet::requires_refresh(st, Perspective) || !st->previous - || st->previous->next != st) - { - // compute accumulator from scratch for this position - update_accumulator_refresh_cache(pos, cache); - if (st != pos.state()) - // when computing an accumulator from scratch we can use it to - // efficiently compute the accumulator backwards, until we get to a king - // move. We expect that we will need these accumulators later anyway, so - // computing them now will save some work. - update_accumulator_incremental( - pos.square(Perspective), st, pos.state()); - return; - } - st = st->previous; - } while (!(st->*accPtr).computed[Perspective]); - - // Start from the oldest computed accumulator, update all the - // accumulators up to the current position. - update_accumulator_incremental(pos.square(Perspective), pos.state(), st); - } - - template - friend struct AccumulatorCaches::Cache; - alignas(CacheLineSize) BiasType biases[HalfDimensions]; alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions]; alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets]; diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 2220684da..809d454b5 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -120,9 +120,12 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat format_cp_compact(value, &board[y + 2][x + 2], pos); }; + AccumulatorStack accumulators; + accumulators.reset(pos, networks, caches); + // 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, &caches.big); + auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big); Value base = psqt + positional; base = pos.side_to_move() == WHITE ? base : -base; @@ -135,18 +138,15 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat if (pc != NO_PIECE && type_of(pc) != KING) { - auto st = pos.state(); - pos.remove_piece(sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; - std::tie(psqt, positional) = networks.big.evaluate(pos, &caches.big); + accumulators.reset(pos, networks, caches); + 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; pos.put_piece(pc, sq); - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false; } writeSquare(f, r, pc, v); @@ -157,7 +157,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat ss << board[row] << '\n'; ss << '\n'; - auto t = networks.big.trace_evaluate(pos, &caches.big); + accumulators.reset(pos, networks, caches); + 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 diff --git a/src/perft.h b/src/perft.h index f0d38ab72..229debd40 100644 --- a/src/perft.h +++ b/src/perft.h @@ -34,7 +34,6 @@ template uint64_t perft(Position& pos, Depth depth) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); uint64_t cnt, nodes = 0; const bool leaf = (depth == 2); diff --git a/src/position.cpp b/src/position.cpp index 14599a76d..52e1004e8 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -34,7 +34,6 @@ #include "bitboard.h" #include "misc.h" #include "movegen.h" -#include "nnue/nnue_common.h" #include "syzygy/tbprobe.h" #include "tt.h" #include "uci.h" @@ -83,7 +82,6 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING)) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Position p; p.set(pos.fen(), pos.is_chess960(), &st); @@ -685,10 +683,10 @@ 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 -void Position::do_move(Move m, - StateInfo& newSt, - bool givesCheck, - const TranspositionTable* tt = nullptr) { +DirtyPiece Position::do_move(Move m, + StateInfo& newSt, + bool givesCheck, + const TranspositionTable* tt = nullptr) { assert(m.is_ok()); assert(&newSt != st); @@ -709,11 +707,7 @@ void Position::do_move(Move m, ++st->rule50; ++st->pliesFromNull; - // Used by NNUE - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - - auto& dp = st->dirtyPiece; + DirtyPiece dp; dp.dirty_num = 1; Color us = sideToMove; @@ -733,7 +727,7 @@ void Position::do_move(Move m, assert(captured == make_piece(us, ROOK)); Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + do_castling(us, from, to, rfrom, rto, &dp); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; @@ -906,6 +900,8 @@ void Position::do_move(Move m, } assert(pos_is_ok()); + + return dp; } @@ -975,23 +971,25 @@ void Position::undo_move(Move m) { // Helper used to do/undo a castling move. This is a bit // tricky in Chess960 where from/to squares can overlap. template -void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { +void Position::do_castling( + Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) { bool kingSide = to > from; rfrom = to; // Castling is encoded as "king captures friendly rook" rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + assert(!Do || dp); + if (Do) { - auto& dp = st->dirtyPiece; - dp.piece[0] = make_piece(us, KING); - dp.from[0] = from; - dp.to[0] = to; - dp.piece[1] = make_piece(us, ROOK); - dp.from[1] = rfrom; - dp.to[1] = rto; - dp.dirty_num = 2; + dp->piece[0] = make_piece(us, KING); + dp->from[0] = from; + dp->to[0] = to; + dp->piece[1] = make_piece(us, ROOK); + dp->from[1] = rfrom; + dp->to[1] = rto; + dp->dirty_num = 2; } // Remove both pieces first since squares could overlap in Chess960 @@ -1011,7 +1009,7 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { assert(!checkers()); assert(&newSt != st); - std::memcpy(&newSt, st, offsetof(StateInfo, accumulatorBig)); + std::memcpy(&newSt, st, sizeof(StateInfo)); newSt.previous = st; st->next = &newSt; @@ -1026,11 +1024,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { st->key ^= Zobrist::side; prefetch(tt.first_entry(key())); - st->dirtyPiece.dirty_num = 0; - st->dirtyPiece.piece[0] = NO_PIECE; // Avoid checks in UpdateAccumulator() - st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = - st->accumulatorSmall.computed[WHITE] = st->accumulatorSmall.computed[BLACK] = false; - st->pliesFromNull = 0; sideToMove = ~sideToMove; diff --git a/src/position.h b/src/position.h index eeea1b74c..75f22c7df 100644 --- a/src/position.h +++ b/src/position.h @@ -26,8 +26,6 @@ #include #include "bitboard.h" -#include "nnue/nnue_accumulator.h" -#include "nnue/nnue_architecture.h" #include "types.h" namespace Stockfish { @@ -61,11 +59,6 @@ struct StateInfo { Bitboard checkSquares[PIECE_TYPE_NB]; Piece capturedPiece; int repetition; - - // Used by NNUE - DirtyPiece dirtyPiece; - Eval::NNUE::Accumulator accumulatorBig; - Eval::NNUE::Accumulator accumulatorSmall; }; @@ -140,11 +133,11 @@ class Position { Piece captured_piece() const; // Doing and undoing moves - void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt); - void 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); + DirtyPiece 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(); // Static Exchange Evaluation bool see_ge(Move m, int threshold = 0) const; @@ -187,7 +180,12 @@ class Position { // Other helpers void move_piece(Square from, Square to); template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + void do_castling(Color us, + Square from, + Square& to, + Square& rfrom, + Square& rto, + DirtyPiece* const dp = nullptr); template Key adjust_key50(Key k) const; diff --git a/src/search.cpp b/src/search.cpp index baf99c0c6..8bc73b710 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -41,7 +41,6 @@ #include "movepick.h" #include "nnue/network.h" #include "nnue/nnue_accumulator.h" -#include "nnue/nnue_common.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -197,6 +196,8 @@ void Search::Worker::ensure_network_replicated() { void Search::Worker::start_searching() { + accumulatorStack.reset(rootPos, networks[numaAccessToken], refreshTable); + // Non-main threads go directly to iterative_deepening() if (!is_mainthread()) { @@ -552,6 +553,26 @@ void Search::Worker::iterative_deepening() { skill.best ? skill.best : skill.pick_best(rootMoves, multiPV))); } + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st) { + do_move(pos, move, st, pos.gives_check(move)); +} + +void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck) { + DirtyPiece dp = pos.do_move(move, st, givesCheck, &tt); + accumulatorStack.push(dp); +} + +void Search::Worker::do_null_move(Position& pos, StateInfo& st) { pos.do_null_move(st, tt); } + +void Search::Worker::undo_move(Position& pos, const Move move) { + pos.undo_move(move); + accumulatorStack.pop(); +} + +void Search::Worker::undo_null_move(Position& pos) { pos.undo_null_move(); } + + // Reset histories, usually before a new game void Search::Worker::clear() { mainHistory.fill(66); @@ -614,7 +635,6 @@ Value Search::Worker::search( Move pv[MAX_PLY + 1]; StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Key posKey; Move move, excludedMove, bestMove; @@ -859,11 +879,11 @@ Value Search::Worker::search( ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; ss->continuationCorrectionHistory = &thisThread->continuationCorrectionHistory[NO_PIECE][0]; - pos.do_null_move(st, tt); + do_null_move(pos, st); Value nullValue = -search(pos, ss + 1, -beta, -beta + 1, depth - R, false); - pos.undo_null_move(); + undo_null_move(pos); // Do not return unproven mate or TB scores if (nullValue >= beta && !is_win(nullValue)) @@ -925,7 +945,7 @@ Value Search::Worker::search( movedPiece = pos.moved_piece(move); - pos.do_move(move, st, &tt); + do_move(pos, move, st); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; @@ -943,7 +963,7 @@ Value Search::Worker::search( value = -search(pos, ss + 1, -probCutBeta, -probCutBeta + 1, probCutDepth, !cutNode); - pos.undo_move(move); + undo_move(pos, move); if (value >= probCutBeta) { @@ -1165,7 +1185,7 @@ moves_loop: // When in check, search starts here } // Step 16. Make the move - pos.do_move(move, st, givesCheck, &tt); + do_move(pos, move, st, givesCheck); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Add extension to new depth @@ -1290,7 +1310,7 @@ moves_loop: // When in check, search starts here } // Step 19. Undo move - pos.undo_move(move); + undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1510,7 +1530,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Move pv[MAX_PLY + 1]; StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); Key posKey; Move move, bestMove; @@ -1674,7 +1693,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Step 7. Make and search the move Piece movedPiece = pos.moved_piece(move); - pos.do_move(move, st, givesCheck, &tt); + do_move(pos, move, st, givesCheck); thisThread->nodes.fetch_add(1, std::memory_order_relaxed); // Update the current move @@ -1685,7 +1704,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; value = -qsearch(pos, ss + 1, -beta, -alpha); - pos.undo_move(move); + undo_move(pos, move); assert(value > -VALUE_INFINITE && value < VALUE_INFINITE); @@ -1752,7 +1771,7 @@ TimePoint Search::Worker::elapsed() const { TimePoint Search::Worker::elapsed_time() const { return main_manager()->tm.elapsed_time(); } Value Search::Worker::evaluate(const Position& pos) { - return Eval::evaluate(networks[numaAccessToken], pos, refreshTable, + return Eval::evaluate(networks[numaAccessToken], pos, accumulatorStack, refreshTable, optimism[pos.side_to_move()]); } @@ -2178,7 +2197,6 @@ void SearchManager::pv(Search::Worker& worker, bool RootMove::extract_ponder_from_tt(const TranspositionTable& tt, Position& pos) { StateInfo st; - ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize); assert(pv.size() == 1); if (pv[0] == Move::none()) diff --git a/src/search.h b/src/search.h index 326480e4d..cd3b6ad98 100644 --- a/src/search.h +++ b/src/search.h @@ -295,6 +295,12 @@ class Worker { private: void iterative_deepening(); + void do_move(Position& pos, const Move move, StateInfo& st); + void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck); + void do_null_move(Position& pos, StateInfo& st); + void undo_move(Position& pos, const Move move); + void undo_null_move(Position& pos); + // This is the main search function, for both PV and non-PV nodes template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); @@ -347,6 +353,7 @@ class Worker { const LazyNumaReplicated& networks; // Used by NNUE + Eval::NNUE::AccumulatorStack accumulatorStack; Eval::NNUE::AccumulatorCaches refreshTable; friend class Stockfish::ThreadPool; From 4afd7f1a7b2379f9094a973676f1f03b2058428b Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 9 Mar 2025 18:35:14 +0300 Subject: [PATCH 19/39] Removing contHist[1] from pruning formula Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51552 W: 13297 L: 13091 D: 25164 Ptnml(0-2): 166, 6009, 13215, 6225, 161 https://tests.stockfishchess.org/tests/view/67c4de79685e87e15e7c43f5 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 244554 W: 62135 L: 62142 D: 120277 Ptnml(0-2): 137, 26612, 68794, 26589, 145 https://tests.stockfishchess.org/tests/view/67c50982685e87e15e7c443d closes https://github.com/official-stockfish/Stockfish/pull/5928 Bench: 1980385 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8bc73b710..bb0156c5d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1679,10 +1679,9 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] - + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][pos.moved_piece(move)] [move.to_sq()] - <= 5923) + <= 6290) continue; // Do not search moves with bad enough SEE values From 652a8874b523360a3b19c5003c8ba9894ac54d0f Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Fri, 14 Mar 2025 20:32:38 +0100 Subject: [PATCH 20/39] Allow more than 1024 threads on high-end machines closes https://github.com/official-stockfish/Stockfish/pull/5929 No functional change --- src/engine.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index 6c8799a15..a4c0bb1eb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -18,6 +18,7 @@ #include "engine.h" +#include #include #include #include @@ -32,6 +33,7 @@ #include "misc.h" #include "nnue/network.h" #include "nnue/nnue_common.h" +#include "numa.h" #include "perft.h" #include "position.h" #include "search.h" @@ -44,8 +46,9 @@ namespace Stockfish { namespace NN = Eval::NNUE; -constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; -constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048; +int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency())); Engine::Engine(std::optional path) : binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""), @@ -74,7 +77,7 @@ Engine::Engine(std::optional path) : })); options.add( // - "Threads", Option(1, 1, 1024, [this](const Option&) { + "Threads", Option(1, 1, MaxThreads, [this](const Option&) { resize_threads(); return thread_allocation_information_as_string(); })); From 8fc5e92005aad8f952817161818ec0082f9642e6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 16 Mar 2025 01:48:49 +0300 Subject: [PATCH 21/39] Use slightly different formula for stat score when position is in check Use formula that is closer to movepicker one. Passed STC: https://tests.stockfishchess.org/tests/view/67cffb337be98c1ad9b021ee LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 250432 W: 64978 L: 64343 D: 121111 Ptnml(0-2): 795, 29390, 64159, 30129, 743 Passed LTC: https://tests.stockfishchess.org/tests/view/67d3905d517865b4a2dfce8a LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 140004 W: 35742 L: 35215 D: 69047 Ptnml(0-2): 60, 15111, 39151, 15602, 78 closes https://github.com/official-stockfish/Stockfish/pull/5930 Bench: 2147336 --- src/search.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index bb0156c5d..3eab34be4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1235,6 +1235,9 @@ moves_loop: // When in check, search starts here 846 * int(PieceValue[pos.captured_piece()]) / 128 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - 4822; + else if (ss->inCheck) + ss->statScore = thisThread->mainHistory[us][move.from_to()] + + (*contHist[0])[movedPiece][move.to_sq()] - 2771; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] From 9045f17c3f19a7b779e4baf71689ddd650f5d64c Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 14 Mar 2025 02:21:48 -0700 Subject: [PATCH 22/39] Simplify captures PCM Passed Non-regression STC: LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 229856 W: 59258 L: 59252 D: 111346 Ptnml(0-2): 746, 27330, 58714, 27448, 690 https://tests.stockfishchess.org/tests/view/67d3fdac517865b4a2dfcef4 Passed Non-regression LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 107652 W: 27470 L: 27338 D: 52844 Ptnml(0-2): 56, 11646, 30280, 11798, 46 https://tests.stockfishchess.org/tests/view/67d5f972517865b4a2dfd2ec closes https://github.com/official-stockfish/Stockfish/pull/5931 Bench: 1842520 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3eab34be4..4b6ad52e4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1442,7 +1442,7 @@ moves_loop: // When in check, search starts here update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, bestMove == ttData.move, moveCount); - // Bonus for prior countermove that caused the fail low + // Bonus for prior quiet countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) { int bonusScale = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) @@ -1466,13 +1466,12 @@ moves_loop: // When in check, search starts here << scaledBonus * 1055 / 32768; } + // Bonus for prior capture countermove that caused the fail low else if (priorCapture && prevSq != SQ_NONE) { - // bonus for prior countermoves that caused the fail low Piece capturedPiece = pos.captured_piece(); assert(capturedPiece != NO_PIECE); - thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] - << std::min(300 * depth - 182, 2995); + thisThread->captureHistory[pos.piece_on(prevSq)][prevSq][type_of(capturedPiece)] << 1100; } if (PvNode) From 43d8ccf85641addd49a5c7e295919fa16c88b72c Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 16 Mar 2025 13:02:16 +0100 Subject: [PATCH 23/39] change the bonusScale depth component Passed STC: https://tests.stockfishchess.org/tests/view/67d35f66517865b4a2dfc801 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 110816 W: 28875 L: 28449 D: 53492 Ptnml(0-2): 329, 13064, 28231, 13420, 364 Passed LTC: https://tests.stockfishchess.org/tests/view/67d4bdf0517865b4a2dfd131 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 121824 W: 31047 L: 30559 D: 60218 Ptnml(0-2): 52, 13056, 34214, 13532, 58 closes https://github.com/official-stockfish/Stockfish/pull/5932 Bench: 2128807 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4b6ad52e4..a91fb3c1b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1445,7 +1445,8 @@ 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 = (112 * (depth > 5) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + int bonusScale = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode + + 164 * ((ss - 1)->moveCount > 8) + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) From 0dabf4f3fab2356111cbcf058be167bd43ac2479 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 16 Mar 2025 15:37:42 +0300 Subject: [PATCH 24/39] Removing the conditional bonus calculation The new value is just a guessed value. Passed STC: https://tests.stockfishchess.org/tests/view/67d5ee09517865b4a2dfd2df LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 52128 W: 13516 L: 13312 D: 25300 Ptnml(0-2): 157, 6044, 13451, 6262, 150 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 42384 W: 10855 L: 10657 D: 20872 Ptnml(0-2): 19, 4554, 11852, 4744, 23 https://tests.stockfishchess.org/tests/view/67d5f9d3517865b4a2dfd2ef closes https://github.com/official-stockfish/Stockfish/pull/5933 Bench: 2030154 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a91fb3c1b..2da19f32e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1279,7 +1279,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = (value >= beta) * 1800; + int bonus = 1600; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } else if (value > alpha && value < bestValue + 9) From 6ceaca4c7b2cc1fa87617b1b9e83d38d8e880924 Mon Sep 17 00:00:00 2001 From: mstembera Date: Thu, 20 Mar 2025 12:40:27 -0700 Subject: [PATCH 25/39] Change layout of CorrectionHistory https://tests.stockfishchess.org/tests/view/67da5b158c7f315cc372a9d2 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 150368 W: 38874 L: 38401 D: 73093 Ptnml(0-2): 424, 16821, 40262, 17212, 465 Make CorrectionHistory\ handle both black and white internally. A follow up to https://github.com/official-stockfish/Stockfish/pull/5816 closes https://github.com/official-stockfish/Stockfish/pull/5934 No functional change --- src/history.h | 6 ++++++ src/search.cpp | 11 +++++------ src/search.h | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/history.h b/src/history.h index fd9b98b98..ec245230a 100644 --- a/src/history.h +++ b/src/history.h @@ -155,6 +155,12 @@ struct CorrHistTypedef { using type = MultiArray::type, PIECE_NB, SQUARE_NB>; }; +template<> +struct CorrHistTypedef { + using type = + Stats; +}; + } template diff --git a/src/search.cpp b/src/search.cpp index 2da19f32e..0c543c308 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -87,8 +87,8 @@ int correction_value(const Worker& w, const Position& pos, const Stack* const ss const auto m = (ss - 1)->currentMove; const auto pcv = w.pawnCorrectionHistory[pawn_structure_index(pos)][us]; const auto micv = w.minorPieceCorrectionHistory[minor_piece_index(pos)][us]; - const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; - const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; + const auto wnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us]; + const auto bnpcv = w.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; @@ -141,9 +141,9 @@ void update_correction_history(const Position& pos, workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] << bonus * 111 / 128; workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; - workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][WHITE][us] << bonus * nonPawnWeight / 128; - workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] + workerThread.nonPawnCorrectionHistory[non_pawn_index(pos)][BLACK][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) @@ -581,8 +581,7 @@ void Search::Worker::clear() { pawnHistory.fill(-1262); pawnCorrectionHistory.fill(6); minorPieceCorrectionHistory.fill(0); - nonPawnCorrectionHistory[WHITE].fill(0); - nonPawnCorrectionHistory[BLACK].fill(0); + nonPawnCorrectionHistory.fill(0); for (auto& to : continuationCorrectionHistory) for (auto& h : to) diff --git a/src/search.h b/src/search.h index cd3b6ad98..071773f81 100644 --- a/src/search.h +++ b/src/search.h @@ -289,7 +289,7 @@ class Worker { CorrectionHistory pawnCorrectionHistory; CorrectionHistory minorPieceCorrectionHistory; - CorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; + CorrectionHistory nonPawnCorrectionHistory; CorrectionHistory continuationCorrectionHistory; private: From 12d023ed0635e7dd4ada28ad764ea975de55103b Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 22 Mar 2025 09:28:58 +0100 Subject: [PATCH 26/39] Update CPU contributors closes https://github.com/official-stockfish/Stockfish/pull/5937 No functional change --- Top CPU Contributors.txt | 217 +++++++++++++++++++++------------------ 1 file changed, 119 insertions(+), 98 deletions(-) diff --git a/Top CPU Contributors.txt b/Top CPU Contributors.txt index 3d8c52361..4e598ecfc 100644 --- a/Top CPU Contributors.txt +++ b/Top CPU Contributors.txt @@ -1,118 +1,126 @@ -Contributors to Fishtest with >10,000 CPU hours, as of 2024-08-31. +Contributors to Fishtest with >10,000 CPU hours, as of 2025-03-22. Thank you! Username CPU Hours Games played ------------------------------------------------------------------ -noobpwnftw 40428649 3164740143 -technologov 23581394 1076895482 -vdv 19425375 718302718 -linrock 10034115 643194527 +noobpwnftw 41712226 3294628533 +vdv 28993864 954145232 +technologov 24984442 1115931964 +linrock 11463033 741692823 mlang 3026000 200065824 -okrout 2572676 237511408 -pemo 1836785 62226157 +okrout 2726068 248285678 +olafm 2420096 161297116 +pemo 1838361 62294199 +TueRens 1804847 80170868 dew 1689162 100033738 -TueRens 1648780 77891164 -sebastronomy 1468328 60859092 -grandphish2 1466110 91776075 +sebastronomy 1655637 67294942 +grandphish2 1474752 92156319 JojoM 1130625 73666098 -olafm 1067009 74807270 +rpngn 973590 59996557 +oz 921203 60370346 tvijlbrief 796125 51897690 -oz 781847 53910686 -rpngn 768460 49812975 -gvreuls 751085 52177668 +gvreuls 792215 55184194 mibere 703840 46867607 -leszek 566598 42024615 -cw 519601 34988161 +leszek 599745 44681421 +cw 519602 34988289 fastgm 503862 30260818 -CSU_Dynasty 468784 31385034 -maximmasiutin 439192 27893522 -ctoks 435148 28541909 +CSU_Dynasty 474794 31654170 +maximmasiutin 441753 28129452 +robal 437950 28869118 +ctoks 435150 28542141 crunchy 427414 27371625 bcross 415724 29061187 -robal 371112 24642270 -mgrabiak 367963 26464704 +mgrabiak 380202 27586936 velislav 342588 22140902 ncfish1 329039 20624527 Fisherman 327231 21829379 +Sylvain27 317021 11494912 +marrco 310446 19587107 Dantist 296386 18031762 -tolkki963 262050 22049676 -Sylvain27 255595 8864404 +Fifis 289595 14969251 +tolkki963 286043 23596996 +Calis007 272677 17281620 +cody 258835 13301710 nordlandia 249322 16420192 -Fifis 237657 13065577 -marrco 234581 17714473 -Calis007 217537 14450582 +javran 212141 16507618 glinscott 208125 13277240 drabel 204167 13930674 mhoram 202894 12601997 bking_US 198894 11876016 +Wencey 198537 9606420 Thanar 179852 12365359 -javran 169679 13481966 -armo9494 162863 10937118 +sschnee 170521 10891112 +armo9494 168141 11177514 +DesolatedDodo 160605 10392474 spams 157128 10319326 -DesolatedDodo 156683 10211206 -Wencey 152308 8375444 +maposora 155839 13963260 sqrt2 147963 9724586 -vdbergh 140311 9225125 +vdbergh 140514 9242985 jcAEie 140086 10603658 CoffeeOne 137100 5024116 malala 136182 8002293 +Goatminola 134893 11640524 xoto 133759 9159372 -Dubslow 129614 8519312 +markkulix 132104 11000548 +naclosagc 131472 4660806 +Dubslow 129685 8527664 davar 129023 8376525 DMBK 122960 8980062 dsmith 122059 7570238 -CypressChess 120784 8672620 -sschnee 120526 7547722 -maposora 119734 10749710 +Wolfgang 120919 8619168 +CypressChess 120902 8683904 amicic 119661 7938029 -Wolfgang 115713 8159062 +cuistot 116864 7828864 +sterni1971 113754 6054022 Data 113305 8220352 BrunoBanani 112960 7436849 -markkulix 112897 9133168 -cuistot 109802 7121030 +megaman7de 109139 7360928 skiminki 107583 7218170 -sterni1971 104431 5938282 +zeryl 104523 6618969 MaZePallas 102823 6633619 sunu 100167 7040199 -zeryl 99331 6221261 -thirdlife 99156 2245320 +thirdlife 99178 2246544 ElbertoOne 99028 7023771 -megaman7de 98456 6675076 -Goatminola 96765 8257832 +TataneSan 97257 4239502 +romangol 95662 7784954 bigpen0r 94825 6529241 brabos 92118 6186135 Maxim 90818 3283364 psk 89957 5984901 +szupaw 89775 7800606 +jromang 87260 5988073 racerschmacer 85805 6122790 Vizvezdenec 83761 5344740 0x3C33 82614 5271253 -szupaw 82495 7151686 +Spprtr 82103 5663635 BRAVONE 81239 5054681 +MarcusTullius 78930 5189659 +Mineta 78731 4947996 +Torom 77978 2651656 nssy 76497 5259388 -cody 76126 4492126 -jromang 76106 5236025 -MarcusTullius 76103 5061991 -woutboat 76072 6022922 -Spprtr 75977 5252287 +woutboat 76379 6031688 teddybaer 75125 5407666 Pking_cda 73776 5293873 +Viren6 73664 1356502 yurikvelo 73611 5046822 -Mineta 71130 4711422 Bobo1239 70579 4794999 solarlight 70517 5028306 dv8silencer 70287 3883992 manap 66273 4121774 tinker 64333 4268790 qurashee 61208 3429862 -AGI 58195 4329580 +DanielMiao1 60181 1317252 +AGI 58316 4336328 +jojo2357 57435 4944212 robnjr 57262 4053117 Freja 56938 3733019 MaxKlaxxMiner 56879 3423958 ttruscott 56010 3680085 rkl 55132 4164467 -jmdana 54697 4012593 +jmdana 54988 4041917 notchris 53936 4184018 renouve 53811 3501516 +CounterFlow 52536 3203740 finfish 51360 3370515 eva42 51272 3599691 eastorwest 51117 3454811 @@ -122,33 +130,36 @@ GPUex 48686 3684998 OuaisBla 48626 3445134 ronaldjerum 47654 3240695 biffhero 46564 3111352 -oryx 45639 3546530 +oryx 46141 3583236 +jibarbosa 45890 4541218 +DeepnessFulled 45734 3944282 +abdicj 45577 2631772 VoyagerOne 45476 3452465 +mecevdimitar 44240 2584396 speedycpu 43842 3003273 jbwiebe 43305 2805433 +gopeto 43046 2821514 +YvesKn 42628 2177630 Antihistamine 41788 2761312 mhunt 41735 2691355 -jibarbosa 41640 4145702 +somethingintheshadows 41502 3330418 homyur 39893 2850481 gri 39871 2515779 -DeepnessFulled 39020 3323102 +vidar808 39774 1656372 Garf 37741 2999686 SC 37299 2731694 -Gaster319 37118 3279678 -naclosagc 36562 1279618 +Gaster319 37229 3289674 csnodgrass 36207 2688994 +ZacHFX 35528 2486328 +icewulf 34782 2415146 strelock 34716 2074055 -gopeto 33717 2245606 EthanOConnor 33370 2090311 slakovv 32915 2021889 -jojo2357 32890 2826662 -shawnxu 32019 2802552 +shawnxu 32144 2814668 Gelma 31771 1551204 -vidar808 31560 1351810 +srowen 31181 1732120 kdave 31157 2198362 manapbk 30987 1810399 -ZacHFX 30966 2272416 -TataneSan 30713 1513402 votoanthuan 30691 2460856 Prcuvu 30377 2170122 anst 30301 2190091 @@ -157,145 +168,155 @@ spcc 29925 1901692 hyperbolic.tom 29840 2017394 chuckstablers 29659 2093438 Pyafue 29650 1902349 +WoodMan777 29300 2579864 belzedar94 28846 1811530 -mecevdimitar 27610 1721382 chriswk 26902 1868317 xwziegtm 26897 2124586 +Jopo12321 26818 1816482 achambord 26582 1767323 -somethingintheshadows 26496 2186404 Patrick_G 26276 1801617 yorkman 26193 1992080 -srowen 25743 1490684 -Ulysses 25413 1702830 -Jopo12321 25227 1652482 +Ulysses 25517 1711634 SFTUser 25182 1675689 nabildanial 25068 1531665 Sharaf_DG 24765 1786697 rodneyc 24376 1416402 jsys14 24297 1721230 +AndreasKrug 24235 1934711 agg177 23890 1395014 -AndreasKrug 23754 1890115 Ente 23752 1678188 JanErik 23408 1703875 Isidor 23388 1680691 Norabor 23371 1603244 -WoodMan777 23253 2023048 Nullvalue 23155 2022752 +fishtester 23115 1581502 +wizardassassin 23073 1789536 +Skiff84 22984 1053680 cisco2015 22920 1763301 +ols 22914 1322047 +Hjax 22561 1566151 Zirie 22542 1472937 team-oh 22272 1636708 +mkstockfishtester 22253 2029566 Roady 22220 1465606 MazeOfGalious 21978 1629593 sg4032 21950 1643373 -tsim67 21747 1330880 +tsim67 21939 1343944 ianh2105 21725 1632562 -Skiff84 21711 1014212 +Serpensin 21704 1809188 xor12 21628 1680365 dex 21612 1467203 nesoneg 21494 1463031 +IslandLambda 21468 1239756 user213718 21454 1404128 -Serpensin 21452 1790510 sphinx 21211 1384728 qoo_charly_cai 21136 1514927 -IslandLambda 21062 1220838 jjoshua2 21001 1423089 Zake9298 20938 1565848 horst.prack 20878 1465656 -fishtester 20729 1348888 0xB00B1ES 20590 1208666 -ols 20477 1195945 Dinde 20459 1292774 +t3hf1sht3ster 20456 670646 j3corre 20405 941444 +0x539 20332 1039516 Adrian.Schmidt123 20316 1281436 +malfoy 20313 1350694 +purpletree 20019 1461026 wei 19973 1745989 teenychess 19819 1762006 rstoesser 19569 1293588 eudhan 19274 1283717 +nalanzeyu 19211 396674 vulcan 18871 1729392 -wizardassassin 18795 1376884 Karpovbot 18766 1053178 jundery 18445 1115855 -mkstockfishtester 18350 1690676 +Farseer 18281 1074642 +sebv15 18267 1262588 +whelanh 17887 347974 ville 17883 1384026 chris 17698 1487385 purplefishies 17595 1092533 dju 17414 981289 iisiraider 17275 1049015 +Karby 17177 1030688 DragonLord 17014 1162790 -Karby 17008 1013160 -pirt 16965 1271519 +pirt 16991 1274215 redstone59 16842 1461780 Alb11747 16787 1213990 Naven94 16414 951718 -scuzzi 16115 994341 +scuzzi 16155 995347 IgorLeMasson 16064 1147232 ako027ako 15671 1173203 +xuhdev 15516 1528278 infinigon 15285 965966 Nikolay.IT 15154 1068349 Andrew Grant 15114 895539 OssumOpossum 14857 1007129 LunaticBFF57 14525 1190310 enedene 14476 905279 -Hjax 14394 1005013 +YELNAMRON 14475 1141330 +RickGroszkiewicz 14272 1385984 +joendter 14269 982014 bpfliegel 14233 882523 -YELNAMRON 14230 1128094 mpx86 14019 759568 jpulman 13982 870599 getraideBFF 13871 1172846 +crocogoat 13817 1119086 Nesa92 13806 1116101 -crocogoat 13803 1117422 joster 13710 946160 mbeier 13650 1044928 Pablohn26 13552 1088532 wxt9861 13550 1312306 Dark_wizzie 13422 1007152 Rudolphous 13244 883140 +Jackfish 13177 894206 +MooTheCow 13091 892304 Machariel 13010 863104 -nalanzeyu 12996 232590 mabichito 12903 749391 -Jackfish 12895 868928 thijsk 12886 722107 AdrianSA 12860 804972 Flopzee 12698 894821 -whelanh 12682 266404 +szczur90 12684 977536 +Kyrega 12661 456438 mschmidt 12644 863193 korposzczur 12606 838168 fatmurphy 12547 853210 Oakwen 12532 855759 -icewulf 12447 854878 SapphireBrand 12416 969604 deflectooor 12386 579392 modolief 12386 896470 -Farseer 12249 694108 +ckaz 12273 754644 Hongildong 12201 648712 pgontarz 12151 848794 dbernier 12103 860824 -szczur90 12035 942376 -FormazChar 12019 910409 +FormazChar 12051 913497 +shreven 12044 884734 rensonthemove 11999 971993 stocky 11954 699440 -MooTheCow 11923 779432 3cho 11842 1036786 -ckaz 11792 732276 +ImperiumAeternum 11482 979142 infinity 11470 727027 aga 11412 695127 +Def9Infinity 11408 700682 torbjo 11395 729145 Thomas A. Anderson 11372 732094 savage84 11358 670860 -Def9Infinity 11345 696552 d64 11263 789184 ali-al-zhrani 11245 779246 -ImperiumAeternum 11155 952000 +vaskoul 11144 953906 snicolet 11106 869170 dapper 11032 771402 Ethnikoi 10993 945906 Snuuka 10938 435504 Karmatron 10871 678306 +gerbil 10871 1005842 +OliverClarke 10696 942654 basepi 10637 744851 +michaelrpg 10624 748179 Cubox 10621 826448 -gerbil 10519 971688 -michaelrpg 10509 739239 +dragon123118 10421 936506 OIVAS7572 10420 995586 +GBx3TV 10388 339952 Garruk 10365 706465 dzjp 10343 732529 -RickGroszkiewicz 10263 990798 +borinot 10026 902130 From 6028264cb991b99b3a51cf2fd01b6f1f6ce24cc3 Mon Sep 17 00:00:00 2001 From: Michel Van den Bergh Date: Sat, 22 Mar 2025 09:58:47 +0100 Subject: [PATCH 27/39] Delay check for curl/wget until really needed closes https://github.com/official-stockfish/Stockfish/pull/5938 No functional change --- scripts/net.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/net.sh b/scripts/net.sh index 0bc57a19e..1aa1fbfb1 100755 --- a/scripts/net.sh +++ b/scripts/net.sh @@ -3,11 +3,6 @@ wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \ (command -v curl > /dev/null 2>&1 && echo "curl -skL")) -if [ -z "$wget_or_curl" ]; then - >&2 printf "%s\n" "Neither wget or curl is installed." \ - "Install one of these tools to download NNUE files automatically." - exit 1 -fi sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \ (command -v sha256sum > /dev/null 2>&1 && echo "sha256sum")) @@ -47,6 +42,12 @@ fetch_network() { fi fi + if [ -z "$wget_or_curl" ]; then + >&2 printf "%s\n" "Neither wget or curl is installed." \ + "Install one of these tools to download NNUE files automatically." + exit 1 + fi + for url in \ "https://tests.stockfishchess.org/api/nn/$_filename" \ "https://github.com/official-stockfish/networks/raw/master/$_filename"; do From 4a869f41c6113f1ccdd0f11551858fdc849a245a Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sat, 22 Mar 2025 11:09:33 +0100 Subject: [PATCH 28/39] Update the WDL model This PR updates the internal WDL model, using data from 2.6M games played by the revisions since f3bfce353168b03e4fedce515de1898c691f81ec. Note that the normalizing constant increases only moderately from 372 to 377. ``` > ./updateWDL.sh --firstrev f3bfce353168b03e4fedce515de1898c691f81ec Running: ./updateWDL.sh --firstrev f3bfce353168b03e4fedce515de1898c691f81ec --lasttrev HEAD --materialMin 17 --EloDiffMax 5 started at: Sat 22 Mar 11:02:14 CET 2025 Look recursively in directory pgns for games with max nElo difference 5 using books matching "UHO_Lichess_4852_v..epd" for SF revisions between f3bfce353168b03e4fedce515de1898c691f81ec (from 2025-02-28 09:50:59 +0100) and HEAD (from 2025-03-21 11:22:59 +0100). Based on 138253430 positions from 2579360 games, NormalizeToPawnValue should change from 372 to 377. ended at: Sat 22 Mar 11:04:00 CET 2025 ``` ``` > cat scoreWDL.log Converting evals with NormalizeData = {'momType': 'material', 'momMin': 17, 'momMax': 78, 'momTarget': 58, 'as': [-37.45051876, 121.19101539, -132.78783573, 420.70576692]}. Reading eval stats from updateWDL.json. Retained (W,D,L) = (33794348, 69943262, 34515820) positions. Saved distribution plot to updateWDLdistro.png. Fit WDL model based on material. Initial objective function: 0.3648260131692729 Final objective function: 0.36482338611818094 Optimization terminated successfully. const int NormalizeToPawnValue = 377; Corresponding spread = 71; Corresponding normalized spread = 0.1879431202530567; Draw rate at 0.0 eval at material 58 = 0.9902694872976331; Parameters in internal value units: p_a = ((-13.500 * x / 58 + 40.928) * x / 58 + -36.828) * x / 58 + 386.830 p_b = ((96.534 * x / 58 + -165.791) * x / 58 + 90.897) * x / 58 + 49.296 constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; Preparing plots. Saved graphics to updateWDL.png. Total elapsed time = 46.92s. ``` Only affects displayed `cp` and `wdl` values. closes https://github.com/official-stockfish/Stockfish/pull/5939 No functional change --- src/uci.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/uci.cpp b/src/uci.cpp index 4b8e8b7f5..500e88184 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -513,8 +513,8 @@ WinRateParams win_rate_params(const Position& pos) { double m = std::clamp(material, 17, 78) / 58.0; // Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model - constexpr double as[] = {-37.45051876, 121.19101539, -132.78783573, 420.70576692}; - constexpr double bs[] = {90.26261072, -137.26549898, 71.10130540, 51.35259597}; + constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070}; + constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889}; double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3]; double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3]; From aafc732bcbfbf847e2ce15bb5371902c1801de36 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sat, 29 Mar 2025 11:42:26 +0100 Subject: [PATCH 29/39] Silence "may be used uninitialized" GCC warning Helps gcc silence the warning about ``` warning: 'added' may be used uninitialized [-Wmaybe-uninitialized] const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; ``` closes https://github.com/official-stockfish/Stockfish/pull/5950 No functional change --- src/nnue/nnue_accumulator.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index 0a3d95ad4..d693cc031 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -25,13 +25,25 @@ #include "../bitboard.h" #include "../position.h" #include "../types.h" -#include "nnue_architecture.h" #include "network.h" +#include "nnue_architecture.h" #include "nnue_common.h" #include "nnue_feature_transformer.h" namespace Stockfish::Eval::NNUE { +#if defined(__GNUC__) && !defined(__clang__) + #define sf_assume(cond) \ + do \ + { \ + if (!(cond)) \ + __builtin_unreachable(); \ + } while (0) +#else + // do nothing for other compilers + #define sf_assume(cond) +#endif + namespace { template(&(computed.*accPtr).accumulation[Perspective][0]); From 03e27488f3d21d8ff4dbf3065603afa21dbd0ef3 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 29 Mar 2025 12:44:36 +0100 Subject: [PATCH 30/39] Stockfish 17.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Official release version of Stockfish 17.1 Bench: 2030154 --- Stockfish 17.1 Today, we have the pleasure to announce Stockfish 17.1. As always, you can **freely** download it at [stockfishchess.org/download][1] and use it in the [GUI of your choice][2]. Join our [Discord server][3] to get in touch with the community of developers and users of the project! Quality of chess play In our testing against its predecessor, Stockfish 17.1 shows a consistent improvement in performance, with an [Elo gain of up to 20 points][4] and winning close to 2 times more game pairs than it loses. Update highlights New speedtest command The new `speedtest` command benchmarks your computer's performance (measured in nodes per second) using a realistic and stable test. To run it, you'll need [command line access][5]—give it a try and share your results with the community! Improved hardware support Stockfish is [no longer limited to 1024 threads][6] and will allow users to specify whatever their system is capable of. Additionally, hardware such as ppc64 and Loongson is now better supported at build time. Bug fixes for tablebase support Our previous release introduced improved engine lines (principal variations) ending in mate as soon as a mate score is announced. A side effect of this improvement was a [rare corner case][7] involving cursed tablebase wins, only relevant in correspondence chess when the 50-move rule does not apply, which has now been fixed. Relatedly, [time management][8] has also been improved to avoid potential time losses. Shoutouts Download page redesign We've redesigned the [download page][1] to address unclear wording and simplify a previously cluttered experience. The page now features a modernized layout, streamlined navigation, and clearer guidance to help you select the right binary for your system. Fishtest framework Our testing framework has been improved in various ways, both on the worker side, including the adoption of [Fastchess][9] as a new game manager, and on the server side, such as streamlined configuration. The reliable availability of testing resources is key for the progress of the engine. Thank you The Stockfish project builds on a thriving community of enthusiasts (thanks everybody!) who contribute their expertise, time, and resources to build a free and open-source chess engine that is robust, widely available, and very strong. We would like to express our gratitude for the [12k stars][10] that light up our GitHub project! Thank you for your support and encouragement – your recognition means a lot to us. We invite our chess fans to [join the Fishtest testing framework][11] to contribute compute resources needed for development. Programmers can contribute to the project either directly to [Stockfish][12] (C++), to [Fishtest][13] (HTML, CSS, JavaScript, and Python), to our trainer [nnue-pytorch][14] (C++ and Python), or to our [website][15] (HTML, CSS/SCSS, and JavaScript). The Stockfish team [1]: https://stockfishchess.org/download [2]: https://official-stockfish.github.io/docs/stockfish-wiki/Download-and-usage.html#download-a-chess-gui [3]: https://discord.gg/GWDRS3kU6R [4]: https://tests.stockfishchess.org/tests/view/67e7d2fd6682f97da2178fbd [5]: https://official-stockfish.github.io/docs/stockfish-wiki/UCI-&-Commands.html#speedtest [6]: https://github.com/official-stockfish/Stockfish/commit/652a8874b523360a3b19c5003c8ba9894ac54d0f [7]: https://github.com/official-stockfish/Stockfish/commit/6c7c5c7e471c16f14518229428e51a3e00c0f1dd [8]: https://github.com/official-stockfish/Stockfish/commit/0f9ae0d11cd034288a49ef3892c580dfed025091 [9]: https://github.com/Disservin/fastchess [10]: https://github.com/official-stockfish/Stockfish/stargazers [11]: https://official-stockfish.github.io/docs/fishtest-wiki/Running-the-Worker.html [12]: https://github.com/official-stockfish/Stockfish [13]: https://github.com/official-stockfish/fishtest [14]: https://github.com/official-stockfish/nnue-pytorch [15]: https://github.com/official-stockfish/stockfish-web --- src/misc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc.cpp b/src/misc.cpp index f85356c59..c5ac45f5c 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "dev"; +constexpr std::string_view version = "17.1"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From 0475c8653f6d7d6940918120d3e994845906b6d1 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 30 Mar 2025 14:47:31 +0200 Subject: [PATCH 31/39] Restore development closes https://github.com/official-stockfish/Stockfish/pull/5956 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 c5ac45f5c..f85356c59 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -40,7 +40,7 @@ namespace Stockfish { namespace { // Version number or dev. -constexpr std::string_view version = "17.1"; +constexpr std::string_view version = "dev"; // Our fancy logging facility. The trick here is to replace cin.rdbuf() and // cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We From c2ff7a95c3ffee1c964735a0a4bd0e34cf76cab8 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 21 Mar 2025 11:24:11 -0700 Subject: [PATCH 32/39] Cleanup fused updates Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 70656 W: 18257 L: 18077 D: 34322 Ptnml(0-2): 217, 7912, 18879, 8114, 206 https://tests.stockfishchess.org/tests/view/67e23ae78888403457d876d4 closes https://github.com/official-stockfish/Stockfish/pull/5941 No functional change --- src/nnue/nnue_accumulator.cpp | 280 +++++++++++----------------- src/nnue/nnue_common.h | 2 +- src/nnue/nnue_feature_transformer.h | 54 ++++++ src/types.h | 9 + 4 files changed, 169 insertions(+), 176 deletions(-) diff --git a/src/nnue/nnue_accumulator.cpp b/src/nnue/nnue_accumulator.cpp index d693cc031..efa8df905 100644 --- a/src/nnue/nnue_accumulator.cpp +++ b/src/nnue/nnue_accumulator.cpp @@ -21,8 +21,10 @@ #include #include #include +#include #include "../bitboard.h" +#include "../misc.h" #include "../position.h" #include "../types.h" #include "network.h" @@ -185,7 +187,7 @@ void AccumulatorStack::backward_update_incremental( const Square ksq = pos.square(Perspective); for (std::size_t next = m_current_idx - 2; next >= end; next--) - update_accumulator_incremental( + update_accumulator_incremental( featureTransformer, ksq, m_accumulators[next], m_accumulators[next + 1]); assert((m_accumulators[end].*accPtr).computed[Perspective]); @@ -208,6 +210,67 @@ AccumulatorStack::evaluate, bool> = true> +void fused_row_reduce(const ElementType* in, ElementType* out, const Ts* const... rows) { + constexpr IndexType size = Width * sizeof(ElementType) / sizeof(typename VectorWrapper::type); + + auto* vecIn = reinterpret_cast(in); + auto* vecOut = reinterpret_cast(out); + + for (IndexType i = 0; i < size; ++i) + vecOut[i] = fused( + vecIn[i], reinterpret_cast(rows)[i]...); +} + +template AccumulatorState::*accPtr> +struct AccumulatorUpdateContext { + const FeatureTransformer& featureTransformer; + const AccumulatorState& from; + AccumulatorState& to; + + AccumulatorUpdateContext(const FeatureTransformer& ft, + const AccumulatorState& accF, + AccumulatorState& accT) noexcept : + featureTransformer{ft}, + from{accF}, + to{accT} {} + + template, bool> = true> + void apply(const Ts... indices) { + auto to_weight_vector = [&](const IndexType index) { + return &featureTransformer.weights[index * Dimensions]; + }; + + auto to_psqt_weight_vector = [&](const IndexType index) { + return &featureTransformer.psqtWeights[index * PSQTBuckets]; + }; + + fused_row_reduce((from.*accPtr).accumulation[Perspective], + (to.*accPtr).accumulation[Perspective], + to_weight_vector(indices)...); + + fused_row_reduce( + (from.*accPtr).psqtAccumulation[Perspective], (to.*accPtr).psqtAccumulation[Perspective], + to_psqt_weight_vector(indices)...); + } +}; + +template AccumulatorState::*accPtr> +auto make_accumulator_update_context( + const FeatureTransformer& featureTransformer, + const AccumulatorState& accumulatorFrom, + AccumulatorState& accumulatorTo) noexcept { + return AccumulatorUpdateContext{ + featureTransformer, accumulatorFrom, accumulatorTo}; +} + template(ksq, computed.dirtyPiece, added, removed); - if (removed.size() == 0 && added.size() == 0) + assert(added.size() == 1 || added.size() == 2); + assert(removed.size() == 1 || removed.size() == 2); + + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); + + // Workaround compiler warning for uninitialized variables, replicated on + // profile builds on windows with gcc 14.2.0. + // TODO remove once unneeded + sf_assume(added.size() == 1 || added.size() == 2); + sf_assume(removed.size() == 1 || removed.size() == 2); + + auto updateContext = + make_accumulator_update_context(featureTransformer, computed, target_state); + + if ((Forward && removed.size() == 1) || (Backward && added.size() == 1)) { - std::memcpy((target_state.*accPtr).accumulation[Perspective], - (computed.*accPtr).accumulation[Perspective], - TransformedFeatureDimensions * sizeof(BiasType)); - std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], - (computed.*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); + assert(added.size() == 1 && removed.size() == 1); + updateContext.template apply(added[0], removed[0]); + } + else if (Forward && added.size() == 1) + { + assert(removed.size() == 2); + updateContext.template apply(added[0], removed[0], removed[1]); + } + else if (Backward && removed.size() == 1) + { + assert(added.size() == 2); + updateContext.template apply(added[0], added[1], removed[0]); } else { - assert(added.size() == 1 || added.size() == 2); - assert(removed.size() == 1 || removed.size() == 2); - - if (Forward) - assert(added.size() <= removed.size()); - else - assert(removed.size() <= added.size()); - - // Workaround compiler warning for uninitialized variables, replicated on - // profile builds on windows with gcc 14.2.0. - // TODO remove once unneeded - sf_assume(added.size() == 1 || added.size() == 2); - sf_assume(removed.size() == 1 || removed.size() == 2); - -#ifdef VECTOR - auto* accIn = - reinterpret_cast(&(computed.*accPtr).accumulation[Perspective][0]); - auto* accOut = - reinterpret_cast(&(target_state.*accPtr).accumulation[Perspective][0]); - - const IndexType offsetA0 = TransformedFeatureDimensions * added[0]; - auto* columnA0 = reinterpret_cast(&featureTransformer.weights[offsetA0]); - const IndexType offsetR0 = TransformedFeatureDimensions * removed[0]; - auto* columnR0 = reinterpret_cast(&featureTransformer.weights[offsetR0]); - - if ((Forward && removed.size() == 1) || (Backwards && added.size() == 1)) - { - assert(added.size() == 1 && removed.size() == 1); - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); - } - else if (Forward && added.size() == 1) - { - assert(removed.size() == 2); - const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), - vec_add_16(columnR0[i], columnR1[i])); - } - else if (Backwards && removed.size() == 1) - { - assert(added.size() == 2); - const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(vec_add_16(accIn[i], columnA0[i]), - vec_sub_16(columnA1[i], columnR0[i])); - } - else - { - assert(added.size() == 2 && removed.size() == 2); - const IndexType offsetA1 = TransformedFeatureDimensions * added[1]; - auto* columnA1 = reinterpret_cast(&featureTransformer.weights[offsetA1]); - const IndexType offsetR1 = TransformedFeatureDimensions * removed[1]; - auto* columnR1 = reinterpret_cast(&featureTransformer.weights[offsetR1]); - - for (IndexType i = 0; - i < TransformedFeatureDimensions * sizeof(WeightType) / sizeof(vec_t); ++i) - accOut[i] = vec_add_16(accIn[i], vec_sub_16(vec_add_16(columnA0[i], columnA1[i]), - vec_add_16(columnR0[i], columnR1[i]))); - } - - auto* accPsqtIn = - reinterpret_cast(&(computed.*accPtr).psqtAccumulation[Perspective][0]); - auto* accPsqtOut = - reinterpret_cast(&(target_state.*accPtr).psqtAccumulation[Perspective][0]); - - const IndexType offsetPsqtA0 = PSQTBuckets * added[0]; - auto* columnPsqtA0 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA0]); - const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; - auto* columnPsqtR0 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR0]); - - if ((Forward && removed.size() == 1) - || (Backwards && added.size() == 1)) // added.size() == removed.size() == 1 - { - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = - vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[i], columnPsqtR0[i]), columnPsqtA0[i]); - } - else if (Forward && added.size() == 1) - { - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); - } - else if (Backwards && removed.size() == 1) - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_add_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), - vec_sub_psqt_32(columnPsqtA1[i], columnPsqtR0[i])); - } - else - { - const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; - auto* columnPsqtA1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtA1]); - const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; - auto* columnPsqtR1 = - reinterpret_cast(&featureTransformer.psqtWeights[offsetPsqtR1]); - - for (std::size_t i = 0; i < PSQTBuckets * sizeof(PSQTWeightType) / sizeof(psqt_vec_t); - ++i) - accPsqtOut[i] = vec_add_psqt_32( - accPsqtIn[i], vec_sub_psqt_32(vec_add_psqt_32(columnPsqtA0[i], columnPsqtA1[i]), - vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i]))); - } -#else - std::memcpy((target_state.*accPtr).accumulation[Perspective], - (computed.*accPtr).accumulation[Perspective], - TransformedFeatureDimensions * sizeof(BiasType)); - std::memcpy((target_state.*accPtr).psqtAccumulation[Perspective], - (computed.*accPtr).psqtAccumulation[Perspective], - PSQTBuckets * sizeof(PSQTWeightType)); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = TransformedFeatureDimensions * index; - for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) - (target_state.*accPtr).accumulation[Perspective][i] -= - featureTransformer.weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (target_state.*accPtr).psqtAccumulation[Perspective][i] -= - featureTransformer.psqtWeights[index * PSQTBuckets + i]; - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = TransformedFeatureDimensions * index; - for (IndexType i = 0; i < TransformedFeatureDimensions; ++i) - (target_state.*accPtr).accumulation[Perspective][i] += - featureTransformer.weights[offset + i]; - - for (std::size_t i = 0; i < PSQTBuckets; ++i) - (target_state.*accPtr).psqtAccumulation[Perspective][i] += - featureTransformer.psqtWeights[index * PSQTBuckets + i]; - } -#endif + assert(added.size() == 2 && removed.size() == 2); + updateContext.template apply(added[0], added[1], removed[0], + removed[1]); } (target_state.*accPtr).computed[Perspective] = true; @@ -477,7 +407,7 @@ void update_accumulator_refresh_cache( auto* columnA = reinterpret_cast(&featureTransformer.weights[offsetA]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(acc[k], vec_sub_16(columnA[k], columnR[k])); + acc[k] = fused(acc[k], columnA[k], columnR[k]); } if (combineLast3) { @@ -496,8 +426,8 @@ void update_accumulator_refresh_cache( reinterpret_cast(&featureTransformer.weights[offsetR2]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_sub_16(vec_add_16(acc[k], columnA[k]), - vec_add_16(columnR[k], columnR2[k])); + acc[k] = fused(acc[k], columnA[k], columnR[k], + columnR2[k]); } else { @@ -507,8 +437,8 @@ void update_accumulator_refresh_cache( reinterpret_cast(&featureTransformer.weights[offsetA2]); for (IndexType k = 0; k < Tiling::NumRegs; ++k) - acc[k] = vec_add_16(vec_sub_16(acc[k], columnR[k]), - vec_add_16(columnA[k], columnA2[k])); + acc[k] = fused(acc[k], columnA[k], columnA2[k], + columnR[k]); } } else diff --git a/src/nnue/nnue_common.h b/src/nnue/nnue_common.h index b217c3583..e6e3017d2 100644 --- a/src/nnue/nnue_common.h +++ b/src/nnue/nnue_common.h @@ -281,7 +281,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size enum IncUpdateDirection { FORWARD, - BACKWARDS + BACKWARD }; } // namespace Stockfish::Eval::NNUE diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 20e85be3c..9dee29c19 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -143,6 +143,60 @@ using psqt_vec_t = int32x4_t; #endif +struct Vec16Wrapper { +#ifdef VECTOR + using type = vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); } +#else + using type = BiasType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +struct Vec32Wrapper { +#ifdef VECTOR + using type = psqt_vec_t; + static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); } + static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); } +#else + using type = PSQTWeightType; + static type add(const type& lhs, const type& rhs) { return lhs + rhs; } + static type sub(const type& lhs, const type& rhs) { return lhs - rhs; } +#endif +}; + +enum UpdateOperation { + Add, + Sub +}; + +template = true> +typename VecWrapper::type fused(const typename VecWrapper::type& in) { + return in; +} + +template, bool> = true, + std::enable_if_t = true> +typename VecWrapper::type +fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) { + switch (update_op) + { + case Add : + return fused(VecWrapper::add(in, operand), operands...); + case Sub : + return fused(VecWrapper::sub(in, operand), operands...); + } +} + // Returns the inverse of a permutation template constexpr std::array diff --git a/src/types.h b/src/types.h index 6465dfd6b..d6af929e5 100644 --- a/src/types.h +++ b/src/types.h @@ -38,6 +38,7 @@ #include #include + #include #if defined(_MSC_VER) // Disable some silly and noisy warnings from MSVC compiler @@ -429,6 +430,14 @@ class Move { std::uint16_t data; }; +template +struct is_all_same { + static constexpr bool value = (std::is_same_v && ...); +}; + +template +constexpr auto is_all_same_v = is_all_same::value; + } // namespace Stockfish #endif // #ifndef TYPES_H_INCLUDED From ee35a51c40eff7fb237f217d87d9ca7c73874924 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 26 Mar 2025 16:19:43 -0500 Subject: [PATCH 33/39] Remove extra division closes https://github.com/official-stockfish/Stockfish/pull/5943 No functional change --- src/search.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 0c543c308..8e8e93538 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1444,12 +1444,12 @@ 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 = (std::clamp(160 * (depth - 4) / 2, 0, 200) + 34 * !allNode - + 164 * ((ss - 1)->moveCount > 8) - + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) - + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) - + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) - + std::min(-(ss - 1)->statScore / 112, 303)); + int bonusScale = + (std::clamp(80 * depth - 320, 0, 200) + 34 * !allNode + 164 * ((ss - 1)->moveCount > 8) + + 141 * (!ss->inCheck && bestValue <= ss->staticEval - 100) + + 121 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 75) + + 86 * ((ss - 1)->isTTMove) + 86 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 112, 303)); bonusScale = std::max(bonusScale, 0); From dfef7e75209a6d14f16618dd6040c0898522d89d Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 28 Mar 2025 17:57:04 +0300 Subject: [PATCH 34/39] Remove redundant assignment closes https://github.com/official-stockfish/Stockfish/pull/5944 No functional change --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 8e8e93538..99bf47825 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -298,14 +298,10 @@ void Search::Worker::iterative_deepening() { &this->continuationHistory[0][0][NO_PIECE][0]; // Use as a sentinel (ss - i)->continuationCorrectionHistory = &this->continuationCorrectionHistory[NO_PIECE][0]; (ss - i)->staticEval = VALUE_NONE; - (ss - i)->reduction = 0; } for (int i = 0; i <= MAX_PLY + 2; ++i) - { - (ss + i)->ply = i; - (ss + i)->reduction = 0; - } + (ss + i)->ply = i; ss->pv = pv; From d2cb927a04829e5bfa3e91ce3c1da327ed65d520 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 19 Mar 2025 17:06:11 -0700 Subject: [PATCH 35/39] Simplify TT cutoff conthist updates Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 86304 W: 22251 L: 22084 D: 41969 Ptnml(0-2): 250, 10214, 22123, 10249, 316 https://tests.stockfishchess.org/tests/view/67db60cd8c7f315cc372aae7 Passed Non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 199158 W: 50655 L: 50617 D: 97886 Ptnml(0-2): 103, 21579, 56182, 21607, 108 https://tests.stockfishchess.org/tests/view/67dcdc5b8c7f315cc372ac12 closes https://github.com/official-stockfish/Stockfish/pull/5945 Bench: 2069191 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 99bf47825..ee4e89556 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -720,8 +720,7 @@ Value Search::Worker::search( // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) - update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -std::min(809 * (depth + 1) - 249, 3052)); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -2200); } // Partial workaround for the graph history interaction problem From 1a395f1b565e4140a3f15bdd7b8add92fd11537a Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 28 Mar 2025 14:39:11 -0700 Subject: [PATCH 36/39] Remove pawn_attacks_bb() Generalize attacks_bb() to handle pawns and remove pawn_attacks_bb() https://tests.stockfishchess.org/tests/view/67e9496231d7cf8afdc44a2e LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 134688 W: 34660 L: 34553 D: 65475 Ptnml(0-2): 298, 14619, 37462, 14608, 357 closes https://github.com/official-stockfish/Stockfish/pull/5947 No functional change --- src/bitboard.cpp | 5 ++--- src/bitboard.h | 12 +++--------- src/movegen.cpp | 2 +- src/position.cpp | 18 +++++++++--------- 4 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 8798c5701..9b1d674c0 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -32,7 +32,6 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; alignas(64) Magic Magics[SQUARE_NB][2]; @@ -86,8 +85,8 @@ void Bitboards::init() { for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { - PawnAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - PawnAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + PseudoAttacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + PseudoAttacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) PseudoAttacks[KING][s1] |= safe_destination(s1, step); diff --git a/src/bitboard.h b/src/bitboard.h index df15113bd..941299c0d 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -62,7 +62,6 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; -extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square @@ -155,11 +154,6 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) { : shift(b) | shift(b); } -inline Bitboard pawn_attacks_bb(Color c, Square s) { - - assert(is_ok(s)); - return PawnAttacks[c][s]; -} // Returns a bitboard representing an entire line (from board edge // to board edge) that intersects the two given squares. If the given squares @@ -216,10 +210,10 @@ inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); } // Returns the pseudo attacks of the given piece type // assuming an empty board. template -inline Bitboard attacks_bb(Square s) { +inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { - assert((Pt != PAWN) && (is_ok(s))); - return PseudoAttacks[Pt][s]; + assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s))); + return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; } diff --git a/src/movegen.cpp b/src/movegen.cpp index 8653a828f..a73bd8501 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -134,7 +134,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; - b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square()); + b1 = pawnsNotOn7 & attacks_bb(pos.ep_square(), Them); assert(b1); diff --git a/src/position.cpp b/src/position.cpp index 52e1004e8..02fd6c7a9 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -270,7 +270,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) { // a) side to move have a pawn threatening epSquare // b) there is an enemy pawn in front of epSquare // c) there is no piece on epSquare or behind epSquare - enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN) + enpassant = attacks_bb(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN) && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))) && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove)))); } @@ -321,7 +321,7 @@ void Position::set_check_info() const { Square ksq = square(~sideToMove); - st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq); + st->checkSquares[PAWN] = attacks_bb(ksq, ~sideToMove); st->checkSquares[KNIGHT] = attacks_bb(ksq); st->checkSquares[BISHOP] = attacks_bb(ksq, pieces()); st->checkSquares[ROOK] = attacks_bb(ksq, pieces()); @@ -487,8 +487,8 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) - | (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN)) - | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN)) + | (attacks_bb(s, BLACK) & pieces(WHITE, PAWN)) + | (attacks_bb(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING)); } @@ -498,7 +498,7 @@ bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const { && (attacks_bb(s, occupied) & pieces(c, ROOK, QUEEN))) || ((attacks_bb(s) & pieces(c, BISHOP, QUEEN)) && (attacks_bb(s, occupied) & pieces(c, BISHOP, QUEEN))) - || (((pawn_attacks_bb(~c, s) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) + || (((attacks_bb(s, ~c) & pieces(PAWN)) | (attacks_bb(s) & pieces(KNIGHT)) | (attacks_bb(s) & pieces(KING))) & pieces(c)); } @@ -597,9 +597,9 @@ bool Position::pseudo_legal(const Move m) const { if ((Rank8BB | Rank1BB) & to) return false; - if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture - && !((from + pawn_push(us) == to) && empty(to)) // Not a single push - && !((from + 2 * pawn_push(us) == to) // Not a double push + if (!(attacks_bb(from, us) & pieces(~us) & to) // Not a capture + && !((from + pawn_push(us) == to) && empty(to)) // Not a single push + && !((from + 2 * pawn_push(us) == to) // Not a double push && (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us)))) return false; } @@ -812,7 +812,7 @@ DirtyPiece Position::do_move(Move m, { // Set en passant square if the moved pawn can be captured if ((int(to) ^ int(from)) == 16 - && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN))) + && (attacks_bb(to - pawn_push(us), us) & pieces(them, PAWN))) { st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; From ed89817f626d4abad0b2e90d8ccd29f68377dff2 Mon Sep 17 00:00:00 2001 From: mstembera Date: Fri, 28 Mar 2025 18:09:38 -0700 Subject: [PATCH 37/39] Various cleanups Various simplifications, cleanups, consistancy improvements, and warning mitigations. Passed Non-Regression STC: https://tests.stockfishchess.org/tests/view/67e7dd2d6682f97da2178fd8 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 386848 W: 99593 L: 99751 D: 187504 Ptnml(0-2): 1024, 41822, 107973, 41498, 1107 closes https://github.com/official-stockfish/Stockfish/pull/5948 No functional change --- src/bitboard.h | 18 +++++++++--------- src/evaluate.cpp | 10 ++++------ src/evaluate.h | 2 +- src/movepick.cpp | 2 +- src/nnue/layers/affine_transform.h | 4 ---- src/position.cpp | 7 +++---- src/search.cpp | 17 ++++++++--------- src/types.h | 14 +++++++------- 8 files changed, 33 insertions(+), 41 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 941299c0d..f959bcb86 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -102,17 +102,17 @@ constexpr Bitboard square_bb(Square s) { // Overloads of bitwise operators between a Bitboard and a Square for testing // whether a given bit is set in a bitboard, and for setting and clearing bits. -inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } -inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } -inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } -inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } -inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } +constexpr Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); } +constexpr Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); } +constexpr Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); } +constexpr Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); } +constexpr Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); } -inline Bitboard operator&(Square s, Bitboard b) { return b & s; } -inline Bitboard operator|(Square s, Bitboard b) { return b | s; } -inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; } +constexpr Bitboard operator&(Square s, Bitboard b) { return b & s; } +constexpr Bitboard operator|(Square s, Bitboard b) { return b | s; } +constexpr Bitboard operator^(Square s, Bitboard b) { return b ^ s; } -inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } +constexpr Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; } constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } diff --git a/src/evaluate.cpp b/src/evaluate.cpp index ccb089d97..92b03af37 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -38,17 +38,15 @@ namespace Stockfish { // Returns a static, purely materialistic evaluation of the position from -// the point of view of the given color. It can be divided by PawnValue to get +// the point of view of the side to move. It can be divided by PawnValue to get // an approximation of the material advantage on the board in terms of pawns. -int Eval::simple_eval(const Position& pos, Color c) { +int Eval::simple_eval(const Position& pos) { + Color c = pos.side_to_move(); return PawnValue * (pos.count(c) - pos.count(~c)) + (pos.non_pawn_material(c) - pos.non_pawn_material(~c)); } -bool Eval::use_smallnet(const Position& pos) { - int simpleEval = simple_eval(pos, pos.side_to_move()); - return std::abs(simpleEval) > 962; -} +bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; } // Evaluate is the evaluator for the outer world. It returns a static evaluation // of the position from the point of view of the side to move. diff --git a/src/evaluate.h b/src/evaluate.h index 07b914007..2a6c9afa9 100644 --- a/src/evaluate.h +++ b/src/evaluate.h @@ -44,7 +44,7 @@ class AccumulatorStack; std::string trace(Position& pos, const Eval::NNUE::Networks& networks); -int simple_eval(const Position& pos, Color c); +int simple_eval(const Position& pos); bool use_smallnet(const Position& pos); Value evaluate(const NNUE::Networks& networks, const Position& pos, diff --git a/src/movepick.cpp b/src/movepick.cpp index c762e7e45..6ee5fad7c 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -176,7 +176,7 @@ void MovePicker::score() { : 0; // malus for putting piece en prise - m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000 + m.value -= (pt == QUEEN && bool(to & threatenedByRook) ? 49000 : pt == ROOK && bool(to & threatenedByMinor) ? 24335 : 0); diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index dac727e23..3920efb17 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -245,19 +245,16 @@ class AffineTransform { #if defined(USE_AVX2) using vec_t = __m256i; #define vec_setzero() _mm256_setzero_si256() - #define vec_set_32 _mm256_set1_epi32 #define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32 #define vec_hadd Simd::m256_hadd #elif defined(USE_SSSE3) using vec_t = __m128i; #define vec_setzero() _mm_setzero_si128() - #define vec_set_32 _mm_set1_epi32 #define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32 #define vec_hadd Simd::m128_hadd #elif defined(USE_NEON_DOTPROD) using vec_t = int32x4_t; #define vec_setzero() vdupq_n_s32(0) - #define vec_set_32 vdupq_n_s32 #define vec_add_dpbusd_32(acc, a, b) \ Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \ vreinterpretq_s8_s32(b)) @@ -282,7 +279,6 @@ class AffineTransform { output[0] = vec_hadd(sum0, biases[0]); #undef vec_setzero - #undef vec_set_32 #undef vec_add_dpbusd_32 #undef vec_hadd } diff --git a/src/position.cpp b/src/position.cpp index 02fd6c7a9..0e5748e6a 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -54,8 +54,8 @@ namespace { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; +static constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING}; } // namespace @@ -733,8 +733,7 @@ DirtyPiece Position::do_move(Move m, st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; captured = NO_PIECE; } - - if (captured) + else if (captured) { Square capsq = to; diff --git a/src/search.cpp b/src/search.cpp index ee4e89556..10e5047f1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -492,7 +492,8 @@ void Search::Worker::iterative_deepening() { // Do we have time for the next iteration? Can we stop searching now? if (limits.use_time_management() && !threads.stop && !mainThread->stopOnPonderhit) { - int nodesEffort = rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); + uint64_t nodesEffort = + rootMoves[0].effort * 100000 / std::max(size_t(1), size_t(nodes)); double fallingEval = (11.396 + 2.035 * (mainThread->bestPreviousAverageScore - bestValue) @@ -929,10 +930,7 @@ Value Search::Worker::search( { assert(move.is_ok()); - if (move == excludedMove) - continue; - - if (!pos.legal(move)) + if (move == excludedMove || !pos.legal(move)) continue; assert(pos.capture_stage(move)); @@ -1572,12 +1570,13 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) return ttData.value; // Step 4. Static evaluation of the position - Value unadjustedStaticEval = VALUE_NONE; - const auto correctionValue = correction_value(*thisThread, pos, ss); + Value unadjustedStaticEval = VALUE_NONE; if (ss->inCheck) bestValue = futilityBase = -VALUE_INFINITE; else { + const auto correctionValue = correction_value(*thisThread, pos, ss); + if (ss->ttHit) { // Never assume anything about values stored in TT @@ -1930,8 +1929,8 @@ Move Skill::pick_best(const RootMoves& rootMoves, size_t multiPV) { for (size_t i = 0; i < multiPV; ++i) { // This is our magic formula - int push = (weakness * int(topScore - rootMoves[i].score) - + delta * (rng.rand() % int(weakness))) + int push = int(weakness * int(topScore - rootMoves[i].score) + + delta * (rng.rand() % int(weakness))) / 128; if (rootMoves[i].score + push >= maxScore) diff --git a/src/types.h b/src/types.h index d6af929e5..5f9cb421e 100644 --- a/src/types.h +++ b/src/types.h @@ -290,8 +290,8 @@ struct DirtyPiece { }; #define ENABLE_INCR_OPERATORS_ON(T) \ - inline T& operator++(T& d) { return d = T(int(d) + 1); } \ - inline T& operator--(T& d) { return d = T(int(d) - 1); } + constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \ + constexpr T& operator--(T& d) { return d = T(int(d) - 1); } ENABLE_INCR_OPERATORS_ON(PieceType) ENABLE_INCR_OPERATORS_ON(Square) @@ -304,10 +304,10 @@ constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); } // Additional operators to add a Direction to a Square -constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } -constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } -inline Square& operator+=(Square& s, Direction d) { return s = s + d; } -inline Square& operator-=(Square& s, Direction d) { return s = s - d; } +constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +constexpr Square& operator+=(Square& s, Direction d) { return s = s + d; } +constexpr Square& operator-=(Square& s, Direction d) { return s = s - d; } // Toggle color constexpr Color operator~(Color c) { return Color(c ^ BLACK); } @@ -335,7 +335,7 @@ constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } -inline Color color_of(Piece pc) { +constexpr Color color_of(Piece pc) { assert(pc != NO_PIECE); return Color(pc >> 3); } From 3d61f932cbdbcca8f4a5f20459e706cfa2415648 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 29 Mar 2025 15:34:48 -0400 Subject: [PATCH 38/39] Squash out post-lmr bonus variable Squash out bonus variable for post-lmr now that the bonus is constant. closes https://github.com/official-stockfish/Stockfish/pull/5953 No functional change --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 10e5047f1..7a1a36100 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1271,8 +1271,7 @@ moves_loop: // When in check, search starts here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); // Post LMR continuation history updates - int bonus = 1600; - update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); + update_continuation_histories(ss, movedPiece, move.to_sq(), 1600); } else if (value > alpha && value < bestValue + 9) newDepth--; From d942e13398aa5de55224c7d81bfad6b0f5b9e488 Mon Sep 17 00:00:00 2001 From: Daniel Samek Date: Mon, 31 Mar 2025 18:27:36 +0200 Subject: [PATCH 39/39] Less fail high cnt in the condition Passed STC: https://tests.stockfishchess.org/tests/view/67e027538888403457d87535 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 164000 W: 42535 L: 42034 D: 79431 Ptnml(0-2): 478, 19228, 42113, 19677, 504 Passed LTC: https://tests.stockfishchess.org/tests/view/67e3c21b8888403457d87808 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 139176 W: 35500 L: 34975 D: 68701 Ptnml(0-2): 54, 15038, 38898, 15525, 73 closes https://github.com/official-stockfish/Stockfish/pull/5960 Bench: 1921404 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index a345bb69f..092980fe5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -58,6 +58,7 @@ Daniel Axtens (daxtens) Daniel Dugovic (ddugovic) Daniel Monroe (Ergodice) Dan Schmidt (dfannius) +DanSamek Dariusz Orzechowski (dorzechowski) David (dav1312) David Zar diff --git a/src/search.cpp b/src/search.cpp index 7a1a36100..5f5934a49 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1215,7 +1215,7 @@ moves_loop: // When in check, search starts here r += 1171 + (depth < 8) * 985; // Increase reduction if next ply has a lot of fail high - if ((ss + 1)->cutoffCnt > 3) + if ((ss + 1)->cutoffCnt > 2) r += 1042 + allNode * 864; // For first picked move (ttMove) reduce reduction