From 8e3e22b3d4f214e12aa83771be543aac1196d713 Mon Sep 17 00:00:00 2001 From: Joost VandeVondele Date: Sat, 18 Jan 2025 21:32:18 +0100 Subject: [PATCH 01/59] Replace depth increase condition with !opponentWorsening Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 220544 W: 57417 L: 57399 D: 105728 Ptnml(0-2): 816, 26554, 55540, 26520, 842 https://tests.stockfishchess.org/tests/view/678970e38082388fa0cbfe02 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 132600 W: 33868 L: 33760 D: 64972 Ptnml(0-2): 126, 14770, 36390, 14898, 116 https://tests.stockfishchess.org/tests/view/678accabc00c743bc9e9fc7f closes https://github.com/official-stockfish/Stockfish/pull/5798 bench 1632964 --- src/search.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d5e86ca94..e99d5d3fc 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -781,10 +781,8 @@ Value Search::Worker::search( opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; - if (priorReduction >= 3 && ss->staticEval + (ss - 1)->staticEval < 0) - { + if (priorReduction >= 3 && !opponentWorsening) depth++; - } // Step 7. Razoring (~1 Elo) // If eval is really low, skip search entirely and return the qsearch value. From 62ecdfe82cb33f5d0d3394c07bac2c7be97ff84b Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 12 Jan 2025 12:53:08 -0800 Subject: [PATCH 02/59] simplify accumulator updates After #5759 accumulator updates are strictly on a per-move basis. Therefore, the generic code for updating multiple moves at once is no longer needed. Passed Non-regression STC: LLR: 3.00 (-2.94,2.94) <-1.75,0.25> Total: 81696 W: 21204 L: 21039 D: 39453 Ptnml(0-2): 210, 8431, 23416, 8566, 225 https://tests.stockfishchess.org/tests/view/67823a24a31c4c13e83518a8 closes https://github.com/official-stockfish/Stockfish/pull/5760 no functional change --- src/nnue/nnue_feature_transformer.h | 193 ++++++++++++---------------- 1 file changed, 81 insertions(+), 112 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 14fdecd72..7a37cda82 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -472,25 +472,20 @@ class FeatureTransformer { return st; } - // Computes the accumulator of the next position. + // Given a computed accumulator, computes the accumulator of the next position. template void update_accumulator_incremental(const Position& pos, StateInfo* computed) const { assert((computed->*accPtr).computed[Perspective]); assert(computed->next != nullptr); -#ifdef VECTOR - // Gcc-10.2 unnecessarily spills AVX2 registers if this array - // is defined in the VECTOR code below, once in each branch. - vec_t acc[Tiling::NumRegs]; - psqt_vec_t psqt[Tiling::NumPsqtRegs]; -#endif - const Square ksq = pos.square(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; FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, removed, added); @@ -498,51 +493,76 @@ class FeatureTransformer { StateInfo* next = computed->next; assert(!(next->*accPtr).computed[Perspective]); -#ifdef VECTOR - if ((removed.size() == 1 || removed.size() == 2) && added.size() == 1) + 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); + assert(added.size() <= removed.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]); - const IndexType offsetA = HalfDimensions * added[0]; - auto* columnA = reinterpret_cast(&weights[offsetA]); if (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]), columnA[i]); + accOut[i] = vec_add_16(vec_sub_16(accIn[i], columnR0[i]), columnA0[i]); } - else + else if (added.size() == 1) { 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], columnA[i]), + accOut[i] = vec_sub_16(vec_add_16(accIn[i], columnA0[i]), vec_add_16(columnR0[i], columnR1[i])); } + else + { + 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]); - const IndexType offsetPsqtA = PSQTBuckets * added[0]; - auto* columnPsqtA = reinterpret_cast(&psqtWeights[offsetPsqtA]); if (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]), - columnPsqtA[i]); + columnPsqtA0[i]); } - else + else if (added.size() == 1) { const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; auto* columnPsqtR1 = @@ -551,110 +571,58 @@ class FeatureTransformer { 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], columnPsqtA[i]), + vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[i], columnPsqtA0[i]), vec_add_psqt_32(columnPsqtR0[i], columnPsqtR1[i])); } - } - else - { - for (IndexType i = 0; i < HalfDimensions / Tiling::TileHeight; ++i) + else { - // Load accumulator - auto* accTileIn = reinterpret_cast( - &(computed->*accPtr).accumulation[Perspective][i * Tiling::TileHeight]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - acc[j] = vec_load(&accTileIn[j]); + const IndexType offsetPsqtA1 = PSQTBuckets * added[1]; + auto* columnPsqtA1 = + reinterpret_cast(&psqtWeights[offsetPsqtA1]); + const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; + auto* columnPsqtR1 = + reinterpret_cast(&psqtWeights[offsetPsqtR1]); - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = HalfDimensions * index + i * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - acc[j] = vec_sub_16(acc[j], column[j]); - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = HalfDimensions * index + i * Tiling::TileHeight; - auto* column = reinterpret_cast(&weights[offset]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - acc[j] = vec_add_16(acc[j], column[j]); - } - - // Store accumulator - auto* accTileOut = reinterpret_cast( - &(next->*accPtr).accumulation[Perspective][i * Tiling::TileHeight]); - for (IndexType j = 0; j < Tiling::NumRegs; ++j) - vec_store(&accTileOut[j], acc[j]); + 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]))); } - - for (IndexType i = 0; i < PSQTBuckets / Tiling::PsqtTileHeight; ++i) - { - // Load accumulator - auto* accTilePsqtIn = reinterpret_cast( - &(computed->*accPtr).psqtAccumulation[Perspective][i * Tiling::PsqtTileHeight]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - psqt[j] = vec_load_psqt(&accTilePsqtIn[j]); - - // Difference calculation for the deactivated features - for (const auto index : removed) - { - const IndexType offset = PSQTBuckets * index + i * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - psqt[j] = vec_sub_psqt_32(psqt[j], columnPsqt[j]); - } - - // Difference calculation for the activated features - for (const auto index : added) - { - const IndexType offset = PSQTBuckets * index + i * Tiling::PsqtTileHeight; - auto* columnPsqt = reinterpret_cast(&psqtWeights[offset]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - psqt[j] = vec_add_psqt_32(psqt[j], columnPsqt[j]); - } - - // Store accumulator - auto* accTilePsqtOut = reinterpret_cast( - &(next->*accPtr).psqtAccumulation[Perspective][i * Tiling::PsqtTileHeight]); - for (std::size_t j = 0; j < Tiling::NumPsqtRegs; ++j) - vec_store_psqt(&accTilePsqtOut[j], psqt[j]); - } - } #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)); + 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]; + // 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]; - } + 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]; + // 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]; - } + for (std::size_t i = 0; i < PSQTBuckets; ++i) + (next->*accPtr).psqtAccumulation[Perspective][i] += + psqtWeights[index * PSQTBuckets + i]; + } #endif + } (next->*accPtr).computed[Perspective] = true; @@ -662,6 +630,7 @@ class FeatureTransformer { update_accumulator_incremental(pos, next); } + template void update_accumulator_refresh_cache(const Position& pos, AccumulatorCaches::Cache* cache) const { From 18b3465a8668f46b7313c7ad6b4dedda7b4709bf Mon Sep 17 00:00:00 2001 From: mstembera Date: Sat, 18 Jan 2025 14:26:41 -0800 Subject: [PATCH 03/59] Generate lookup indices at compile time. Credit to @Disservin: the constexpr lsb is just the De Bruijn bitscan with the xor from the cpw https://www.chessprogramming.org/BitScan closes https://github.com/official-stockfish/Stockfish/pull/5801 No functional change --- .../layers/affine_transform_sparse_input.h | 126 +++++------------- 1 file changed, 34 insertions(+), 92 deletions(-) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 248e19dd0..be5e30b5e 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,99 +38,41 @@ namespace Stockfish::Eval::NNUE::Layers { #if (USE_SSSE3 | (USE_NEON >= 8)) +static constexpr int lsb_index64[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63}; + +constexpr int constexpr_lsb(uint64_t bb) { + assert(bb != 0); + constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL; + return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; +} + +alignas(CacheLineSize) static constexpr struct OffsetIndices { #if (USE_SSE41) -alignas(CacheLineSize) static constexpr std::uint8_t + std::uint8_t offset_indices[256][8]; #else -alignas(CacheLineSize) static constexpr std::uint16_t + std::uint16_t offset_indices[256][8]; #endif - lookup_indices[256][8] = { - {0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0}, {1, 0, 0, 0, 0, 0, 0, 0}, - {0, 1, 0, 0, 0, 0, 0, 0}, {2, 0, 0, 0, 0, 0, 0, 0}, {0, 2, 0, 0, 0, 0, 0, 0}, - {1, 2, 0, 0, 0, 0, 0, 0}, {0, 1, 2, 0, 0, 0, 0, 0}, {3, 0, 0, 0, 0, 0, 0, 0}, - {0, 3, 0, 0, 0, 0, 0, 0}, {1, 3, 0, 0, 0, 0, 0, 0}, {0, 1, 3, 0, 0, 0, 0, 0}, - {2, 3, 0, 0, 0, 0, 0, 0}, {0, 2, 3, 0, 0, 0, 0, 0}, {1, 2, 3, 0, 0, 0, 0, 0}, - {0, 1, 2, 3, 0, 0, 0, 0}, {4, 0, 0, 0, 0, 0, 0, 0}, {0, 4, 0, 0, 0, 0, 0, 0}, - {1, 4, 0, 0, 0, 0, 0, 0}, {0, 1, 4, 0, 0, 0, 0, 0}, {2, 4, 0, 0, 0, 0, 0, 0}, - {0, 2, 4, 0, 0, 0, 0, 0}, {1, 2, 4, 0, 0, 0, 0, 0}, {0, 1, 2, 4, 0, 0, 0, 0}, - {3, 4, 0, 0, 0, 0, 0, 0}, {0, 3, 4, 0, 0, 0, 0, 0}, {1, 3, 4, 0, 0, 0, 0, 0}, - {0, 1, 3, 4, 0, 0, 0, 0}, {2, 3, 4, 0, 0, 0, 0, 0}, {0, 2, 3, 4, 0, 0, 0, 0}, - {1, 2, 3, 4, 0, 0, 0, 0}, {0, 1, 2, 3, 4, 0, 0, 0}, {5, 0, 0, 0, 0, 0, 0, 0}, - {0, 5, 0, 0, 0, 0, 0, 0}, {1, 5, 0, 0, 0, 0, 0, 0}, {0, 1, 5, 0, 0, 0, 0, 0}, - {2, 5, 0, 0, 0, 0, 0, 0}, {0, 2, 5, 0, 0, 0, 0, 0}, {1, 2, 5, 0, 0, 0, 0, 0}, - {0, 1, 2, 5, 0, 0, 0, 0}, {3, 5, 0, 0, 0, 0, 0, 0}, {0, 3, 5, 0, 0, 0, 0, 0}, - {1, 3, 5, 0, 0, 0, 0, 0}, {0, 1, 3, 5, 0, 0, 0, 0}, {2, 3, 5, 0, 0, 0, 0, 0}, - {0, 2, 3, 5, 0, 0, 0, 0}, {1, 2, 3, 5, 0, 0, 0, 0}, {0, 1, 2, 3, 5, 0, 0, 0}, - {4, 5, 0, 0, 0, 0, 0, 0}, {0, 4, 5, 0, 0, 0, 0, 0}, {1, 4, 5, 0, 0, 0, 0, 0}, - {0, 1, 4, 5, 0, 0, 0, 0}, {2, 4, 5, 0, 0, 0, 0, 0}, {0, 2, 4, 5, 0, 0, 0, 0}, - {1, 2, 4, 5, 0, 0, 0, 0}, {0, 1, 2, 4, 5, 0, 0, 0}, {3, 4, 5, 0, 0, 0, 0, 0}, - {0, 3, 4, 5, 0, 0, 0, 0}, {1, 3, 4, 5, 0, 0, 0, 0}, {0, 1, 3, 4, 5, 0, 0, 0}, - {2, 3, 4, 5, 0, 0, 0, 0}, {0, 2, 3, 4, 5, 0, 0, 0}, {1, 2, 3, 4, 5, 0, 0, 0}, - {0, 1, 2, 3, 4, 5, 0, 0}, {6, 0, 0, 0, 0, 0, 0, 0}, {0, 6, 0, 0, 0, 0, 0, 0}, - {1, 6, 0, 0, 0, 0, 0, 0}, {0, 1, 6, 0, 0, 0, 0, 0}, {2, 6, 0, 0, 0, 0, 0, 0}, - {0, 2, 6, 0, 0, 0, 0, 0}, {1, 2, 6, 0, 0, 0, 0, 0}, {0, 1, 2, 6, 0, 0, 0, 0}, - {3, 6, 0, 0, 0, 0, 0, 0}, {0, 3, 6, 0, 0, 0, 0, 0}, {1, 3, 6, 0, 0, 0, 0, 0}, - {0, 1, 3, 6, 0, 0, 0, 0}, {2, 3, 6, 0, 0, 0, 0, 0}, {0, 2, 3, 6, 0, 0, 0, 0}, - {1, 2, 3, 6, 0, 0, 0, 0}, {0, 1, 2, 3, 6, 0, 0, 0}, {4, 6, 0, 0, 0, 0, 0, 0}, - {0, 4, 6, 0, 0, 0, 0, 0}, {1, 4, 6, 0, 0, 0, 0, 0}, {0, 1, 4, 6, 0, 0, 0, 0}, - {2, 4, 6, 0, 0, 0, 0, 0}, {0, 2, 4, 6, 0, 0, 0, 0}, {1, 2, 4, 6, 0, 0, 0, 0}, - {0, 1, 2, 4, 6, 0, 0, 0}, {3, 4, 6, 0, 0, 0, 0, 0}, {0, 3, 4, 6, 0, 0, 0, 0}, - {1, 3, 4, 6, 0, 0, 0, 0}, {0, 1, 3, 4, 6, 0, 0, 0}, {2, 3, 4, 6, 0, 0, 0, 0}, - {0, 2, 3, 4, 6, 0, 0, 0}, {1, 2, 3, 4, 6, 0, 0, 0}, {0, 1, 2, 3, 4, 6, 0, 0}, - {5, 6, 0, 0, 0, 0, 0, 0}, {0, 5, 6, 0, 0, 0, 0, 0}, {1, 5, 6, 0, 0, 0, 0, 0}, - {0, 1, 5, 6, 0, 0, 0, 0}, {2, 5, 6, 0, 0, 0, 0, 0}, {0, 2, 5, 6, 0, 0, 0, 0}, - {1, 2, 5, 6, 0, 0, 0, 0}, {0, 1, 2, 5, 6, 0, 0, 0}, {3, 5, 6, 0, 0, 0, 0, 0}, - {0, 3, 5, 6, 0, 0, 0, 0}, {1, 3, 5, 6, 0, 0, 0, 0}, {0, 1, 3, 5, 6, 0, 0, 0}, - {2, 3, 5, 6, 0, 0, 0, 0}, {0, 2, 3, 5, 6, 0, 0, 0}, {1, 2, 3, 5, 6, 0, 0, 0}, - {0, 1, 2, 3, 5, 6, 0, 0}, {4, 5, 6, 0, 0, 0, 0, 0}, {0, 4, 5, 6, 0, 0, 0, 0}, - {1, 4, 5, 6, 0, 0, 0, 0}, {0, 1, 4, 5, 6, 0, 0, 0}, {2, 4, 5, 6, 0, 0, 0, 0}, - {0, 2, 4, 5, 6, 0, 0, 0}, {1, 2, 4, 5, 6, 0, 0, 0}, {0, 1, 2, 4, 5, 6, 0, 0}, - {3, 4, 5, 6, 0, 0, 0, 0}, {0, 3, 4, 5, 6, 0, 0, 0}, {1, 3, 4, 5, 6, 0, 0, 0}, - {0, 1, 3, 4, 5, 6, 0, 0}, {2, 3, 4, 5, 6, 0, 0, 0}, {0, 2, 3, 4, 5, 6, 0, 0}, - {1, 2, 3, 4, 5, 6, 0, 0}, {0, 1, 2, 3, 4, 5, 6, 0}, {7, 0, 0, 0, 0, 0, 0, 0}, - {0, 7, 0, 0, 0, 0, 0, 0}, {1, 7, 0, 0, 0, 0, 0, 0}, {0, 1, 7, 0, 0, 0, 0, 0}, - {2, 7, 0, 0, 0, 0, 0, 0}, {0, 2, 7, 0, 0, 0, 0, 0}, {1, 2, 7, 0, 0, 0, 0, 0}, - {0, 1, 2, 7, 0, 0, 0, 0}, {3, 7, 0, 0, 0, 0, 0, 0}, {0, 3, 7, 0, 0, 0, 0, 0}, - {1, 3, 7, 0, 0, 0, 0, 0}, {0, 1, 3, 7, 0, 0, 0, 0}, {2, 3, 7, 0, 0, 0, 0, 0}, - {0, 2, 3, 7, 0, 0, 0, 0}, {1, 2, 3, 7, 0, 0, 0, 0}, {0, 1, 2, 3, 7, 0, 0, 0}, - {4, 7, 0, 0, 0, 0, 0, 0}, {0, 4, 7, 0, 0, 0, 0, 0}, {1, 4, 7, 0, 0, 0, 0, 0}, - {0, 1, 4, 7, 0, 0, 0, 0}, {2, 4, 7, 0, 0, 0, 0, 0}, {0, 2, 4, 7, 0, 0, 0, 0}, - {1, 2, 4, 7, 0, 0, 0, 0}, {0, 1, 2, 4, 7, 0, 0, 0}, {3, 4, 7, 0, 0, 0, 0, 0}, - {0, 3, 4, 7, 0, 0, 0, 0}, {1, 3, 4, 7, 0, 0, 0, 0}, {0, 1, 3, 4, 7, 0, 0, 0}, - {2, 3, 4, 7, 0, 0, 0, 0}, {0, 2, 3, 4, 7, 0, 0, 0}, {1, 2, 3, 4, 7, 0, 0, 0}, - {0, 1, 2, 3, 4, 7, 0, 0}, {5, 7, 0, 0, 0, 0, 0, 0}, {0, 5, 7, 0, 0, 0, 0, 0}, - {1, 5, 7, 0, 0, 0, 0, 0}, {0, 1, 5, 7, 0, 0, 0, 0}, {2, 5, 7, 0, 0, 0, 0, 0}, - {0, 2, 5, 7, 0, 0, 0, 0}, {1, 2, 5, 7, 0, 0, 0, 0}, {0, 1, 2, 5, 7, 0, 0, 0}, - {3, 5, 7, 0, 0, 0, 0, 0}, {0, 3, 5, 7, 0, 0, 0, 0}, {1, 3, 5, 7, 0, 0, 0, 0}, - {0, 1, 3, 5, 7, 0, 0, 0}, {2, 3, 5, 7, 0, 0, 0, 0}, {0, 2, 3, 5, 7, 0, 0, 0}, - {1, 2, 3, 5, 7, 0, 0, 0}, {0, 1, 2, 3, 5, 7, 0, 0}, {4, 5, 7, 0, 0, 0, 0, 0}, - {0, 4, 5, 7, 0, 0, 0, 0}, {1, 4, 5, 7, 0, 0, 0, 0}, {0, 1, 4, 5, 7, 0, 0, 0}, - {2, 4, 5, 7, 0, 0, 0, 0}, {0, 2, 4, 5, 7, 0, 0, 0}, {1, 2, 4, 5, 7, 0, 0, 0}, - {0, 1, 2, 4, 5, 7, 0, 0}, {3, 4, 5, 7, 0, 0, 0, 0}, {0, 3, 4, 5, 7, 0, 0, 0}, - {1, 3, 4, 5, 7, 0, 0, 0}, {0, 1, 3, 4, 5, 7, 0, 0}, {2, 3, 4, 5, 7, 0, 0, 0}, - {0, 2, 3, 4, 5, 7, 0, 0}, {1, 2, 3, 4, 5, 7, 0, 0}, {0, 1, 2, 3, 4, 5, 7, 0}, - {6, 7, 0, 0, 0, 0, 0, 0}, {0, 6, 7, 0, 0, 0, 0, 0}, {1, 6, 7, 0, 0, 0, 0, 0}, - {0, 1, 6, 7, 0, 0, 0, 0}, {2, 6, 7, 0, 0, 0, 0, 0}, {0, 2, 6, 7, 0, 0, 0, 0}, - {1, 2, 6, 7, 0, 0, 0, 0}, {0, 1, 2, 6, 7, 0, 0, 0}, {3, 6, 7, 0, 0, 0, 0, 0}, - {0, 3, 6, 7, 0, 0, 0, 0}, {1, 3, 6, 7, 0, 0, 0, 0}, {0, 1, 3, 6, 7, 0, 0, 0}, - {2, 3, 6, 7, 0, 0, 0, 0}, {0, 2, 3, 6, 7, 0, 0, 0}, {1, 2, 3, 6, 7, 0, 0, 0}, - {0, 1, 2, 3, 6, 7, 0, 0}, {4, 6, 7, 0, 0, 0, 0, 0}, {0, 4, 6, 7, 0, 0, 0, 0}, - {1, 4, 6, 7, 0, 0, 0, 0}, {0, 1, 4, 6, 7, 0, 0, 0}, {2, 4, 6, 7, 0, 0, 0, 0}, - {0, 2, 4, 6, 7, 0, 0, 0}, {1, 2, 4, 6, 7, 0, 0, 0}, {0, 1, 2, 4, 6, 7, 0, 0}, - {3, 4, 6, 7, 0, 0, 0, 0}, {0, 3, 4, 6, 7, 0, 0, 0}, {1, 3, 4, 6, 7, 0, 0, 0}, - {0, 1, 3, 4, 6, 7, 0, 0}, {2, 3, 4, 6, 7, 0, 0, 0}, {0, 2, 3, 4, 6, 7, 0, 0}, - {1, 2, 3, 4, 6, 7, 0, 0}, {0, 1, 2, 3, 4, 6, 7, 0}, {5, 6, 7, 0, 0, 0, 0, 0}, - {0, 5, 6, 7, 0, 0, 0, 0}, {1, 5, 6, 7, 0, 0, 0, 0}, {0, 1, 5, 6, 7, 0, 0, 0}, - {2, 5, 6, 7, 0, 0, 0, 0}, {0, 2, 5, 6, 7, 0, 0, 0}, {1, 2, 5, 6, 7, 0, 0, 0}, - {0, 1, 2, 5, 6, 7, 0, 0}, {3, 5, 6, 7, 0, 0, 0, 0}, {0, 3, 5, 6, 7, 0, 0, 0}, - {1, 3, 5, 6, 7, 0, 0, 0}, {0, 1, 3, 5, 6, 7, 0, 0}, {2, 3, 5, 6, 7, 0, 0, 0}, - {0, 2, 3, 5, 6, 7, 0, 0}, {1, 2, 3, 5, 6, 7, 0, 0}, {0, 1, 2, 3, 5, 6, 7, 0}, - {4, 5, 6, 7, 0, 0, 0, 0}, {0, 4, 5, 6, 7, 0, 0, 0}, {1, 4, 5, 6, 7, 0, 0, 0}, - {0, 1, 4, 5, 6, 7, 0, 0}, {2, 4, 5, 6, 7, 0, 0, 0}, {0, 2, 4, 5, 6, 7, 0, 0}, - {1, 2, 4, 5, 6, 7, 0, 0}, {0, 1, 2, 4, 5, 6, 7, 0}, {3, 4, 5, 6, 7, 0, 0, 0}, - {0, 3, 4, 5, 6, 7, 0, 0}, {1, 3, 4, 5, 6, 7, 0, 0}, {0, 1, 3, 4, 5, 6, 7, 0}, - {2, 3, 4, 5, 6, 7, 0, 0}, {0, 2, 3, 4, 5, 6, 7, 0}, {1, 2, 3, 4, 5, 6, 7, 0}, - {0, 1, 2, 3, 4, 5, 6, 7}}; + + constexpr OffsetIndices() : + offset_indices() { + for (int i = 0; i < 256; ++i) + { + std::uint64_t j = i, k = 0; + while (j) + { + offset_indices[i][k++] = constexpr_lsb(j); + j &= j - 1; + } + while (k < 8) + offset_indices[i][k++] = 0; + } + } + +} Lookup; // Find indices of nonzero numbers in an int32_t array template @@ -196,9 +138,9 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou } for (IndexType j = 0; j < OutputsPerChunk; ++j) { - const auto lookup = (nnz >> (j * 8)) & 0xFF; - const auto offsets = - vec128_load(reinterpret_cast(&lookup_indices[lookup])); + const unsigned lookup = (nnz >> (j * 8)) & 0xFF; + const vec128_t offsets = + vec128_load(reinterpret_cast(&Lookup.offset_indices[lookup])); vec128_storeu(reinterpret_cast(out + count), vec128_add(base, offsets)); count += popcount(lookup); base = vec128_add(base, increment); From e7367cef0f5e7ccef36836ba072f165f5147e4f6 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 12 Jan 2025 19:38:02 -0800 Subject: [PATCH 04/59] Clean up pack reordering closes https://github.com/official-stockfish/Stockfish/pull/5802 no functional change --- src/nnue/nnue_feature_transformer.h | 125 ++++++++++++++++------------ 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 7a37cda82..4f0ce6cf6 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "../position.h" @@ -145,6 +146,46 @@ using psqt_vec_t = int32x4_t; #endif +// Returns the inverse of a permutation +template +constexpr std::array +invert_permutation(const std::array& order) { + std::array inverse{}; + for (std::size_t i = 0; i < order.size(); i++) + inverse[order[i]] = i; + return inverse; +} + +// Divide a byte region of size TotalSize to chunks of size +// BlockSize, and permute the blocks by a given order +template +void permute(T (&data)[N], const std::array& order) { + constexpr std::size_t TotalSize = N * sizeof(T); + + static_assert(TotalSize % (BlockSize * OrderSize) == 0, + "ChunkSize * OrderSize must perfectly divide TotalSize"); + + constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize; + + std::array buffer{}; + + std::byte* const bytes = reinterpret_cast(data); + + for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize) + { + std::byte* const values = &bytes[i]; + + for (std::size_t j = 0; j < OrderSize; j++) + { + auto* const buffer_chunk = &buffer[j * BlockSize]; + auto* const value_chunk = &values[order[j] * BlockSize]; + + std::copy(value_chunk, value_chunk + BlockSize, buffer_chunk); + } + + std::copy(std::begin(buffer), std::end(buffer), values); + } +} // Compute optimal SIMD register count for feature transformer accumulation. template @@ -223,62 +264,42 @@ class FeatureTransformer { // Size of forward propagation buffer static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType); + // Store the order by which 128-bit blocks of a 1024-bit data must + // be permuted so that calling packus on adjacent vectors of 16-bit + // integers loaded from the data results in the pre-permutation order + static constexpr auto PackusEpi16Order = []() -> std::array { +#if defined(USE_AVX512) + // _mm512_packus_epi16 after permutation: + // | 0 | 2 | 4 | 6 | // Vector 0 + // | 1 | 3 | 5 | 7 | // Vector 1 + // | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 4, 6, 1, 3, 5, 7}; +#elif defined(USE_AVX2) + // _mm256_packus_epi16 after permutation: + // | 0 | 2 | | 4 | 6 | // Vector 0, 2 + // | 1 | 3 | | 5 | 7 | // Vector 1, 3 + // | 0 | 1 | 2 | 3 | | 4 | 5 | 6 | 7 | // Packed Result + return {0, 2, 1, 3, 4, 6, 5, 7}; +#else + return {0, 1, 2, 3, 4, 5, 6, 7}; +#endif + }(); + + static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order); + // Hash value embedded in the evaluation file static constexpr std::uint32_t get_hash_value() { return FeatureSet::HashValue ^ (OutputDimensions * 2); } - static constexpr void order_packs([[maybe_unused]] uint64_t* v) { -#if defined(USE_AVX512) // _mm512_packs_epi16 ordering - uint64_t tmp0 = v[2], tmp1 = v[3]; - v[2] = v[8], v[3] = v[9]; - v[8] = v[4], v[9] = v[5]; - v[4] = tmp0, v[5] = tmp1; - tmp0 = v[6], tmp1 = v[7]; - v[6] = v[10], v[7] = v[11]; - v[10] = v[12], v[11] = v[13]; - v[12] = tmp0, v[13] = tmp1; -#elif defined(USE_AVX2) // _mm256_packs_epi16 ordering - std::swap(v[2], v[4]); - std::swap(v[3], v[5]); -#endif + void permute_weights() { + permute<16>(biases, PackusEpi16Order); + permute<16>(weights, PackusEpi16Order); } - static constexpr void inverse_order_packs([[maybe_unused]] uint64_t* v) { -#if defined(USE_AVX512) // Inverse _mm512_packs_epi16 ordering - uint64_t tmp0 = v[2], tmp1 = v[3]; - v[2] = v[4], v[3] = v[5]; - v[4] = v[8], v[5] = v[9]; - v[8] = tmp0, v[9] = tmp1; - tmp0 = v[6], tmp1 = v[7]; - v[6] = v[12], v[7] = v[13]; - v[12] = v[10], v[13] = v[11]; - v[10] = tmp0, v[11] = tmp1; -#elif defined(USE_AVX2) // Inverse _mm256_packs_epi16 ordering - std::swap(v[2], v[4]); - std::swap(v[3], v[5]); -#endif - } - - void permute_weights([[maybe_unused]] void (*order_fn)(uint64_t*)) { -#if defined(USE_AVX2) - #if defined(USE_AVX512) - constexpr IndexType di = 16; - #else - constexpr IndexType di = 8; - #endif - uint64_t* b = reinterpret_cast(&biases[0]); - for (IndexType i = 0; i < HalfDimensions * sizeof(BiasType) / sizeof(uint64_t); i += di) - order_fn(&b[i]); - - for (IndexType j = 0; j < InputDimensions; ++j) - { - uint64_t* w = reinterpret_cast(&weights[j * HalfDimensions]); - for (IndexType i = 0; i < HalfDimensions * sizeof(WeightType) / sizeof(uint64_t); - i += di) - order_fn(&w[i]); - } -#endif + void unpermute_weights() { + permute<16>(biases, InversePackusEpi16Order); + permute<16>(weights, InversePackusEpi16Order); } inline void scale_weights(bool read) { @@ -300,7 +321,7 @@ class FeatureTransformer { read_leb_128(stream, weights, HalfDimensions * InputDimensions); read_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - permute_weights(inverse_order_packs); + permute_weights(); scale_weights(true); return !stream.fail(); } @@ -308,14 +329,14 @@ class FeatureTransformer { // Write network parameters bool write_parameters(std::ostream& stream) { - permute_weights(order_packs); + unpermute_weights(); scale_weights(false); write_leb_128(stream, biases, HalfDimensions); write_leb_128(stream, weights, HalfDimensions * InputDimensions); write_leb_128(stream, psqtWeights, PSQTBuckets * InputDimensions); - permute_weights(inverse_order_packs); + permute_weights(); scale_weights(true); return !stream.fail(); } From 59c578ad284f057ec32afe506814aaf0f8f7b4f4 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sun, 19 Jan 2025 18:31:31 +0100 Subject: [PATCH 05/59] Add move count based reduction. Do less reduction which is linear increasing with move count (factor = 64). Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 191488 W: 49982 L: 49432 D: 92074 Ptnml(0-2): 731, 22523, 48614, 23217, 659 https://tests.stockfishchess.org/tests/view/678d0b29d63764e34db4904b Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 90582 W: 23150 L: 22717 D: 44715 Ptnml(0-2): 73, 9936, 24822, 10405, 55 https://tests.stockfishchess.org/tests/view/678d347cd63764e34db4916f closes https://github.com/official-stockfish/Stockfish/pull/5807 Bench: 1803474 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e99d5d3fc..de4318437 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1155,7 +1155,7 @@ moves_loop: // When in check, search starts here // These reduction adjustments have no proven non-linear scaling - r += 307; + r += 307 - moveCount * 64; r -= std::abs(correctionValue) / 34112; From 4975b2bc6fb12d42a7441899a4698cf0d14914dd Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 19 Jan 2025 20:33:05 +0100 Subject: [PATCH 06/59] Increase prior countermove bonus if TT move Passed STC: https://tests.stockfishchess.org/tests/view/678c4c8bf4dc0a8b4ae8db5c LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 273408 W: 71089 L: 70415 D: 131904 Ptnml(0-2): 937, 32466, 69229, 33130, 942 Passed LTC: https://tests.stockfishchess.org/tests/view/678ccabdf4dc0a8b4ae8dd7a LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 148614 W: 38138 L: 37584 D: 72892 Ptnml(0-2): 97, 16450, 40689, 16944, 127 closes https://github.com/official-stockfish/Stockfish/pull/5809 Bench: 1582867 --- src/search.cpp | 5 ++++- src/search.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index de4318437..951d197d2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -889,6 +889,7 @@ Value Search::Worker::search( thisThread->nodes.fetch_add(1, std::memory_order_relaxed); ss->currentMove = move; + ss->isTTMove = (move == ttData.move); ss->continuationHistory = &this->continuationHistory[ss->inCheck][true][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1138,6 +1139,7 @@ moves_loop: // When in check, search starts here // Update the current move (this must be done after singular extension search) ss->currentMove = move; + ss->isTTMove = (move == ttData.move); ss->continuationHistory = &thisThread->continuationHistory[ss->inCheck][capture][movedPiece][move.to_sq()]; ss->continuationCorrectionHistory = @@ -1387,7 +1389,8 @@ moves_loop: // When in check, search starts here { int bonusScale = (118 * (depth > 5) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8) + 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102) - + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82)); + + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) + + 80 * ((ss - 1)->isTTMove)); // Proportional to "how much damage we have to undo" bonusScale += std::min(-(ss - 1)->statScore / 106, 318); diff --git a/src/search.h b/src/search.h index 3983e0f33..3a1b3a77d 100644 --- a/src/search.h +++ b/src/search.h @@ -75,6 +75,7 @@ struct Stack { bool ttHit; int cutoffCnt; int reduction; + bool isTTMove; }; From aa894c0f93201cac899f000411fa59cdbc386fd6 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Mon, 20 Jan 2025 00:06:26 +0300 Subject: [PATCH 07/59] Comments Tweak * Remove from comments, hardcoded exact values for parameters that are subject to tuning. * Remove the Elo worth, as they are now completely outdated, making them irrelevant and potentially misleading. * Consolidated scaling-related comments into a single section for clarity. Used asterisks (*) to highlight parameters significantly affected by scaling, given their separation in the code. closes https://github.com/official-stockfish/Stockfish/pull/5810 No functional change --- src/search.cpp | 116 +++++++++++++++++++++++------------------------- src/timeman.cpp | 2 +- 2 files changed, 56 insertions(+), 62 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 951d197d2..3c8f33b41 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -64,6 +64,12 @@ using namespace Search; namespace { +// (*Scalers): +// The values with Scaler asterisks have proven non-linear scaling. +// They are optimized to time controls of 180 + 1.8 and longer, +// so changing them or adding conditions that are similar requires +// tests at these types of time controls. + // Futility margin Value futility_margin(Depth d, bool noTtCutNode, bool improving, bool oppWorsening) { Value futilityMult = 112 - 26 * noTtCutNode; @@ -320,7 +326,7 @@ void Search::Worker::iterative_deepening() { alpha = std::max(avg - delta, -VALUE_INFINITE); beta = std::min(avg + delta, VALUE_INFINITE); - // Adjust optimism based on root move's averageScore (~4 Elo) + // Adjust optimism based on root move's averageScore optimism[us] = 141 * avg / (std::abs(avg) + 83); optimism[~us] = -optimism[us]; @@ -647,15 +653,14 @@ Value Search::Worker::search( && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) && (cutNode == (ttData.value >= beta) || depth > 9)) { - // If ttMove is quiet, update move sorting heuristics on TT hit (~2 Elo) + // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) { - // Bonus for a quiet ttMove that fails high (~2 Elo) + // Bonus for a quiet ttMove that fails high if (!ttCapture) update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 746 / 1024); - // Extra penalty for early quiet moves of - // the previous ply (~1 Elo on STC, ~2 Elo on LTC) + // Extra penalty for early quiet moves of the previous ply if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -stat_malus(depth + 1) * 1042 / 1024); @@ -733,7 +738,6 @@ Value Search::Worker::search( else if (excludedMove) { // Providing the hint that this node's accumulator will be used often - // brings significant Elo gain (~13 Elo). Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); unadjustedStaticEval = eval = ss->staticEval; } @@ -748,7 +752,7 @@ Value Search::Worker::search( ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - // ttValue can be used as a better position evaluation (~7 Elo) + // ttValue can be used as a better position evaluation if (is_valid(ttData.value) && (ttData.bound & (ttData.value > eval ? BOUND_LOWER : BOUND_UPPER))) eval = ttData.value; @@ -763,7 +767,7 @@ Value Search::Worker::search( unadjustedStaticEval, tt.generation()); } - // Use static evaluation difference to improve quiet move ordering (~9 Elo) + // 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), -1881, 1413) + 616; @@ -784,13 +788,13 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; - // Step 7. Razoring (~1 Elo) + // 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 - 462 - 297 * depth * depth) return qsearch(pos, ss, alpha, beta); - // Step 8. Futility pruning: child node (~40 Elo) + // 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) @@ -801,7 +805,7 @@ Value Search::Worker::search( improving |= ss->staticEval >= beta + 97; - // Step 9. Null move search with verification search (~35 Elo) + // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta && ss->staticEval >= beta - 20 * depth + 440 && !excludedMove && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) @@ -842,11 +846,9 @@ Value Search::Worker::search( } } - // Step 10. Internal iterative reductions (~9 Elo) + // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. - // This heuristic is known to scale non-linearly, current version was tested at VVLTC. - // Further improvements need to be tested at similar time control if they make IIR - // more aggressive. + // (* Scaler) Especially if they make IIR more aggressive. if ((PvNode || (cutNode && depth >= 7)) && !ttData.move) depth -= 2; @@ -854,7 +856,7 @@ Value Search::Worker::search( if (depth <= 0) return qsearch(pos, ss, alpha, beta); - // Step 11. ProbCut (~10 Elo) + // 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 + 174 - 56 * improving; @@ -919,7 +921,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here - // Step 12. A small Probcut idea (~4 Elo) + // Step 12. A small Probcut idea probCutBeta = beta + 412; if ((ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 4 && ttData.value >= probCutBeta && !is_decisive(beta) && is_valid(ttData.value) && !is_decisive(ttData.value)) @@ -980,15 +982,15 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); - // Decrease reduction if position is or has been on the PV (~7 Elo) + // Decrease reduction if position is or has been on the PV (*Scaler) if (ss->ttPv) r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; - // Step 14. Pruning at shallow depth (~120 Elo). + // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) { - // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold (~8 Elo) + // Skip quiet moves if movecount exceeds our FutilityMoveCount threshold if (moveCount >= futility_move_count(improving, depth)) mp.skip_quiet_moves(); @@ -1001,7 +1003,7 @@ moves_loop: // When in check, search starts here int captHist = thisThread->captureHistory[movedPiece][move.to_sq()][type_of(capturedPiece)]; - // Futility pruning for captures (~2 Elo) + // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { Value futilityValue = ss->staticEval + 271 + 243 * lmrDepth @@ -1010,7 +1012,7 @@ moves_loop: // When in check, search starts here continue; } - // SEE based pruning for captures and checks (~11 Elo) + // SEE based pruning for captures and checks int seeHist = std::clamp(captHist / 37, -152 * depth, 141 * depth); if (!pos.see_ge(move, -156 * depth - seeHist)) continue; @@ -1022,7 +1024,7 @@ moves_loop: // When in check, search starts here + (*contHist[1])[movedPiece][move.to_sq()] + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; - // Continuation history based pruning (~2 Elo) + // Continuation history based pruning if (history < -3901 * depth) continue; @@ -1033,7 +1035,7 @@ moves_loop: // When in check, search starts here Value futilityValue = ss->staticEval + (bestValue < ss->staticEval - 47 ? 137 : 47) + 142 * lmrDepth; - // Futility pruning: parent node (~13 Elo) + // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue) @@ -1044,27 +1046,24 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); - // Prune moves with negative SEE (~4 Elo) + // Prune moves with negative SEE if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) continue; } } - // Step 15. Extensions (~100 Elo) + // Step 15. Extensions // We take care to not overdo to avoid search getting stuck. if (ss->ply < thisThread->rootDepth * 2) { - // Singular extension search (~76 Elo, ~170 nElo). If all moves but one + // Singular extension search. If all moves but one // fail low on a search of (alpha-s, beta-s), and just one fails high on // (alpha, beta), then that move is singular and should be extended. To // verify this we do a reduced search on the position excluding the ttMove // and if the result is lower than ttValue minus a margin, then we will // extend the ttMove. Recursive singular search is avoided. - // Note: the depth margin and singularBeta margin are known for having - // non-linear scaling. Their values are optimized to time controls of - // 180+1.8 and longer so changing them requires tests at these types of - // time controls. Generally, higher singularBeta (i.e closer to ttValue) + // (* Scaler) Generally, higher singularBeta (i.e closer to ttValue) // and lower extension margins scale well. if (!rootNode && move == ttData.move && !excludedMove @@ -1112,17 +1111,17 @@ moves_loop: // When in check, search starts here // if the ttMove is singular or can do a multi-cut, so we reduce the // ttMove in favor of other moves based on some conditions: - // If the ttMove is assumed to fail high over current beta (~7 Elo) + // If the ttMove is assumed to fail high over current beta else if (ttData.value >= beta) extension = -3; // If we are on a cutNode but the ttMove is not assumed to fail high - // over current beta (~1 Elo) + // over current beta else if (cutNode) extension = -2; } - // Extension for capturing the previous moved piece (~1 Elo at LTC) + // Extension for capturing the previous moved piece else if (PvNode && move.to_sq() == prevSq && thisThread->captureHistory[movedPiece][move.to_sq()] [type_of(pos.piece_on(move.to_sq()))] @@ -1146,12 +1145,7 @@ moves_loop: // When in check, search starts here &thisThread->continuationCorrectionHistory[movedPiece][move.to_sq()]; uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; - // These reduction adjustments have proven non-linear scaling. - // They are optimized to time controls of 180 + 1.8 and longer, - // so changing them or adding conditions that are similar requires - // tests at these types of time controls. - - // Decrease reduction for PvNodes (~0 Elo on STC, ~2 Elo on LTC) + // Decrease reduction for PvNodes (*Scaler) if (PvNode) r -= 1018; @@ -1161,19 +1155,19 @@ moves_loop: // When in check, search starts here r -= std::abs(correctionValue) / 34112; - // Increase reduction for cut nodes (~4 Elo) + // Increase reduction for cut nodes if (cutNode) r += 2355 - (ttData.depth >= depth && ss->ttPv) * 1141; - // Increase reduction if ttMove is a capture but the current move is not a capture (~3 Elo) + // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) r += 1087 + (depth < 8) * 990; - // Increase reduction if next ply has a lot of fail high (~5 Elo) + // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) r += 940 + allNode * 887; - // For first picked move (ttMove) reduce reduction (~3 Elo) + // For first picked move (ttMove) reduce reduction else if (move == ttData.move) r -= 1960; @@ -1187,10 +1181,10 @@ moves_loop: // When in check, search starts here + (*contHist[0])[movedPiece][move.to_sq()] + (*contHist[1])[movedPiece][move.to_sq()] - 3874; - // Decrease/increase reduction for moves with a good/bad history (~8 Elo) + // Decrease/increase reduction for moves with a good/bad history r -= ss->statScore * 1451 / 16384; - // Step 17. Late moves reduction / extension (LMR, ~117 Elo) + // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) { // In general we want to cap the LMR depth search at newDepth, but when @@ -1214,15 +1208,15 @@ 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 + 40 + 2 * newDepth); // (~1 Elo) - const bool doShallowerSearch = value < bestValue + 10; // (~2 Elo) + const bool doDeeperSearch = value > (bestValue + 40 + 2 * newDepth); + const bool doShallowerSearch = value < bestValue + 10; newDepth += doDeeperSearch - doShallowerSearch; if (newDepth > d) value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth, !cutNode); - // Post LMR continuation history updates (~1 Elo) + // Post LMR continuation history updates int bonus = (value >= beta) * 2048; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } @@ -1231,11 +1225,11 @@ moves_loop: // When in check, search starts here // Step 18. Full-depth search when LMR is skipped else if (!PvNode || moveCount > 1) { - // Increase reduction if ttMove is not present (~6 Elo) + // Increase reduction if ttMove is not present if (!ttData.move) r += 2111; - // Note that if expected reduction is high, we reduce search depth by 1 here (~9 Elo) + // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3444), !cutNode); } @@ -1342,7 +1336,7 @@ moves_loop: // When in check, search starts here } else { - // Reduce other moves if we have found at least one score improvement (~2 Elo) + // Reduce other moves if we have found at least one score improvement if (depth > 2 && depth < 14 && !is_decisive(value)) depth -= 2; @@ -1422,7 +1416,7 @@ moves_loop: // When in check, search starts here bestValue = std::min(bestValue, maxValue); // If no good move is found and the previous position was ttPv, then the previous - // opponent move is probably good and the new position is added to the search tree. (~7 Elo) + // opponent move is probably good and the new position is added to the search tree. if (bestValue <= alpha) ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); @@ -1467,7 +1461,7 @@ moves_loop: // When in check, search starts here // Quiescence search function, which is called by the main search function with // depth zero, or recursively with further decreasing depth. With depth <= 0, we // "should" be using static eval only, but tactical moves may confuse the static eval. -// To fight this horizon effect, we implement this qsearch of tactical moves (~155 Elo). +// To fight this horizon effect, we implement this qsearch of tactical moves. // See https://www.chessprogramming.org/Horizon_Effect // and https://www.chessprogramming.org/Quiescence_Search template @@ -1479,7 +1473,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); - // Check if we have an upcoming move that draws by repetition (~1 Elo) + // Check if we have an upcoming move that draws by repetition if (alpha < VALUE_DRAW && pos.upcoming_repetition(ss->ply)) { alpha = value_draw(this->nodes); @@ -1551,7 +1545,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) ss->staticEval = bestValue = to_corrected_static_eval(unadjustedStaticEval, correctionValue); - // ttValue can be used as a better position evaluation (~13 Elo) + // ttValue can be used as a better position evaluation if (is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & (ttData.value > bestValue ? BOUND_LOWER : BOUND_UPPER))) bestValue = ttData.value; @@ -1611,7 +1605,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) // Step 6. Pruning if (!is_loss(bestValue) && pos.non_pawn_material(us)) { - // Futility pruning and moveCount pruning (~10 Elo) + // Futility pruning and moveCount pruning if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) && move.type_of() != PROMOTION) { @@ -1621,7 +1615,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Value futilityValue = futilityBase + PieceValue[pos.piece_on(move.to_sq())]; // If static eval + value of piece we are going to capture is - // much lower than alpha, we can prune this move. (~2 Elo) + // much lower than alpha, we can prune this move. if (futilityValue <= alpha) { bestValue = std::max(bestValue, futilityValue); @@ -1629,7 +1623,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } // If static exchange evaluation is low enough - // we can prune this move. (~2 Elo) + // we can prune this move. if (!pos.see_ge(move, alpha - futilityBase)) { bestValue = std::min(alpha, futilityBase); @@ -1637,7 +1631,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) } } - // Continuation history based pruning (~3 Elo) + // Continuation history based pruning if (!capture && (*contHist[0])[pos.moved_piece(move)][move.to_sq()] + (*contHist[1])[pos.moved_piece(move)][move.to_sq()] @@ -1646,7 +1640,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) <= 5228) continue; - // Do not search moves with bad enough SEE values (~5 Elo) + // Do not search moves with bad enough SEE values if (!pos.see_ge(move, -80)) continue; } diff --git a/src/timeman.cpp b/src/timeman.cpp index d073a84a9..29ebffcaa 100644 --- a/src/timeman.cpp +++ b/src/timeman.cpp @@ -87,7 +87,7 @@ void TimeManagement::init(Search::LimitsType& limits, const TimePoint scaledTime = limits.time[us] / scaleFactor; const TimePoint scaledInc = limits.inc[us] / scaleFactor; - // Maximum move horizon of 50 moves + // Maximum move horizon int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051; // If less than one second, gradually reduce mtg From d606311e5508ffccb0fb7e88537c8fe899f702ac Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 19 Jan 2025 17:55:53 -0800 Subject: [PATCH 08/59] Fix undefined behavior From cppreference: "It is undefined behavior to read from the member of the union that wasn't most recently written. Many compilers implement, as a non-standard language extension, the ability to read inactive members of a union." closes https://github.com/official-stockfish/Stockfish/pull/5811 no functional change --- src/bitboard.h | 10 +++++----- src/misc.h | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index 6f9cca0bd..df15113bd 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -268,11 +269,10 @@ inline int popcount(Bitboard b) { #ifndef USE_POPCNT - union { - Bitboard bb; - uint16_t u[4]; - } v = {b}; - return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]]; + std::uint16_t indices[4]; + std::memcpy(indices, &b, sizeof(b)); + return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]] + + PopCnt16[indices[3]]; #elif defined(_MSC_VER) diff --git a/src/misc.h b/src/misc.h index 81c7b17fe..8adbac68a 100644 --- a/src/misc.h +++ b/src/misc.h @@ -120,11 +120,8 @@ void sync_cout_start(); void sync_cout_end(); // True if and only if the binary is compiled on a little-endian machine -static inline const union { - uint32_t i; - char c[4]; -} Le = {0x01020304}; -static inline const bool IsLittleEndian = (Le.c[0] == 4); +static inline const std::uint16_t Le = 1; +static inline const bool IsLittleEndian = *reinterpret_cast(&Le) == 1; template From 75b75bc16a4277a24af9587fe236555ed24599e6 Mon Sep 17 00:00:00 2001 From: pkrisz99 <5463243+pkrisz99@users.noreply.github.com> Date: Sat, 18 Jan 2025 23:58:25 +0100 Subject: [PATCH 09/59] Add improving to a condition of NMP This patch makes one of the conditions for null-move pruning depend on whether we're improving. Keep in mind that it relies on the "classical" definiton, rather than the refined one. Passed STC: https://tests.stockfishchess.org/tests/view/678d5267d63764e34db49720 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 38976 W: 10296 L: 9974 D: 18706 Ptnml(0-2): 135, 4504, 9902, 4798, 149 Passed LTC: https://tests.stockfishchess.org/tests/view/678d5891d63764e34db49731 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 275772 W: 70655 L: 69836 D: 135281 Ptnml(0-2): 217, 30615, 75394, 31452, 208 closes https://github.com/official-stockfish/Stockfish/pull/5813 Bench: 2475787 --- AUTHORS | 1 + src/search.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index f6468c561..1e5e51e63 100644 --- a/AUTHORS +++ b/AUTHORS @@ -129,6 +129,7 @@ Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) Kojirion +Krisztián Peőcz Krystian Kuzniarek (kuzkry) Leonardo Ljubičić (ICCF World Champion) Leonid Pechenik (lp--) diff --git a/src/search.cpp b/src/search.cpp index 3c8f33b41..9f29f8285 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -803,12 +803,10 @@ Value Search::Worker::search( && eval >= beta && (!ttData.move || ttCapture) && !is_loss(beta) && !is_win(eval)) return beta + (eval - beta) / 3; - improving |= ss->staticEval >= beta + 97; - // Step 9. Null move search with verification search if (cutNode && (ss - 1)->currentMove != Move::null() && eval >= beta - && ss->staticEval >= beta - 20 * depth + 440 && !excludedMove && pos.non_pawn_material(us) - && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) + && ss->staticEval >= beta - 20 * depth + 470 - 60 * improving && !excludedMove + && pos.non_pawn_material(us) && ss->ply >= thisThread->nmpMinPly && !is_loss(beta)) { assert(eval - beta >= 0); @@ -846,6 +844,8 @@ Value Search::Worker::search( } } + improving |= ss->staticEval >= beta + 97; + // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. // (* Scaler) Especially if they make IIR more aggressive. From 6c7c5c7e471c16f14518229428e51a3e00c0f1dd Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Tue, 21 Jan 2025 17:23:51 +0100 Subject: [PATCH 10/59] Do not change TB cursed wins to draws if requested If Syzygy50MoveRule is false, do not calls to is_draw() need to be guarded. Also fixes a TB rootmove ranking issue in this case. closes https://github.com/official-stockfish/Stockfish/pull/5814 No functional change --- src/position.cpp | 7 ++++--- src/position.h | 1 + src/search.cpp | 7 ++++--- src/syzygy/tbprobe.cpp | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 49f520e01..02614d13f 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1187,11 +1187,12 @@ bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) return true; - // Return a draw score if a position repeats once earlier but strictly - // after the root, or repeats twice before or at the root. - return st->repetition && st->repetition < ply; + return is_repetition(ply); } +// Return a draw score if a position repeats once earlier but strictly +// after the root, or repeats twice before or at the root. +bool Position::is_repetition(int ply) const { return st->repetition && st->repetition < ply; } // Tests whether there has been at least one repetition // of positions since the last capture or pawn move. diff --git a/src/position.h b/src/position.h index 0d49a60a9..d955d9f98 100644 --- a/src/position.h +++ b/src/position.h @@ -163,6 +163,7 @@ class Position { int game_ply() const; bool is_chess960() const; bool is_draw(int ply) const; + bool is_repetition(int ply) const; bool upcoming_repetition(int ply) const; bool has_repeated() const; int rule50_count() const; diff --git a/src/search.cpp b/src/search.cpp index 9f29f8285..f23631623 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1949,6 +1949,7 @@ void syzygy_extend_pv(const OptionsMap& options, auto t_start = std::chrono::steady_clock::now(); int moveOverhead = int(options["Move Overhead"]); + bool rule50 = bool(options["Syzygy50MoveRule"]); // Do not use more than moveOverhead / 2 time, if time management is active auto time_abort = [&t_start, &moveOverhead, &limits]() -> bool { @@ -1986,7 +1987,7 @@ void syzygy_extend_pv(const OptionsMap& options, pos.do_move(pvMove, st); // Do not allow for repetitions or drawing moves along the PV in TB regime - if (config.rootInTB && pos.is_draw(ply)) + if (config.rootInTB && ((rule50 && pos.is_draw(ply)) || pos.is_repetition(ply))) { pos.undo_move(pvMove); ply--; @@ -2005,7 +2006,7 @@ void syzygy_extend_pv(const OptionsMap& options, // Step 2, now extend the PV to mate, as if the user explored syzygy-tables.info // using top ranked moves (minimal DTZ), which gives optimal mates only for simple // endgames e.g. KRvK. - while (!pos.is_draw(0)) + while (!(rule50 && pos.is_draw(0))) { if (time_abort()) break; @@ -2048,7 +2049,7 @@ void syzygy_extend_pv(const OptionsMap& options, pos.do_move(pvMove, st); } - // Finding a draw in this function is an exceptional case, that cannot happen + // Finding a draw in this function is an exceptional case, that cannot happen when rule50 is false or // during engine game play, since we have a winning score, and play correctly // with TB support. However, it can be that a position is draw due to the 50 move // rule if it has been been reached on the board with a non-optimal 50 move counter diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 120e64885..cbf8dce5e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -1620,7 +1620,7 @@ bool Tablebases::root_probe(Position& pos, WDLScore wdl = -probe_wdl(pos, &result); dtz = dtz_before_zeroing(wdl); } - else if (pos.is_draw(1)) + else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1)) { // In case a root move leads to a draw by repetition or 50-move rule, // we set dtz to zero. Note: since we are only 1 ply from the root, From 435ba3dbb5ceb081592233d9f4b71e51c492dc22 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:18:23 +0000 Subject: [PATCH 11/59] Revert "Moving up the if position is or has been on the PV reduction" Passed VVLTC 1: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 68362 W: 17830 L: 17523 D: 33009 Ptnml(0-2): 9, 6253, 21347, 6566, 6 https://tests.stockfishchess.org/tests/view/6790271cfc8c306ba6cea2c1 Passed VVLTC 2: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 113256 W: 29158 L: 28721 D: 55377 Ptnml(0-2): 13, 10521, 35122, 10960, 12 https://tests.stockfishchess.org/tests/view/678d3e47d63764e34db491a3 closes https://github.com/official-stockfish/Stockfish/pull/5815 bench 1943998 --- src/search.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f23631623..990cbae33 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -982,10 +982,6 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); - // Decrease reduction if position is or has been on the PV (*Scaler) - if (ss->ttPv) - r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; - // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) @@ -1146,6 +1142,9 @@ moves_loop: // When in check, search starts here uint64_t nodeCount = rootNode ? uint64_t(nodes) : 0; // Decrease reduction for PvNodes (*Scaler) + if (ss->ttPv) + r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + if (PvNode) r -= 1018; From 889fed448c280f2f559c03672faa521ea4dcc1f2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 21 Jan 2025 17:41:33 -0800 Subject: [PATCH 12/59] Better nonpawn indexing Improves indexing scheme, by noting that both sides are likely to access the same non_pawn_index nearby. LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 75936 W: 19905 L: 19554 D: 36477 Ptnml(0-2): 190, 7863, 21554, 8128, 233 https://tests.stockfishchess.org/tests/view/67904d0cfc8c306ba6cea332 closes https://github.com/official-stockfish/Stockfish/pull/5816 No functional change Co-authored-by: Andrew Grant --- src/history.h | 7 ++++++- src/search.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/history.h b/src/history.h index 15095cd0b..4543fc55e 100644 --- a/src/history.h +++ b/src/history.h @@ -138,7 +138,7 @@ enum CorrHistType { Pawn, // By color and pawn structure Major, // By color and positions of major pieces (Queen, Rook) and King Minor, // By color and positions of minor pieces (Knight, Bishop) and King - NonPawn, // By color and non-pawn material positions + NonPawn, // By Non-pawn material positions and color PieceTo, // By [piece][to] move Continuation, // Combined history of move pairs }; @@ -150,6 +150,11 @@ struct CorrHistTypedef { using type = Stats; }; +template<> +struct CorrHistTypedef { + using type = Stats; +}; + template<> struct CorrHistTypedef { using type = Stats; diff --git a/src/search.cpp b/src/search.cpp index 990cbae33..e11da9128 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -89,8 +89,8 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)]; const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)]; - const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)]; - const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)]; + const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; + const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; const auto cntcv = m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; @@ -1442,9 +1442,9 @@ moves_loop: // When in check, search starts here << bonus * 114 / 128; thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 163 / 128; thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128; - thisThread->nonPawnCorrectionHistory[WHITE][us][non_pawn_index(pos)] + thisThread->nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; - thisThread->nonPawnCorrectionHistory[BLACK][us][non_pawn_index(pos)] + thisThread->nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) From 1b31e266b0b83f362f73eaa184840a05fc15e7a7 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Mon, 20 Jan 2025 15:40:41 +0100 Subject: [PATCH 13/59] Consider more nodes as ttPv nodes. Remove depth condition in propagation rule for ttPv state from a node to it childs. Because this change marks more nodes as ttPv, we have a time sensitive ttPv reduction rule and the STC snd LTC seems to show bad scaling. So i have also submitted a VLTC non-regression to check the scaling at higher time control. The results gives a little indication that we have perhaps good scaling with more ttPv nodes so that could be further explored. Passed non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 82528 W: 21627 L: 21453 D: 39448 Ptnml(0-2): 317, 9809, 20891, 9877, 370 https://tests.stockfishchess.org/tests/view/678e608cd63764e34db49ad7 Passed non-regression LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 310440 W: 78879 L: 78956 D: 152605 Ptnml(0-2): 255, 34915, 84938, 34876, 236 https://tests.stockfishchess.org/tests/view/678fab89ac8f8f5496155f3c Passed non-regression VLTC for scaling verification: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 59496 W: 15158 L: 14983 D: 29355 Ptnml(0-2): 15, 6039, 17470, 6204, 20 https://tests.stockfishchess.org/tests/view/6794bd1f4f7de645171fb33b closes https://github.com/official-stockfish/Stockfish/pull/5819 Bench: 1829507 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index e11da9128..3a161fee1 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1417,7 +1417,7 @@ moves_loop: // When in check, search starts here // If no good move is found and the previous position was ttPv, then the previous // opponent move is probably good and the new position is added to the search tree. if (bestValue <= alpha) - ss->ttPv = ss->ttPv || ((ss - 1)->ttPv && depth > 3); + ss->ttPv = ss->ttPv || (ss - 1)->ttPv; // Write gathered information in transposition table. Note that the // static evaluation is saved as it was before correction history. From 27e747d1d727386bec6eea01456fcbeae604bfe3 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 23 Jan 2025 20:02:24 -0800 Subject: [PATCH 14/59] Simplify futility margin in lmr for quiets. Replace the "low bestValue condition" with whether there is a best move. Passed Simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 102560 W: 26517 L: 26367 D: 49676 Ptnml(0-2): 328, 12223, 26036, 12357, 336 https://tests.stockfishchess.org/tests/view/679310e4ca18a2c66da02af8 Passed Simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 66942 W: 17130 L: 16953 D: 32859 Ptnml(0-2): 52, 7459, 18290, 7600, 70 https://tests.stockfishchess.org/tests/view/679459a3e96bfb672ad18ddf closes https://github.com/official-stockfish/Stockfish/pull/5820 Bench: 1438043 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 3a161fee1..ab8825f83 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1028,8 +1028,7 @@ moves_loop: // When in check, search starts here lmrDepth += history / 3459; - Value futilityValue = - ss->staticEval + (bestValue < ss->staticEval - 47 ? 137 : 47) + 142 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 47 : 137) + 142 * lmrDepth; // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) From 69be04d38e10003853e78e4aa2b32aa252a82850 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sat, 25 Jan 2025 23:54:09 +0300 Subject: [PATCH 15/59] Simplify cutoffCnt Passed STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 235872 W: 61156 L: 61155 D: 113561 Ptnml(0-2): 843, 28269, 59658, 28376, 790 https://tests.stockfishchess.org/tests/view/6794dd3e4f7de645171fb380 Passed LTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 61494 W: 15644 L: 15462 D: 30388 Ptnml(0-2): 61, 6822, 16788, 7026, 50 https://tests.stockfishchess.org/tests/view/6794f86a406a4efe9eb7d093 closes https://github.com/official-stockfish/Stockfish/pull/5821 Bench: 2168937 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index ab8825f83..316201886 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1328,7 +1328,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - ss->cutoffCnt += !ttData.move + (extension < 2); + ss->cutoffCnt += (extension < 2); assert(value >= beta); // Fail high break; } From 831cb01cea9e41d7f09406a9a0cf0913bf205cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Nov=C3=A1k?= Date: Sat, 25 Jan 2025 21:09:45 +0100 Subject: [PATCH 16/59] Remove major corrhist Remove major correction history and slightly increase all other correction weights. Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 50080 W: 13171 L: 12959 D: 23950 Ptnml(0-2): 196, 5998, 12462, 6166, 218 https://tests.stockfishchess.org/tests/live_elo/67954526406a4efe9eb7d176 Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51504 W: 13188 L: 12995 D: 25321 Ptnml(0-2): 54, 5658, 14128, 5865, 47 https://tests.stockfishchess.org/tests/live_elo/67954961406a4efe9eb7d251 closes https://github.com/official-stockfish/Stockfish/pull/5822 Bench: 2081366 --- src/history.h | 5 ----- src/position.cpp | 24 ++++-------------------- src/position.h | 4 ---- src/search.cpp | 5 +---- src/search.h | 1 - 5 files changed, 5 insertions(+), 34 deletions(-) diff --git a/src/history.h b/src/history.h index 4543fc55e..654ee19f2 100644 --- a/src/history.h +++ b/src/history.h @@ -54,10 +54,6 @@ inline int pawn_structure_index(const Position& pos) { return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1); } -inline int major_piece_index(const Position& pos) { - return pos.major_piece_key() & (CORRECTION_HISTORY_SIZE - 1); -} - inline int minor_piece_index(const Position& pos) { return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1); } @@ -136,7 +132,6 @@ using PawnHistory = Statskey = st->materialKey = 0; - st->majorPieceKey = st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -360,16 +359,12 @@ void Position::set_state() const { { st->nonPawnMaterial[color_of(pc)] += PieceValue[pc]; - if (type_of(pc) >= ROOK) - st->majorPieceKey ^= Zobrist::psq[pc][s]; - - else + if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][s]; } else { - st->majorPieceKey ^= Zobrist::psq[pc][s]; st->minorPieceKey ^= Zobrist::psq[pc][s]; } } @@ -742,7 +737,6 @@ void Position::do_move(Move m, do_castling(us, from, to, rfrom, rto); k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; - st->majorPieceKey ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto]; captured = NO_PIECE; } @@ -773,10 +767,7 @@ void Position::do_move(Move m, st->nonPawnMaterial[them] -= PieceValue[captured]; st->nonPawnKey[them] ^= Zobrist::psq[captured][capsq]; - if (type_of(captured) >= ROOK) - st->majorPieceKey ^= Zobrist::psq[captured][capsq]; - - else + if (type_of(captured) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[captured][capsq]; } @@ -858,10 +849,7 @@ void Position::do_move(Move m, st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; - if (promotionType >= ROOK) - st->majorPieceKey ^= Zobrist::psq[promotion][to]; - - else + if (promotionType <= BISHOP) st->minorPieceKey ^= Zobrist::psq[promotion][to]; // Update material @@ -881,14 +869,10 @@ void Position::do_move(Move m, if (type_of(pc) == KING) { - st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } - else if (type_of(pc) >= ROOK) - st->majorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - - else + else if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } diff --git a/src/position.h b/src/position.h index d955d9f98..53269c197 100644 --- a/src/position.h +++ b/src/position.h @@ -43,7 +43,6 @@ struct StateInfo { // Copied when making a move Key materialKey; Key pawnKey; - Key majorPieceKey; Key minorPieceKey; Key nonPawnKey[COLOR_NB]; Value nonPawnMaterial[COLOR_NB]; @@ -154,7 +153,6 @@ class Position { Key key() const; Key material_key() const; Key pawn_key() const; - Key major_piece_key() const; Key minor_piece_key() const; Key non_pawn_key(Color c) const; @@ -305,8 +303,6 @@ inline Key Position::pawn_key() const { return st->pawnKey; } inline Key Position::material_key() const { return st->materialKey; } -inline Key Position::major_piece_key() const { return st->majorPieceKey; } - inline Key Position::minor_piece_key() const { return st->minorPieceKey; } inline Key Position::non_pawn_key(Color c) const { return st->nonPawnKey[c]; } diff --git a/src/search.cpp b/src/search.cpp index 316201886..e07c719eb 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -87,7 +87,6 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { const Color us = pos.side_to_move(); const auto m = (ss - 1)->currentMove; const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; - const auto macv = w.majorPieceCorrectionHistory[us][major_piece_index(pos)]; const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)]; const auto wnpcv = w.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us]; const auto bnpcv = w.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us]; @@ -95,7 +94,7 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { m.is_ok() ? (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] : 0; - return (6922 * pcv + 3837 * macv + 6238 * micv + 7490 * (wnpcv + bnpcv) + 6270 * cntcv); + return (7000 * pcv + 6300 * micv + 7550 * (wnpcv + bnpcv) + 6320 * cntcv); } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -516,7 +515,6 @@ void Search::Worker::clear() { captureHistory.fill(-631); pawnHistory.fill(-1210); pawnCorrectionHistory.fill(0); - majorPieceCorrectionHistory.fill(0); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); @@ -1439,7 +1437,6 @@ moves_loop: // When in check, search starts here -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] << bonus * 114 / 128; - thisThread->majorPieceCorrectionHistory[us][major_piece_index(pos)] << bonus * 163 / 128; thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128; thisThread->nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; diff --git a/src/search.h b/src/search.h index 3a1b3a77d..326480e4d 100644 --- a/src/search.h +++ b/src/search.h @@ -288,7 +288,6 @@ class Worker { PawnHistory pawnHistory; CorrectionHistory pawnCorrectionHistory; - CorrectionHistory majorPieceCorrectionHistory; CorrectionHistory minorPieceCorrectionHistory; CorrectionHistory nonPawnCorrectionHistory[COLOR_NB]; CorrectionHistory continuationCorrectionHistory; From a016abd6982d1f8f1b0f5175b0005c8749c0925b Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 26 Jan 2025 01:16:09 +0100 Subject: [PATCH 17/59] Decrease all stats malus according to move count Passed STC: https://tests.stockfishchess.org/tests/view/6794c4634f7de645171fb341 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 28096 W: 7412 L: 7106 D: 13578 Ptnml(0-2): 97, 3194, 7148, 3524, 85 Passed LTC: https://tests.stockfishchess.org/tests/view/6794ea13406a4efe9eb7d06b LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 58086 W: 15049 L: 14684 D: 28353 Ptnml(0-2): 27, 6344, 15957, 6667, 48 closes https://github.com/official-stockfish/Stockfish/pull/5823 Bench: 1711170 --- src/search.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index e07c719eb..7317b7e43 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -125,7 +125,8 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - bool isTTMove); + bool isTTMove, + int moveCount); } // namespace @@ -1372,7 +1373,7 @@ moves_loop: // When in check, search starts here // we update the stats of searched moves. else if (bestMove) update_all_stats(pos, ss, *this, bestMove, prevSq, quietsSearched, capturesSearched, depth, - bestMove == ttData.move); + bestMove == ttData.move, moveCount); // Bonus for prior countermove that caused the fail low else if (!priorCapture && prevSq != SQ_NONE) @@ -1792,14 +1793,15 @@ void update_all_stats(const Position& pos, ValueList& quietsSearched, ValueList& capturesSearched, Depth depth, - bool isTTMove) { + bool isTTMove, + int moveCount) { CapturePieceToHistory& captureHistory = workerThread.captureHistory; Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; int bonus = stat_bonus(depth) + 300 * isTTMove; - int malus = stat_malus(depth); + int malus = stat_malus(depth) - 34 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { From ebdc7ba2da13b97579871b8a621124e3778bd495 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Sat, 25 Jan 2025 13:49:47 -0800 Subject: [PATCH 18/59] Refactor prior countermove bonus Passed simplification STC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 155424 W: 40252 L: 40159 D: 75013 Ptnml(0-2): 511, 18655, 39328, 18666, 552 https://tests.stockfishchess.org/tests/view/6794084fe96bfb672ad18d90 Passed rebased simplification LTC LLR: 2.97 (-2.94,2.94) <-1.75,0.25> Total: 103944 W: 26567 L: 26427 D: 50950 Ptnml(0-2): 69, 11640, 28418, 11772, 73 https://tests.stockfishchess.org/tests/view/67955c9a406a4efe9eb7d7e4 closes https://github.com/official-stockfish/Stockfish/pull/5825 Bench: 1839554 --- src/search.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 7317b7e43..5eb5049a7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1381,23 +1381,21 @@ moves_loop: // When in check, search starts here int bonusScale = (118 * (depth > 5) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8) + 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102) + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) - + 80 * ((ss - 1)->isTTMove)); - - // Proportional to "how much damage we have to undo" - bonusScale += std::min(-(ss - 1)->statScore / 106, 318); + + 80 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); bonusScale = std::max(bonusScale, 0); - const int scaledBonus = stat_bonus(depth) * bonusScale / 32; + const int scaledBonus = stat_bonus(depth) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 436 / 1024); + scaledBonus * 436 / 32768); - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << scaledBonus * 207 / 1024; + thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] + << scaledBonus * 207 / 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 * 1195 / 1024; + << scaledBonus * 1195 / 32768; } else if (priorCapture && prevSq != SQ_NONE) From c180163540e18e818e6f5361e8b28d0785422a69 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Sun, 26 Jan 2025 15:32:45 +0100 Subject: [PATCH 19/59] Update fastchess CI Version This PR updates the fastchess version used as part of the CI to the one used on fishtest, see https://github.com/official-stockfish/fishtest/pull/2180. Also change the name/repo from fast-chess to fastchess. closes https://github.com/official-stockfish/Stockfish/pull/5826 No functional change --- .github/workflows/games.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/games.yml b/.github/workflows/games.yml index f0bca442f..a50ca594b 100644 --- a/.github/workflows/games.yml +++ b/.github/workflows/games.yml @@ -19,22 +19,22 @@ jobs: working-directory: Stockfish/src run: make -j build debug=yes - - name: Checkout fast-chess repo + - name: Checkout fastchess repo uses: actions/checkout@v4 with: - repository: Disservin/fast-chess - path: fast-chess - ref: d54af1910d5479c669dc731f1f54f9108a251951 + repository: Disservin/fastchess + path: fastchess + ref: 894616028492ae6114835195f14a899f6fa237d3 persist-credentials: false - - name: fast-chess build - working-directory: fast-chess + - name: fastchess build + working-directory: fastchess run: make -j - name: Run games - working-directory: fast-chess + working-directory: fastchess run: | - ./fast-chess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\ + ./fastchess -rounds 4 -games 2 -repeat -concurrency 4 -openings file=app/tests/data/openings.epd format=epd order=random -srand $RANDOM\ -engine name=sf1 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\ -engine name=sf2 cmd=/home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish\ -ratinginterval 1 -report penta=true -each proto=uci tc=4+0.04 -log file=fast.log | tee fast.out From f50d52aa7f0141d0a7676c68d04afaf2b44160d7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Sun, 26 Jan 2025 03:14:15 +0300 Subject: [PATCH 20/59] No Ply Restriction in the condition that limits the depth extension to a certain point No Ply Restriction in the condition that limits the depth extension to a certain point. Passed again LTC rebased: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 195108 W: 49916 L: 49872 D: 95320 Ptnml(0-2): 170, 21846, 53464, 21918, 156 https://tests.stockfishchess.org/tests/view/6795542a406a4efe9eb7d361 closes https://github.com/official-stockfish/Stockfish/pull/5824 Bench: 1767398 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5eb5049a7..9a5425fc7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1239,7 +1239,7 @@ moves_loop: // When in check, search starts here (ss + 1)->pv[0] = Move::none(); // Extend move from transposition table if we are about to dive into qsearch. - if (move == ttData.move && ss->ply <= thisThread->rootDepth * 2) + if (move == ttData.move && thisThread->rootDepth > 8) newDepth = std::max(newDepth, 1); value = -search(pos, ss + 1, -beta, -alpha, newDepth, false); From 4a77fb213f7f620769900a3aa9c97d6745992e77 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Mon, 27 Jan 2025 15:24:37 -0800 Subject: [PATCH 21/59] Clean up corrhist Passed Non-regression STC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 89056 W: 23225 L: 23067 D: 42764 Ptnml(0-2): 292, 9688, 24470, 9726, 352 https://tests.stockfishchess.org/tests/view/679816b2ae346be6da0ee8e7 closes https://github.com/official-stockfish/Stockfish/pull/5830 Bench: 1767398 --- src/history.h | 9 ++------- src/search.cpp | 43 ++++++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/history.h b/src/history.h index 654ee19f2..bec055d52 100644 --- a/src/history.h +++ b/src/history.h @@ -133,20 +133,15 @@ using PawnHistory = Stats +template struct CorrHistTypedef { - using type = Stats; -}; - -template<> -struct CorrHistTypedef { using type = Stats; }; diff --git a/src/search.cpp b/src/search.cpp index 9a5425fc7..89233e85b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -83,11 +83,11 @@ constexpr int futility_move_count(bool improving, Depth depth) { return (3 + depth * depth) / (2 - improving); } -int correction_value(const Worker& w, const Position& pos, const Stack* ss) { +int correction_value(const Worker& w, const Position& pos, const Stack* const ss) { const Color us = pos.side_to_move(); const auto m = (ss - 1)->currentMove; - const auto pcv = w.pawnCorrectionHistory[us][pawn_structure_index(pos)]; - const auto micv = w.minorPieceCorrectionHistory[us][minor_piece_index(pos)]; + 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 cntcv = @@ -99,10 +99,31 @@ int correction_value(const Worker& w, const Position& pos, const Stack* ss) { // Add correctionHistory value to raw staticEval and guarantee evaluation // does not hit the tablebase range. -Value to_corrected_static_eval(Value v, const int cv) { +Value to_corrected_static_eval(const Value v, const int cv) { return std::clamp(v + cv / 131072, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1); } +void update_correction_history(const Position& pos, + Stack* const ss, + Search::Worker& workerThread, + const int bonus) { + const Move m = (ss - 1)->currentMove; + const Color us = pos.side_to_move(); + + static constexpr int nonPawnWeight = 165; + + workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] + << bonus * 114 / 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] + << bonus * nonPawnWeight / 128; + + if (m.is_ok()) + (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; +} + // History and stats update bonus, based on depth int stat_bonus(Depth d) { return std::min(154 * d - 102, 1661); } @@ -1429,21 +1450,9 @@ moves_loop: // When in check, search starts here && ((bestValue < ss->staticEval && bestValue < beta) // negative correction & no fail high || (bestValue > ss->staticEval && bestMove))) // positive correction & no fail low { - const auto m = (ss - 1)->currentMove; - constexpr int nonPawnWeight = 165; - auto bonus = std::clamp(int(bestValue - ss->staticEval) * depth / 8, -CORRECTION_HISTORY_LIMIT / 4, CORRECTION_HISTORY_LIMIT / 4); - thisThread->pawnCorrectionHistory[us][pawn_structure_index(pos)] - << bonus * 114 / 128; - thisThread->minorPieceCorrectionHistory[us][minor_piece_index(pos)] << bonus * 146 / 128; - thisThread->nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] - << bonus * nonPawnWeight / 128; - thisThread->nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] - << bonus * nonPawnWeight / 128; - - if (m.is_ok()) - (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; + update_correction_history(pos, ss, *thisThread, bonus); } assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); From 7684b6e4d8788d24e679d7fe737d1b34bad913b5 Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Sat, 18 Jan 2025 20:58:14 +0100 Subject: [PATCH 22/59] Don't increase rule50 when doing null moves also prefetch a bit earlier while we're at it passed STC: https://tests.stockfishchess.org/tests/view/678c0860f4dc0a8b4ae8cf58 LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 67328 W: 17608 L: 17418 D: 32302 Ptnml(0-2): 256, 7905, 17156, 8087, 260 passed LTC: https://tests.stockfishchess.org/tests/view/678c1a56f4dc0a8b4ae8cfb1 LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 340896 W: 86577 L: 86685 D: 167634 Ptnml(0-2): 291, 38325, 93332, 38201, 299 closes https://github.com/official-stockfish/Stockfish/pull/5831 Bench: 1910281 --- src/position.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index e5df1a9ec..fdaec13ad 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1023,11 +1023,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { st->next = &newSt; st = &newSt; - 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; - if (st->epSquare != SQ_NONE) { st->key ^= Zobrist::enpassant[file_of(st->epSquare)]; @@ -1035,9 +1030,13 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) { } st->key ^= Zobrist::side; - ++st->rule50; 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; From 5ef1f2b13276c11814187b4ee115454803d399e3 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 29 Jan 2025 00:19:22 -0800 Subject: [PATCH 23/59] Refactor prior reduction Make index of reduction consistent with rest of Stack closes https://github.com/official-stockfish/Stockfish/pull/5832 No functional change --- src/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 89233e85b..fbc97bb41 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -600,8 +600,8 @@ Value Search::Worker::search( Value bestValue, value, eval, maxValue, probCutBeta; bool givesCheck, improving, priorCapture, opponentWorsening; bool capture, ttCapture; - int priorReduction = ss->reduction; - ss->reduction = 0; + int priorReduction = (ss - 1)->reduction; + (ss - 1)->reduction = 0; Piece movedPiece; ValueList capturesSearched; @@ -1215,10 +1215,10 @@ moves_loop: // When in check, search starts here Depth d = std::max( 1, std::min(newDepth - r / 1024, newDepth + !allNode + (PvNode && !bestMove))); - (ss + 1)->reduction = newDepth - d; + ss->reduction = newDepth - d; - value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); - (ss + 1)->reduction = 0; + value = -search(pos, ss + 1, -(alpha + 1), -alpha, d, true); + ss->reduction = 0; // Do a full-depth search when reduced LMR search fails high From 40e0486d02fd8682c8d369c20bf24b6a5ebc9927 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Thu, 30 Jan 2025 05:36:53 +0300 Subject: [PATCH 24/59] Make IIR for PvNodes less aggressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In line with previous experiments on improving scaling of IIR. Now it disables IIR for pv nodes with depth <= 2, so disallowing for it to perform a qsearch dive. Fixed games STC: https://tests.stockfishchess.org/tests/view/679ae6a951037ccaf3e30fb3 Elo: -10.36 ± 2.5 (95%) LOS: 0.0% Total: 20020 W: 4902 L: 5499 D: 9619 Ptnml(0-2): 128, 2653, 4976, 2194, 59 Passed VVLTC with STC bounds: https://tests.stockfishchess.org/tests/view/67954f2e406a4efe9eb7d266 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 196758 W: 50725 L: 50258 D: 95775 Ptnml(0-2): 21, 18153, 61564, 18620, 21 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/6795a26bf6281b7d7b18698b LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 323092 W: 83679 L: 82857 D: 156556 Ptnml(0-2): 48, 29475, 101659, 30335, 29 closes https://github.com/official-stockfish/Stockfish/pull/5834 Bench: 3464332 --- src/search.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index fbc97bb41..2f0b164ab 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -869,13 +869,9 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. // (* Scaler) Especially if they make IIR more aggressive. - if ((PvNode || (cutNode && depth >= 7)) && !ttData.move) + if (((PvNode || cutNode) && depth >= 7 - 4 * PvNode) && !ttData.move) depth -= 2; - // Use qsearch if depth <= 0 - if (depth <= 0) - return qsearch(pos, ss, alpha, beta); - // 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. From 7690fac5cf4fcce779573d2104d9a81db563de28 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 30 Jan 2025 15:24:25 -0800 Subject: [PATCH 25/59] Simp probcut disable condition Disable probcut check when we the ttValue is not at least probCutBeta, regardless of tt depth. Passed simplification STC LLR: 2.92 (-2.94,2.94) <-1.75,0.25> Total: 60896 W: 16030 L: 15835 D: 29031 Ptnml(0-2): 220, 7164, 15507, 7315, 242 https://tests.stockfishchess.org/tests/view/679c0a3251037ccaf3e3141e Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 76644 W: 19557 L: 19392 D: 37695 Ptnml(0-2): 50, 8486, 21104, 8613, 69 https://tests.stockfishchess.org/tests/view/679c380b0774dfd78deaf35c closes https://github.com/official-stockfish/Stockfish/pull/5840 Bench: 3543770 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 2f0b164ab..d6e9bb75d 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -882,7 +882,7 @@ Value Search::Worker::search( // probCut there and in further interactions with transposition table cutoff // depth is set to depth - 3 because probCut search has depth set to depth - 4 // but we also do a move before it. So effective depth is equal to depth - 3. - && !(ttData.depth >= depth - 3 && is_valid(ttData.value) && ttData.value < probCutBeta)) + && !(is_valid(ttData.value) && ttData.value < probCutBeta)) { assert(probCutBeta < VALUE_INFINITE && probCutBeta > beta); From c83ddd9e4b9d5c57c9398565eee52c3ea5940f80 Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Thu, 30 Jan 2025 22:36:00 +0100 Subject: [PATCH 26/59] Tweak correction history factors The values are taken from this tuning https://tests.stockfishchess.org/tests/view/679c4e150774dfd78deaf376 which added also a new material correction history. The full tune doesn't work but ignoring the new history results in this changes. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 102368 W: 27057 L: 26638 D: 48673 Ptnml(0-2): 394, 12031, 25949, 12382, 428 https://tests.stockfishchess.org/tess/view/679d2ca70774dfd78deaf796 Passed LTC: LLR: 2.96 (-2.94,2.94) <0.50,2.50> Total: 55044 W: 14215 L: 13855 D: 26974 Ptnml(0-2): 43, 5956, 15172, 6300, 51 https://tests.stockfishchess.org/tests/view/679d30be0774dfd78deafda2 closes https://github.com/official-stockfish/Stockfish/pull/5841 Bench: 3068583 --- src/history.h | 2 +- src/position.cpp | 13 ++----------- src/search.cpp | 11 ++++++----- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/history.h b/src/history.h index bec055d52..9ae7bdadc 100644 --- a/src/history.h +++ b/src/history.h @@ -132,7 +132,7 @@ using PawnHistory = Statskey = st->materialKey = 0; + st->minorPieceKey = 0; st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0; st->pawnKey = Zobrist::noPawns; st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO; @@ -362,11 +363,6 @@ void Position::set_state() const { if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][s]; } - - else - { - st->minorPieceKey ^= Zobrist::psq[pc][s]; - } } } @@ -867,12 +863,7 @@ void Position::do_move(Move m, { st->nonPawnKey[us] ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - if (type_of(pc) == KING) - { - st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - } - - else if (type_of(pc) <= BISHOP) + if (type_of(pc) <= BISHOP) st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; } diff --git a/src/search.cpp b/src/search.cpp index d6e9bb75d..a35aad45a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,7 +94,7 @@ 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 (7000 * pcv + 6300 * micv + 7550 * (wnpcv + bnpcv) + 6320 * cntcv); + return 7037 * pcv + 6671 * micv + 7631 * (wnpcv + bnpcv) + 6362 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -110,18 +110,19 @@ 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 = 159; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 114 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 146 / 128; + << bonus * 104 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; if (m.is_ok()) - (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] << bonus; + (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] + << bonus * 146 / 128; } // History and stats update bonus, based on depth From 344e89275abe2b67c0be6a5013def81725c5a5c2 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Wed, 29 Jan 2025 13:04:01 -0800 Subject: [PATCH 27/59] Simplify Away Quadruple Extensions Passed Non-regression LTC: LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 95856 W: 24551 L: 24404 D: 46901 Ptnml(0-2): 85, 10621, 26364, 10778, 80 https://tests.stockfishchess.org/tests/view/679a9aedae346be6da0eebd6 Passed Non-regression VLTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 157536 W: 40000 L: 39921 D: 77615 Ptnml(0-2): 43, 16416, 45775, 16487, 47 https://tests.stockfishchess.org/tests/view/679aed8f51037ccaf3e30fbf Passed Non-regression VVLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 51598 W: 13345 L: 13172 D: 25081 Ptnml(0-2): 0, 4735, 16162, 4896, 6 https://tests.stockfishchess.org/tests/view/679d368b0774dfd78deb0163 closes https://github.com/official-stockfish/Stockfish/pull/5843 Bench: 2399312 --- src/search.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index a35aad45a..9af7a115f 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1097,12 +1097,9 @@ moves_loop: // When in check, search starts here int doubleMargin = 249 * PvNode - 194 * !ttCapture - corrValAdj; int tripleMargin = 94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj; - int quadMargin = - 394 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj; extension = 1 + (value < singularBeta - doubleMargin) - + (value < singularBeta - tripleMargin) - + (value < singularBeta - quadMargin); + + (value < singularBeta - tripleMargin); depth += ((!PvNode) && (depth < 15)); } From dabffbceffee2e68352bf5865987d5b2a91ce410 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Sun, 2 Feb 2025 06:06:13 +0300 Subject: [PATCH 28/59] Make pruning at ttpv nodes more aggressive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Continuation of work done by @FauziAkram and @Viren6 They had a series of patches that decrease pruning for ttPv nodes - and it passed as a gainer at lower time controls while revert passed as a gainer at higher time controls. So it's a logical continuation of this work that increases pruning for ttPv nodes in hopes of scaling to longer TCs. Fixed games STC: https://tests.stockfishchess.org/tests/view/679ee3910774dfd78deb0efd Elo: -4.98 ± 2.1 (95%) LOS: 0.0% Total: 28584 W: 7229 L: 7639 D: 13716 Ptnml(0-2): 143, 3579, 7219, 3247, 104 nElo: -9.54 ± 4.0 (95%) PairsRatio: 0.90 Passed VVLTC with STC bounds: https://tests.stockfishchess.org/tests/view/679d21f70774dfd78deaf553 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 323282 W: 83729 L: 83105 D: 156448 Ptnml(0-2): 37, 29842, 101269, 30446, 47 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/679e7a970774dfd78deb0cd3 LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 113712 W: 29485 L: 29051 D: 55176 Ptnml(0-2): 13, 10376, 35640, 10818, 9 closes https://github.com/official-stockfish/Stockfish/pull/5844 Bench: 2964045 --- src/search.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 9af7a115f..60f716cd3 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,6 +999,12 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); + // Increase reduction for ttPv nodes (*Scaler) + // Smaller or even negative value is better for short time controls + // Bigger value is better for long time controls + if (ss->ttPv) + r += 1024; + // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. if (!rootNode && pos.non_pawn_material(us) && !is_loss(bestValue)) @@ -1156,7 +1162,7 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 1037 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + r -= 2061 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; if (PvNode) r -= 1018; From 65a9a391e90cf2551a7b7beb26d5ee7c3ccf585b Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 2 Feb 2025 13:49:54 +0100 Subject: [PATCH 29/59] Silence clang-format issue No functional change --- src/tt.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tt.h b/src/tt.h index 065380ca8..26b63c1ad 100644 --- a/src/tt.h +++ b/src/tt.h @@ -53,6 +53,8 @@ struct TTData { bool is_pv; TTData() = delete; + + // clang-format off TTData(Move m, Value v, Value ev, Depth d, Bound b, bool pv) : move(m), value(v), @@ -60,6 +62,7 @@ struct TTData { depth(d), bound(b), is_pv(pv) {}; + // clang-format on }; From 9f0844c101fda0526637cb8468b80e67699ea451 Mon Sep 17 00:00:00 2001 From: Kenneth Lee <71492754+kennethlee33@users.noreply.github.com> Date: Sat, 25 Jan 2025 20:14:08 -0800 Subject: [PATCH 30/59] Simplify extensions depth increase condition Passed STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 42784 W: 11198 L: 10979 D: 20607 Ptnml(0-2): 166, 5024, 10822, 5185, 195 https://tests.stockfishchess.org/tests/view/6795b6f8f6281b7d7b1869d6 Failed LTC: LLR: -2.95 (-2.94,2.94) <-1.75,0.25> Total: 283614 W: 72046 L: 72587 D: 138981 Ptnml(0-2): 241, 32097, 77647, 31606, 216 https://tests.stockfishchess.org/tests/view/6795cbb6f6281b7d7b186a07 Passed VLTC: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 59678 W: 15387 L: 15211 D: 29080 Ptnml(0-2): 23, 6169, 17273, 6357, 17 https://tests.stockfishchess.org/tests/view/6795c6dbf6281b7d7b1869f9 closes https://github.com/official-stockfish/Stockfish/pull/5827 Bench: 3088494 --- AUTHORS | 1 + src/search.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1e5e51e63..a345bb69f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -125,6 +125,7 @@ jundery Justin Blanchard (UncombedCoconut) Kelly Wilson Ken Takusagawa +Kenneth Lee (kennethlee33) Kian E (KJE-98) kinderchocolate Kiran Panditrao (Krgp) diff --git a/src/search.cpp b/src/search.cpp index 60f716cd3..5b26b6657 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1107,7 +1107,7 @@ moves_loop: // When in check, search starts here extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += ((!PvNode) && (depth < 15)); + depth += (depth < 15); } // Multi-cut pruning From d46c0b6f492bc00fa0a91d91f18e474c14541330 Mon Sep 17 00:00:00 2001 From: "Robert Nurnberg @ elitebook" Date: Mon, 27 Jan 2025 08:55:15 +0100 Subject: [PATCH 31/59] Add cursed win checks to CI matetrack tests This PR adds a run for the `matecheck.py` script from the matetrack repo with the option `--syzygy50MoveRule false`. The new tests guard against a re-introduction of the bugs recently fixed by https://github.com/official-stockfish/Stockfish/pull/5814. closes https://github.com/official-stockfish/Stockfish/pull/5829 No functional change --- .github/workflows/matetrack.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/matetrack.yml b/.github/workflows/matetrack.yml index dc8dff8d5..85c2be3e7 100644 --- a/.github/workflows/matetrack.yml +++ b/.github/workflows/matetrack.yml @@ -24,7 +24,7 @@ jobs: with: repository: vondele/matetrack path: matetrack - ref: 814160f82e6428ed2f6522dc06c2a6fa539cd413 + ref: 4f8a80860ed8f3607f05a9195df8b40203bdc360 persist-credentials: false - name: matetrack install deps @@ -50,5 +50,22 @@ jobs: - name: Run matetrack working-directory: matetrack run: | - python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out ! grep "issues were detected" matecheckout.out > /dev/null + + - name: Run matetrack with --syzygy50MoveRule false + working-directory: matetrack + run: | + grep 5men cursed.epd > cursed5.epd + python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed.out + ! grep "issues were detected" matecheckcursed.out > /dev/null + + - name: Verify mate and TB win count for matecheckcursed.out + working-directory: matetrack + run: | + mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}') + tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}') + if [ $(($mates + $tbwins)) -ne 32 ]; then + echo "Sum of mates and TB wins is not 32 in matecheckcursed.out" >&2 + exit 1 + fi From 2a1ab11ab019ce588f4f4f4b175ddd6e60d1df36 Mon Sep 17 00:00:00 2001 From: Guenther Demetz Date: Fri, 31 Jan 2025 15:41:03 +0100 Subject: [PATCH 32/59] Micro-optimization for SEE: remove a superfluous condition This condition can never be true, it's superfluous. It never triggers even with a bench 16 1 20 run. To met the condition it would imply that the previous recapture was done by a higher rated piece than a Queen. This is only the case when the King recaptures and that's already handled in line 1161: (return (attackers & ~pieces(stm)) ? res ^ 1). closes https://github.com/official-stockfish/Stockfish/pull/5839 No functional change --- src/position.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 439d4ae9d..37871aa24 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1137,8 +1137,9 @@ bool Position::see_ge(Move m, int threshold) const { else if ((bb = stmAttackers & pieces(QUEEN))) { - if ((swap = QueenValue - swap) < res) - break; + swap = QueenValue - swap; + // implies that the previous recapture was done by a higher rated piece than a Queen (King is excluded) + assert(swap >= res); occupied ^= least_significant_square_bb(bb); attackers |= (attacks_bb(to, occupied) & pieces(BISHOP, QUEEN)) From 8c73472ac84314d69c28dd886ecbf916a75ebf96 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Fri, 31 Jan 2025 17:36:55 -0800 Subject: [PATCH 33/59] Simplify depth increase condition further Passed Non-regression STC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 51232 W: 13560 L: 13351 D: 24321 Ptnml(0-2): 183, 6075, 12920, 6226, 212 https://tests.stockfishchess.org/tests/view/679d7b2b0774dfd78deb043f Passed Non-regression LTC (v. #5827): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 172398 W: 44108 L: 44042 D: 84248 Ptnml(0-2): 122, 19207, 47489, 19245, 136 https://tests.stockfishchess.org/tests/view/679d7fb10774dfd78deb05d2 Passed Non-regression VLTC (v. #5827): LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 388540 W: 99314 L: 99464 D: 189762 Ptnml(0-2): 89, 40454, 113350, 40272, 105 https://tests.stockfishchess.org/tests/view/679da3be0774dfd78deb0ad4 closes https://github.com/official-stockfish/Stockfish/pull/5846 Bench: 2688175 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 5b26b6657..1b5463117 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1107,7 +1107,7 @@ moves_loop: // When in check, search starts here extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); - depth += (depth < 15); + depth++; } // Multi-cut pruning From c12dbdedd9366bc7ffb29b355038bc7dea5f9c48 Mon Sep 17 00:00:00 2001 From: Disservin Date: Sun, 2 Feb 2025 18:05:16 +0100 Subject: [PATCH 34/59] Disallow same option being added twice Now exits during startup. ``` ./stockfish Stockfish dev-20250202-243c7c6a by the Stockfish developers (see AUTHORS file) x1,5,0,10,0.5,0.0020 Option: "x1" was already added! ``` i.e. prevents and helps debug this case ```cpp int x1 = 5; TUNE(x1); TUNE(x1); ``` closes https://github.com/official-stockfish/Stockfish/pull/5847 No functional change --- src/engine.cpp | 121 ++++++++++++++++++++++++++++------------------ src/tune.cpp | 2 +- src/ucioption.cpp | 40 +++++++-------- src/ucioption.h | 8 +-- 4 files changed, 100 insertions(+), 71 deletions(-) diff --git a/src/engine.cpp b/src/engine.cpp index d835fc8e4..6c8799a15 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -59,57 +59,83 @@ Engine::Engine(std::optional path) : NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) { pos.set(StartFEN, false, &states->back()); - options["Debug Log File"] << Option("", [](const Option& o) { - start_logger(o); - return std::nullopt; - }); - options["NumaPolicy"] << Option("auto", [this](const Option& o) { - set_numa_config_from_option(o); - return numa_config_information_as_string() + "\n" - + thread_allocation_information_as_string(); - }); + options.add( // + "Debug Log File", Option("", [](const Option& o) { + start_logger(o); + return std::nullopt; + })); - options["Threads"] << Option(1, 1, 1024, [this](const Option&) { - resize_threads(); - return thread_allocation_information_as_string(); - }); + options.add( // + "NumaPolicy", Option("auto", [this](const Option& o) { + set_numa_config_from_option(o); + return numa_config_information_as_string() + "\n" + + thread_allocation_information_as_string(); + })); - options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { - set_tt_size(o); - return std::nullopt; - }); + options.add( // + "Threads", Option(1, 1, 1024, [this](const Option&) { + resize_threads(); + return thread_allocation_information_as_string(); + })); - options["Clear Hash"] << Option([this](const Option&) { - search_clear(); - return std::nullopt; - }); - options["Ponder"] << Option(false); - options["MultiPV"] << Option(1, 1, MAX_MOVES); - options["Skill Level"] << Option(20, 0, 20); - options["Move Overhead"] << Option(10, 0, 5000); - options["nodestime"] << Option(0, 0, 10000); - options["UCI_Chess960"] << Option(false); - options["UCI_LimitStrength"] << Option(false); - options["UCI_Elo"] << Option(Stockfish::Search::Skill::LowestElo, - Stockfish::Search::Skill::LowestElo, - Stockfish::Search::Skill::HighestElo); - options["UCI_ShowWDL"] << Option(false); - options["SyzygyPath"] << Option("", [](const Option& o) { - Tablebases::init(o); - return std::nullopt; - }); - options["SyzygyProbeDepth"] << Option(1, 1, 100); - options["Syzygy50MoveRule"] << Option(true); - options["SyzygyProbeLimit"] << Option(7, 0, 7); - options["EvalFile"] << Option(EvalFileDefaultNameBig, [this](const Option& o) { - load_big_network(o); - return std::nullopt; - }); - options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) { - load_small_network(o); - return std::nullopt; - }); + options.add( // + "Hash", Option(16, 1, MaxHashMB, [this](const Option& o) { + set_tt_size(o); + return std::nullopt; + })); + + options.add( // + "Clear Hash", Option([this](const Option&) { + search_clear(); + return std::nullopt; + })); + + options.add( // + "Ponder", Option(false)); + + options.add( // + "MultiPV", Option(1, 1, MAX_MOVES)); + + options.add("Skill Level", Option(20, 0, 20)); + + options.add("Move Overhead", Option(10, 0, 5000)); + + options.add("nodestime", Option(0, 0, 10000)); + + options.add("UCI_Chess960", Option(false)); + + options.add("UCI_LimitStrength", Option(false)); + + options.add("UCI_Elo", + Option(Stockfish::Search::Skill::LowestElo, Stockfish::Search::Skill::LowestElo, + Stockfish::Search::Skill::HighestElo)); + + options.add("UCI_ShowWDL", Option(false)); + + options.add( // + "SyzygyPath", Option("", [](const Option& o) { + Tablebases::init(o); + return std::nullopt; + })); + + options.add("SyzygyProbeDepth", Option(1, 1, 100)); + + options.add("Syzygy50MoveRule", Option(true)); + + options.add("SyzygyProbeLimit", Option(7, 0, 7)); + + options.add( // + "EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) { + load_big_network(o); + return std::nullopt; + })); + + options.add( // + "EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) { + load_small_network(o); + return std::nullopt; + })); load_networks(); resize_threads(); @@ -340,5 +366,4 @@ std::string Engine::thread_allocation_information_as_string() const { return ss.str(); } - } diff --git a/src/tune.cpp b/src/tune.cpp index aff96c8cb..f53a0eb52 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -55,7 +55,7 @@ void Tune::make_option(OptionsMap* opts, const string& n, int v, const SetRange& if (TuneResults.count(n)) v = TuneResults[n]; - (*opts)[n] << Option(v, r(v).first, r(v).second, on_tune); + opts->add(n, Option(v, r(v).first, r(v).second, on_tune)); LastOption = &((*opts)[n]); // Print formatted parameters, ready to be copy-pasted in Fishtest diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 56cf41edc..a76bd3ace 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -57,17 +58,31 @@ void OptionsMap::setoption(std::istringstream& is) { sync_cout << "No such option: " << name << sync_endl; } -Option OptionsMap::operator[](const std::string& name) const { +const Option& OptionsMap::operator[](const std::string& name) const { auto it = options_map.find(name); - return it != options_map.end() ? it->second : Option(this); + assert(it != options_map.end()); + return it->second; } -Option& OptionsMap::operator[](const std::string& name) { +// Inits options and assigns idx in the correct printing order +void OptionsMap::add(const std::string& name, const Option& option) { if (!options_map.count(name)) - options_map[name] = Option(this); - return options_map[name]; + { + static size_t insert_order = 0; + + options_map[name] = option; + + options_map[name].parent = this; + options_map[name].idx = insert_order++; + } + else + { + std::cerr << "Option \"" << name << "\" was already added!" << std::endl; + std::exit(EXIT_FAILURE); + } } + std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); } Option::Option(const OptionsMap* map) : @@ -130,19 +145,6 @@ bool Option::operator==(const char* s) const { bool Option::operator!=(const char* s) const { return !(*this == s); } -// Inits options and assigns idx in the correct printing order - -void Option::operator<<(const Option& o) { - - static size_t insert_order = 0; - - auto p = this->parent; - *this = o; - - this->parent = p; - idx = insert_order++; -} - // Updates currentValue and triggers on_change() action. It's up to // the GUI to check for option's limits, but we could receive the new value // from the user by console window, so let's check the bounds anyway. @@ -161,7 +163,7 @@ Option& Option::operator=(const std::string& v) { std::string token; std::istringstream ss(defaultValue); while (ss >> token) - comboMap[token] << Option(); + comboMap.add(token, Option()); if (!comboMap.count(v) || v == "var") return *this; } diff --git a/src/ucioption.h b/src/ucioption.h index c9f6787d3..3d7386c30 100644 --- a/src/ucioption.h +++ b/src/ucioption.h @@ -54,12 +54,13 @@ class Option { friend std::ostream& operator<<(std::ostream&, const OptionsMap&); + int operator<<(const Option&) = delete; + private: friend class OptionsMap; friend class Engine; friend class Tune; - void operator<<(const Option&); std::string defaultValue, currentValue, type; int min, max; @@ -82,8 +83,9 @@ class OptionsMap { void setoption(std::istringstream&); - Option operator[](const std::string&) const; - Option& operator[](const std::string&); + const Option& operator[](const std::string&) const; + + void add(const std::string&, const Option& option); std::size_t count(const std::string&) const; From fccc6f624e0ea9bd55064ab839bbc720b2816d69 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:37:13 +0100 Subject: [PATCH 35/59] Reduce full depth search twice Passed STC: https://tests.stockfishchess.org/tests/view/679f429e0774dfd78deb10a5 LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 71584 W: 18905 L: 18529 D: 34150 Ptnml(0-2): 302, 8372, 18081, 8722, 315 Passed LTC: https://tests.stockfishchess.org/tests/view/679f72a00774dfd78deb1102 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 353952 W: 91007 L: 90024 D: 172921 Ptnml(0-2): 375, 39163, 96921, 40138, 379 closes https://github.com/official-stockfish/Stockfish/pull/5848 Bench: 3642363 --- src/search.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index 1b5463117..d23e91ef8 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1248,8 +1248,8 @@ moves_loop: // When in check, search starts here r += 2111; // Note that if expected reduction is high, we reduce search depth here - value = - -search(pos, ss + 1, -(alpha + 1), -alpha, newDepth - (r > 3444), !cutNode); + value = -search(pos, ss + 1, -(alpha + 1), -alpha, + newDepth - (r > 3444) - (r > 5588 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, From 9ed1725e7842df98aee612201ed11f3bda724926 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 2 Feb 2025 20:18:21 +0100 Subject: [PATCH 36/59] Simplify bonusScale formula Passed STC: https://tests.stockfishchess.org/tests/view/679ea7bc0774dfd78deb0d68 LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 47680 W: 12575 L: 12364 D: 22741 Ptnml(0-2): 179, 5589, 12139, 5708, 225 Passed LTC: https://tests.stockfishchess.org/tests/view/679eb7760774dfd78deb0dbb LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 314220 W: 80110 L: 80189 D: 153921 Ptnml(0-2): 265, 35121, 86420, 35036, 268 closes https://github.com/official-stockfish/Stockfish/pull/5849 Bench: 3161782 --- src/search.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index d23e91ef8..a33150810 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1399,10 +1399,10 @@ 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) + 37 * !allNode + 169 * ((ss - 1)->moveCount > 8) - + 128 * (!ss->inCheck && bestValue <= ss->staticEval - 102) - + 115 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) - + 80 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); + int bonusScale = (125 * (depth > 5) + 176 * ((ss - 1)->moveCount > 8) + + 135 * (!ss->inCheck && bestValue <= ss->staticEval - 102) + + 122 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) + + 87 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); bonusScale = std::max(bonusScale, 0); From 09623abbe84341baf1ec52383da4d01fb683e6d0 Mon Sep 17 00:00:00 2001 From: Kenneth Lee <71492754+kennethlee33@users.noreply.github.com> Date: Sat, 1 Feb 2025 18:41:08 -0800 Subject: [PATCH 37/59] Simplify cutoffCnt further Based off [Simplify cutoffCnt](https://github.com/official-stockfish/Stockfish/commit/69be04d38e10003853e78e4aa2b32aa252a82850) commit Original [commit](https://github.com/kennethlee33/Stockfish/commit/a77a895c3b7460f86b11a3ddfe3528f5be1276b9) adding extension condition seems to not be improving strength anymore Passed STC: LLR: 2.93 (-2.94,2.94) <-1.75,0.25> Total: 54176 W: 14331 L: 14125 D: 25720 Ptnml(0-2): 261, 6340, 13676, 6554, 257 https://tests.stockfishchess.org/tests/view/679edb7c0774dfd78deb0eed Passed LTC: LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 267198 W: 68148 L: 68179 D: 130871 Ptnml(0-2): 232, 30051, 73055, 30038, 223 https://tests.stockfishchess.org/tests/view/679ef2c70774dfd78deb0f43 closes https://github.com/official-stockfish/Stockfish/pull/5851 Bench: 3119355 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index a33150810..f98098701 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1348,7 +1348,7 @@ moves_loop: // When in check, search starts here if (value >= beta) { - ss->cutoffCnt += (extension < 2); + ss->cutoffCnt++; assert(value >= beta); // Fail high break; } From 3b8bfeb38a3cfb7805e61f0877e186d09805a6e0 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Mon, 3 Feb 2025 02:52:44 +0300 Subject: [PATCH 38/59] Do less aggressive pruning for higher movecounts Move part of heuristic that makes reduction less before pruning stage. Passed STC: https://tests.stockfishchess.org/tests/view/679fdf1b0774dfd78deb13b3 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 47136 W: 12484 L: 12146 D: 22506 Ptnml(0-2): 211, 5472, 11866, 5806, 213 Passed LTC: https://tests.stockfishchess.org/tests/view/679fe6790774dfd78deb1753 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 100536 W: 25837 L: 25383 D: 49316 Ptnml(0-2): 103, 10990, 27622, 11456, 97 closes https://github.com/official-stockfish/Stockfish/pull/5853 Bench: 3265587 --- src/search.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f98098701..cf7c7715e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -999,6 +999,8 @@ moves_loop: // When in check, search starts here Depth r = reduction(improving, depth, moveCount, delta); + r -= 32 * moveCount; + // Increase reduction for ttPv nodes (*Scaler) // Smaller or even negative value is better for short time controls // Bigger value is better for long time controls @@ -1169,7 +1171,7 @@ moves_loop: // When in check, search starts here // These reduction adjustments have no proven non-linear scaling - r += 307 - moveCount * 64; + r += 307 - moveCount * 32; r -= std::abs(correctionValue) / 34112; From ec7f1d622942b860d422808fc4df10104695bae2 Mon Sep 17 00:00:00 2001 From: Viren6 <94880762+Viren6@users.noreply.github.com> Date: Mon, 3 Feb 2025 07:06:01 +0000 Subject: [PATCH 39/59] Increment cutoffCnt less often after fail high Only increment when extension is less than 2 or it's a PvNode. Tested vs #5851. Failed STC: LLR: -2.97 (-2.94,2.94) <0.00,2.00> Total: 360064 W: 94546 L: 94271 D: 171247 Ptnml(0-2): 1835, 42826, 90314, 43343, 1714 https://tests.stockfishchess.org/tests/view/679f79cc0774dfd78deb1112 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 443076 W: 113942 L: 113081 D: 216053 Ptnml(0-2): 480, 49076, 121579, 49909, 494 https://tests.stockfishchess.org/tests/view/679fa21b0774dfd78deb1178 Passed VLTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 187184 W: 48098 L: 47495 D: 91591 Ptnml(0-2): 59, 19036, 54792, 19653, 52 https://tests.stockfishchess.org/tests/view/679fb6000774dfd78deb11e8 closes https://github.com/official-stockfish/Stockfish/pull/5855 Bench: 3018089 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index cf7c7715e..c9832c878 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1350,7 +1350,8 @@ moves_loop: // When in check, search starts here if (value >= beta) { - ss->cutoffCnt++; + // (* Scaler) Especially if they make cutoffCnt increment more often. + ss->cutoffCnt += (extension < 2) || PvNode; assert(value >= beta); // Fail high break; } From 67573218e1f57155541f8f7a3a90fe809d63c868 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Tue, 4 Feb 2025 00:57:06 +0300 Subject: [PATCH 40/59] VVLTC parameters tweak Some notes: - Both tests were conducted on top of #5848. - Based on tuning suggestions, the extension for capturing the previously moved piece was removed/simplified. (Developers can attempt to reintroduce it post-merge if needed.) - Initially, bonusScale = std::max(bonusScale, -2); was included but later removed in the second test upon Viz's request, however, it was nearly non-functional anyway. Passed VVLTC under STC bounds: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 31508 W: 8153 L: 7895 D: 15460 Ptnml(0-2): 1, 2747, 10005, 2995, 6 https://tests.stockfishchess.org/tests/view/679fdc7a0774dfd78deb1350 Passed VVLTC under LTC bounds: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 55026 W: 14370 L: 14046 D: 26610 Ptnml(0-2): 7, 4957, 17262, 5279, 8 https://tests.stockfishchess.org/tests/view/679fec920774dfd78deb19b8 closes https://github.com/official-stockfish/Stockfish/pull/5856 Bench: 2757788 --- src/search.cpp | 182 ++++++++++++++++++++++++------------------------- 1 file changed, 88 insertions(+), 94 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index c9832c878..b82217157 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -94,7 +94,7 @@ 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 7037 * pcv + 6671 * micv + 7631 * (wnpcv + bnpcv) + 6362 * cntcv; + return 6995 * pcv + 6593 * micv + 7753 * (wnpcv + bnpcv) + 6049 * cntcv; } // Add correctionHistory value to raw staticEval and guarantee evaluation @@ -110,11 +110,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 = 159; + static constexpr int nonPawnWeight = 165; workerThread.pawnCorrectionHistory[pawn_structure_index(pos)][us] - << bonus * 104 / 128; - workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 145 / 128; + << bonus * 109 / 128; + workerThread.minorPieceCorrectionHistory[minor_piece_index(pos)][us] << bonus * 141 / 128; workerThread.nonPawnCorrectionHistory[WHITE][non_pawn_index(pos)][us] << bonus * nonPawnWeight / 128; workerThread.nonPawnCorrectionHistory[BLACK][non_pawn_index(pos)][us] @@ -122,14 +122,14 @@ void update_correction_history(const Position& pos, if (m.is_ok()) (*(ss - 2)->continuationCorrectionHistory)[pos.piece_on(m.to_sq())][m.to_sq()] - << bonus * 146 / 128; + << bonus * 138 / 128; } // History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(154 * d - 102, 1661); } +int stat_bonus(Depth d) { return std::min(158 * d - 98, 1622); } // History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(831 * d - 269, 2666); } +int stat_malus(Depth d) { return std::min(802 * d - 243, 2850); } // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } @@ -307,7 +307,7 @@ void Search::Worker::iterative_deepening() { int searchAgainCounter = 0; - lowPlyHistory.fill(97); + lowPlyHistory.fill(95); // Iterative deepening loop until requested to stop or the target depth is reached while (++rootDepth < MAX_PLY && !threads.stop @@ -343,13 +343,13 @@ void Search::Worker::iterative_deepening() { selDepth = 0; // Reset aspiration window starting size - delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 12991; + delta = 5 + std::abs(rootMoves[pvIdx].meanSquaredScore) / 13000; 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] = 141 * avg / (std::abs(avg) + 83); + optimism[us] = 138 * avg / (std::abs(avg) + 81); optimism[~us] = -optimism[us]; // Start with a small aspiration window and, in the case of a fail @@ -533,11 +533,11 @@ void Search::Worker::iterative_deepening() { // Reset histories, usually before a new game void Search::Worker::clear() { - mainHistory.fill(63); - lowPlyHistory.fill(108); - captureHistory.fill(-631); - pawnHistory.fill(-1210); - pawnCorrectionHistory.fill(0); + mainHistory.fill(65); + lowPlyHistory.fill(107); + captureHistory.fill(-655); + pawnHistory.fill(-1215); + pawnCorrectionHistory.fill(4); minorPieceCorrectionHistory.fill(0); nonPawnCorrectionHistory[WHITE].fill(0); nonPawnCorrectionHistory[BLACK].fill(0); @@ -550,10 +550,10 @@ void Search::Worker::clear() { for (StatsType c : {NoCaptures, Captures}) for (auto& to : continuationHistory[inCheck][c]) for (auto& h : to) - h.fill(-479); + h.fill(-493); for (size_t i = 1; i < reductions.size(); ++i) - reductions[i] = int(2143 / 100.0 * std::log(i)); + reductions[i] = int(2937 / 128.0 * std::log(i)); refreshTable.clear(networks[numaAccessToken]); } @@ -679,12 +679,12 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 746 / 1024); + update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 784 / 1024); // Extra penalty for early quiet moves of the previous ply - if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 2 && !priorCapture) + if (prevSq != SQ_NONE && (ss - 1)->moveCount <= 3 && !priorCapture) update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - -stat_malus(depth + 1) * 1042 / 1024); + -stat_malus(depth + 1) * 1018 / 1024); } // Partial workaround for the graph history interaction problem @@ -791,11 +791,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), -1881, 1413) + 616; - thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] << bonus * 1151 / 1024; + 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; 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 * 1107 / 1024; + << bonus * 1195 / 1024; } // Set up the improving flag, which is true if current static evaluation is @@ -804,7 +804,7 @@ Value Search::Worker::search( // false otherwise. The improving flag is used in various pruning heuristics. improving = ss->staticEval > (ss - 2)->staticEval; - opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 2; + opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 5; if (priorReduction >= 3 && !opponentWorsening) depth++; @@ -812,27 +812,27 @@ Value Search::Worker::search( // 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 - 462 - 297 * depth * depth) + if (!PvNode && eval < alpha - 446 - 303 * 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 / 310 + 40 - std::abs(correctionValue) / 131072 + - (ss - 1)->statScore / 326 + 37 - std::abs(correctionValue) / 132821 >= 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 - 20 * depth + 470 - 60 * improving && !excludedMove + && ss->staticEval >= beta - 21 * depth + 455 - 60 * improving && !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) / 215, 7) + depth / 3 + 5; + Depth R = std::min(int(eval - beta) / 237, 6) + depth / 3 + 5; ss->currentMove = Move::null(); ss->continuationHistory = &thisThread->continuationHistory[0][0][NO_PIECE][0]; @@ -870,13 +870,13 @@ Value Search::Worker::search( // Step 10. Internal iterative reductions // For PV nodes without a ttMove as well as for deep enough cutNodes, we decrease depth. // (* Scaler) Especially if they make IIR more aggressive. - if (((PvNode || cutNode) && depth >= 7 - 4 * PvNode) && !ttData.move) - depth -= 2; + if (((PvNode || cutNode) && depth >= 7 - 3 * PvNode) && !ttData.move) + depth--; // 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 + 174 - 56 * improving; + probCutBeta = beta + 187 - 55 * improving; if (depth >= 3 && !is_decisive(beta) // If value from transposition table is lower than probCutBeta, don't attempt @@ -939,7 +939,7 @@ Value Search::Worker::search( moves_loop: // When in check, search starts here // Step 12. A small Probcut idea - probCutBeta = beta + 412; + probCutBeta = beta + 413; 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; @@ -1005,7 +1005,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 += 1024; + r += 1031; // Step 14. Pruning at shallow depth. // Depth conditions are important for mate finding. @@ -1027,15 +1027,15 @@ moves_loop: // When in check, search starts here // Futility pruning for captures if (!givesCheck && lmrDepth < 7 && !ss->inCheck) { - Value futilityValue = ss->staticEval + 271 + 243 * lmrDepth - + PieceValue[capturedPiece] + captHist / 7; + Value futilityValue = ss->staticEval + 242 + 238 * lmrDepth + + PieceValue[capturedPiece] + 95 * captHist / 700; if (futilityValue <= alpha) continue; } // SEE based pruning for captures and checks - int seeHist = std::clamp(captHist / 37, -152 * depth, 141 * depth); - if (!pos.see_ge(move, -156 * depth - seeHist)) + int seeHist = std::clamp(captHist / 36, -153 * depth, 134 * depth); + if (!pos.see_ge(move, -157 * depth - seeHist)) continue; } else @@ -1046,14 +1046,14 @@ moves_loop: // When in check, search starts here + thisThread->pawnHistory[pawn_structure_index(pos)][movedPiece][move.to_sq()]; // Continuation history based pruning - if (history < -3901 * depth) + if (history < -4107 * depth) continue; - history += 2 * thisThread->mainHistory[us][move.from_to()]; + history += 68 * thisThread->mainHistory[us][move.from_to()] / 32; - lmrDepth += history / 3459; + lmrDepth += history / 3576; - Value futilityValue = ss->staticEval + (bestMove ? 47 : 137) + 142 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 49 : 135) + 150 * lmrDepth; // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) @@ -1067,7 +1067,7 @@ moves_loop: // When in check, search starts here lmrDepth = std::max(lmrDepth, 0); // Prune moves with negative SEE - if (!pos.see_ge(move, -25 * lmrDepth * lmrDepth)) + if (!pos.see_ge(move, -26 * lmrDepth * lmrDepth)) continue; } } @@ -1087,11 +1087,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 > 33) + ss->ttPv + && depth >= 5 - (thisThread->completedDepth > 32) + ss->ttPv && is_valid(ttData.value) && !is_decisive(ttData.value) && (ttData.bound & BOUND_LOWER) && ttData.depth >= depth - 3) { - Value singularBeta = ttData.value - (52 + 74 * (ss->ttPv && !PvNode)) * depth / 64; + Value singularBeta = ttData.value - (55 + 81 * (ss->ttPv && !PvNode)) * depth / 58; Depth singularDepth = newDepth / 2; ss->excludedMove = move; @@ -1101,10 +1101,11 @@ moves_loop: // When in check, search starts here if (value < singularBeta) { - int corrValAdj = std::abs(correctionValue) / 262144; - int doubleMargin = 249 * PvNode - 194 * !ttCapture - corrValAdj; + int corrValAdj1 = std::abs(correctionValue) / 265083; + int corrValAdj2 = std::abs(correctionValue) / 253680; + int doubleMargin = 267 * PvNode - 181 * !ttCapture - corrValAdj1; int tripleMargin = - 94 + 287 * PvNode - 249 * !ttCapture + 99 * ss->ttPv - corrValAdj; + 96 + 282 * PvNode - 250 * !ttCapture + 103 * ss->ttPv - corrValAdj2; extension = 1 + (value < singularBeta - doubleMargin) + (value < singularBeta - tripleMargin); @@ -1137,13 +1138,6 @@ moves_loop: // When in check, search starts here else if (cutNode) extension = -2; } - - // Extension for capturing the previous moved piece - else if (PvNode && move.to_sq() == prevSq - && thisThread->captureHistory[movedPiece][move.to_sq()] - [type_of(pos.piece_on(move.to_sq()))] - > 4126) - extension = 1; } // Step 16. Make the move @@ -1164,45 +1158,45 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2061 + (ttData.value > alpha) * 965 + (ttData.depth >= depth) * 960; + r -= 2230 + (ttData.value > alpha) * 925 + (ttData.depth >= depth) * 971; if (PvNode) - r -= 1018; + r -= 1013; // These reduction adjustments have no proven non-linear scaling - r += 307 - moveCount * 32; + r += 316 - moveCount * 63; - r -= std::abs(correctionValue) / 34112; + r -= std::abs(correctionValue) / 31568; // Increase reduction for cut nodes if (cutNode) - r += 2355 - (ttData.depth >= depth && ss->ttPv) * 1141; + r += 2608 - (ttData.depth >= depth && ss->ttPv) * 1159; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) - r += 1087 + (depth < 8) * 990; + r += 1123 + (depth < 8) * 982; // Increase reduction if next ply has a lot of fail high if ((ss + 1)->cutoffCnt > 3) - r += 940 + allNode * 887; + r += 981 + allNode * 833; // For first picked move (ttMove) reduce reduction else if (move == ttData.move) - r -= 1960; + r -= 1982; if (capture) ss->statScore = - 7 * int(PieceValue[pos.captured_piece()]) + 688 * int(PieceValue[pos.captured_piece()]) / 100 + thisThread->captureHistory[movedPiece][move.to_sq()][type_of(pos.captured_piece())] - - 4666; + - 4653; else ss->statScore = 2 * thisThread->mainHistory[us][move.from_to()] + (*contHist[0])[movedPiece][move.to_sq()] - + (*contHist[1])[movedPiece][move.to_sq()] - 3874; + + (*contHist[1])[movedPiece][move.to_sq()] - 3591; // Decrease/increase reduction for moves with a good/bad history - r -= ss->statScore * 1451 / 16384; + r -= ss->statScore * 1407 / 16384; // Step 17. Late moves reduction / extension (LMR) if (depth >= 2 && moveCount > 1) @@ -1228,8 +1222,8 @@ 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 + 40 + 2 * newDepth); - const bool doShallowerSearch = value < bestValue + 10; + const bool doDeeperSearch = value > (bestValue + 41 + 2 * newDepth); + const bool doShallowerSearch = value < bestValue + 9; newDepth += doDeeperSearch - doShallowerSearch; @@ -1237,7 +1231,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) * 2048; + int bonus = (value >= beta) * 2010; update_continuation_histories(ss, movedPiece, move.to_sq(), bonus); } } @@ -1247,11 +1241,11 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 2111; + r += 2117; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, - newDepth - (r > 3444) - (r > 5588 && newDepth > 2), !cutNode); + newDepth - (r > 3554) - (r > 5373 && newDepth > 2), !cutNode); } // For PV nodes only, do a full PV search on the first move or after a fail high, @@ -1358,7 +1352,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 < 14 && !is_decisive(value)) + if (depth > 2 && depth < 15 && !is_decisive(value)) depth -= 2; assert(depth > 0); @@ -1402,24 +1396,24 @@ 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 = (125 * (depth > 5) + 176 * ((ss - 1)->moveCount > 8) - + 135 * (!ss->inCheck && bestValue <= ss->staticEval - 102) - + 122 * (!(ss - 1)->inCheck && bestValue <= -(ss - 1)->staticEval - 82) - + 87 * ((ss - 1)->isTTMove) + std::min(-(ss - 1)->statScore / 106, 318)); + 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) + std::min(-(ss - 1)->statScore / 108, 320)); bonusScale = std::max(bonusScale, 0); const int scaledBonus = stat_bonus(depth) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, - scaledBonus * 436 / 32768); + scaledBonus * 416 / 32768); thisThread->mainHistory[~us][((ss - 1)->currentMove).from_to()] - << scaledBonus * 207 / 32768; + << scaledBonus * 219 / 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 * 1195 / 32768; + << scaledBonus * 1103 / 32768; } else if (priorCapture && prevSq != SQ_NONE) @@ -1580,7 +1574,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) if (bestValue > alpha) alpha = bestValue; - futilityBase = ss->staticEval + 301; + futilityBase = ss->staticEval + 325; } const PieceToHistory* contHist[] = {(ss - 1)->continuationHistory, @@ -1643,11 +1637,11 @@ 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()] - <= 5228) + <= 5389) continue; // Do not search moves with bad enough SEE values - if (!pos.see_ge(move, -80)) + if (!pos.see_ge(move, -75)) continue; } @@ -1714,7 +1708,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 * 768 / rootDelta + !i * reductionScale * 108 / 300 + 1168; + return reductionScale - delta * 735 / rootDelta + !i * reductionScale * 191 / 512 + 1132; } // elapsed() returns the time elapsed since the search started. If the @@ -1810,35 +1804,35 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = stat_bonus(depth) + 300 * isTTMove; - int malus = stat_malus(depth) - 34 * (moveCount - 1); + int bonus = stat_bonus(depth) + 298 * isTTMove; + int malus = stat_malus(depth) - 32 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { - update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1216 / 1024); + update_quiet_histories(pos, ss, workerThread, bestMove, bonus * 1202 / 1024); // Decrease stats for all non-best quiet moves for (Move move : quietsSearched) - update_quiet_histories(pos, ss, workerThread, move, -malus * 1062 / 1024); + update_quiet_histories(pos, ss, workerThread, move, -malus * 1152 / 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 * 1272 / 1024; + captureHistory[moved_piece][bestMove.to_sq()][captured] << bonus * 1236 / 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 * 966 / 1024); + update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, -malus * 976 / 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 * 1205 / 1024; + captureHistory[moved_piece][move.to_sq()][captured] << -malus * 1224 / 1024; } } @@ -1847,7 +1841,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, 1025}, {2, 621}, {3, 325}, {4, 512}, {5, 122}, {6, 534}}}; + {{1, 1029}, {2, 656}, {3, 326}, {4, 536}, {5, 120}, {6, 537}}}; for (const auto [i, weight] : conthist_bonuses) { @@ -1868,12 +1862,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 * 879 / 1024; + workerThread.lowPlyHistory[ss->ply][move.from_to()] << bonus * 844 / 1024; - update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 888 / 1024); + update_continuation_histories(ss, pos.moved_piece(move), move.to_sq(), bonus * 964 / 1024); int pIndex = pawn_structure_index(pos); - workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 634 / 1024; + workerThread.pawnHistory[pIndex][pos.moved_piece(move)][move.to_sq()] << bonus * 615 / 1024; } } From e852d9880a6d3e25d92b6db8216f497ad98c2c57 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Tue, 4 Feb 2025 22:14:13 +0300 Subject: [PATCH 41/59] Reduce less for positions without tt move Continuation of work on scaling. In line with previous scaling patches this one massively reduces reduction for moves that don't go thru lmr for position without a tt move. Passed VVLTC with STC bounds: https://tests.stockfishchess.org/tests/view/679fd2450774dfd78deb12b2 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 74718 W: 19354 L: 19042 D: 36322 Ptnml(0-2): 5, 6724, 23595, 7024, 11 Passed VVLTC with LTC bounds: https://tests.stockfishchess.org/tests/view/67a009930774dfd78deb2346 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 82638 W: 21587 L: 21212 D: 39839 Ptnml(0-2): 15, 7476, 25953, 7869, 6 closes https://github.com/official-stockfish/Stockfish/pull/5860 Bench: 2887850 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index b82217157..99aff5e96 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1241,7 +1241,7 @@ moves_loop: // When in check, search starts here { // Increase reduction if ttMove is not present if (!ttData.move) - r += 2117; + r += 1111; // Note that if expected reduction is high, we reduce search depth here value = -search(pos, ss + 1, -(alpha + 1), -alpha, From 2a5b41fd12184d5ab8dedd6ed03d9c2b0fb218a3 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Feb 2025 22:35:10 +0100 Subject: [PATCH 42/59] Fixes a wrongly combined merge conflict from the previous merge wave. Passed STC: https://tests.stockfishchess.org/tests/view/67a288aaeb183d11c65945f1 LLR: 2.99 (-2.94,2.94) <0.00,2.00> Total: 51424 W: 13588 L: 13237 D: 24599 Ptnml(0-2): 223, 6039, 12860, 6344, 246 Passed LTC: https://tests.stockfishchess.org/tests/view/67a28c0aeb183d11c6594609 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 54144 W: 13900 L: 13543 D: 26701 Ptnml(0-2): 42, 5881, 14870, 6236, 43 closes https://github.com/official-stockfish/Stockfish/pull/5863 Bench: 2345723 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 99aff5e96..f5d2f5cb7 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1165,7 +1165,7 @@ moves_loop: // When in check, search starts here // These reduction adjustments have no proven non-linear scaling - r += 316 - moveCount * 63; + r += 316 - moveCount * 32; r -= std::abs(correctionValue) / 31568; From 4c6d2bf9215e2ceb7c22a0bd8ae40077f1751d63 Mon Sep 17 00:00:00 2001 From: Disservin Date: Tue, 4 Feb 2025 22:21:44 +0100 Subject: [PATCH 43/59] Show stdout/stderr in CI/CD tests makes it easier to fix based on warnings shown with e.g. valgrind closes https://github.com/official-stockfish/Stockfish/pull/5862 No functional change --- tests/testing.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/testing.py b/tests/testing.py index bc1f6b15b..3a4b537e9 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -97,14 +97,17 @@ class Syzygy: tarball_path = os.path.join(tmpdirname, f"{file}.tar.gz") response = requests.get(url, stream=True) - with open(tarball_path, 'wb') as f: + with open(tarball_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) with tarfile.open(tarball_path, "r:gz") as tar: tar.extractall(tmpdirname) - shutil.move(os.path.join(tmpdirname, file), os.path.join(PATH, "syzygy")) + shutil.move( + os.path.join(tmpdirname, file), os.path.join(PATH, "syzygy") + ) + class OrderedClassMembers(type): @classmethod @@ -307,7 +310,10 @@ class Stockfish: text=True, ) - self.process.stdout + if self.process.returncode != 0: + print(self.process.stdout) + print(self.process.stderr) + print(f"Process failed with return code {self.process.returncode}") return From 3dfbc5de25705fadcb4b5b7a551eacb3eb75d171 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 4 Feb 2025 15:48:40 -0800 Subject: [PATCH 44/59] Remove non-pawn material check in qsearch pruning Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 47712 W: 12621 L: 12409 D: 22682 Ptnml(0-2): 224, 5349, 12480, 5597, 206 https://tests.stockfishchess.org/tests/view/67a1b4fb612069de394afc37 Passed rebased simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 188274 W: 47727 L: 47677 D: 92870 Ptnml(0-2): 171, 20429, 52867, 20519, 151 https://tests.stockfishchess.org/tests/view/67a2a761fedef70e42ac3300 closes https://github.com/official-stockfish/Stockfish/pull/5866 bench 2654242 --- src/search.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index f5d2f5cb7..c68b3b7ac 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1490,7 +1490,6 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) Value bestValue, value, futilityBase; bool pvHit, givesCheck, capture; int moveCount; - Color us = pos.side_to_move(); // Step 1. Initialize node if (PvNode) @@ -1603,7 +1602,7 @@ Value Search::Worker::qsearch(Position& pos, Stack* ss, Value alpha, Value beta) moveCount++; // Step 6. Pruning - if (!is_loss(bestValue) && pos.non_pawn_material(us)) + if (!is_loss(bestValue)) { // Futility pruning and moveCount pruning if (!givesCheck && move.to_sq() != prevSq && !is_loss(futilityBase) From d66e603070a4ae76dcc8aeae69882d7d10ac3846 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Tue, 4 Feb 2025 15:06:58 -0800 Subject: [PATCH 45/59] Increase PCM bonus when cutOffCnt is low Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 36832 W: 9763 L: 9438 D: 17631 Ptnml(0-2): 159, 4267, 9254, 4562, 174 https://tests.stockfishchess.org/tests/view/67a29dbafedef70e42ac329a Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 19728 W: 5124 L: 4839 D: 9765 Ptnml(0-2): 18, 2029, 5485, 2314, 18 https://tests.stockfishchess.org/tests/view/67a2a1abfedef70e42ac32b7 closes https://github.com/official-stockfish/Stockfish/pull/5865 Bench: 3197798 --- src/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index c68b3b7ac..36823a08e 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1399,7 +1399,8 @@ moves_loop: // When in check, search starts here 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) + std::min(-(ss - 1)->statScore / 108, 320)); + + 81 * ((ss - 1)->isTTMove) + 100 * (ss->cutoffCnt <= 3) + + std::min(-(ss - 1)->statScore / 108, 320)); bonusScale = std::max(bonusScale, 0); From e089f723d87e18dc15c95a4628a65db62f44ed9c Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Tue, 7 Jan 2025 01:47:37 +0100 Subject: [PATCH 46/59] Remove two xors by setting the hash keys for unreachable squares to zero performance before: 3.6714 +- 0.20% Gcycles 3.6620 +- 0.12% Gcycles 3.6704 +- 0.26% Gcycles 3.6602 +- 0.27% Gcycles 3.6799 +- 0.37% Gcycles after: 3.6540 +- 0.30% Gcycles 3.6388 +- 0.25% Gcycles 3.6557 +- 0.17% Gcycles 3.6449 +- 0.15% Gcycles 3.6460 +- 0.26% Gcycles (every line is a different `profile-build` and shows the number of cycles needed for `./stockfish bench`, measured with `perf stat -r 10`) closes https://github.com/official-stockfish/Stockfish/pull/5754 No functional change --- src/position.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/position.cpp b/src/position.cpp index 37871aa24..37e9a2eb5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -119,6 +119,9 @@ void Position::init() { for (Piece pc : Pieces) for (Square s = SQ_A1; s <= SQ_H8; ++s) Zobrist::psq[pc][s] = rng.rand(); + // pawns on these squares will promote + std::fill_n(Zobrist::psq[W_PAWN] + SQ_A8, 8, 0); + std::fill_n(Zobrist::psq[B_PAWN], 8, 0); for (File f = FILE_A; f <= FILE_H; ++f) Zobrist::enpassant[f] = rng.rand(); @@ -376,7 +379,7 @@ void Position::set_state() const { for (Piece pc : Pieces) for (int cnt = 0; cnt < pieceCount[pc]; ++cnt) - st->materialKey ^= Zobrist::psq[pc][cnt]; + st->materialKey ^= Zobrist::psq[pc][8 + cnt]; } @@ -776,7 +779,7 @@ void Position::do_move(Move m, remove_piece(capsq); k ^= Zobrist::psq[captured][capsq]; - st->materialKey ^= Zobrist::psq[captured][pieceCount[captured]]; + st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]]; // Reset rule 50 counter st->rule50 = 0; @@ -840,10 +843,10 @@ void Position::do_move(Move m, dp.dirty_num++; // Update hash keys - k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to]; - st->pawnKey ^= Zobrist::psq[pc][to]; - st->materialKey ^= - Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]]; + // Zobrist::psq[pc][to] is zero, so we don't need to clear it + k ^= Zobrist::psq[promotion][to]; + st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1] + ^ Zobrist::psq[pc][8 + pieceCount[pc]]; if (promotionType <= BISHOP) st->minorPieceKey ^= Zobrist::psq[promotion][to]; From 3a0418c0d0353ad5720e1fea83510cbee5608485 Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Tue, 4 Feb 2025 15:16:28 -0800 Subject: [PATCH 47/59] Simplify opponent worsening Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 57120 W: 14712 L: 14526 D: 27882 Ptnml(0-2): 53, 6241, 15796, 6407, 63 https://tests.stockfishchess.org/tests/view/67a26153eb183d11c659454d Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 313452 W: 79893 L: 79973 D: 153586 Ptnml(0-2): 279, 35053, 86156, 34945, 293 https://tests.stockfishchess.org/tests/view/67a29fe0fedef70e42ac32ae closes https://github.com/official-stockfish/Stockfish/pull/5867 Bench: 2582245 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 36823a08e..be5117916 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -804,7 +804,7 @@ Value Search::Worker::search( // false otherwise. The improving flag is used in various pruning heuristics. improving = ss->staticEval > (ss - 2)->staticEval; - opponentWorsening = ss->staticEval + (ss - 1)->staticEval > 5; + opponentWorsening = ss->staticEval > -(ss - 1)->staticEval; if (priorReduction >= 3 && !opponentWorsening) depth++; From 7258567804ccd0730d7c309f150d96c8f6c3816d Mon Sep 17 00:00:00 2001 From: Stefan Geschwentner Date: Sat, 8 Feb 2025 20:17:47 +0100 Subject: [PATCH 48/59] Refactor reduction rules Refactor reduction rules so that all ttPv/Pv related stuff is in one rule and the scaling becomes more clear. No functional change closes https://github.com/official-stockfish/Stockfish/pull/5871 No functional change --- src/search.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index be5117916..4dfdbaaf0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1158,10 +1158,8 @@ moves_loop: // When in check, search starts here // Decrease reduction for PvNodes (*Scaler) if (ss->ttPv) - r -= 2230 + (ttData.value > alpha) * 925 + (ttData.depth >= depth) * 971; - - if (PvNode) - r -= 1013; + r -= 2230 + PvNode * 1013 + (ttData.value > alpha) * 925 + + (ttData.depth >= depth) * (971 + cutNode * 1159); // These reduction adjustments have no proven non-linear scaling @@ -1171,7 +1169,7 @@ moves_loop: // When in check, search starts here // Increase reduction for cut nodes if (cutNode) - r += 2608 - (ttData.depth >= depth && ss->ttPv) * 1159; + r += 2608; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) From 9cc15b30490675713466b6746d4afdce5c715bd6 Mon Sep 17 00:00:00 2001 From: Michael Chaly Date: Wed, 12 Feb 2025 01:53:41 +0300 Subject: [PATCH 49/59] Do more reductions for cut nodes without a tt move Logic is somewhat similar to IIR but in LMR. Usually things like reducing more in IIR scale badly but this patch does this in LMR where reducing more for cutNodes is in general good, so I believe there is no non-linear scaling. Passed STC: https://tests.stockfishchess.org/tests/view/67abc9aaa04df5eb8dbeb452 LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 42304 W: 11223 L: 10892 D: 20189 Ptnml(0-2): 184, 4904, 10669, 5187, 208 Passed LTC: https://tests.stockfishchess.org/tests/view/67abcd7ba04df5eb8dbeb96c LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 32334 W: 8386 L: 8074 D: 15874 Ptnml(0-2): 26, 3446, 8916, 3748, 31 closes https://github.com/official-stockfish/Stockfish/pull/5875 Bench: 2612849 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4dfdbaaf0..4bc6109ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1169,7 +1169,7 @@ moves_loop: // When in check, search starts here // Increase reduction for cut nodes if (cutNode) - r += 2608; + r += 2608 + 1024 * !ttData.move; // Increase reduction if ttMove is a capture but the current move is not a capture if (ttCapture && !capture) From a4edacb87aeba72483e1524ade5bf79129c88513 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:15:32 +0100 Subject: [PATCH 50/59] Tweak the cutnode depth condition for TT cutoffs Passed STC: https://tests.stockfishchess.org/tests/view/67ab396ab5c93ee812d851f3 LLR: 2.96 (-2.94,2.94) <0.00,2.00> Total: 83648 W: 21964 L: 21571 D: 40113 Ptnml(0-2): 339, 9779, 21217, 10128, 361 Passed LTC: https://tests.stockfishchess.org/tests/view/67ab9647133d55b1d3bc171e LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 68160 W: 17551 L: 17166 D: 33443 Ptnml(0-2): 62, 7353, 18870, 7728, 67 closes https://github.com/official-stockfish/Stockfish/pull/5876 Bench: 3087275 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index 4bc6109ed..02323d1e0 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -672,7 +672,7 @@ Value Search::Worker::search( if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) - && (cutNode == (ttData.value >= beta) || depth > 9)) + && (cutNode == (ttData.value >= beta) || (depth > 9 || (rootDepth > 10 && depth > 5)))) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) From d54240c50af51b1f3a96b81514ec60286ae1056b Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 12 Feb 2025 15:46:07 -0800 Subject: [PATCH 51/59] Decrease lmr depth if static eval decreases a lot This tweak originally had some more conditions which have been simplified away. Passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 60064 W: 15797 L: 15439 D: 28828 Ptnml(0-2): 236, 7080, 15106, 7310, 300 https://tests.stockfishchess.org/tests/view/67a2af9cfedef70e42ac3325 Passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 76794 W: 19740 L: 19337 D: 37717 Ptnml(0-2): 61, 8327, 21236, 8694, 79 https://tests.stockfishchess.org/tests/view/67a2c904fedef70e42ac374d Passed Non-Regression VVLTC scaling check LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 29046 W: 7581 L: 7389 D: 14076 Ptnml(0-2): 2, 2557, 9213, 2749, 2 https://tests.stockfishchess.org/tests/view/67a54b591c4a3ea87241cb83 Passed simplification STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 212448 W: 55244 L: 55217 D: 101987 Ptnml(0-2): 932, 25283, 53707, 25430, 872 https://tests.stockfishchess.org/tests/view/67aaacb02554387b116f698f Passed simplification LTC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 185736 W: 47270 L: 47217 D: 91249 Ptnml(0-2): 141, 20568, 51394, 20627, 138 https://tests.stockfishchess.org/tests/view/67ab8efa133d55b1d3bc1397 closes https://github.com/official-stockfish/Stockfish/pull/5878 Bench: 2512420 --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index 02323d1e0..cd90bbfd2 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -808,6 +808,8 @@ Value Search::Worker::search( if (priorReduction >= 3 && !opponentWorsening) depth++; + if (priorReduction >= 1 && depth >= 2 && ss->staticEval + (ss - 1)->staticEval > 200) + depth--; // Step 7. Razoring // If eval is really low, skip search entirely and return the qsearch value. From fa6c30af814fe91e6a6c2d1bcaa8d951e3724ae7 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 13 Feb 2025 14:47:19 +0300 Subject: [PATCH 52/59] FutilityValue formula tweak Passed STC: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 29600 W: 7979 L: 7662 D: 13959 Ptnml(0-2): 138, 3446, 7324, 3745, 147 https://tests.stockfishchess.org/tests/view/67ac7dff52879dfd14d7e7da Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 49662 W: 12850 L: 12502 D: 24310 Ptnml(0-2): 41, 5354, 13689, 5710, 37 https://tests.stockfishchess.org/tests/view/67acc1b252879dfd14d7e81d closes https://github.com/official-stockfish/Stockfish/pull/5879 Bench: 2581469 --- src/search.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index cd90bbfd2..67e28d3e5 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1055,7 +1055,10 @@ moves_loop: // When in check, search starts here lmrDepth += history / 3576; - Value futilityValue = ss->staticEval + (bestMove ? 49 : 135) + 150 * lmrDepth; + Value futilityValue = ss->staticEval + (bestMove ? 49 : 143) + 116 * lmrDepth; + + if (bestValue < ss->staticEval - 150 && lmrDepth < 7) + futilityValue += 108; // Futility pruning: parent node if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) From e9997afb1cb046ed1812974f27c532f6e4d8dbcc Mon Sep 17 00:00:00 2001 From: Carlos Esparza Date: Mon, 3 Feb 2025 22:44:09 -0800 Subject: [PATCH 53/59] Replace hint_common_parent_position() by backwards accumulator updates Only calls to `evaluate()` now trigger NNUE accumulator updates. To make sure that we are likely to find parent positions from which to update the accumulators we perform a backwards NNUE update whenever we compute the accumulator from scratch for some position. passed STC LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 39680 W: 10474 L: 10164 D: 19042 Ptnml(0-2): 171, 4068, 11042, 4398, 161 https://tests.stockfishchess.org/tests/view/67a27f26eb183d11c65945be passed LTC LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 337308 W: 86408 L: 85550 D: 165350 Ptnml(0-2): 276, 30551, 106126, 31441, 260 https://tests.stockfishchess.org/tests/view/67a287efeb183d11c65945ee then simplified: STC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 28608 W: 7641 L: 7413 D: 13554 Ptnml(0-2): 132, 3036, 7744, 3256, 136 https://tests.stockfishchess.org/tests/view/67a4703719f522d3866d3345 LTC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 200226 W: 51026 L: 50990 D: 98210 Ptnml(0-2): 170, 18468, 62799, 18508, 168 https://tests.stockfishchess.org/tests/view/67a4f255229c1a170cc08964 The version in this PR is a bit different from the simplified version, but it's compile-time changes only. closes https://github.com/official-stockfish/Stockfish/pull/5870 No functional change --- src/nnue/network.cpp | 6 -- src/nnue/network.h | 3 - src/nnue/nnue_feature_transformer.h | 141 ++++++++++++++++++---------- src/nnue/nnue_misc.cpp | 10 -- src/nnue/nnue_misc.h | 3 - src/position.h | 2 +- src/search.cpp | 4 - 7 files changed, 92 insertions(+), 77 deletions(-) diff --git a/src/nnue/network.cpp b/src/nnue/network.cpp index b96625a79..5ac8b8d98 100644 --- a/src/nnue/network.cpp +++ b/src/nnue/network.cpp @@ -278,12 +278,6 @@ void Network::verify(std::string } -template -void Network::hint_common_access( - const Position& pos, AccumulatorCaches::Cache* cache) const { - featureTransformer->hint_common_access(pos, cache); -} - template NnueEvalTrace Network::trace_evaluate(const Position& pos, diff --git a/src/nnue/network.h b/src/nnue/network.h index f99fa1182..764481d94 100644 --- a/src/nnue/network.h +++ b/src/nnue/network.h @@ -67,9 +67,6 @@ class Network { AccumulatorCaches::Cache* cache) const; - void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache) const; - void verify(std::string evalfilePath, const std::function&) const; NnueEvalTrace trace_evaluate(const Position& pos, AccumulatorCaches::Cache* cache) const; diff --git a/src/nnue/nnue_feature_transformer.h b/src/nnue/nnue_feature_transformer.h index 4f0ce6cf6..931d9aed5 100644 --- a/src/nnue/nnue_feature_transformer.h +++ b/src/nnue/nnue_feature_transformer.h @@ -41,6 +41,11 @@ 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,6 +254,7 @@ class FeatureTransformer { // Number of output dimensions for one side static constexpr IndexType HalfDimensions = TransformedFeatureDimensions; + static constexpr bool Big = TransformedFeatureDimensions == TransformedFeatureDimensionsBig; private: using Tiling = SIMDTiling; @@ -468,38 +474,20 @@ class FeatureTransformer { return psqt; } // end of function transform() - void hint_common_access(const Position& pos, - AccumulatorCaches::Cache* cache) const { - update_accumulator(pos, cache); - update_accumulator(pos, cache); - } - private: - template - StateInfo* try_find_computed_accumulator(const Position& pos) const { - // Look for a usable accumulator of an earlier position. We keep track - // of the estimated gain in terms of features to be added/subtracted. - StateInfo* st = pos.state(); - int gain = FeatureSet::refresh_cost(pos); - while (st->previous && !(st->*accPtr).computed[Perspective]) - { - // This governs when a full feature refresh is needed and how many - // updates are better than just one full refresh. - if (FeatureSet::requires_refresh(st, Perspective) - || (gain -= FeatureSet::update_cost(st) + 1) < 0) - break; - st = st->previous; - } - return st; - } - - // Given a computed accumulator, computes the accumulator of the next position. - template - void update_accumulator_incremental(const Position& pos, StateInfo* computed) const { + // 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]); - assert(computed->next != nullptr); - const Square ksq = pos.square(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 @@ -508,11 +496,11 @@ class FeatureTransformer { // 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; - FeatureSet::append_changed_indices(ksq, computed->next->dirtyPiece, removed, - added); - - StateInfo* next = computed->next; - assert(!(next->*accPtr).computed[Perspective]); + 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) { @@ -527,7 +515,10 @@ class FeatureTransformer { { assert(added.size() == 1 || added.size() == 2); assert(removed.size() == 1 || removed.size() == 2); - assert(added.size() <= removed.size()); + if (Forward) + assert(added.size() <= removed.size()); + else + assert(removed.size() <= added.size()); #ifdef VECTOR auto* accIn = @@ -539,13 +530,15 @@ class FeatureTransformer { const IndexType offsetR0 = HalfDimensions * removed[0]; auto* columnR0 = reinterpret_cast(&weights[offsetR0]); - if (removed.size() == 1) + 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 (added.size() == 1) + else if (Forward && added.size() == 1) { + assert(removed.size() == 2); const IndexType offsetR1 = HalfDimensions * removed[1]; auto* columnR1 = reinterpret_cast(&weights[offsetR1]); @@ -553,8 +546,19 @@ class FeatureTransformer { 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]; @@ -576,14 +580,15 @@ class FeatureTransformer { const IndexType offsetPsqtR0 = PSQTBuckets * removed[0]; auto* columnPsqtR0 = reinterpret_cast(&psqtWeights[offsetPsqtR0]); - if (removed.size() == 1) + 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 (added.size() == 1) + else if (Forward && added.size() == 1) { const IndexType offsetPsqtR1 = PSQTBuckets * removed[1]; auto* columnPsqtR1 = @@ -595,6 +600,18 @@ class FeatureTransformer { 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]; @@ -647,8 +664,8 @@ class FeatureTransformer { (next->*accPtr).computed[Perspective] = true; - if (next != pos.state()) - update_accumulator_incremental(pos, next); + if (next != target_state) + update_accumulator_incremental(ksq, target_state, next); } @@ -815,16 +832,40 @@ class FeatureTransformer { template void update_accumulator(const Position& pos, AccumulatorCaches::Cache* cache) const { - if ((pos.state()->*accPtr).computed[Perspective]) - return; - StateInfo* oldest = try_find_computed_accumulator(pos); + StateInfo* st = pos.state(); + if ((st->*accPtr).computed[Perspective]) + return; // nothing to do - if ((oldest->*accPtr).computed[Perspective] && oldest != pos.state()) - // Start from the oldest computed accumulator, update all the - // accumulators up to the current position. - update_accumulator_incremental(pos, oldest); - else - update_accumulator_refresh_cache(pos, cache); + [[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. + do + { + if (FeatureSet::requires_refresh(st, Perspective) + || (!Big && (gain -= FeatureSet::update_cost(st) < 0)) || !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 + // 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 diff --git a/src/nnue/nnue_misc.cpp b/src/nnue/nnue_misc.cpp index 1e2690503..2220684da 100644 --- a/src/nnue/nnue_misc.cpp +++ b/src/nnue/nnue_misc.cpp @@ -30,7 +30,6 @@ #include #include -#include "../evaluate.h" #include "../position.h" #include "../types.h" #include "../uci.h" @@ -43,15 +42,6 @@ namespace Stockfish::Eval::NNUE { constexpr std::string_view PieceToChar(" PNBRQK pnbrqk"); -void hint_common_parent_position(const Position& pos, - const Networks& networks, - AccumulatorCaches& caches) { - if (Eval::use_smallnet(pos)) - networks.small.hint_common_access(pos, &caches.small); - else - networks.big.hint_common_access(pos, &caches.big); -} - namespace { // Converts a Value into (centi)pawns and writes it in a buffer. // The buffer must have capacity for at least 5 chars. diff --git a/src/nnue/nnue_misc.h b/src/nnue/nnue_misc.h index a7647f846..02212160a 100644 --- a/src/nnue/nnue_misc.h +++ b/src/nnue/nnue_misc.h @@ -54,9 +54,6 @@ struct Networks; struct AccumulatorCaches; std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches); -void hint_common_parent_position(const Position& pos, - const Networks& networks, - AccumulatorCaches& caches); } // namespace Stockfish::Eval::NNUE } // namespace Stockfish diff --git a/src/position.h b/src/position.h index 53269c197..eeea1b74c 100644 --- a/src/position.h +++ b/src/position.h @@ -63,9 +63,9 @@ struct StateInfo { int repetition; // Used by NNUE + DirtyPiece dirtyPiece; Eval::NNUE::Accumulator accumulatorBig; Eval::NNUE::Accumulator accumulatorSmall; - DirtyPiece dirtyPiece; }; diff --git a/src/search.cpp b/src/search.cpp index 67e28d3e5..f292118ed 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -41,7 +41,6 @@ #include "nnue/network.h" #include "nnue/nnue_accumulator.h" #include "nnue/nnue_common.h" -#include "nnue/nnue_misc.h" #include "position.h" #include "syzygy/tbprobe.h" #include "thread.h" @@ -759,7 +758,6 @@ Value Search::Worker::search( else if (excludedMove) { // Providing the hint that this node's accumulator will be used often - Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); unadjustedStaticEval = eval = ss->staticEval; } else if (ss->ttHit) @@ -768,8 +766,6 @@ Value Search::Worker::search( unadjustedStaticEval = ttData.eval; if (!is_valid(unadjustedStaticEval)) unadjustedStaticEval = evaluate(pos); - else if (PvNode) - Eval::NNUE::hint_common_parent_position(pos, networks[numaAccessToken], refreshTable); ss->staticEval = eval = to_corrected_static_eval(unadjustedStaticEval, correctionValue); From 76c319f4388faabcbfbe9b6d4c2e030f766e455f Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Wed, 12 Feb 2025 15:01:35 -0800 Subject: [PATCH 54/59] Simplify ttcut depth condition Simplify ttcut depth condition in a recent tweak of Nonlinear (PR #5876) Passed simplification STC LLR: 2.95 (-2.94,2.94) <-1.75,0.25> Total: 235328 W: 61646 L: 61644 D: 112038 Ptnml(0-2): 1039, 27947, 59676, 27977, 1025 https://tests.stockfishchess.org/tests/view/67abc7fba04df5eb8dbeb442 Passed simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 63744 W: 16306 L: 16128 D: 31310 Ptnml(0-2): 58, 6918, 17748, 7084, 64 https://tests.stockfishchess.org/tests/view/67abd776a04df5eb8dbeb9c1 closes https://github.com/official-stockfish/Stockfish/pull/5877 Bench: 2667779 --- src/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/search.cpp b/src/search.cpp index f292118ed..da1ea3497 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -671,7 +671,7 @@ Value Search::Worker::search( if (!PvNode && !excludedMove && ttData.depth > depth - (ttData.value <= beta) && is_valid(ttData.value) // Can happen when !ttHit or when access race in probe() && (ttData.bound & (ttData.value >= beta ? BOUND_LOWER : BOUND_UPPER)) - && (cutNode == (ttData.value >= beta) || (depth > 9 || (rootDepth > 10 && depth > 5)))) + && (cutNode == (ttData.value >= beta) || depth > 5)) { // If ttMove is quiet, update move sorting heuristics on TT hit if (ttData.move && ttData.value >= beta) From ee7259e48bb33fd291fd972950ce7e9294d3e463 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Thu, 13 Feb 2025 23:09:30 +0300 Subject: [PATCH 55/59] Small code cleanup Use std::is_arithmetic_v as it is the more modern and concise way to check for arithmetic types. While at it, fixing a static assert in misc.h, thanks to Shawn and Disservin for helping. closes https://github.com/official-stockfish/Stockfish/pull/5883 No functional change --- src/history.h | 2 +- src/misc.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/history.h b/src/history.h index 9ae7bdadc..fd9b98b98 100644 --- a/src/history.h +++ b/src/history.h @@ -71,7 +71,7 @@ inline int non_pawn_index(const Position& pos) { template class StatsEntry { - static_assert(std::is_arithmetic::value, "Not an arithmetic type"); + static_assert(std::is_arithmetic_v, "Not an arithmetic type"); static_assert(D <= std::numeric_limits::max(), "D overflows T"); T entry; diff --git a/src/misc.h b/src/misc.h index 8adbac68a..d2cbb699d 100644 --- a/src/misc.h +++ b/src/misc.h @@ -155,6 +155,10 @@ struct MultiArrayHelper { using ChildType = T; }; +template +constexpr bool is_strictly_assignable_v = + std::is_assignable_v && (std::is_same_v || !std::is_convertible_v); + } // MultiArray is a generic N-dimensional array. @@ -212,7 +216,8 @@ class MultiArray { template void fill(const U& v) { - static_assert(std::is_assignable_v, "Cannot assign fill value to entry type"); + static_assert(Detail::is_strictly_assignable_v, + "Cannot assign fill value to entry type"); for (auto& ele : data_) { if constexpr (sizeof...(Sizes) == 0) From 095d19afea0b4f4a7f3ec91449cc7a66f7bbfc42 Mon Sep 17 00:00:00 2001 From: FauziAkram Date: Fri, 14 Feb 2025 03:07:39 +0300 Subject: [PATCH 56/59] Use neon_m128_reduce_add_epi32 for NEON vector reduction Accomplishing the entire horizontal addition in a single NEON instruction closes https://github.com/official-stockfish/Stockfish/pull/5885 No functional change --- src/nnue/layers/affine_transform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nnue/layers/affine_transform.h b/src/nnue/layers/affine_transform.h index f5c640fb9..dac727e23 100644 --- a/src/nnue/layers/affine_transform.h +++ b/src/nnue/layers/affine_transform.h @@ -102,7 +102,7 @@ static void affine_transform_non_ssse3(std::int32_t* output, product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]); sum = vpadalq_s16(sum, product); } - output[i] = sum[0] + sum[1] + sum[2] + sum[3]; + output[i] = Simd::neon_m128_reduce_add_epi32(sum); #endif } From 45b2b06cea0eae5935baee769142002a027e3ccb Mon Sep 17 00:00:00 2001 From: Daniel Monroe Date: Thu, 13 Feb 2025 23:32:53 -0800 Subject: [PATCH 57/59] Use same term for small and large net for nnue complexity adjustment Passed simplification STC LLR: 2.96 (-2.94,2.94) <-1.75,0.25> Total: 386496 W: 100682 L: 100850 D: 184964 Ptnml(0-2): 1686, 46399, 97218, 46287, 1658 https://tests.stockfishchess.org/tests/view/67a9cc6d851bb0f25324449e Passed rebased simplification LTC LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 160884 W: 41090 L: 41012 D: 78782 Ptnml(0-2): 133, 17883, 44321, 17983, 122 https://tests.stockfishchess.org/tests/view/67aef2e91a4c73ae1f930e85 closes https://github.com/official-stockfish/Stockfish/pull/5886 Bench: 2962718 --- src/evaluate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 4fce86e3a..dddb56860 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -76,7 +76,7 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks, // Blend optimism and eval with nnue complexity int nnueComplexity = std::abs(psqt - positional); optimism += optimism * nnueComplexity / 468; - nnue -= nnue * nnueComplexity / (smallNet ? 20233 : 17879); + nnue -= nnue * nnueComplexity / 18000; int material = 535 * pos.count() + pos.non_pawn_material(); int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777; From fc2139fedc8c74d52fcc641813a287b5b7d8f0b9 Mon Sep 17 00:00:00 2001 From: Nonlinear2 <131959792+Nonlinear2@users.noreply.github.com> Date: Sun, 16 Feb 2025 16:40:36 +0100 Subject: [PATCH 58/59] se separate parameters for stat values The code has been refactored to remove the `stat_bonus` and `stat_malus` functions, as the code for each bonus/malus is now different. This allows for future tests to modify these formulas individually. Passed LTC with STC bounds: https://tests.stockfishchess.org/tests/view/67b115dd6c6b9e172ad1592f LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 75756 W: 19393 L: 19044 D: 37319 Ptnml(0-2): 60, 8251, 20913, 8588, 66 Passed LTC with LTC bounds: https://tests.stockfishchess.org/tests/view/67af5f5d6c6b9e172ad15765 LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 108126 W: 27880 L: 27412 D: 52834 Ptnml(0-2): 85, 11786, 29866, 12228, 98 closes https://github.com/official-stockfish/Stockfish/pull/5887 Bench: 2809143 --- src/search.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/search.cpp b/src/search.cpp index da1ea3497..d2c77365b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -124,12 +124,6 @@ void update_correction_history(const Position& pos, << bonus * 138 / 128; } -// History and stats update bonus, based on depth -int stat_bonus(Depth d) { return std::min(158 * d - 98, 1622); } - -// History and stats update malus, based on depth -int stat_malus(Depth d) { return std::min(802 * d - 243, 2850); } - // Add a small random component to draw evaluations to avoid 3-fold blindness Value value_draw(size_t nodes) { return VALUE_DRAW - 1 + Value(nodes & 0x2); } Value value_to_tt(Value v, int ply); @@ -678,12 +672,14 @@ Value Search::Worker::search( { // Bonus for a quiet ttMove that fails high if (!ttCapture) - update_quiet_histories(pos, ss, *this, ttData.move, stat_bonus(depth) * 784 / 1024); + update_quiet_histories(pos, ss, *this, ttData.move, + std::min(117600 * depth - 71344, 1244992) / 1024); // 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, - -stat_malus(depth + 1) * 1018 / 1024); + -std::min(779788 * (depth + 1) - 271806, 2958308) + / 1024); } // Partial workaround for the graph history interaction problem @@ -1403,7 +1399,7 @@ moves_loop: // When in check, search starts here bonusScale = std::max(bonusScale, 0); - const int scaledBonus = stat_bonus(depth) * bonusScale; + const int scaledBonus = std::min(160 * depth - 106, 1523) * bonusScale; update_continuation_histories(ss - 1, pos.piece_on(prevSq), prevSq, scaledBonus * 416 / 32768); @@ -1422,7 +1418,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)] - << stat_bonus(depth) * 2; + << std::min(330 * depth - 198, 3320); } if (PvNode) @@ -1803,8 +1799,8 @@ void update_all_stats(const Position& pos, Piece moved_piece = pos.moved_piece(bestMove); PieceType captured; - int bonus = stat_bonus(depth) + 298 * isTTMove; - int malus = stat_malus(depth) - 32 * (moveCount - 1); + int bonus = std::min(162 * depth - 92, 1587) + 298 * isTTMove; + int malus = std::min(694 * depth - 230, 2503) - 32 * (moveCount - 1); if (!pos.capture_stage(bestMove)) { From 43b2d65d7275b11fd47c7225f8a0d19afbab4cd1 Mon Sep 17 00:00:00 2001 From: Shawn Xu Date: Sun, 16 Feb 2025 20:54:34 -0800 Subject: [PATCH 59/59] Add scaling note to futility pruning Note that both patches below effectively reduces the frequency of futility pruning. Passed STC: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 51680 W: 13599 L: 13253 D: 24828 Ptnml(0-2): 217, 6056, 12959, 6380, 228 https://tests.stockfishchess.org/tests/view/67ac218fa04df5eb8dbebf26 Passed LTC: LLR: 2.94 (-2.94,2.94) <0.50,2.50> Total: 51798 W: 13338 L: 12986 D: 25474 Ptnml(0-2): 42, 5584, 14310, 5906, 57 https://tests.stockfishchess.org/tests/view/67acf04152879dfd14d7e846 Regression at STC SMP: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 231552 W: 60226 L: 59642 D: 111684 Ptnml(0-2): 565, 25994, 62031, 26664, 522 https://tests.stockfishchess.org/tests/view/67ae390c1a4c73ae1f930dbf Passed STC: LLR: 2.93 (-2.94,2.94) <0.00,2.00> Total: 22560 W: 6022 L: 5725 D: 10813 Ptnml(0-2): 87, 2524, 5762, 2819, 88 https://tests.stockfishchess.org/tests/view/67ac202aa04df5eb8dbebf22 Passed LTC: LLR: 2.95 (-2.94,2.94) <0.50,2.50> Total: 66138 W: 16953 L: 16572 D: 32613 Ptnml(0-2): 62, 7103, 18360, 7480, 64 https://tests.stockfishchess.org/tests/view/67ad47d852879dfd14d7e899 Regression at VVLTC SMP: LLR: -2.94 (-2.94,2.94) <-1.75,0.25> Total: 29138 W: 7408 L: 7655 D: 14075 Ptnml(0-2): 0, 2816, 9189, 2559, 5 https://tests.stockfishchess.org/tests/view/67b159ce6c6b9e172ad1598f closes https://github.com/official-stockfish/Stockfish/pull/5888 No functional change --- src/search.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/search.cpp b/src/search.cpp index d2c77365b..7736c4121 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -1053,6 +1053,8 @@ moves_loop: // When in check, search starts here futilityValue += 108; // Futility pruning: parent node + // (*Scaler): Generally, more frequent futility pruning + // scales well with respect to time and threads if (!ss->inCheck && lmrDepth < 12 && futilityValue <= alpha) { if (bestValue <= futilityValue && !is_decisive(bestValue)